[views/optimization] Ensure we call rset.possible_actions with the same argument
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 23 Nov 2016 17:19:51 +0100
changeset 11874 ea1d92b677b5
parent 11873 8758b42d6c72
child 11875 011730a4af73
[views/optimization] Ensure we call rset.possible_actions with the same argument rset.possible_actions (which should definitly not be an ResultSet method, but that's another story) has been designed to hold a cache to compute possible actions for a only once, since this may be a fairly costly operation (notably because of the 'has_editable_relations' predicates). But this has been broken by introduction of a new 'view' parameter which is not given by every call. To fix this, this cset adds the missing view argument where necessary and reimplements the rset's method to assert it's always called with the same key. Unfortunatly, those changes have to be ported to squareui and bootstrap cubes as well.
cubicweb/devtools/testlib.py
cubicweb/rset.py
cubicweb/test/unittest_rset.py
cubicweb/web/views/basecomponents.py
cubicweb/web/views/basetemplates.py
cubicweb/web/views/boxes.py
--- a/cubicweb/devtools/testlib.py	Fri Nov 18 18:19:10 2016 +0100
+++ b/cubicweb/devtools/testlib.py	Wed Nov 23 17:19:51 2016 +0100
@@ -684,7 +684,8 @@
     def list_boxes_for(self, rset):
         """returns the list of boxes that can be applied on `rset`"""
         req = rset.req
-        for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset):
+        for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset,
+                                                               view=None):
             yield box
 
     def list_startup_views(self):
--- a/cubicweb/rset.py	Fri Nov 18 18:19:10 2016 +0100
+++ b/cubicweb/rset.py	Wed Nov 23 17:19:51 2016 +0100
@@ -73,7 +73,7 @@
         # set by the cursor which returned this resultset
         self.req = None
         # actions cache
-        self._rsetactions = None
+        self._actions_cache = None
 
     def __str__(self):
         if not self.rows:
@@ -100,19 +100,20 @@
                                     for r, d in zip(rows, self.description)))
 
     def possible_actions(self, **kwargs):
-        if self._rsetactions is None:
-            self._rsetactions = {}
-        if kwargs:
-            key = tuple(sorted(kwargs.items()))
-        else:
-            key = None
-        try:
-            return self._rsetactions[key]
-        except KeyError:
+        """Return possible actions on this result set. Should always be called with the same
+        arguments so it may be computed only once.
+        """
+        key = tuple(sorted(kwargs.items()))
+        if self._actions_cache is None:
             actions = self.req.vreg['actions'].poss_visible_objects(
                 self.req, rset=self, **kwargs)
-            self._rsetactions[key] = actions
+            self._actions_cache = (key, actions)
             return actions
+        else:
+            assert key == self._actions_cache[0], \
+                'unexpected new arguments for possible actions (%s vs %s)' % (
+                    key, self._actions_cache[0])
+            return self._actions_cache[1]
 
     def __len__(self):
         """returns the result set's size"""
--- a/cubicweb/test/unittest_rset.py	Fri Nov 18 18:19:10 2016 +0100
+++ b/cubicweb/test/unittest_rset.py	Wed Nov 23 17:19:51 2016 +0100
@@ -576,6 +576,13 @@
                               '(Any X,N WHERE X is CWGroup, X name N)'
                               ')')
 
+    def test_possible_actions_cache(self):
+        with self.admin_access.web_request() as req:
+            rset = req.execute('Any D, COUNT(U) GROUPBY D WHERE U is CWUser, U creation_date D')
+            rset.possible_actions(argument='Value')
+            self.assertRaises(AssertionError, rset.possible_actions, argument='OtherValue')
+            self.assertRaises(AssertionError, rset.possible_actions, other_argument='Value')
+
     def test_count_users_by_date(self):
         with self.admin_access.web_request() as req:
             rset = req.execute('Any D, COUNT(U) GROUPBY D WHERE U is CWUser, U creation_date D')
--- a/cubicweb/web/views/basecomponents.py	Fri Nov 18 18:19:10 2016 +0100
+++ b/cubicweb/web/views/basecomponents.py	Wed Nov 23 17:19:51 2016 +0100
@@ -169,7 +169,8 @@
     def render(self, w):
         # display useractions and siteactions
         self._cw.add_css('cubicweb.pictograms.css')
-        actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset)
+        actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset,
+                                                            view=self.cw_extra_kwargs['view'])
         box = MenuWidget('', 'userActionsBox', _class='', islist=False)
         menu = PopupBoxMenu(self._cw.user.login, isitem=False, link_class='icon-user')
         box.append(menu)
--- a/cubicweb/web/views/basetemplates.py	Fri Nov 18 18:19:10 2016 +0100
+++ b/cubicweb/web/views/basetemplates.py	Wed Nov 23 17:19:51 2016 +0100
@@ -208,7 +208,7 @@
         self.w(u'</td>\n')
         self.nav_column(view, 'right')
         self.w(u'</tr></table></div>\n')
-        self.wview('footer', rset=self.cw_rset)
+        self.wview('footer', rset=self.cw_rset, view=view)
         self.w(u'</body>')
 
     def nav_column(self, view, context):
@@ -395,7 +395,8 @@
 
     def footer_content(self):
         actions = self._cw.vreg['actions'].possible_actions(self._cw,
-                                                            rset=self.cw_rset)
+                                                            rset=self.cw_rset,
+                                                            view=self.cw_extra_kwargs['view'])
         footeractions = actions.get('footer', ())
         for i, action in enumerate(footeractions):
             self.w(u'<a href="%s">%s</a>' % (action.url(),
--- a/cubicweb/web/views/boxes.py	Fri Nov 18 18:19:10 2016 +0100
+++ b/cubicweb/web/views/boxes.py	Wed Nov 23 17:19:51 2016 +0100
@@ -67,7 +67,7 @@
         self._menus_by_id = {}
         # build list of actions
         actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset,
-                                                            **self.cw_extra_kwargs)
+                                                            view=self.cw_extra_kwargs['view'])
         other_menu = self._get_menu('moreactions', _('more actions'))
         for category, defaultmenu in (('mainactions', self),
                                       ('moreactions', other_menu),