web/views/boxes.py
branch3.5
changeset 3219 be8cfc00ae04
parent 3084 096d680c9da2
child 3377 dd9d292b6a6d
child 3511 581de819106f
equal deleted inserted replaced
3218:2a4bbe3fa4f3 3219:be8cfc00ae04
    14 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    14 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    15 """
    15 """
    16 __docformat__ = "restructuredtext en"
    16 __docformat__ = "restructuredtext en"
    17 _ = unicode
    17 _ = unicode
    18 
    18 
       
    19 from warnings import warn
       
    20 
    19 from logilab.mtconverter import xml_escape
    21 from logilab.mtconverter import xml_escape
    20 
    22 
    21 from cubicweb.selectors import match_user_groups, non_final_entity
    23 from cubicweb.selectors import match_user_groups, non_final_entity
    22 from cubicweb.view import EntityView
    24 from cubicweb.view import EntityView
    23 from cubicweb.schema import display_name
    25 from cubicweb.schema import display_name
    24 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
    26 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
    25 from cubicweb.web import uicfg
    27 from cubicweb.web import uicfg
    26 from cubicweb.web.box import BoxTemplate
    28 from cubicweb.web.box import BoxTemplate
    27 
    29 
    28 
    30 
    29 class EditBox(BoxTemplate):
    31 class EditBox(BoxTemplate): # XXX rename to ActionsBox
    30     """
    32     """
    31     box with all actions impacting the entity displayed: edit, copy, delete
    33     box with all actions impacting the entity displayed: edit, copy, delete
    32     change state, add related entities
    34     change state, add related entities
    33     """
    35     """
    34     id = 'edit_box'
    36     id = 'edit_box'
    35     __select__ = BoxTemplate.__select__ & non_final_entity()
    37     __select__ = BoxTemplate.__select__ & non_final_entity()
    36 
    38 
    37     title = _('actions')
    39     title = _('actions')
    38     order = 2
    40     order = 2
    39     # class attributes below are actually stored in the uicfg module since we
       
    40     # don't want them to be reloaded
       
    41     appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
       
    42 
    41 
    43     def call(self, view=None, **kwargs):
    42     def call(self, view=None, **kwargs):
    44         _ = self.req._
    43         _ = self.req._
    45         title = _(self.title)
    44         title = _(self.title)
    46         if self.rset:
    45         if self.rset:
    48             if len(etypes) == 1:
    47             if len(etypes) == 1:
    49                 plural = self.rset.rowcount > 1 and 'plural' or ''
    48                 plural = self.rset.rowcount > 1 and 'plural' or ''
    50                 etypelabel = display_name(self.req, iter(etypes).next(), plural)
    49                 etypelabel = display_name(self.req, iter(etypes).next(), plural)
    51                 title = u'%s - %s' % (title, etypelabel.lower())
    50                 title = u'%s - %s' % (title, etypelabel.lower())
    52         box = BoxWidget(title, self.id, _class="greyBoxFrame")
    51         box = BoxWidget(title, self.id, _class="greyBoxFrame")
       
    52         self._menus_in_order = []
       
    53         self._menus_by_id = {}
    53         # build list of actions
    54         # build list of actions
    54         actions = self.vreg['actions'].possible_actions(self.req, self.rset,
    55         actions = self.vreg['actions'].possible_actions(self.req, self.rset,
    55                                                         view=view)
    56                                                         view=view)
    56         add_menu = BoxMenu(_('add')) # 'addrelated' category
    57         other_menu = self._get_menu('moreactions', _('more actions'))
    57         other_menu = BoxMenu(_('more actions')) # 'moreactions' category
    58         for category, defaultmenu in (('mainactions', box),
    58         searchstate = self.req.search_state[0]
    59                                       ('moreactions', other_menu),
    59         for category, menu in (('mainactions', box),
    60                                       ('addrelated', None)):
    60                                ('addrelated', add_menu),
       
    61                                ('moreactions', other_menu)):
       
    62             for action in actions.get(category, ()):
    61             for action in actions.get(category, ()):
    63                 menu.append(self.box_action(action))
    62                 if category == 'addrelated':
    64         if self.rset and self.rset.rowcount == 1 and \
    63                     warn('"addrelated" category is deprecated, use "moreaction"'
    65                not self.schema[self.rset.description[0][0]].is_final() and \
    64                          ' category w/ "addrelated" submenu',
    66                searchstate == 'normal':
    65                          DeprecationWarning)
    67             entity = self.rset.get_entity(0, 0)
    66                     defaultmenu = self._get_menu('addrelated', _('add'), _('add'))
    68             for action in self.schema_actions(entity):
    67                 if action.submenu:
    69                 add_menu.append(action)
    68                     menu = self._get_menu(action.submenu)
    70             self.workflow_actions(entity, box)
    69                 else:
       
    70                     menu = defaultmenu
       
    71                 action.fill_menu(self, menu)
    71         if box.is_empty() and not other_menu.is_empty():
    72         if box.is_empty() and not other_menu.is_empty():
    72             box.items = other_menu.items
    73             box.items = other_menu.items
    73             other_menu.items = []
    74             other_menu.items = []
    74         self.add_submenu(box, add_menu, _('add'))
    75         else: # ensure 'more actions' menu appears last
    75         self.add_submenu(box, other_menu)
    76             self._menus_in_order.remove(other_menu)
       
    77             self._menus_in_order.append(other_menu)
       
    78         for submenu in self._menus_in_order:
       
    79             self.add_submenu(box, submenu)
    76         if not box.is_empty():
    80         if not box.is_empty():
    77             box.render(self.w)
    81             box.render(self.w)
    78 
    82 
       
    83     def _get_menu(self, id, title=None, label_prefix=None):
       
    84         try:
       
    85             return self._menus_by_id[id]
       
    86         except KeyError:
       
    87             if title is None:
       
    88                 title = self.req._(id)
       
    89             self._menus_by_id[id] = menu = BoxMenu(title)
       
    90             menu.label_prefix = label_prefix
       
    91             self._menus_in_order.append(menu)
       
    92             return menu
       
    93 
    79     def add_submenu(self, box, submenu, label_prefix=None):
    94     def add_submenu(self, box, submenu, label_prefix=None):
    80         if len(submenu.items) == 1:
    95         appendanyway = getattr(submenu, 'append_anyway', False)
       
    96         if len(submenu.items) == 1 and not appendanyway:
    81             boxlink = submenu.items[0]
    97             boxlink = submenu.items[0]
    82             if label_prefix:
    98             if submenu.label_prefix:
    83                 boxlink.label = u'%s %s' % (label_prefix, boxlink.label)
    99                 boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label)
    84             box.append(boxlink)
   100             box.append(boxlink)
    85         elif submenu.items:
   101         elif submenu.items:
    86             box.append(submenu)
   102             box.append(submenu)
    87 
   103         elif appendanyway:
    88     def schema_actions(self, entity):
   104             box.append(RawBoxItem(xml_escape(submenu.label)))
    89         user = self.req.user
       
    90         actions = []
       
    91         _ = self.req._
       
    92         eschema = entity.e_schema
       
    93         for rschema, teschema, x in self.add_related_schemas(entity):
       
    94             if x == 'subject':
       
    95                 label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
       
    96                 url = self.linkto_url(entity, rschema, teschema, 'object')
       
    97             else:
       
    98                 label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
       
    99                 url = self.linkto_url(entity, rschema, teschema, 'subject')
       
   100             actions.append(self.mk_action(_(label), url))
       
   101         return actions
       
   102 
       
   103     def add_related_schemas(self, entity):
       
   104         """this is actually used ui method to generate 'addrelated' actions from
       
   105         the schema.
       
   106 
       
   107         If you don't want any auto-generated actions, you should overrides this
       
   108         method to return an empty list. If you only want some, you can configure
       
   109         them by using uicfg.actionbox_appearsin_addmenu
       
   110         """
       
   111         req = self.req
       
   112         eschema = entity.e_schema
       
   113         for role, rschemas in (('subject', eschema.subject_relations()),
       
   114                                ('object', eschema.object_relations())):
       
   115             for rschema in rschemas:
       
   116                 if rschema.is_final():
       
   117                     continue
       
   118                 # check the relation can be added as well
       
   119                 # XXX consider autoform_permissions_overrides?
       
   120                 if role == 'subject'and not rschema.has_perm(req, 'add',
       
   121                                                              fromeid=entity.eid):
       
   122                     continue
       
   123                 if role == 'object'and not rschema.has_perm(req, 'add',
       
   124                                                             toeid=entity.eid):
       
   125                     continue
       
   126                 # check the target types can be added as well
       
   127                 for teschema in rschema.targets(eschema, role):
       
   128                     if not self.appearsin_addmenu.etype_get(eschema, rschema,
       
   129                                                             role, teschema):
       
   130                         continue
       
   131                     if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
       
   132                         yield rschema, teschema, role
       
   133 
       
   134 
       
   135     def workflow_actions(self, entity, box):
       
   136         if entity.e_schema.has_subject_relation('in_state') and entity.in_state:
       
   137             _ = self.req._
       
   138             menu_title = u'%s: %s' % (_('state'), entity.printable_state)
       
   139             menu_items = []
       
   140             for tr in entity.possible_transitions():
       
   141                 url = entity.absolute_url(vid='statuschange', treid=tr.eid)
       
   142                 menu_items.append(self.mk_action(_(tr.name), url))
       
   143             # don't propose to see wf if user can't pass any transition
       
   144             if menu_items:
       
   145                 wfurl = self.build_url('cwetype/%s'%entity.e_schema, vid='workflow')
       
   146                 menu_items.append(self.mk_action(_('view workflow'), wfurl))
       
   147             if entity.workflow_history:
       
   148                 wfurl = entity.absolute_url(vid='wfhistory')
       
   149                 menu_items.append(self.mk_action(_('view history'), wfurl))
       
   150             box.append(BoxMenu(menu_title, menu_items))
       
   151         return None
       
   152 
       
   153     def linkto_url(self, entity, rtype, etype, target):
       
   154         return self.build_url(vid='creation', etype=etype,
       
   155                               __linkto='%s:%s:%s' % (rtype, entity.eid, target),
       
   156                               __redirectpath=entity.rest_path(), # should not be url quoted!
       
   157                               __redirectvid=self.req.form.get('vid', ''))
       
   158 
   105 
   159 
   106 
   160 class SearchBox(BoxTemplate):
   107 class SearchBox(BoxTemplate):
   161     """display a box with a simple search form"""
   108     """display a box with a simple search form"""
   162     id = 'search_box'
   109     id = 'search_box'