--- 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)
--- 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):
--- 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):
--- 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