web/box.py
changeset 6141 b8287e54b528
parent 6140 65a619eb31c4
child 6490 34359fbde6ef
--- a/web/box.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/box.py	Wed Aug 25 10:29:07 2010 +0200
@@ -23,14 +23,10 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import class_deprecated, class_renamed
 
-from cubicweb import Unauthorized, role as get_role, target as get_target, tags
+from cubicweb import Unauthorized, role as get_role, target as get_target
 from cubicweb.schema import display_name
-from cubicweb.selectors import (no_cnx, one_line_rset,  primary_view,
-                                match_context_prop, partial_relation_possible,
-                                partial_has_related_entities)
-from cubicweb.appobject import AppObject
-from cubicweb.view import View, ReloadableMixIn, Component
-from cubicweb.uilib import domid, js
+from cubicweb.selectors 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)
@@ -55,324 +51,14 @@
     return result
 
 
-class EditRelationMixIn(ReloadableMixIn):
-    def box_item(self, entity, etarget, rql, label):
-        """builds HTML link to edit relation between `entity` and `etarget`"""
-        role, target = get_role(self), get_target(self)
-        args = {role[0] : entity.eid, target[0] : etarget.eid}
-        url = self._cw.user_rql_callback((rql, args))
-        # for each target, provide a link to edit the relation
-        return u'[<a href="%s">%s</a>] %s' % (xml_escape(url), label,
-                                              etarget.view('incontext'))
-
-    def related_boxitems(self, entity):
-        rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
-        return [self.box_item(entity, etarget, rql, u'-')
-                for etarget in self.related_entities(entity)]
-
-    def related_entities(self, entity):
-        return entity.related(self.rtype, get_role(self), entities=True)
-
-    def unrelated_boxitems(self, entity):
-        rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
-        return [self.box_item(entity, etarget, rql, u'+')
-                for etarget in self.unrelated_entities(entity)]
-
-    def unrelated_entities(self, entity):
-        """returns the list of unrelated entities, using the entity's
-        appropriate vocabulary function
-        """
-        skip = set(unicode(e.eid) for e in entity.related(self.rtype, get_role(self),
-                                                          entities=True))
-        skip.add(None)
-        skip.add(INTERNAL_FIELD_VALUE)
-        filteretype = getattr(self, 'etype', None)
-        entities = []
-        form = self._cw.vreg['forms'].select('edition', self._cw,
-                                             rset=self.cw_rset,
-                                             row=self.cw_row or 0)
-        field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
-        for _, eid in field.vocabulary(form):
-            if eid not in skip:
-                entity = self._cw.entity_from_eid(eid)
-                if filteretype is None or entity.__regid__ == filteretype:
-                    entities.append(entity)
-        return entities
-
-
-# generic classes for the new box system #######################################
-
-from cubicweb.selectors import match_context, contextual
-
-class EmptyComponent(Exception):
-    """some selectable component has actually no content and should not be
-    rendered
-    """
-
-class Layout(Component):
-    __regid__ = 'layout'
-    __abstract__ = True
-
-
-class Box(AppObject): # XXX ContextComponent
-    __registry__ = 'boxes'
-    __select__ = ~no_cnx() & match_context_prop()
-
-    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'
-    contextual = False
-    title = None
-    # XXX support kwargs for compat with old boxes which gets the view as
-    # argument
-    def render(self, w, **kwargs):
-        getlayout = self._cw.vreg['components'].select
-        try:
-            # XXX ensure context is given when the component is reloaded through
-            # ajax
-            context = self.cw_extra_kwargs['context']
-        except KeyError:
-            context = self.cw_propval('context')
-        layout = getlayout('layout', self._cw, rset=self.cw_rset,
-                           row=self.cw_row, col=self.cw_col,
-                           view=self, context=context)
-        layout.render(w)
-
-    def init_rendering(self):
-        """init rendering callback: that's the good time to check your component
-        has some content to display. If not, you can still raise
-        :exc:`EmptyComponent` to inform it should be skipped.
-
-        Also, :exc:`Unauthorized` will be catched, logged, then the component
-        will be skipped.
-        """
-        self.items = []
-
-    @property
-    def domid(self):
-        """return the HTML DOM identifier for this component"""
-        return domid(self.__regid__)
-
-    @property
-    def cssclass(self):
-        """return the CSS class name for this component"""
-        return domid(self.__regid__)
-
-    def render_title(self, w):
-        """return the title for this component"""
-        if self.title is None:
-            raise NotImplementedError()
-        w(self._cw._(self.title))
-
-    def render_body(self, w):
-        """return the body (content) for this component"""
-        raise NotImplementedError()
-
-    def render_items(self, w, items=None, klass=u'boxListing'):
-        if items is None:
-            items = self.items
-        assert items
-        w(u'<ul class="%s">' % klass)
-        for item in items:
-            if hasattr(item, 'render'):
-                item.render(w) # XXX display <li> by itself
-            else:
-                w(u'<li>')
-                w(item)
-                w(u'</li>')
-        w(u'</ul>')
-
-    def append(self, item):
-        self.items.append(item)
-
-    def box_action(self, action): # XXX action_link
-        return self.build_link(self._cw._(action.title), action.url())
-
-    def build_link(self, title, url, **kwargs):
-        if self._cw.selected(url):
-            try:
-                kwargs['klass'] += ' selected'
-            except KeyError:
-                kwargs['klass'] = 'selected'
-        return tags.a(title, href=url, **kwargs)
-
-
-class EntityBox(Box): # XXX ContextEntityComponent
-    """base class for boxes related to a single entity"""
-    __select__ = Box.__select__ & one_line_rset()
-    context = 'incontext'
-    contextual = True
-
-    def __init__(self, *args, **kwargs):
-        super(EntityBox, self).__init__(*args, **kwargs)
-        try:
-            entity = kwargs['entity']
-        except KeyError:
-            entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        self.entity = entity
-
-    @property
-    def domid(self):
-        return domid(self.__regid__) + unicode(self.entity.eid)
-
-
-# high level abstract box classes ##############################################
-
-
-class RQLBox(Box):
-    """abstract box for boxes displaying the content of a rql query not
-    related to the current result set.
-    """
-    rql  = None
-
-    def to_display_rql(self):
-        assert self.rql is not None, self.__regid__
-        return (self.rql,)
-
-    def init_rendering(self):
-        rset = self._cw.execute(*self.to_display_rql())
-        if not rset:
-            raise EmptyComponent()
-        if len(rset[0]) == 2:
-            self.items = []
-            for i, (eid, label) in enumerate(rset):
-                entity = rset.get_entity(i, 0)
-                self.items.append(self.build_link(label, entity.absolute_url()))
-        else:
-            self.items = [self.build_link(e.dc_title(), e.absolute_url())
-                          for e in rset.entities()]
-
-    def render_body(self, w):
-        self.render_items(w)
-
-
-class EditRelationBox(EditRelationMixIn, EntityBox):
-    """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.
-    """
-    def render_title(self, w):
-        return display_name(self._cw, self.rtype, get_role(self),
-                            context=self.entity.__regid__)
-
-    def render_body(self, w):
-        self._cw.add_js('cubicweb.ajax.js')
-        related = self.related_boxitems(self.entity)
-        unrelated = self.unrelated_boxitems(self.entity)
-        self.items.extend(related)
-        if related and unrelated:
-            self.items.append(BoxSeparator())
-        self.items.extend(unrelated)
-        self.render_items(w)
-
-
-class AjaxEditRelationBox(EntityBox):
-    __select__ = EntityBox.__select__ & (
-        partial_relation_possible(action='add') | partial_has_related_entities())
-
-    # view used to display related entties
-    item_vid = 'incontext'
-    # values separator when multiple values are allowed
-    separator = ','
-    # msgid of the message to display when some new relation has been added/removed
-    added_msg = None
-    removed_msg = None
-
-    # class attributes below *must* be set in concret classes (additionaly to
-    # rtype / role [/ target_etype]. They should correspond to js_* methods on
-    # the json controller
-
-    # function(eid)
-    # -> expected to return a list of values to display as input selector
-    #    vocabulary
-    fname_vocabulary = None
-
-    # function(eid, value)
-    # -> handle the selector's input (eg create necessary entities and/or
-    # relations). If the relation is multiple, you'll get a list of value, else
-    # a single string value.
-    fname_validate = None
-
-    # function(eid, linked entity eid)
-    # -> remove the relation
-    fname_remove = None
-
-    def __init__(self, *args, **kwargs):
-        super(AjaxEditRelationBox, self).__init__(*args, **kwargs)
-        self.rdef = self.entity.e_schema.rdef(self.rtype, self.role, self.target_etype)
-
-    def render_title(self, w):
-        w(self.rdef.rtype.display_name(self._cw, self.role,
-                                       context=self.entity.__regid__))
-
-    def render_body(self, w):
-        req = self._cw
-        entity = self.entity
-        related = entity.related(self.rtype, self.role)
-        if self.role == 'subject':
-            mayadd = self.rdef.has_perm(req, 'add', fromeid=entity.eid)
-            maydel = self.rdef.has_perm(req, 'delete', fromeid=entity.eid)
-        else:
-            mayadd = self.rdef.has_perm(req, 'add', toeid=entity.eid)
-            maydel = self.rdef.has_perm(req, 'delete', toeid=entity.eid)
-        if mayadd or maydel:
-            req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js'))
-        _ = req._
-        if related:
-            w(u'<table>')
-            for rentity in related.entities():
-                # for each related entity, provide a link to remove the relation
-                subview = rentity.view(self.item_vid)
-                if maydel:
-                    jscall = unicode(js.ajaxBoxRemoveLinkedEntity(
-                        self.__regid__, entity.eid, rentity.eid,
-                        self.fname_remove,
-                        self.removed_msg and _(self.removed_msg)))
-                    w(u'<tr><td>[<a href="javascript: %s">-</a>]</td>'
-                      '<td class="tagged"> %s</td></tr>' % (xml_escape(jscall),
-                                                            subview))
-                else:
-                    w(u'<tr><td class="tagged">%s</td></tr>' % (subview))
-            w(u'</table>')
-        else:
-            w(_('no related entity'))
-        if mayadd:
-            req.add_js('jquery.autocomplete.js')
-            req.add_css('jquery.autocomplete.css')
-            multiple = self.rdef.role_cardinality(self.role) in '*+'
-            w(u'<table><tr><td>')
-            jscall = unicode(js.ajaxBoxShowSelector(
-                self.__regid__, entity.eid, self.fname_vocabulary,
-                self.fname_validate, self.added_msg and _(self.added_msg),
-                _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]),
-                multiple and self.separator))
-            w('<a class="button sglink" href="javascript: %s">%s</a>' % (
-                xml_escape(jscall),
-                multiple and _('add_relation') or _('update_relation')))
-            w(u'</td><td>')
-            w(u'<div id="%sHolder"></div>' % self.domid)
-            w(u'</td></tr></table>')
-
-
 # old box system, 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 on of the formatting callbacks is this is not necessary
+    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
@@ -381,10 +67,10 @@
         box.render(self.w)
     """
     __metaclass__ = class_deprecated
-    __deprecation_warning__ = '*BoxTemplate classes are deprecated, use *Box instead'
+    __deprecation_warning__ = '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)'
 
-    __registry__ = 'boxes'
-    __select__ = ~no_cnx() & match_context_prop()
+    __registry__ = 'ctxcomponents'
+    __select__ = ~no_cnx()
 
     categories_in_order = ()
     cw_property_defs = {
@@ -465,13 +151,15 @@
 
 class EntityBoxTemplate(BoxTemplate):
     """base class for boxes related to a single entity"""
-    __select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
+    __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
@@ -502,6 +190,6 @@
 
 
 AjaxEditRelationBoxTemplate = class_renamed(
-    'AjaxEditRelationBoxTemplate', AjaxEditRelationBox,
-    '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationBox')
+    'AjaxEditRelationBoxTemplate', AjaxEditRelationCtxComponent,
+    '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationCtxComponent')