web/box.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """abstract box classes for CubicWeb web client"""
       
    19 
       
    20 __docformat__ = "restructuredtext en"
       
    21 from cubicweb import _
       
    22 
       
    23 from six import add_metaclass
       
    24 
       
    25 from logilab.mtconverter import xml_escape
       
    26 from logilab.common.deprecation import class_deprecated, class_renamed
       
    27 
       
    28 from cubicweb import Unauthorized, role as get_role
       
    29 from cubicweb.schema import display_name
       
    30 from cubicweb.predicates import no_cnx, one_line_rset
       
    31 from cubicweb.view import View
       
    32 from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs
       
    33 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
       
    34                                       RawBoxItem, BoxSeparator)
       
    35 from cubicweb.web.action import UnregisteredAction
       
    36 
       
    37 
       
    38 def sort_by_category(actions, categories_in_order=None):
       
    39     """return a list of (category, actions_sorted_by_title)"""
       
    40     result = []
       
    41     actions_by_cat = {}
       
    42     for action in actions:
       
    43         actions_by_cat.setdefault(action.category, []).append(
       
    44             (action.title, action) )
       
    45     for key, values in actions_by_cat.items():
       
    46         actions_by_cat[key] = [act for title, act in sorted(values, key=lambda x: x[0])]
       
    47     if categories_in_order:
       
    48         for cat in categories_in_order:
       
    49             if cat in actions_by_cat:
       
    50                 result.append( (cat, actions_by_cat[cat]) )
       
    51     for item in sorted(actions_by_cat.items()):
       
    52         result.append(item)
       
    53     return result
       
    54 
       
    55 
       
    56 # old box system, deprecated ###################################################
       
    57 
       
    58 @add_metaclass(class_deprecated)
       
    59 class BoxTemplate(View):
       
    60     """base template for boxes, usually a (contextual) list of possible
       
    61     actions. Various classes attributes may be used to control the box
       
    62     rendering.
       
    63 
       
    64     You may override one of the formatting callbacks if this is not necessary
       
    65     for your custom box.
       
    66 
       
    67     Classes inheriting from this class usually only have to override call
       
    68     to fetch desired actions, and then to do something like  ::
       
    69 
       
    70         box.render(self.w)
       
    71     """
       
    72     __deprecation_warning__ = '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)'
       
    73 
       
    74     __registry__ = 'ctxcomponents'
       
    75     __select__ = ~no_cnx()
       
    76 
       
    77     categories_in_order = ()
       
    78     cw_property_defs = {
       
    79         _('visible'): dict(type='Boolean', default=True,
       
    80                            help=_('display the box or not')),
       
    81         _('order'):   dict(type='Int', default=99,
       
    82                            help=_('display order of the box')),
       
    83         # XXX 'incontext' boxes are handled by the default primary view
       
    84         _('context'): dict(type='String', default='left',
       
    85                            vocabulary=(_('left'), _('incontext'), _('right')),
       
    86                            help=_('context where this box should be displayed')),
       
    87         }
       
    88     context = 'left'
       
    89 
       
    90     def sort_actions(self, actions):
       
    91         """return a list of (category, actions_sorted_by_title)"""
       
    92         return sort_by_category(actions, self.categories_in_order)
       
    93 
       
    94     def mk_action(self, title, url, escape=True, **kwargs):
       
    95         """factory function to create dummy actions compatible with the
       
    96         .format_actions method
       
    97         """
       
    98         if escape:
       
    99             title = xml_escape(title)
       
   100         return self.box_action(self._action(title, url, **kwargs))
       
   101 
       
   102     def _action(self, title, url, **kwargs):
       
   103         return UnregisteredAction(self._cw, title, url, **kwargs)
       
   104 
       
   105     # formating callbacks
       
   106 
       
   107     def boxitem_link_tooltip(self, action):
       
   108         if action.__regid__:
       
   109             return u'keyword: %s' % action.__regid__
       
   110         return u''
       
   111 
       
   112     def box_action(self, action):
       
   113         klass = getattr(action, 'html_class', lambda: None)()
       
   114         return BoxLink(action.url(), self._cw._(action.title),
       
   115                        klass, self.boxitem_link_tooltip(action))
       
   116 
       
   117 
       
   118 class RQLBoxTemplate(BoxTemplate):
       
   119     """abstract box for boxes displaying the content of a rql query not
       
   120     related to the current result set.
       
   121     """
       
   122 
       
   123     # to be defined in concrete classes
       
   124     rql = title = None
       
   125 
       
   126     def to_display_rql(self):
       
   127         assert self.rql is not None, self.__regid__
       
   128         return (self.rql,)
       
   129 
       
   130     def call(self, **kwargs):
       
   131         try:
       
   132             rset = self._cw.execute(*self.to_display_rql())
       
   133         except Unauthorized:
       
   134             # can't access to something in the query, forget this box
       
   135             return
       
   136         if len(rset) == 0:
       
   137             return
       
   138         box = BoxWidget(self._cw._(self.title), self.__regid__)
       
   139         for i, (teid, tname) in enumerate(rset):
       
   140             entity = rset.get_entity(i, 0)
       
   141             box.append(self.mk_action(tname, entity.absolute_url()))
       
   142         box.render(w=self.w)
       
   143 
       
   144 
       
   145 class UserRQLBoxTemplate(RQLBoxTemplate):
       
   146     """same as rql box template but the rql is build using the eid of the
       
   147     request's user
       
   148     """
       
   149 
       
   150     def to_display_rql(self):
       
   151         assert self.rql is not None, self.__regid__
       
   152         return (self.rql, {'x': self._cw.user.eid})
       
   153 
       
   154 
       
   155 class EntityBoxTemplate(BoxTemplate):
       
   156     """base class for boxes related to a single entity"""
       
   157     __select__ = BoxTemplate.__select__ & one_line_rset()
       
   158     context = 'incontext'
       
   159 
       
   160     def call(self, row=0, col=0, **kwargs):
       
   161         """classes inheriting from EntityBoxTemplate should define cell_call"""
       
   162         self.cell_call(row, col, **kwargs)
       
   163 
       
   164 from cubicweb.web.component import AjaxEditRelationCtxComponent, EditRelationMixIn
       
   165 
       
   166 
       
   167 class EditRelationBoxTemplate(EditRelationMixIn, EntityBoxTemplate):
       
   168     """base class for boxes which let add or remove entities linked
       
   169     by a given relation
       
   170 
       
   171     subclasses should define at least id, rtype and target
       
   172     class attributes.
       
   173     """
       
   174     rtype = None
       
   175     def cell_call(self, row, col, view=None, **kwargs):
       
   176         self._cw.add_js('cubicweb.ajax.js')
       
   177         entity = self.cw_rset.get_entity(row, col)
       
   178         title = display_name(self._cw, self.rtype, get_role(self),
       
   179                              context=entity.cw_etype)
       
   180         box = SideBoxWidget(title, self.__regid__)
       
   181         related = self.related_boxitems(entity)
       
   182         unrelated = self.unrelated_boxitems(entity)
       
   183         box.extend(related)
       
   184         if related and unrelated:
       
   185             box.append(BoxSeparator())
       
   186         box.extend(unrelated)
       
   187         box.render(self.w)
       
   188 
       
   189     def box_item(self, entity, etarget, rql, label):
       
   190         label = super(EditRelationBoxTemplate, self).box_item(
       
   191             entity, etarget, rql, label)
       
   192         return RawBoxItem(label, liclass=u'invisible')
       
   193 
       
   194 
       
   195 AjaxEditRelationBoxTemplate = class_renamed(
       
   196     'AjaxEditRelationBoxTemplate', AjaxEditRelationCtxComponent,
       
   197     '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationCtxComponent (%(cls)s)')