diff -r 65a619eb31c4 -r b8287e54b528 web/box.py --- 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'[%s] %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'') - - 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'') - 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'' - '' % (xml_escape(jscall), - subview)) - else: - w(u'' % (subview)) - w(u'
[-] %s
%s
') - 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'
') - 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('%s' % ( - xml_escape(jscall), - multiple and _('add_relation') or _('update_relation'))) - w(u'') - w(u'
' % self.domid) - w(u'
') - - # 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')