[PATCH 13 of 18 seda] [itree] Implement moving a child entities at a given index

Sylvain Thenault sylvain.thenault at logilab.fr
Mon Dec 11 16:53:45 CET 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1512997910 -3600
#      Mon Dec 11 14:11:50 2017 +0100
# Node ID c5789035e5683604503063d1bb03138fb128f3a1
# Parent  f8d111c4af68113adbf46d6ed6250382a5b570ab
[itree] Implement moving a child entities at a given index

this is only unit-tested for now, it will be actually used by later commits.

diff --git a/cubicweb_seda/entities/itree.py b/cubicweb_seda/entities/itree.py
--- a/cubicweb_seda/entities/itree.py
+++ b/cubicweb_seda/entities/itree.py
@@ -147,10 +147,33 @@ class ITreeBaseArchiveTransferAdapter(IC
 
     def parent(self):
         return None
 
 
+# Tree ordering management
+# ------------------------
+#
+# when several child entities of the same types are allowed, we want to control
+# their relative order. This is done using the `ordering` attribute of the child
+# entities.
+#
+# Code below handle moving nodes among the tree. As such it has to handle this
+# attribute so that:
+#
+# * move may control order
+#
+# * reparenting will keep consistent order
+#
+# In order to minimize the number of queries and to keep code clearer (avoid
+# transmitting ordering number through json among other), I've (syt) choosen
+# that `ordering` attribute's value:
+#
+# * starts at `1` for the first element,
+#
+# * is a sequential list, i.e. `2` for the 2nd child, `3` for the 3rd child, and
+#   up to `len(children)` for the last one.
+
 ETYPE_PARENT_RTYPE = dict(MULTIPLE_CHILDREN)
 
 
 def next_child_ordering(cnx, parent_eid, rtype):
     """Return value for the `ordering` attribute of a child freshly appended through
@@ -159,10 +182,42 @@ def next_child_ordering(cnx, parent_eid,
     rql = 'Any MAX(O) WHERE X {rtype} P, P eid %(p)s, X ordering O'
     ordering = cnx.execute(rql.format(rtype=rtype), {'p': parent_eid})[0][0]
     return 1 if ordering is None else ordering + 1
 
 
+def move_child_at_index(cnx, parent_eid, child_eid, index, reparenting=False):
+    """Given a parent node and one of its child, move it at `index` position (0 for
+    the first position, and `len(children)` for the last).
+
+    `reparenting` should be true when the child has just been appended, hence
+    it's previous ordering should not be considered.
+    """
+    child = cnx.entity_from_eid(child_eid)
+    assert reparenting or child.ordering not in (index, index + 1), \
+        "moving a child before or after itself doesn't make sense"
+    rtype = ETYPE_PARENT_RTYPE[child.cw_etype]
+    if reparenting:
+        rql = ('SET X ordering XO + 1 WHERE X ordering XO, '
+               'X ordering > %(index)s, '
+               'X {rtype} P, P eid %(p)s'.format(rtype=rtype))
+        order = index + 1
+    elif child.ordering > index:
+        rql = ('SET X ordering XO + 1 WHERE X ordering XO, '
+               'X ordering > %(index)s, X ordering < %(porder)s, '
+               'X {rtype} P, P eid %(p)s'.format(rtype=rtype))
+        order = index + 1
+    else:
+        rql = ('SET X ordering XO - 1 WHERE X ordering XO, '
+               'X ordering <= %(index)s, X ordering > %(porder)s, '
+               'X {rtype} P, P eid %(p)s'.format(rtype=rtype))
+        order = index
+    cnx.execute(rql, {'index': index,
+                      'porder': None if reparenting else child.ordering,
+                      'p': parent_eid}).rows
+    child.cw_set(ordering=order)
+
+
 def reparent(entity, new_parent_eid):
     """Move `entity` as a children of `new_parent_eid`.
     """
     rtype = ETYPE_PARENT_RTYPE[entity.cw_etype]
     entity.cw_set(**{rtype: new_parent_eid})
diff --git a/test/test_entities.py b/test/test_entities.py
--- a/test/test_entities.py
+++ b/test/test_entities.py
@@ -24,11 +24,11 @@ from logilab.common import attrdict
 
 from cubicweb.devtools.testlib import CubicWebTC
 
 from cubicweb_seda.entities import (seda_profile_container_def, simplified_profile,
                                     full_seda2_profile, parent_and_container,
-                                    rule_type_from_etype, custom)
+                                    rule_type_from_etype, custom, itree)
 
 from cubicweb_seda.testutils import create_archive_unit, create_data_object
 
 
 def sort_container(container_def):
@@ -275,10 +275,45 @@ class ITreeTC(CubicWebTC):
             cnx.commit()
 
             self.assertChildren(transfer, [bdo2.eid, bdo.eid, au2.eid, au.eid])
 
 
+class ReorderTC(CubicWebTC):
+
+    def test_reorder_base(self):
+        with self.admin_access.cnx() as cnx:
+            transfer = cnx.create_entity('SEDAArchiveTransfer', title=u'Test')
+            bdo1 = create_data_object(transfer)
+            bdo2 = create_data_object(transfer)
+            bdo3 = create_data_object(transfer)
+            cnx.commit()
+
+            itree.move_child_at_index(cnx, transfer.eid, bdo3.eid, 0)
+            transfer.cw_clear_all_caches()
+            self.assertEqual([(x.eid, x.ordering)
+                              for x in transfer.cw_adapt_to('ITreeBase').iterchildren()],
+                             [(bdo3.eid, 1), (bdo1.eid, 2), (bdo2.eid, 3)])
+
+            itree.move_child_at_index(cnx, transfer.eid, bdo3.eid, 3)
+            transfer.cw_clear_all_caches()
+            self.assertEqual([(x.eid, x.ordering)
+                              for x in transfer.cw_adapt_to('ITreeBase').iterchildren()],
+                             [(bdo1.eid, 1), (bdo2.eid, 2), (bdo3.eid, 3)])
+
+            itree.move_child_at_index(cnx, transfer.eid, bdo1.eid, 2)
+            transfer.cw_clear_all_caches()
+            self.assertEqual([(x.eid, x.ordering)
+                              for x in transfer.cw_adapt_to('ITreeBase').iterchildren()],
+                             [(bdo2.eid, 1), (bdo1.eid, 2), (bdo3.eid, 3)])
+
+            itree.move_child_at_index(cnx, transfer.eid, bdo3.eid, 1)
+            transfer.cw_clear_all_caches()
+            self.assertEqual([(x.eid, x.ordering)
+                              for x in transfer.cw_adapt_to('ITreeBase').iterchildren()],
+                             [(bdo2.eid, 1), (bdo3.eid, 2), (bdo1.eid, 3)])
+
+
 class RuleFromETypeTC(unittest.TestCase):
     def test_rule_from_etype(self):
         for rule_type in ('access', 'appraisal', 'classification',
                           'reuse', 'dissemination', 'storage'):
             for prefix, suffix in [


More information about the saem-devel mailing list