web/box.py
changeset 1808 aa09e20dd8c0
parent 1696 ee0bea49e0e1
child 1885 c2011d238e98
--- a/web/box.py	Tue May 05 17:18:49 2009 +0200
+++ b/web/box.py	Thu May 14 12:48:11 2009 +0200
@@ -1,24 +1,19 @@
 """abstract box classes for CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.common.decorators import cached
 from logilab.mtconverter import html_escape
 
-from cubicweb import Unauthorized, role as get_role
-from cubicweb.common.registerers import (
-    accepts_registerer, extresources_registerer,
-    etype_rtype_priority_registerer)
-from cubicweb.common.selectors import (
-    etype_rtype_selector, one_line_rset, accept, has_relation,
-    primary_view, match_context_prop, has_related_entities,
-    _rql_condition)
-from cubicweb.common.view import Template
-from cubicweb.common.appobject import ReloadableMixIn
+from cubicweb import Unauthorized, role as get_role, target as get_target
+from cubicweb.selectors import (one_line_rset,  primary_view,
+                                match_context_prop, partial_has_related_entities,
+                                accepts_compat, has_relation_compat,
+                                condition_compat, require_group_compat)
+from cubicweb.view import View, ReloadableMixIn
 
 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
                                       RawBoxItem, BoxSeparator)
@@ -27,23 +22,24 @@
 _ = unicode
 
 
-class BoxTemplate(Template):
+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
     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)
     """
     __registry__ = 'boxes'
-    __selectors__ = Template.__selectors__ + (match_context_prop,)
-    
+    __select__ = match_context_prop()
+    registered = classmethod(require_group_compat(View.registered))
+
     categories_in_order = ()
     property_defs = {
         _('visible'): dict(type='Boolean', default=True,
@@ -80,9 +76,9 @@
         if escape:
             title = html_escape(title)
         return self.box_action(self._action(title, path, **kwargs))
-    
+
     def _action(self, title, path, **kwargs):
-        return UnregisteredAction(self.req, self.rset, title, path, **kwargs)        
+        return UnregisteredAction(self.req, self.rset, title, path, **kwargs)
 
     # formating callbacks
 
@@ -95,25 +91,24 @@
         cls = getattr(action, 'html_class', lambda: None)() or self.htmlitemclass
         return BoxLink(action.url(), self.req._(action.title),
                        cls, 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.
-    
+
     It rely on etype, rtype (both optional, usable to control registration
     according to application schema and display according to connected
     user's rights) and rql attributes
     """
-    __registerer__ = etype_rtype_priority_registerer
-    __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
+#XXX    __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
 
     rql  = None
-    
+
     def to_display_rql(self):
         assert self.rql is not None, self.id
         return (self.rql,)
-    
+
     def call(self, **kwargs):
         try:
             rset = self.req.execute(*self.to_display_rql())
@@ -128,7 +123,7 @@
             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
@@ -137,34 +132,22 @@
     def to_display_rql(self):
         assert self.rql is not None, self.id
         return (self.rql, {'x': self.req.user.eid}, 'x')
-    
-
-class ExtResourcesBoxTemplate(BoxTemplate):
-    """base class for boxes displaying external resources such as the RSS logo.
-    It should list necessary resources with the .need_resources attribute.
-    """
-    __registerer__ = extresources_registerer
-    need_resources = ()
 
 
 class EntityBoxTemplate(BoxTemplate):
     """base class for boxes related to a single entity"""
-    __registerer__ = accepts_registerer
-    __selectors__ = (one_line_rset, primary_view,
-                     match_context_prop, etype_rtype_selector,
-                     has_relation, accept, _rql_condition)
-    accepts = ('Any',)
+    __select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
+    registered = accepts_compat(has_relation_compat(condition_compat(BoxTemplate.registered)))
     context = 'incontext'
-    condition = None
-    
+
     def call(self, row=0, col=0, **kwargs):
         """classes inheriting from EntityBoxTemplate should define cell_call"""
         self.cell_call(row, col, **kwargs)
 
 
 class RelatedEntityBoxTemplate(EntityBoxTemplate):
-    __selectors__ = EntityBoxTemplate.__selectors__ + (has_related_entities,)
-    
+    __select__ = EntityBoxTemplate.__select__ & partial_has_related_entities()
+
     def cell_call(self, row, col, **kwargs):
         entity = self.entity(row, col)
         limit = self.req.property_value('navigation.related-limit') + 1
@@ -182,37 +165,32 @@
     subclasses should define at least id, rtype and target
     class attributes.
     """
-    
-    def cell_call(self, row, col, view=None):
+
+    def cell_call(self, row, col, view=None, **kwargs):
         self.req.add_js('cubicweb.ajax.js')
         entity = self.entity(row, col)
         box = SideBoxWidget(display_name(self.req, self.rtype), self.id)
         count = self.w_related(box, entity)
         if count:
             box.append(BoxSeparator())
-        self.w_unrelated(box, entity)
+        if not self.w_unrelated(box, entity):
+            del box.items[-1] # remove useless separator
         box.render(self.w)
 
     def div_id(self):
         return self.id
 
-    @cached
-    def xtarget(self):
-        if self.target == 'subject':
-            return 'object', 'subject'
-        return 'subject', 'object'
-        
     def box_item(self, entity, etarget, rql, label):
         """builds HTML link to edit relation between `entity` and `etarget`
         """
-        x, target = self.xtarget()
-        args = {x[0] : entity.eid, target[0] : etarget.eid}
+        role, target = get_role(self), get_target(self)
+        args = {role[0] : entity.eid, target[0] : etarget.eid}
         url = self.user_rql_callback((rql, args))
         # for each target, provide a link to edit the relation
         label = u'[<a href="%s">%s</a>] %s' % (url, label,
                                                etarget.view('incontext'))
         return RawBoxItem(label, liclass=u'invisible')
-    
+
     def w_related(self, box, entity):
         """appends existing relations to the `box`"""
         rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
@@ -220,12 +198,15 @@
         for etarget in related:
             box.append(self.box_item(entity, etarget, rql, u'-'))
         return len(related)
-    
+
     def w_unrelated(self, box, entity):
         """appends unrelated entities to the `box`"""
         rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
+        i = 0
         for etarget in self.unrelated_entities(entity):
             box.append(self.box_item(entity, etarget, rql, u'+'))
+            i += 1
+        return i
 
     def unrelated_entities(self, entity):
         """returns the list of unrelated entities
@@ -233,19 +214,20 @@
         if etype is not defined on the Box's class, the default
         behaviour is to use the entity's appropraite vocabulary function
         """
-        x, target = self.xtarget()
         # use entity.unrelated if we've been asked for a particular etype
         if hasattr(self, 'etype'):
-            return entity.unrelated(self.rtype, self.etype, x).entities()
+            return entity.unrelated(self.rtype, self.etype, get_role(self)).entities()
         # in other cases, use vocabulary functions
         entities = []
-        for _, eid in entity.vocabulary(self.rtype, x):
+        form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
+                                       row=self.row or 0)
+        field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
+        for _, eid in form.form_field_vocabulary(field):
             if eid is not None:
                 rset = self.req.eid_rset(eid)
                 entities.append(rset.get_entity(0, 0))
         return entities
-        
+
     def related_entities(self, entity):
-        x, target = self.xtarget()
-        return entity.related(self.rtype, x, entities=True)
+        return entity.related(self.rtype, get_role(self), entities=True)