--- 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)