[PATCH 4 of 5 saem_ref] Use a jQuery widget to make some lists sortable

Frank Bessou frank.bessou at logilab.fr
Tue Sep 25 12:03:12 CEST 2018


# HG changeset patch
# User Frank Bessou <frank.bessou at logilab.fr>
# Date 1537867860 -7200
#      Tue Sep 25 11:31:00 2018 +0200
# Node ID 7a7b6e1ebd2eb7b033d16c7d3803f260ee007662
# Parent  39f6872f131110267899f858e395781416438b1b
# Available At http://hg.logilab.org/review/cubes/saem_ref
#              hg pull http://hg.logilab.org/review/cubes/saem_ref -r 7a7b6e1ebd2e
# EXP-Topic CONSEJIRA-578-584
Use a jQuery widget to make some lists sortable

This jQuery widget calls a new ajax controller which uses ISortable
properties to sort elements in database.

We make RelatedEntitiesListView use a listvid property to select which
view must be used to display a given list.
The SortableListView adds js corresponding to sorting and adds the
needed attributes on the generated markup.

diff --git a/cubicweb_saem_ref/data/cubes.saem_ref.js b/cubicweb_saem_ref/data/cubes.saem_ref.js
--- a/cubicweb_saem_ref/data/cubes.saem_ref.js
+++ b/cubicweb_saem_ref/data/cubes.saem_ref.js
@@ -156,6 +156,16 @@ saem = {
             $('#' + refresh_view + eid).loadxhtml(
                 AJAX_BASE_URL, ajaxFuncArgs('view', {eid: eid, vid: refresh_view}));
         });
+    },
+    makeSortable: function (listid, rtype) {
+        $('#' + listid).sortable({
+            axis: 'y',
+            stop: ({target}) => {
+                const eids = $(target).children().map((i, e) =>
+                    $(e).attr('data-eid')).get();
+                asyncRemoteExec('sort_relation_targets', rtype, eids);
+            }
+        });
     }
 
 };
diff --git a/cubicweb_saem_ref/entities/__init__.py b/cubicweb_saem_ref/entities/__init__.py
--- a/cubicweb_saem_ref/entities/__init__.py
+++ b/cubicweb_saem_ref/entities/__init__.py
@@ -17,6 +17,7 @@
 
 from logilab.common.decorators import monkeypatch
 
+from cubicweb import ProgrammingError
 from cubicweb.view import Adapter, EntityAdapter
 from cubicweb.predicates import match_kwargs, relation_possible, is_instance
 from cubicweb.entities import AnyEntity, fetch_config, authobjs, lib
@@ -235,10 +236,44 @@ class ISortable(EntityAdapter):
     __regid__ = 'ISortable'
     __abstract__ = True
 
+    role = 'subject'
+
+    @property
+    def rtype(self):
+        """Return the relation making this entity is a component of a composite
+        entity"""
+        raise NotImplementedError
+
+    @property
+    def _reverse_rtype(self):
+        return 'reverse_' + self.rtype
+
+    @property
+    def parent(self):
+        if self.role == 'subject':
+            return getattr(self.entity, self.rtype)[0]
+        elif self.role == 'object':
+            return getattr(self.entity, self._reverse_rtype)[0]
+        else:
+            raise ProgrammingError('Invalid role: ' + self.role)
+
+    @property
+    def collection(self):
+        if self.role == 'subject':
+            return getattr(self.parent, self._reverse_rtype)
+        elif self.role == 'object':
+            return getattr(self.entity, self.rtype)
+        else:
+            raise ProgrammingError('Invalid role: ' + self.role)
+
 
 class AuthorityRecordFunctionSortable(ISortable):
     __select__ = is_instance('AgentFunction')
 
+    rtype = 'function_agent'
+
 
 class AuthorityRecordMandateSortable(ISortable):
     __select__ = is_instance('Mandate')
+
+    rtype = 'mandate_agent'
diff --git a/cubicweb_saem_ref/views/__init__.py b/cubicweb_saem_ref/views/__init__.py
--- a/cubicweb_saem_ref/views/__init__.py
+++ b/cubicweb_saem_ref/views/__init__.py
@@ -17,6 +17,8 @@
 
 from functools import wraps
 
+from six import text_type
+
 from logilab.common.decorators import monkeypatch
 from logilab.mtconverter import xml_escape
 from logilab.common.registry import yes
@@ -26,6 +28,7 @@ from cubicweb import NoSelectableObject,
 from cubicweb.utils import json_dumps, js_href
 from cubicweb.uilib import domid, js
 from cubicweb.view import EntityView
+from cubicweb.web.views import ajaxcontroller
 from cubicweb.predicates import (has_permission, is_instance, multi_lines_rset, match_kwargs,
                                  partial_has_related_entities)
 from cubicweb.web import component, formwidgets as fw
@@ -300,6 +303,23 @@ class SAEMHTMLPageFooter(basetemplates.H
                logo_mairie=self._cw.data_url('mairie_bx.jpg')))
 
 
