diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/web/box.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/box.py Sat Jan 16 13:48:51 2016 +0100 @@ -0,0 +1,197 @@ +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""abstract box classes for CubicWeb web client""" + +__docformat__ = "restructuredtext en" +from cubicweb import _ + +from six import add_metaclass + +from logilab.mtconverter import xml_escape +from logilab.common.deprecation import class_deprecated, class_renamed + +from cubicweb import Unauthorized, role as get_role +from cubicweb.schema import display_name +from cubicweb.predicates import no_cnx, one_line_rset +from cubicweb.view import View +from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs +from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget, + RawBoxItem, BoxSeparator) +from cubicweb.web.action import UnregisteredAction + + +def sort_by_category(actions, categories_in_order=None): + """return a list of (category, actions_sorted_by_title)""" + result = [] + actions_by_cat = {} + for action in actions: + actions_by_cat.setdefault(action.category, []).append( + (action.title, action) ) + for key, values in actions_by_cat.items(): + actions_by_cat[key] = [act for title, act in sorted(values, key=lambda x: x[0])] + if categories_in_order: + for cat in categories_in_order: + if cat in actions_by_cat: + result.append( (cat, actions_by_cat[cat]) ) + for item in sorted(actions_by_cat.items()): + result.append(item) + return result + + +# old box system, deprecated ################################################### + +@add_metaclass(class_deprecated) +class BoxTemplate(View): + """base template for boxes, usually a (contextual) list of possible + actions. Various classes attributes may be used to control the box + rendering. + + You may override one of the formatting callbacks if this is not necessary + for your custom box. + + Classes inheriting from this class usually only have to override call + to fetch desired actions, and then to do something like :: + + box.render(self.w) + """ + __deprecation_warning__ = '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)' + + __registry__ = 'ctxcomponents' + __select__ = ~no_cnx() + + categories_in_order = () + cw_property_defs = { + _('visible'): dict(type='Boolean', default=True, + help=_('display the box or not')), + _('order'): dict(type='Int', default=99, + help=_('display order of the box')), + # XXX 'incontext' boxes are handled by the default primary view + _('context'): dict(type='String', default='left', + vocabulary=(_('left'), _('incontext'), _('right')), + help=_('context where this box should be displayed')), + } + context = 'left' + + def sort_actions(self, actions): + """return a list of (category, actions_sorted_by_title)""" + return sort_by_category(actions, self.categories_in_order) + + def mk_action(self, title, url, escape=True, **kwargs): + """factory function to create dummy actions compatible with the + .format_actions method + """ + if escape: + title = xml_escape(title) + return self.box_action(self._action(title, url, **kwargs)) + + def _action(self, title, url, **kwargs): + return UnregisteredAction(self._cw, title, url, **kwargs) + + # formating callbacks + + def boxitem_link_tooltip(self, action): + if action.__regid__: + return u'keyword: %s' % action.__regid__ + return u'' + + def box_action(self, action): + klass = getattr(action, 'html_class', lambda: None)() + return BoxLink(action.url(), self._cw._(action.title), + klass, self.boxitem_link_tooltip(action)) + + +class RQLBoxTemplate(BoxTemplate): + """abstract box for boxes displaying the content of a rql query not + related to the current result set. + """ + + # to be defined in concrete classes + rql = title = None + + def to_display_rql(self): + assert self.rql is not None, self.__regid__ + return (self.rql,) + + def call(self, **kwargs): + try: + rset = self._cw.execute(*self.to_display_rql()) + except Unauthorized: + # can't access to something in the query, forget this box + return + if len(rset) == 0: + return + box = BoxWidget(self._cw._(self.title), self.__regid__) + for i, (teid, tname) in enumerate(rset): + entity = rset.get_entity(i, 0) + box.append(self.mk_action(tname, entity.absolute_url())) + box.render(w=self.w) + + +class UserRQLBoxTemplate(RQLBoxTemplate): + """same as rql box template but the rql is build using the eid of the + request's user + """ + + def to_display_rql(self): + assert self.rql is not None, self.__regid__ + return (self.rql, {'x': self._cw.user.eid}) + + +class EntityBoxTemplate(BoxTemplate): + """base class for boxes related to a single entity""" + __select__ = BoxTemplate.__select__ & one_line_rset() + context = 'incontext' + + def call(self, row=0, col=0, **kwargs): + """classes inheriting from EntityBoxTemplate should define cell_call""" + self.cell_call(row, col, **kwargs) + +from cubicweb.web.component import AjaxEditRelationCtxComponent, EditRelationMixIn + + +class EditRelationBoxTemplate(EditRelationMixIn, EntityBoxTemplate): + """base class for boxes which let add or remove entities linked + by a given relation + + subclasses should define at least id, rtype and target + class attributes. + """ + rtype = None + def cell_call(self, row, col, view=None, **kwargs): + self._cw.add_js('cubicweb.ajax.js') + entity = self.cw_rset.get_entity(row, col) + title = display_name(self._cw, self.rtype, get_role(self), + context=entity.cw_etype) + box = SideBoxWidget(title, self.__regid__) + related = self.related_boxitems(entity) + unrelated = self.unrelated_boxitems(entity) + box.extend(related) + if related and unrelated: + box.append(BoxSeparator()) + box.extend(unrelated) + box.render(self.w) + + def box_item(self, entity, etarget, rql, label): + label = super(EditRelationBoxTemplate, self).box_item( + entity, etarget, rql, label) + return RawBoxItem(label, liclass=u'invisible') + + +AjaxEditRelationBoxTemplate = class_renamed( + 'AjaxEditRelationBoxTemplate', AjaxEditRelationCtxComponent, + '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationCtxComponent (%(cls)s)')