# HG changeset patch # User Sylvain Thénault # Date 1282724947 -7200 # Node ID b8287e54b528316fc08ab92394e82844af7aa36e # Parent 65a619eb31c45dd6d2d49517b481765bc770ff32 [web api] unify 'contentnav' (VComponent) and 'boxes' registries as 'ctxcomponents' (CtxComponent) diff -r 65a619eb31c4 -r b8287e54b528 cwvreg.py --- a/cwvreg.py Wed Aug 25 10:01:11 2010 +0200 +++ b/cwvreg.py Wed Aug 25 10:29:07 2010 +0200 @@ -421,6 +421,44 @@ VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry +class CtxComponentsRegistry(CWRegistry): + def poss_visible_objects(self, *args, **kwargs): + """return an ordered list of possible components""" + context = kwargs.pop('context') + if kwargs.get('rset') is None: + cache = args[0] + else: + cache = kwargs['rset'] + try: + cached = cache.__components_cache + except AttributeError: + ctxcomps = super(CtxComponentsRegistry, self).poss_visible_objects( + *args, **kwargs) + cached = cache.__components_cache = {} + for component in ctxcomps: + cached.setdefault(component.cw_propval('context'), []).append(component) + thisctxcomps = cached.get(context, ()) + # XXX set context for bw compat (should now be taken by comp.render()) + for component in thisctxcomps: + component.cw_extra_kwargs['context'] = context + return thisctxcomps + +VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry + + +class BwCompatCWRegistry(object): + def __init__(self, vreg, oldreg, redirecttoreg): + self.vreg = vreg + self.oldreg = oldreg + self.redirecto = redirecttoreg + + def __getattr__(self, attr): + warn('[3.10] you should now use the %s registry instead of the %s registry' + % (self.redirecto, self.oldreg), DeprecationWarning, stacklevel=2) + return getattr(self.vreg[self.redirecto], attr) + + def clear(self): pass + def initialization_completed(self): pass class CubicWebVRegistry(VRegistry): """Central registry for the cubicweb instance, extending the generic @@ -433,15 +471,23 @@ stored objects. Currently we have the following registries of objects known by the web instance (library may use some others additional registries): - * etypes - * views - * components - * actions - * forms - * formrenderers - * controllers, which are directly plugged into the application - object to handle request publishing XXX to merge with views - * contentnavigation XXX to merge with components? to kill? + * 'etypes', entity type classes + + * 'views', views and templates (e.g. layout views) + + * 'components', non contextual components, like magic search, url evaluators + + * 'ctxcomponents', contextual components like boxes and dynamic section + + * 'actions', contextual actions, eg links to display in predefined places in + the ui + + * 'forms', describing logic of HTML form + + * 'formrenderers', rendering forms to html + + * 'controllers', primary objects to handle request publishing, directly + plugged into the application """ def __init__(self, config, initlog=True): @@ -456,6 +502,8 @@ # don't clear rtags during test, this may cause breakage with # manually imported appobject modules CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects) + self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents') + self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents') def setdefault(self, regid): try: @@ -751,7 +799,7 @@ def possible_actions(self, req, rset=None, **kwargs): return self["actions"].possible_actions(req, rest=rset, **kwargs) - @deprecated('[3.4] use vreg["boxes"].select_object(...)') + @deprecated('[3.4] use vreg["ctxcomponents"].select_object(...)') def select_box(self, oid, *args, **kwargs): return self['boxes'].select_object(oid, *args, **kwargs) diff -r 65a619eb31c4 -r b8287e54b528 devtools/testlib.py --- a/devtools/testlib.py Wed Aug 25 10:01:11 2010 +0200 +++ b/devtools/testlib.py Wed Aug 25 10:29:07 2010 +0200 @@ -475,7 +475,7 @@ def list_boxes_for(self, rset): """returns the list of boxes that can be applied on `rset`""" req = rset.req - for box in self.vreg['boxes'].possible_objects(req, rset=rset): + for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset): yield box def list_startup_views(self): diff -r 65a619eb31c4 -r b8287e54b528 i18n/en.po --- a/i18n/en.po Wed Aug 25 10:01:11 2010 +0200 +++ b/i18n/en.po Wed Aug 25 10:29:07 2010 +0200 @@ -1119,52 +1119,52 @@ msgid "boxes" msgstr "" -msgid "boxes_bookmarks_box" +msgid "ctxcomponents_bookmarks_box" msgstr "bookmarks box" -msgid "boxes_bookmarks_box_description" +msgid "ctxcomponents_bookmarks_box_description" msgstr "box listing the user's bookmarks" -msgid "boxes_download_box" +msgid "ctxcomponents_download_box" msgstr "download box" -msgid "boxes_download_box_description" -msgstr "" - -msgid "boxes_edit_box" +msgid "ctxcomponents_download_box_description" +msgstr "" + +msgid "ctxcomponents_edit_box" msgstr "actions box" -msgid "boxes_edit_box_description" +msgid "ctxcomponents_edit_box_description" msgstr "box listing the applicable actions on the displayed data" -msgid "boxes_filter_box" +msgid "ctxcomponents_filter_box" msgstr "filter" -msgid "boxes_filter_box_description" +msgid "ctxcomponents_filter_box_description" msgstr "box providing filter within current search results functionality" -msgid "boxes_possible_views_box" +msgid "ctxcomponents_possible_views_box" msgstr "possible views box" -msgid "boxes_possible_views_box_description" +msgid "ctxcomponents_possible_views_box_description" msgstr "box listing the possible views for the displayed data" -msgid "boxes_rss" +msgid "ctxcomponents_rss" msgstr "rss box" -msgid "boxes_rss_description" +msgid "ctxcomponents_rss_description" msgstr "RSS icon to get displayed data as a RSS thread" -msgid "boxes_search_box" +msgid "ctxcomponents_search_box" msgstr "search box" -msgid "boxes_search_box_description" +msgid "ctxcomponents_search_box_description" msgstr "search box" -msgid "boxes_startup_views_box" +msgid "ctxcomponents_startup_views_box" msgstr "startup views box" -msgid "boxes_startup_views_box_description" +msgid "ctxcomponents_startup_views_box_description" msgstr "box listing the possible start pages" msgid "bug report sent" @@ -1440,41 +1440,41 @@ msgid "content type" msgstr "" -msgid "contentnavigation" +msgid "ctxcomponents" msgstr "contextual components" -msgid "contentnavigation_breadcrumbs" +msgid "ctxcomponents_breadcrumbs" msgstr "breadcrumb" -msgid "contentnavigation_breadcrumbs_description" +msgid "ctxcomponents_breadcrumbs_description" msgstr "breadcrumbs bar that display a path locating the page in the site" -msgid "contentnavigation_metadata" +msgid "ctxcomponents_metadata" msgstr "entity's metadata" -msgid "contentnavigation_metadata_description" -msgstr "" - -msgid "contentnavigation_prevnext" +msgid "ctxcomponents_metadata_description" +msgstr "" + +msgid "ctxcomponents_prevnext" msgstr "previous / next entity" -msgid "contentnavigation_prevnext_description" +msgid "ctxcomponents_prevnext_description" msgstr "" "display link to go from one entity to another on entities implementing the " "\"previous/next\" interface." -msgid "contentnavigation_seealso" +msgid "ctxcomponents_seealso" msgstr "see also" -msgid "contentnavigation_seealso_description" +msgid "ctxcomponents_seealso_description" msgstr "" "section containing entities related by the \"see also\" relation on entities " "supporting it." -msgid "contentnavigation_wfhistory" +msgid "ctxcomponents_wfhistory" msgstr "workflow history" -msgid "contentnavigation_wfhistory_description" +msgid "ctxcomponents_wfhistory_description" msgstr "show the workflow's history." msgid "context" diff -r 65a619eb31c4 -r b8287e54b528 i18n/es.po --- a/i18n/es.po Wed Aug 25 10:01:11 2010 +0200 +++ b/i18n/es.po Wed Aug 25 10:29:07 2010 +0200 @@ -1161,53 +1161,53 @@ msgid "boxes" msgstr "Cajas" -msgid "boxes_bookmarks_box" +msgid "ctxcomponents_bookmarks_box" msgstr "Caja de Favoritos" -msgid "boxes_bookmarks_box_description" +msgid "ctxcomponents_bookmarks_box_description" msgstr "Muestra y permite administrar los favoritos del usuario" -msgid "boxes_download_box" +msgid "ctxcomponents_download_box" msgstr "Configuración de caja de descargas" -msgid "boxes_download_box_description" +msgid "ctxcomponents_download_box_description" msgstr "Caja que contiene los elementos descargados" -msgid "boxes_edit_box" +msgid "ctxcomponents_edit_box" msgstr "Caja de Acciones" -msgid "boxes_edit_box_description" +msgid "ctxcomponents_edit_box_description" msgstr "" "Muestra las acciones posibles a ejecutar para los datos seleccionados" -msgid "boxes_filter_box" +msgid "ctxcomponents_filter_box" msgstr "Filtros" -msgid "boxes_filter_box_description" +msgid "ctxcomponents_filter_box_description" msgstr "Muestra los filtros aplicables a una búsqueda realizada" -msgid "boxes_possible_views_box" +msgid "ctxcomponents_possible_views_box" msgstr "Caja de Vistas Posibles" -msgid "boxes_possible_views_box_description" +msgid "ctxcomponents_possible_views_box_description" msgstr "Muestra las vistas posibles a aplicar a los datos seleccionados" -msgid "boxes_rss" +msgid "ctxcomponents_rss" msgstr "Ícono RSS" -msgid "boxes_rss_description" +msgid "ctxcomponents_rss_description" msgstr "Muestra el ícono RSS para vistas RSS" -msgid "boxes_search_box" +msgid "ctxcomponents_search_box" msgstr "Caja de búsqueda" -msgid "boxes_search_box_description" +msgid "ctxcomponents_search_box_description" msgstr "Permite realizar una búsqueda simple para cualquier tipo de dato en la aplicación" -msgid "boxes_startup_views_box" +msgid "ctxcomponents_startup_views_box" msgstr "Caja Vistas de inicio" -msgid "boxes_startup_views_box_description" +msgid "ctxcomponents_startup_views_box_description" msgstr "Muestra las vistas de inicio de la aplicación" msgid "bug report sent" @@ -1490,38 +1490,38 @@ msgid "contentnavigation" msgstr "Componentes contextuales" -msgid "contentnavigation_breadcrumbs" +msgid "ctxcomponents_breadcrumbs" msgstr "Ruta de Navegación" -msgid "contentnavigation_breadcrumbs_description" +msgid "ctxcomponents_breadcrumbs_description" msgstr "Muestra la ruta que permite localizar la página actual en el Sistema" -msgid "contentnavigation_metadata" +msgid "ctxcomponents_metadata" msgstr "Metadatos de la Entidad" -msgid "contentnavigation_metadata_description" +msgid "ctxcomponents_metadata_description" msgstr "" -msgid "contentnavigation_prevnext" +msgid "ctxcomponents_prevnext" msgstr "Elemento anterior / siguiente" -msgid "contentnavigation_prevnext_description" +msgid "ctxcomponents_prevnext_description" msgstr "" "Muestra las ligas que permiten pasar de una entidad a otra en las entidades " "que implementan la interface \"anterior/siguiente\"." -msgid "contentnavigation_seealso" +msgid "ctxcomponents_seealso" msgstr "Vea también" -msgid "contentnavigation_seealso_description" +msgid "ctxcomponents_seealso_description" msgstr "" "sección que muestra las entidades relacionadas por la relación \"vea también\" , " "si la entidad soporta esta relación." -msgid "contentnavigation_wfhistory" +msgid "ctxcomponents_wfhistory" msgstr "Histórico del workflow." -msgid "contentnavigation_wfhistory_description" +msgid "ctxcomponents_wfhistory_description" msgstr "" "Sección que muestra el reporte histórico de las transiciones del workflow." " Aplica solo en entidades con workflow." diff -r 65a619eb31c4 -r b8287e54b528 i18n/fr.po --- a/i18n/fr.po Wed Aug 25 10:01:11 2010 +0200 +++ b/i18n/fr.po Wed Aug 25 10:29:07 2010 +0200 @@ -1164,53 +1164,53 @@ msgid "boxes" msgstr "boîtes" -msgid "boxes_bookmarks_box" +msgid "ctxcomponents_bookmarks_box" msgstr "boîte signets" -msgid "boxes_bookmarks_box_description" +msgid "ctxcomponents_bookmarks_box_description" msgstr "boîte contenant les signets de l'utilisateur" -msgid "boxes_download_box" +msgid "ctxcomponents_download_box" msgstr "boîte de téléchargement" -msgid "boxes_download_box_description" +msgid "ctxcomponents_download_box_description" msgstr "boîte contenant un lien permettant de télécharger la ressource" -msgid "boxes_edit_box" +msgid "ctxcomponents_edit_box" msgstr "boîte d'actions" -msgid "boxes_edit_box_description" +msgid "ctxcomponents_edit_box_description" msgstr "" "boîte affichant les différentes actions possibles sur les données affichées" -msgid "boxes_filter_box" +msgid "ctxcomponents_filter_box" msgstr "filtrer" -msgid "boxes_filter_box_description" +msgid "ctxcomponents_filter_box_description" msgstr "boîte permettant de filtrer parmi les résultats d'une recherche" -msgid "boxes_possible_views_box" +msgid "ctxcomponents_possible_views_box" msgstr "boîte des vues possibles" -msgid "boxes_possible_views_box_description" +msgid "ctxcomponents_possible_views_box_description" msgstr "boîte affichant les vues possibles pour les données courantes" -msgid "boxes_rss" +msgid "ctxcomponents_rss" msgstr "icône RSS" -msgid "boxes_rss_description" +msgid "ctxcomponents_rss_description" msgstr "l'icône RSS permettant de récupérer la vue RSS des données affichées" -msgid "boxes_search_box" +msgid "ctxcomponents_search_box" msgstr "boîte de recherche" -msgid "boxes_search_box_description" +msgid "ctxcomponents_search_box_description" msgstr "boîte avec un champ de recherche simple" -msgid "boxes_startup_views_box" +msgid "ctxcomponents_startup_views_box" msgstr "boîte des vues de départs" -msgid "boxes_startup_views_box_description" +msgid "ctxcomponents_startup_views_box_description" msgstr "boîte affichant les vues de départs de l'application" msgid "bug report sent" @@ -1491,42 +1491,42 @@ msgid "content type" msgstr "type MIME" -msgid "contentnavigation" +msgid "ctxcomponents" msgstr "composants contextuels" -msgid "contentnavigation_breadcrumbs" +msgid "ctxcomponents_breadcrumbs" msgstr "fil d'ariane" -msgid "contentnavigation_breadcrumbs_description" +msgid "ctxcomponents_breadcrumbs_description" msgstr "" "affiche un chemin permettant de localiser la page courante dans le site" -msgid "contentnavigation_metadata" +msgid "ctxcomponents_metadata" msgstr "méta-données de l'entité" -msgid "contentnavigation_metadata_description" +msgid "ctxcomponents_metadata_description" msgstr "" -msgid "contentnavigation_prevnext" +msgid "ctxcomponents_prevnext" msgstr "élément précedent / suivant" -msgid "contentnavigation_prevnext_description" +msgid "ctxcomponents_prevnext_description" msgstr "" "affiche des liens permettant de passer d'une entité à une autre sur les " "entités implémentant l'interface \"précédent/suivant\"." -msgid "contentnavigation_seealso" +msgid "ctxcomponents_seealso" msgstr "voir aussi" -msgid "contentnavigation_seealso_description" +msgid "ctxcomponents_seealso_description" msgstr "" "section affichant les entités liées par la relation \"voir aussi\" si " "l'entité supporte cette relation." -msgid "contentnavigation_wfhistory" +msgid "ctxcomponents_wfhistory" msgstr "historique du workflow." -msgid "contentnavigation_wfhistory_description" +msgid "ctxcomponents_wfhistory_description" msgstr "" "section affichant l'historique du workflow pour les entités ayant un " "workflow." diff -r 65a619eb31c4 -r b8287e54b528 misc/migration/3.10.0_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.10.0_Any.py Wed Aug 25 10:29:07 2010 +0200 @@ -0,0 +1,5 @@ +# rename cwprops for boxes/contentnavigation +for x in rql('Any X,XK WHERE X pkey XK, ' + 'X pkey ~= "boxes.%s" OR ' + 'X pkey ~= "contentnavigation.%s"').entities(): + x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.',1)) diff -r 65a619eb31c4 -r b8287e54b528 req.py --- a/req.py Wed Aug 25 10:01:11 2010 +0200 +++ b/req.py Wed Aug 25 10:29:07 2010 +0200 @@ -15,9 +15,8 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Base class for request/session +"""Base class for request/session""" -""" __docformat__ = "restructuredtext en" from warnings import warn diff -r 65a619eb31c4 -r b8287e54b528 selectors.py --- a/selectors.py Wed Aug 25 10:01:11 2010 +0200 +++ b/selectors.py Wed Aug 25 10:29:07 2010 +0200 @@ -1203,6 +1203,7 @@ # Web request selectors ######################################################## +# XXX deprecate @objectify_selector @lltrace def primary_view(cls, req, view=None, **kwargs): @@ -1252,6 +1253,7 @@ return 1 +# XXX deprecate @objectify_selector @lltrace def match_context_prop(cls, req, context=None, **kwargs): @@ -1272,8 +1274,6 @@ return 1 propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.__regid__)) - if not propval: - propval = cls.context if propval and context != propval: return 0 return 1 diff -r 65a619eb31c4 -r b8287e54b528 utils.py --- a/utils.py Wed Aug 25 10:01:11 2010 +0200 +++ b/utils.py Wed Aug 25 10:29:07 2010 +0200 @@ -75,6 +75,31 @@ return False return True + +class wrap_on_write(object): + def __init__(self, w, tag, closetag=None): + self.written = False + self.tag = unicode(tag) + self.closetag = closetag + self.w = w + + def __enter__(self): + return self + + def __call__(self, data): + if self.written is False: + self.w(self.tag) + self.written = True + self.w(data) + + def __exit__(self, exctype, value, traceback): + if self.written is True: + if self.closetag: + self.w(unicode(self.closetag)) + else: + self.w(self.tag.replace('<', '%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') diff -r 65a619eb31c4 -r b8287e54b528 web/component.py --- a/web/component.py Wed Aug 25 10:01:11 2010 +0200 +++ b/web/component.py Wed Aug 25 10:29:07 2010 +0200 @@ -22,57 +22,20 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.common.deprecation import class_renamed +from logilab.common.deprecation import class_deprecated, class_renamed from logilab.mtconverter import xml_escape -from cubicweb import role -from cubicweb.utils import json_dumps -from cubicweb.view import Component -from cubicweb.selectors import ( - paginated_rset, one_line_rset, primary_view, match_context_prop, - partial_has_related_entities) +from cubicweb import Unauthorized, role, tags +from cubicweb.uilib import js, domid +from cubicweb.view import ReloadableMixIn, Component +from cubicweb.selectors import (no_cnx, paginated_rset, one_line_rset, + non_final_entity, partial_relation_possible, + partial_has_related_entities) +from cubicweb.appobject import AppObject +from cubicweb.web import htmlwidgets, stdmsgs -class EntityVComponent(Component): - """abstract base class for additinal components displayed in content - headers and footer according to: - - * the displayed entity's type - * a context (currently 'header' or 'footer') - - it should be configured using .accepts, .etype, .rtype, .target and - .context class attributes - """ - - __registry__ = 'contentnavigation' - __select__ = one_line_rset() & primary_view() & match_context_prop() - - cw_property_defs = { - _('visible'): dict(type='Boolean', default=True, - help=_('display the component or not')), - _('order'): dict(type='Int', default=99, - help=_('display order of the component')), - _('context'): dict(type='String', default='navtop', - vocabulary=(_('navtop'), _('navbottom'), - _('navcontenttop'), _('navcontentbottom'), - _('ctxtoolbar')), - help=_('context where this component should be displayed')), - } - - context = 'navcontentbottom' - - def call(self, view=None): - if self.cw_rset is None: - self.entity_call(self.cw_extra_kwargs.pop('entity')) - else: - self.cell_call(0, 0, view=view) - - def cell_call(self, row, col, view=None): - self.entity_call(self.cw_rset.get_entity(row, col), view=view) - - def entity_call(self, entity, view=None): - raise NotImplementedError() - +# abstract base class for navigation components ################################ class NavigationComponent(Component): """abstract base class for navigation components""" @@ -145,10 +108,9 @@ elif path == 'json': rql = params.pop('rql', self.cw_rset.printable_rql()) # latest 'true' used for 'swap' mode - url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % ( - json_dumps(params.get('divid', 'pageContent')), - json_dumps(rql), json_dumps(params.pop('vid', None)), - json_dumps(params)) + url = 'javascript: %s' % (js.replacePageChunk( + params.get('divid', 'pageContent'), rql, + params.pop('vid', None), params)) else: url = self._cw.build_url(path, **params) return url @@ -177,6 +139,405 @@ return self.next_page_link_templ % (url, title, content) +# new contextual components system ############################################# + +def override_ctx(cls, **kwargs): + cwpdefs = cls.cw_property_defs.copy() + cwpdefs['context'] = cwpdefs['context'].copy() + cwpdefs['context'].update(kwargs) + return cwpdefs + + +class EmptyComponent(Exception): + """some selectable component has actually no content and should not be + rendered + """ + +class Layout(Component): + __regid__ = 'layout' + __abstract__ = True + + def init_rendering(self): + """init view for rendering. Return true if we should go on, false + if we should stop now. + """ + view = self.cw_extra_kwargs['view'] + try: + view.init_rendering() + except Unauthorized, ex: + self.warning("can't render %s: %s", view, ex) + return False + except EmptyComponent: + return False + return True + + +class CtxComponent(AppObject): + """base class for contextual compontents. The following contexts are + predefined: + + * boxes: 'left', 'incontext', 'right' + * section: 'navcontenttop', 'navcontentbottom', 'navtop', 'navbottom' + * other: 'ctxtoolbar' + + The 'incontext', 'navcontenttop', 'navcontentbottom' and 'ctxtoolbar' + context are handled by the default primary view, others by the default main + template. + + All subclasses may not support all those contexts (for instance if it can't + be displayed as box, or as a toolbar icon). You may restrict allowed context + as followed: + + .. sourcecode:: python + + class MyComponent(CtxComponent): + cw_property_defs = override_ctx(CtxComponent, + vocabulary=[list of contexts]) + context = 'my default context' + + You can configure default component's context by simply giving appropriate + value to the `context` class attribute, as seen above. + """ + __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')), + _('context'): dict(type='String', default='left', + vocabulary=(_('left'), _('incontext'), _('right'), + _('navtop'), _('navbottom'), + _('navcontenttop'), _('navcontentbottom'), + _('ctxtoolbar')), + help=_('context where this component 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: + 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 EntityCtxComponent(CtxComponent): + """base class for boxes related to a single entity""" + __select__ = CtxComponent.__select__ & non_final_entity() & one_line_rset() + context = 'incontext' + contextual = True + + def __init__(self, *args, **kwargs): + super(EntityCtxComponent, 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 classes ################################################## + +class RQLCtxComponent(CtxComponent): + """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 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 + + +class EditRelationCtxComponent(EditRelationMixIn, EntityCtxComponent): + """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(htmlwidgets.BoxSeparator()) + self.items.extend(unrelated) + self.render_items(w) + + +class AjaxEditRelationCtxComponent(EntityCtxComponent): + __select__ = EntityCtxComponent.__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(AjaxEditRelationCtxComponent, 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 contextual components, deprecated ######################################## + +class EntityVComponent(Component): + """abstract base class for additinal components displayed in content + headers and footer according to: + + * the displayed entity's type + * a context (currently 'header' or 'footer') + + it should be configured using .accepts, .etype, .rtype, .target and + .context class attributes + """ + __metaclass__ = class_deprecated + __deprecation_warning__ = '[3.10] *VComponent classes are deprecated, use *CtxComponent instead (%(cls)s)' + + __registry__ = 'ctxcomponents' + __select__ = one_line_rset() + + cw_property_defs = { + _('visible'): dict(type='Boolean', default=True, + help=_('display the component or not')), + _('order'): dict(type='Int', default=99, + help=_('display order of the component')), + _('context'): dict(type='String', default='navtop', + vocabulary=(_('navtop'), _('navbottom'), + _('navcontenttop'), _('navcontentbottom'), + _('ctxtoolbar')), + help=_('context where this component should be displayed')), + } + + context = 'navcontentbottom' + + def call(self, view=None): + if self.cw_rset is None: + self.entity_call(self.cw_extra_kwargs.pop('entity')) + else: + self.cell_call(0, 0, view=view) + + def cell_call(self, row, col, view=None): + self.entity_call(self.cw_rset.get_entity(row, col), view=view) + + def entity_call(self, entity, view=None): + raise NotImplementedError() + + class RelatedObjectsVComponent(EntityVComponent): """a section to display some related entities""" __select__ = EntityVComponent.__select__ & partial_has_related_entities() @@ -203,8 +564,9 @@ self.w(u'') + VComponent = class_renamed('VComponent', Component, - 'VComponent is deprecated, use Component') + '[3.2] VComponent is deprecated, use Component') SingletonVComponent = class_renamed('SingletonVComponent', Component, - 'SingletonVComponent is deprecated, use ' + '[3.2] SingletonVComponent is deprecated, use ' 'Component and explicit registration control') diff -r 65a619eb31c4 -r b8287e54b528 web/test/unittest_views_navigation.py --- a/web/test/unittest_views_navigation.py Wed Aug 25 10:01:11 2010 +0200 +++ b/web/test/unittest_views_navigation.py Wed Aug 25 10:29:07 2010 +0200 @@ -122,26 +122,26 @@ # view = mock_object(is_primary=lambda x: True) # rset = self.execute('CWUser X LIMIT 1') # req = self.request() - # objs = self.vreg['contentnavigation'].poss_visible_objects( + # objs = self.vreg['ctxcomponents'].poss_visible_objects( # req, rset=rset, view=view, context='navtop') # # breadcrumbs should be in headers by default # clsids = set(obj.id for obj in objs) # self.failUnless('breadcrumbs' in clsids) - # objs = self.vreg['contentnavigation'].poss_visible_objects( + # objs = self.vreg['ctxcomponents'].poss_visible_objects( # req, rset=rset, view=view, context='navbottom') # # breadcrumbs should _NOT_ be in footers by default # clsids = set(obj.id for obj in objs) # self.failIf('breadcrumbs' in clsids) - # self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", ' + # self.execute('INSERT CWProperty P: P pkey "ctxcomponents.breadcrumbs.context", ' # 'P value "navbottom"') # # breadcrumbs should now be in footers # req.cnx.commit() - # objs = self.vreg['contentnavigation'].poss_visible_objects( + # objs = self.vreg['ctxcomponents'].poss_visible_objects( # req, rset=rset, view=view, context='navbottom') # clsids = [obj.id for obj in objs] # self.failUnless('breadcrumbs' in clsids) - # objs = self.vreg['contentnavigation'].poss_visible_objects( + # objs = self.vreg['ctxcomponents'].poss_visible_objects( # req, rset=rset, view=view, context='navtop') # clsids = [obj.id for obj in objs] diff -r 65a619eb31c4 -r b8287e54b528 web/test/unittest_viewselector.py --- a/web/test/unittest_viewselector.py Wed Aug 25 10:01:11 2010 +0200 +++ b/web/test/unittest_viewselector.py Wed Aug 25 10:29:07 2010 +0200 @@ -468,18 +468,18 @@ def test_properties(self): self.assertEquals(sorted(k for k in self.vreg['propertydefs'].keys() - if k.startswith('boxes.edit_box')), - ['boxes.edit_box.context', - 'boxes.edit_box.order', - 'boxes.edit_box.visible']) + if k.startswith('ctxcomponents.edit_box')), + ['ctxcomponents.edit_box.context', + 'ctxcomponents.edit_box.order', + 'ctxcomponents.edit_box.visible']) self.assertEquals([k for k in self.vreg['propertyvalues'].keys() if not k.startswith('system.version')], []) - self.assertEquals(self.vreg.property_value('boxes.edit_box.visible'), True) - self.assertEquals(self.vreg.property_value('boxes.edit_box.order'), 2) - self.assertEquals(self.vreg.property_value('boxes.possible_views_box.visible'), False) - self.assertEquals(self.vreg.property_value('boxes.possible_views_box.order'), 10) - self.assertRaises(UnknownProperty, self.vreg.property_value, 'boxes.actions_box') + self.assertEquals(self.vreg.property_value('ctxcomponents.edit_box.visible'), True) + self.assertEquals(self.vreg.property_value('ctxcomponents.edit_box.order'), 2) + self.assertEquals(self.vreg.property_value('ctxcomponents.possible_views_box.visible'), False) + self.assertEquals(self.vreg.property_value('ctxcomponents.possible_views_box.order'), 10) + self.assertRaises(UnknownProperty, self.vreg.property_value, 'ctxcomponents.actions_box') diff -r 65a619eb31c4 -r b8287e54b528 web/views/ajaxedit.py --- a/web/views/ajaxedit.py Wed Aug 25 10:01:11 2010 +0200 +++ b/web/views/ajaxedit.py Wed Aug 25 10:29:07 2010 +0200 @@ -22,7 +22,7 @@ from cubicweb import role from cubicweb.view import View from cubicweb.selectors import match_form_params, match_kwargs -from cubicweb.web.box import EditRelationMixIn, EditRelationBoxTemplate +from cubicweb.web.component import EditRelationMixIn class AddRelationView(EditRelationMixIn, View): """base class for view which let add entities linked by a given relation diff -r 65a619eb31c4 -r b8287e54b528 web/views/basecomponents.py --- a/web/views/basecomponents.py Wed Aug 25 10:01:11 2010 +0200 +++ b/web/views/basecomponents.py Wed Aug 25 10:29:07 2010 +0200 @@ -20,6 +20,7 @@ * the rql input form * the logged user link """ +from __future__ import with_statement __docformat__ = "restructuredtext en" _ = unicode @@ -27,9 +28,11 @@ from logilab.mtconverter import xml_escape from rql import parse -from cubicweb.selectors import (yes, multi_etypes_rset, match_form_params, +from cubicweb.selectors import (yes, multi_etypes_rset, + match_form_params, match_context, anonymous_user, authenticated_user) from cubicweb.schema import display_name +from cubicweb.utils import wrap_on_write from cubicweb.uilib import toggle_action from cubicweb.web import component from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator, @@ -166,18 +169,6 @@ self._cw.base_url(), xml_escape(title))) -class SeeAlsoVComponent(component.RelatedObjectsVComponent): - """display any entity's see also""" - __regid__ = 'seealso' - context = 'navcontentbottom' - rtype = 'see_also' - role = 'subject' - order = 40 - # register msg not generated since no entity use see_also in cubicweb itself - title = _('contentnavigation_seealso') - help = _('contentnavigation_seealso_description') - - class EtypeRestrictionComponent(component.Component): """displays the list of entity types contained in the resultset to be able to filter accordingly. @@ -229,17 +220,46 @@ self.w(u' | '.join(html)) self.w(u'') +# contextual components ######################################################## -class MetaDataComponent(component.EntityVComponent): +# class SeeAlsoVComponent(component.RelatedObjectsVComponent): +# """display any entity's see also""" +# __regid__ = 'seealso' +# context = 'navcontentbottom' +# rtype = 'see_also' +# role = 'subject' +# order = 40 +# # register msg not generated since no entity use see_also in cubicweb itself +# title = _('ctxcomponents_seealso') +# help = _('ctxcomponents_seealso_description') + + +class MetaDataComponent(component.EntityCtxComponent): __regid__ = 'metadata' context = 'navbottom' order = 1 - def cell_call(self, row, col, view=None): - self.wview('metadata', self.cw_rset, row=row, col=col) + def render_body(self, w): + self.entity.view('metadata', w=w) -def registration_callback(vreg): - vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) - if 'see_also' in vreg.schema: - vreg.register(SeeAlsoVComponent) +class SectionLayout(component.Layout): + __select__ = match_context('navtop', 'navbottom', + 'navcontenttop', 'navcontentbottom') + cssclass = 'section' + + def render(self, w): + if self.init_rendering(): + view = self.cw_extra_kwargs['view'] + w(u'
' % (self.cssclass, view.cssclass, + view.domid)) + with wrap_on_write(w, '

') as wow: + view.render_title(wow) + view.render_body(w) + w(u'

\n') + + +# def registration_callback(vreg): +# vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) +# if 'see_also' in vreg.schema: +# vreg.register(SeeAlsoVComponent) diff -r 65a619eb31c4 -r b8287e54b528 web/views/basetemplates.py --- a/web/views/basetemplates.py Wed Aug 25 10:01:11 2010 +0200 +++ b/web/views/basetemplates.py Wed Aug 25 10:29:07 2010 +0200 @@ -188,7 +188,7 @@ self.w(u'') def nav_column(self, view, context): - boxes = list(self._cw.vreg['boxes'].poss_visible_objects( + boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects( self._cw, rset=self.cw_rset, view=view, context=context)) if boxes: getlayout = self._cw.vreg['components'].select @@ -258,7 +258,7 @@ w(u'\n') w(u'