+class SortableListView(baseviews.ListView):
+    __regid__ = 'sortable-list'
+
+    def call(self, **kwargs):
+        self._cw.add_js('cubes.saem_ref.js')
+        listid = kwargs['listid']
+        self._cw.add_onload(
+            js.saem.makeSortable(listid, kwargs['rtype'])
+        )
+        super(SortableListView, self).call(**kwargs)
+
+    def cell_call(self, row, col=0, vid=None, klass=None, **kwargs):
+        self.w(u'<li data-eid={} >'.format(self.cw_rset[row][0]))
+        self.wview(self.item_vid, self.cw_rset, row=row, col=col, vid=vid, **kwargs)
+        self.w(u'</li>')
+
+
 class RelatedEntitiesListView(EntityView):
     """Abstract entity view for displaying an related entities in a list.
 
@@ -312,6 +332,7 @@ class RelatedEntitiesListView(EntityView
     rtype = None
     role = 'object'
     subvid = 'saem.listitem'
+    listvid = 'list'
     target_etype = None
     subvid_kwargs = None
 
@@ -340,7 +361,9 @@ class RelatedEntitiesListView(EntityView
             self.divid = u'{}{}'.format(domid(self.__regid__), entity.eid)
             self.w(u'<div id="{}">'.format(self.divid))
             self.do_paginate(rset=rset)
-            self._cw.view('list', rset=rset, w=self.w, subvid=self.subvid, **kwargs)
+            listid = self.divid + "_list"
+            self._cw.view(self.listvid, rset=rset, w=self.w, rtype=self.rtype,
+                          subvid=self.subvid, listid=listid, **kwargs)
             self.w(u'</div>')
 
     def page_navigation_url(self, navcomp, _path, params):
@@ -478,6 +501,15 @@ class RestPathEvaluator(urlpublishing.Re
         return super(RestPathEvaluator, self).set_vid_for_rset(req, cls, rset)
 
 
+ at ajaxcontroller.ajaxfunc()
+def sort_relation_targets(self, rtype, eids):
+    first_item = self._cw.entity_from_eid(eids[0]).cw_adapt_to('ISortable')
+    collection = first_item.collection
+    for item in collection:
+        index = next(idx for idx, v in enumerate(eids) if v == text_type(item.eid))
+        item.cw_set(index=index)
+
+
 def registration_callback(vreg):
     from cubicweb.web.views import actions, cwuser, tableview, undohistory
     vreg.register_all(globals().values(), __name__, (
diff --git a/cubicweb_saem_ref/views/authorityrecord.py b/cubicweb_saem_ref/views/authorityrecord.py
--- a/cubicweb_saem_ref/views/authorityrecord.py
+++ b/cubicweb_saem_ref/views/authorityrecord.py
@@ -370,6 +370,14 @@ pvs.tag_object_of(('*', 'function_agent'
 
 class SortableListViewMixin(object):
 
+    @property
+    def listvid(self):
+        entity = self.cw_rset.one()
+        if entity.cw_has_perm('update'):
+            return 'sortable-list'
+        else:
+            return 'list'
+
     def related_rset(self, entity):
         rset = super(SortableListViewMixin, self).related_rset(entity)
         return rset.sorted_rset(lambda entity: entity.index)
diff --git a/test/test_views.py b/test/test_views.py
--- a/test/test_views.py
+++ b/test/test_views.py
@@ -701,5 +701,30 @@ class SortableListViewMixinTC(CubicWebTC
             self.assertSequenceEqual(expected, actual)
 
 
+class SortableAjaxControllerTC(CubicWebTC):
+
+    configcls = PostgresApptestConfiguration
+
+    def setup_database(self):
+        with self.admin_access.cnx() as cnx:
+            arecord = testutils.authority_record(cnx, u'record')
+
+            self.eid0 = cnx.create_entity('Mandate', term=u'1', mandate_agent=arecord).eid
+            self.eid1 = cnx.create_entity('Mandate', term=u'2', mandate_agent=arecord).eid
+            self.eid2 = cnx.create_entity('Mandate', term=u'3', mandate_agent=arecord).eid
+            self.eids = [self.eid0, self.eid1, self.eid2]
+            cnx.commit()
+
+    def test_sort_relation_targets_sorts_sortable(self):
+        sorted_eids = [str(eid) for eid in (self.eid1, self.eid0, self.eid2)]
+        with self.remote_calling('sort_relation_targets', 'mandate_agent', sorted_eids) as (_, req):
+            indexes = [req.entity_from_eid(eid).index for eid in self.eids]
+            self.assertEqual([1, 0, 2], indexes)
+        sorted_eids = [str(eid) for eid in (self.eid2, self.eid0, self.eid1)]
+        with self.remote_calling('sort_relation_targets', 'mandate_agent', sorted_eids) as (_, req):
+            indexes = [req.entity_from_eid(eid).index for eid in self.eids]
+            self.assertEqual([1, 2, 0], indexes)
+
+
 if __name__ == '__main__':
     unittest.main()



More information about the saem-devel mailing list