# HG changeset patch # User Sylvain Thénault # Date 1252947684 -7200 # Node ID be8cfc00ae04954bd53d6f8eb275757a11814b22 # Parent 2a4bbe3fa4f38a34dc3cff39df90c77c2154cf74 edit box refactoring to gain more control by using actions diff -r 2a4bbe3fa4f3 -r be8cfc00ae04 web/action.py --- a/web/action.py Mon Sep 14 18:59:17 2009 +0200 +++ b/web/action.py Mon Sep 14 19:01:24 2009 +0200 @@ -34,6 +34,12 @@ } site_wide = True # don't want user to configuration actions eproperties category = 'moreactions' + # actions in category 'moreactions' can specify a sub-menu in which they should be filed + submenu = None + + def fill_menu(self, box, menu): + """add action(s) to the given submenu of the given box""" + menu.append(box.box_action(self)) def url(self): """return the url associated with this action""" @@ -75,7 +81,7 @@ & partial_may_add_relation()) registered = accepts_compat(Action.registered) - category = 'addrelated' + submenu = 'addrelated' def url(self): current_entity = self.rset.get_entity(self.row or 0, self.col or 0) diff -r 2a4bbe3fa4f3 -r be8cfc00ae04 web/views/actions.py --- a/web/views/actions.py Mon Sep 14 18:59:17 2009 +0200 +++ b/web/views/actions.py Mon Sep 14 19:01:24 2009 +0200 @@ -226,6 +226,71 @@ return self.build_url('add/%s' % self.rsettype) +class AddRelatedActions(Action): + """fill 'addrelated' sub-menu of the actions box""" + id = 'addrelated' + __select__ = Action.__select__ & one_line_rset() & non_final_entity() + + submenu = _('addrelated') + order = 20 + + def fill_menu(self, box, menu): + # when there is only one item in the sub-menu, replace the sub-menu by + # item's title prefixed by 'add' + menu.label_prefix = self.req._('add') + entity = self.rset.get_entity(self.row or 0, self.col or 0) + user = self.req.user + actions = [] + _ = self.req._ + eschema = entity.e_schema + for rschema, teschema, x in self.add_related_schemas(entity): + if x == 'subject': + label = 'add %s %s %s %s' % (eschema, rschema, teschema, x) + url = self.linkto_url(entity, rschema, teschema, 'object') + else: + label = 'add %s %s %s %s' % (teschema, rschema, eschema, x) + url = self.linkto_url(entity, rschema, teschema, 'subject') + menu.append(box.mk_action(_(label), url)) + + def add_related_schemas(self, entity): + """this is actually used ui method to generate 'addrelated' actions from + the schema. + + If you don't want any auto-generated actions, you should overrides this + method to return an empty list. If you only want some, you can configure + them by using uicfg.actionbox_appearsin_addmenu + """ + appearsin_addmenu = uicfg.actionbox_appearsin_addmenu + req = self.req + eschema = entity.e_schema + for role, rschemas in (('subject', eschema.subject_relations()), + ('object', eschema.object_relations())): + for rschema in rschemas: + if rschema.is_final(): + continue + # check the relation can be added as well + # XXX consider autoform_permissions_overrides? + if role == 'subject'and not rschema.has_perm(req, 'add', + fromeid=entity.eid): + continue + if role == 'object'and not rschema.has_perm(req, 'add', + toeid=entity.eid): + continue + # check the target types can be added as well + for teschema in rschema.targets(eschema, role): + if not appearsin_addmenu.etype_get(eschema, rschema, + role, teschema): + continue + if teschema.has_local_role('add') or teschema.has_perm(req, 'add'): + yield rschema, teschema, role + + def linkto_url(self, entity, rtype, etype, target): + return self.build_url(vid='creation', etype=etype, + __linkto='%s:%s:%s' % (rtype, entity.eid, target), + __redirectpath=entity.rest_path(), # should not be url quoted! + __redirectvid=self.req.form.get('vid', '')) + + # logged user actions ######################################################### class UserPreferencesAction(Action): diff -r 2a4bbe3fa4f3 -r be8cfc00ae04 web/views/boxes.py --- a/web/views/boxes.py Mon Sep 14 18:59:17 2009 +0200 +++ b/web/views/boxes.py Mon Sep 14 19:01:24 2009 +0200 @@ -16,6 +16,8 @@ __docformat__ = "restructuredtext en" _ = unicode +from warnings import warn + from logilab.mtconverter import xml_escape from cubicweb.selectors import match_user_groups, non_final_entity @@ -26,7 +28,7 @@ from cubicweb.web.box import BoxTemplate -class EditBox(BoxTemplate): +class EditBox(BoxTemplate): # XXX rename to ActionsBox """ box with all actions impacting the entity displayed: edit, copy, delete change state, add related entities @@ -36,9 +38,6 @@ title = _('actions') order = 2 - # class attributes below are actually stored in the uicfg module since we - # don't want them to be reloaded - appearsin_addmenu = uicfg.actionbox_appearsin_addmenu def call(self, view=None, **kwargs): _ = self.req._ @@ -50,111 +49,59 @@ etypelabel = display_name(self.req, iter(etypes).next(), plural) title = u'%s - %s' % (title, etypelabel.lower()) box = BoxWidget(title, self.id, _class="greyBoxFrame") + self._menus_in_order = [] + self._menus_by_id = {} # build list of actions actions = self.vreg['actions'].possible_actions(self.req, self.rset, view=view) - add_menu = BoxMenu(_('add')) # 'addrelated' category - other_menu = BoxMenu(_('more actions')) # 'moreactions' category - searchstate = self.req.search_state[0] - for category, menu in (('mainactions', box), - ('addrelated', add_menu), - ('moreactions', other_menu)): + other_menu = self._get_menu('moreactions', _('more actions')) + for category, defaultmenu in (('mainactions', box), + ('moreactions', other_menu), + ('addrelated', None)): for action in actions.get(category, ()): - menu.append(self.box_action(action)) - if self.rset and self.rset.rowcount == 1 and \ - not self.schema[self.rset.description[0][0]].is_final() and \ - searchstate == 'normal': - entity = self.rset.get_entity(0, 0) - for action in self.schema_actions(entity): - add_menu.append(action) - self.workflow_actions(entity, box) + if category == 'addrelated': + warn('"addrelated" category is deprecated, use "moreaction"' + ' category w/ "addrelated" submenu', + DeprecationWarning) + defaultmenu = self._get_menu('addrelated', _('add'), _('add')) + if action.submenu: + menu = self._get_menu(action.submenu) + else: + menu = defaultmenu + action.fill_menu(self, menu) if box.is_empty() and not other_menu.is_empty(): box.items = other_menu.items other_menu.items = [] - self.add_submenu(box, add_menu, _('add')) - self.add_submenu(box, other_menu) + else: # ensure 'more actions' menu appears last + self._menus_in_order.remove(other_menu) + self._menus_in_order.append(other_menu) + for submenu in self._menus_in_order: + self.add_submenu(box, submenu) if not box.is_empty(): box.render(self.w) + def _get_menu(self, id, title=None, label_prefix=None): + try: + return self._menus_by_id[id] + except KeyError: + if title is None: + title = self.req._(id) + self._menus_by_id[id] = menu = BoxMenu(title) + menu.label_prefix = label_prefix + self._menus_in_order.append(menu) + return menu + def add_submenu(self, box, submenu, label_prefix=None): - if len(submenu.items) == 1: + appendanyway = getattr(submenu, 'append_anyway', False) + if len(submenu.items) == 1 and not appendanyway: boxlink = submenu.items[0] - if label_prefix: - boxlink.label = u'%s %s' % (label_prefix, boxlink.label) + if submenu.label_prefix: + boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label) box.append(boxlink) elif submenu.items: box.append(submenu) - - def schema_actions(self, entity): - user = self.req.user - actions = [] - _ = self.req._ - eschema = entity.e_schema - for rschema, teschema, x in self.add_related_schemas(entity): - if x == 'subject': - label = 'add %s %s %s %s' % (eschema, rschema, teschema, x) - url = self.linkto_url(entity, rschema, teschema, 'object') - else: - label = 'add %s %s %s %s' % (teschema, rschema, eschema, x) - url = self.linkto_url(entity, rschema, teschema, 'subject') - actions.append(self.mk_action(_(label), url)) - return actions - - def add_related_schemas(self, entity): - """this is actually used ui method to generate 'addrelated' actions from - the schema. - - If you don't want any auto-generated actions, you should overrides this - method to return an empty list. If you only want some, you can configure - them by using uicfg.actionbox_appearsin_addmenu - """ - req = self.req - eschema = entity.e_schema - for role, rschemas in (('subject', eschema.subject_relations()), - ('object', eschema.object_relations())): - for rschema in rschemas: - if rschema.is_final(): - continue - # check the relation can be added as well - # XXX consider autoform_permissions_overrides? - if role == 'subject'and not rschema.has_perm(req, 'add', - fromeid=entity.eid): - continue - if role == 'object'and not rschema.has_perm(req, 'add', - toeid=entity.eid): - continue - # check the target types can be added as well - for teschema in rschema.targets(eschema, role): - if not self.appearsin_addmenu.etype_get(eschema, rschema, - role, teschema): - continue - if teschema.has_local_role('add') or teschema.has_perm(req, 'add'): - yield rschema, teschema, role - - - def workflow_actions(self, entity, box): - if entity.e_schema.has_subject_relation('in_state') and entity.in_state: - _ = self.req._ - menu_title = u'%s: %s' % (_('state'), entity.printable_state) - menu_items = [] - for tr in entity.possible_transitions(): - url = entity.absolute_url(vid='statuschange', treid=tr.eid) - menu_items.append(self.mk_action(_(tr.name), url)) - # don't propose to see wf if user can't pass any transition - if menu_items: - wfurl = self.build_url('cwetype/%s'%entity.e_schema, vid='workflow') - menu_items.append(self.mk_action(_('view workflow'), wfurl)) - if entity.workflow_history: - wfurl = entity.absolute_url(vid='wfhistory') - menu_items.append(self.mk_action(_('view history'), wfurl)) - box.append(BoxMenu(menu_title, menu_items)) - return None - - def linkto_url(self, entity, rtype, etype, target): - return self.build_url(vid='creation', etype=etype, - __linkto='%s:%s:%s' % (rtype, entity.eid, target), - __redirectpath=entity.rest_path(), # should not be url quoted! - __redirectvid=self.req.form.get('vid', '')) + elif appendanyway: + box.append(RawBoxItem(xml_escape(submenu.label))) class SearchBox(BoxTemplate): diff -r 2a4bbe3fa4f3 -r be8cfc00ae04 web/views/workflow.py --- a/web/views/workflow.py Mon Sep 14 18:59:17 2009 +0200 +++ b/web/views/workflow.py Mon Sep 14 19:01:24 2009 +0200 @@ -15,7 +15,7 @@ from logilab.common.graph import escape, GraphGenerator, DotBackend from cubicweb import Unauthorized, view -from cubicweb.selectors import (implements, has_related_entities, +from cubicweb.selectors import (implements, has_related_entities, one_line_rset, relation_possible, match_form_params) from cubicweb.interfaces import IWorkflowable from cubicweb.view import EntityView @@ -110,7 +110,36 @@ def cell_call(self, row, col, view=None): self.wview('wfhistory', self.rset, row=row, col=col, view=view) -# workflow entity types views ################################################# + +# workflow actions ############################################################# + +class WorkflowActions(action.Action): + """fill 'workflow' sub-menu of the actions box""" + id = 'workflow' + __select__ = (action.Action.__select__ & one_line_rset() & + relation_possible('in_state')) + + submenu = _('workflow') + order = 10 + + def fill_menu(self, box, menu): + req = self.req + entity = self.rset.get_entity(self.row or 0, self.col or 0) + menu.label = u'%s: %s' % (req._('state'), entity.printable_state) + menu.append_anyway = True + for tr in entity.possible_transitions(): + url = entity.absolute_url(vid='statuschange', treid=tr.eid) + menu.append(box.mk_action(req._(tr.name), url)) + # don't propose to see wf if user can't pass any transition + if menu.items: + wfurl = entity.current_workflow.absolute_url() + menu.append(box.mk_action(req._('view workflow'), wfurl)) + if entity.workflow_history: + wfurl = entity.absolute_url(vid='wfhistory') + menu.append(box.mk_action(req._('view history'), wfurl)) + + +# workflow entity types views ################################################## class CellView(view.EntityView): id = 'cell' @@ -131,7 +160,6 @@ class WorkflowPrimaryView(primary.PrimaryView): - id = 'workflow' __select__ = implements('Workflow') cache_max_age = 60*60*2 # stay in http cache for 2 hours by default