[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.
--- 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),