# HG changeset patch # User Sylvain Thénault # Date 1282723271 -7200 # Node ID 65a619eb31c45dd6d2d49517b481765bc770ff32 # Parent f76599a96238bc4803ddccd8954c4ae10ae6afb9 [boxes] introduce new boxes system * separate box content generation from its layout * refactor css classes to allow moving boxes and still get consistent ui On the way to contentnavigation/boxes unification (in a later patch) * * * some fixes for the previous (default_new_boxes_system) patch * * * some fixes for the previous (default_new_boxes_system) patch diff -r f76599a96238 -r 65a619eb31c4 devtools/testlib.py --- a/devtools/testlib.py Wed Aug 25 09:43:12 2010 +0200 +++ b/devtools/testlib.py Wed Aug 25 10:01:11 2010 +0200 @@ -903,7 +903,8 @@ for action in self.list_actions_for(rset): yield InnerTest(self._testname(rset, action.__regid__, 'action'), self._test_action, action) for box in self.list_boxes_for(rset): - yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render) + w = [].append + yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render, w) @staticmethod def _testname(rset, objid, objtype): diff -r f76599a96238 -r 65a619eb31c4 selectors.py --- a/selectors.py Wed Aug 25 09:43:12 2010 +0200 +++ b/selectors.py Wed Aug 25 10:01:11 2010 +0200 @@ -60,9 +60,9 @@ .. sourcecode:: python - class RSSIconBox(ExtResourcesBoxTemplate): + class RSSIconBox(box.Box): ''' just display the RSS icon on uniform result set ''' - __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity() + __select__ = box.Box.__select__ & non_final_entity() It takes into account: @@ -1220,6 +1220,15 @@ return 1 +@objectify_selector +@lltrace +def contextual(cls, req, view=None, **kwargs): + """Return 1 if view's contextual property is true""" + if view is not None and view.contextual: + return 1 + return 0 + + class match_view(ExpectedValueSelector): """Return 1 if a view is specified an as its registry id is in one of the expected view id given to the initializer. @@ -1231,6 +1240,18 @@ return 1 +class match_context(ExpectedValueSelector): + + @lltrace + def __call__(self, cls, req, context=None, **kwargs): + try: + if not context in self.expected: + return 0 + except AttributeError: + return 1 # class doesn't care about search state, accept it + return 1 + + @objectify_selector @lltrace def match_context_prop(cls, req, context=None, **kwargs): diff -r f76599a96238 -r 65a619eb31c4 tags.py --- a/tags.py Wed Aug 25 09:43:12 2010 +0200 +++ b/tags.py Wed Aug 25 10:01:11 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 . -"""helper classes to generate simple (X)HTML tags +"""helper classes to generate simple (X)HTML tags""" -""" __docformat__ = "restructuredtext en" from cubicweb.uilib import simple_sgml_tag, sgml_attributes diff -r f76599a96238 -r 65a619eb31c4 view.py --- a/view.py Wed Aug 25 09:43:12 2010 +0200 +++ b/view.py Wed Aug 25 10:01:11 2010 +0200 @@ -504,8 +504,13 @@ build_js = build_update_js_call # expect updatable component by default + @property + def domid(self): + return domid(self.__regid__) + + @deprecated('[3.10] use .domid property') def div_id(self): - return '' + return self.domid class Component(ReloadableMixIn, View): @@ -513,14 +518,20 @@ __registry__ = 'components' __select__ = yes() - # XXX huummm, much probably useless + # XXX huummm, much probably useless (should be...) htmlclass = 'mainRelated' + @property + def cssclass(self): + return '%s %s' % (self.htmlclass, domid(self.__regid__)) + + # XXX should rely on ReloadableMixIn.domid + @property + def domid(self): + return '%sComponent' % domid(self.__regid__) + + @deprecated('[3.10] use .cssclass property') def div_class(self): - return '%s %s' % (self.htmlclass, self.__regid__) - - # XXX a generic '%s%s' % (self.__regid__, self.__registry__.capitalize()) would probably be nicer - def div_id(self): - return '%sComponent' % self.__regid__ + return self.cssclass class Adapter(AppObject): diff -r f76599a96238 -r 65a619eb31c4 vregistry.py --- a/vregistry.py Wed Aug 25 09:43:12 2010 +0200 +++ b/vregistry.py Wed Aug 25 10:01:11 2010 +0200 @@ -467,7 +467,7 @@ self.load_module(module) def load_module(self, module): - self.info('loading %s', module) + self.info('loading %s from %s', module.__name__, module.__file__) if hasattr(module, 'registration_callback'): module.registration_callback(self) else: diff -r f76599a96238 -r 65a619eb31c4 web/action.py --- a/web/action.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/action.py Wed Aug 25 10:01:11 2010 +0200 @@ -45,35 +45,31 @@ for action in self.actual_actions(): menu.append(box.box_action(action)) + def html_class(self): + if self._cw.selected(self.url()): + return 'selected' + + def build_action(self, title, url, **kwargs): + return UnregisteredAction(self._cw, title, url, **kwargs) + def url(self): """return the url associated with this action""" raise NotImplementedError - def html_class(self): - if self._cw.selected(self.url()): - return 'selected' - if self.category: - return 'box' + self.category.capitalize() - - def build_action(self, title, path, **kwargs): - return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs) - class UnregisteredAction(Action): - """non registered action used to build boxes. Unless you set them - explicitly, .vreg and .schema attributes at least are None. - """ + """non registered action, used to build boxes""" category = None id = None - def __init__(self, req, rset, title, path, **kwargs): - Action.__init__(self, req, rset=rset) + def __init__(self, req, title, url, **kwargs): + Action.__init__(self, req) self.title = req._(title) - self._path = path + self._url = url self.__dict__.update(kwargs) def url(self): - return self._path + return self._url class LinkToEntityAction(Action): diff -r f76599a96238 -r 65a619eb31c4 web/box.py --- a/web/box.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/box.py Wed Aug 25 10:01:11 2010 +0200 @@ -21,13 +21,15 @@ _ = unicode 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 +from cubicweb import Unauthorized, role as get_role, target as get_target, tags 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.view import View, ReloadableMixIn +from cubicweb.appobject import AppObject +from cubicweb.view import View, ReloadableMixIn, Component from cubicweb.uilib import domid, js from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget, @@ -35,20 +37,84 @@ from cubicweb.web.action import UnregisteredAction -class BoxTemplate(View): - """base template for boxes, usually a (contextual) list of possible +def sort_by_category(actions, categories_in_order=None): + """return a list of (category, actions_sorted_by_title)""" + result = [] + actions_by_cat = {} + for action in actions: + actions_by_cat.setdefault(action.category, []).append( + (action.title, action) ) + for key, values in actions_by_cat.items(): + actions_by_cat[key] = [act for title, act in sorted(values)] + if categories_in_order: + for cat in categories_in_order: + if cat in actions_by_cat: + result.append( (cat, actions_by_cat[cat]) ) + for item in sorted(actions_by_cat.items()): + result.append(item) + return result + - actions. Various classes attributes may be used to control the box - rendering. +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) - You may override on of the formatting callbacks is this is not necessary - for your custom box. + 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)] - Classes inheriting from this class usually only have to override call - to fetch desired actions, and then to do something like :: + 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 - box.render(self.w) + +# 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() @@ -64,34 +130,289 @@ help=_('context where this box should be displayed')), } context = 'left' - htmlitemclass = 'boxItem' + 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 + 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) + """ + __metaclass__ = class_deprecated + __deprecation_warning__ = '*BoxTemplate classes are deprecated, use *Box instead' + + __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' def sort_actions(self, actions): """return a list of (category, actions_sorted_by_title)""" - result = [] - actions_by_cat = {} - for action in actions: - actions_by_cat.setdefault(action.category, []).append( - (action.title, action) ) - for key, values in actions_by_cat.items(): - actions_by_cat[key] = [act for title, act in sorted(values)] - for cat in self.categories_in_order: - if cat in actions_by_cat: - result.append( (cat, actions_by_cat[cat]) ) - for item in sorted(actions_by_cat.items()): - result.append(item) - return result + return sort_by_category(actions, self.categories_in_order) - def mk_action(self, title, path, escape=True, **kwargs): + def mk_action(self, title, url, escape=True, **kwargs): """factory function to create dummy actions compatible with the .format_actions method """ if escape: title = xml_escape(title) - return self.box_action(self._action(title, path, **kwargs)) + return self.box_action(self._action(title, url, **kwargs)) - def _action(self, title, path, **kwargs): - return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs) + def _action(self, title, url, **kwargs): + return UnregisteredAction(self._cw, title, url, **kwargs) # formating callbacks @@ -101,18 +422,14 @@ return u'' def box_action(self, action): - cls = getattr(action, 'html_class', lambda: None)() or self.htmlitemclass + klass = getattr(action, 'html_class', lambda: None)() return BoxLink(action.url(), self._cw._(action.title), - cls, self.boxitem_link_tooltip(action)) + klass, 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 """ rql = None @@ -156,21 +473,7 @@ self.cell_call(row, col, **kwargs) -class RelatedEntityBoxTemplate(EntityBoxTemplate): - __select__ = EntityBoxTemplate.__select__ & partial_has_related_entities() - - def cell_call(self, row, col, **kwargs): - entity = self.cw_rset.get_entity(row, col) - limit = self._cw.property_value('navigation.related-limit') + 1 - role = get_role(self) - self.w(u'') - - -class EditRelationBoxTemplate(ReloadableMixIn, EntityBoxTemplate): +class EditRelationBoxTemplate(EditRelationMixIn, EntityBoxTemplate): """base class for boxes which let add or remove entities linked by a given relation @@ -181,7 +484,8 @@ def cell_call(self, row, col, view=None, **kwargs): self._cw.add_js('cubicweb.ajax.js') entity = self.cw_rset.get_entity(row, col) - title = display_name(self._cw, self.rtype, get_role(self), context=entity.__regid__) + title = display_name(self._cw, self.rtype, get_role(self), + context=entity.__regid__) box = SideBoxWidget(title, self.__regid__) related = self.related_boxitems(entity) unrelated = self.unrelated_boxitems(entity) @@ -191,144 +495,13 @@ box.extend(unrelated) box.render(self.w) - def div_id(self): - return self.__regid__ - 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 - label = u'[%s] %s' % (xml_escape(url), label, - etarget.view('incontext')) + label = super(EditRelationBoxTemplate, self).box_item( + entity, etarget, rql, label) return RawBoxItem(label, liclass=u'invisible') - def related_boxitems(self, entity): - rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype - related = [] - for etarget in self.related_entities(entity): - related.append(self.box_item(entity, etarget, rql, u'-')) - return related - - def unrelated_boxitems(self, entity): - rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype - unrelated = [] - for etarget in self.unrelated_entities(entity): - unrelated.append(self.box_item(entity, etarget, rql, u'+')) - return unrelated - - def related_entities(self, entity): - return entity.related(self.rtype, get_role(self), entities=True) - - 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 AjaxEditRelationBoxTemplate(EntityBoxTemplate): - __select__ = EntityBoxTemplate.__select__ & partial_relation_possible() - - # 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 +AjaxEditRelationBoxTemplate = class_renamed( + 'AjaxEditRelationBoxTemplate', AjaxEditRelationBox, + '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationBox') - def cell_call(self, row, col, **kwargs): - req = self._cw - entity = self.cw_rset.get_entity(row, col) - related = entity.related(self.rtype, self.role) - rdef = entity.e_schema.rdef(self.rtype, self.role, self.target_etype) - if self.role == 'subject': - mayadd = rdef.has_perm(req, 'add', fromeid=entity.eid) - maydel = rdef.has_perm(req, 'delete', fromeid=entity.eid) - else: - mayadd = rdef.has_perm(req, 'add', toeid=entity.eid) - maydel = rdef.has_perm(req, 'delete', toeid=entity.eid) - if not (related or mayadd): - return - if mayadd or maydel: - req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js')) - _ = req._ - w = self.w - divid = domid(self.__regid__) + unicode(entity.eid) - w(u'\n') diff -r f76599a96238 -r 65a619eb31c4 web/component.py --- a/web/component.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/component.py Wed Aug 25 10:01:11 2010 +0200 @@ -197,7 +197,7 @@ rset = self._cw.execute(self.rql(), {'x': eid}) if not rset.rowcount: return - self.w(u'
' % self.div_class()) + self.w(u'
' % self.cssclass) self.w(u'

%s

\n' % self._cw._(self.title).capitalize()) self.wview(self.vid, rset) self.w(u'
') diff -r f76599a96238 -r 65a619eb31c4 web/data/actionBoxHeader.png Binary file web/data/actionBoxHeader.png has changed diff -r f76599a96238 -r 65a619eb31c4 web/data/boxHeader.png Binary file web/data/boxHeader.png has changed diff -r f76599a96238 -r 65a619eb31c4 web/data/contextFreeBoxHeader.png Binary file web/data/contextFreeBoxHeader.png has changed diff -r f76599a96238 -r 65a619eb31c4 web/data/contextualBoxHeader.png Binary file web/data/contextualBoxHeader.png has changed diff -r f76599a96238 -r 65a619eb31c4 web/data/cubicweb.calendar.css --- a/web/data/cubicweb.calendar.css Wed Aug 25 09:43:12 2010 +0200 +++ b/web/data/cubicweb.calendar.css Wed Aug 25 10:01:11 2010 +0200 @@ -230,7 +230,7 @@ .calendar th.month { font-weight:bold; padding-bottom:0.2em; - background: %(actionBoxTitleBgColor)s; + background: %(incontextBoxBodyBgColor)s; } .calendar th.month a{ diff -r f76599a96238 -r 65a619eb31c4 web/data/cubicweb.css --- a/web/data/cubicweb.css Wed Aug 25 09:43:12 2010 +0200 +++ b/web/data/cubicweb.css Wed Aug 25 10:01:11 2010 +0200 @@ -31,19 +31,22 @@ /* h3 { font-size:1.30769em; } */ /* scale traditional */ -h1 { font-size: %(h1FontSize)s; } +h1, +.vtitle { font-size: %(h1FontSize)s; } h2 { font-size: %(h2FontSize)s; } h3 { font-size: %(h3FontSize)s; } /* paddings */ -h1 { +h1, +.vtitle { border-bottom: %(h1BorderBottomStyle)s; padding: %(h1Padding)s; margin: %(h1Margin)s; color: %(h1Color)s; } -h1.plain { +h1.plain, +.vtitle { border-bottom: none; } @@ -100,7 +103,7 @@ } ol ol, -ul ul{ +ul ul { margin-left: 8px; margin-bottom : 0px; } @@ -113,7 +116,7 @@ margin-left: 1.5em; } -img{ +img { border: none; } @@ -139,7 +142,7 @@ border: 1px inset %(headerBgColor)s; } -hr{ +hr { border: none; border-bottom: 1px solid %(defaultColor)s; height: 1px; @@ -234,16 +237,11 @@ /* Popup on login box and userActionBox */ div.popupWrapper { position: relative; - z-index: 100; } div.popup { position: absolute; background: #fff; - /* background-color: #f0eff0; */ - /* background-image: url(popup.png); */ - /* background-repeat: repeat-x; */ - /* background-positon: top left; */ border: 1px solid %(listingBorderColor)s; border-top: none; text-align: left; @@ -261,12 +259,13 @@ margin: %(defaultLayoutMargin)s; } -table#mainLayout #navColumnLeft { +table#mainLayout td#navColumnLeft { width: 16em; padding-right: %(defaultLayoutMargin)s; + } -table#mainLayout #navColumnRight { +table#mainLayout td#navColumnRight { width: 16em; padding-left: %(defaultLayoutMargin)s; } @@ -301,28 +300,15 @@ color: %(defaultColor)s; } -/* rql bar */ - -div#rqlinput { - margin-bottom: %(defaultLayoutMargin)s; -} - -input#rql{ - padding: 0.25em 0.3em; - width: 99%; -} - -/* boxes */ +/* XXX old boxes, deprecated */ div.boxFrame { width: 100%; } div.boxTitle { - overflow: hidden; - font-weight: bold; color: #fff; - background: %(boxTitleBg)s; + background: %(contextualBoxTitleBgColor)s; } div.boxTitle span, @@ -331,14 +317,7 @@ white-space: nowrap; } -div.searchBoxFrame div.boxTitle, -div.greyBoxFrame div.boxTitle { - background: %(actionBoxTitleBg)s; -} - -div.sideBoxTitle span, -div.searchBoxFrame div.boxTitle span, -div.greyBoxFrame div.boxTitle span { +div.sideBoxTitle span { color: %(defaultColor)s; } @@ -352,34 +331,13 @@ border-top: none; } -a.boxMenu { - display: block; - padding: 1px 9px 1px 3px; - background: transparent %(bulletDownImg)s; -} - -a.boxMenu:hover { - background: %(sideBoxBodyBgColor)s %(bulletDownImg)s; - cursor: pointer; -} - -a.popupMenu { - background: transparent url("puce_down_black.png") 2% 6px no-repeat; - padding-left: 2em; -} - -div.searchBoxFrame div.boxContent { - padding: 4px 4px 3px; - background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; -} - div.shadow{ height: 14px; background: url("shadow.gif") no-repeat top right; } div.sideBoxTitle { - background: %(actionBoxTitleBg)s; + background: %(incontextBoxBodyBg)s; display: block; font-weight: bold; } @@ -400,11 +358,11 @@ div.sideBoxBody { padding: 0.2em 5px; - background: %(sideBoxBodyBg)s; + background: %(incontextBoxBodyBg)s; } div.sideBoxBody a { - color: %(sideBoxBodyColor)s; + color: %(incontextBoxBodyColor)s; } div.sideBoxBody a:hover { @@ -415,6 +373,174 @@ padding-right: 1em; } +/* boxes */ + +div.boxTitle { + overflow: hidden; + font-weight: bold; +} + +div.boxTitle span { + padding: 0px 0.5em; + white-space: nowrap; +} + +div.boxBody { + padding: 5px; + border-top: none; + background-color: %(leftrightBoxBodyBgColor)s; +} + +div.boxBody a { + color: %(leftrightBoxBodyColor)s; +} + +div.boxBody a:hover { + text-decoration: none; + cursor: pointer; + background-color: %(leftrightBoxBodyHoverBgColor)s; +} + +/* boxes contextual customization */ + +.contextFreeBox div.boxTitle { + background: %(contextFreeBoxTitleBg)s; + color: %(contextFreeBoxTitleColor)s; +} + +.contextualBox div.boxTitle { + background: %(contextualBoxTitleBg)s; + color: %(contextualBoxTitleColor)s; +} + +.primaryRight div.boxTitle { + background: %(incontextBoxTitleBg)s; + color: %(incontextBoxTitleColor)s; +} + +.primaryRight div.boxBody { + padding: 0.2em 5px; + background: %(incontextBoxBodyBgColor)s; +} + +.primaryRight div.boxBody a { + color: %(incontextBoxBodyColor)s; +} + +.primaryRight div.boxBody a:hover { + background-color: %(incontextBoxBodyHoverBgColor)s; +} + +.primaryRight div.boxFooter { + margin-bottom: 1em; +} + +#navColumnLeft div.boxFooter, #navColumnRight div.boxFooter{ + height: 14px; + background: url("shadow.gif") no-repeat top right; +} + +/* boxes lists and menus */ + +ul.boxListing { + margin: 0; + padding: 0; +} + +ul.boxListing ul { + padding: 1px 3px; +} + +ul.boxListing a { + color: %(defaultColor)s; + display: block; + padding: 1px 9px 1px 3px; +} + +ul.boxListing li { + margin: 0px; + padding: 0px; + background-image: none; +} + +ul.boxListing ul li { + margin: 0px; + padding-left: 8px; +} + +ul.boxListing ul li a { + padding-left: 10px; + background-image: url("bullet_orange.png"); + background-repeat: no-repeat; + background-position: 0 6px; +} + +ul.boxListing .selected { + color: %(aColor)s; + font-weight: bold; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s; +} + +a.boxMenu, +ul.boxListing a.boxMenu{ + display: block; + padding: 1px 3px; + background: transparent %(bulletDownImg)s; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s %(bulletDownImg)s; +} + +a.boxMenu:hover { + cursor: pointer; +} + +a.popupMenu { + background: transparent url("puce_down_black.png") 2% 6px no-repeat; + padding-left: 2em; +} + +/* custom boxes */ + +.search_box div.boxBody { + padding: 4px 4px 3px; + background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; +} + +.bookmarks_box ul.boxListing div a{ + background: #fff; + display: inline; + padding: 0; +} +.bookmarks_box ul.boxListing div a:hover{ + border-bottom: 1px solid #000; +} + +.download_box div.boxTitle { + background : #8fbc8f !important; +} + +.download_box div.boxBody { + background : #eefed9; +} + +/* search box and rql bar */ + +div#rqlinput { + margin-bottom: %(defaultLayoutMargin)s; +} + +input#rql{ + padding: 0.25em 0.3em; + width: 99%; +} + input.rqlsubmit{ display: block; width: 20px; @@ -424,7 +550,7 @@ } input#norql{ - width:13em; + width:155px; margin-right: 2px; } @@ -435,7 +561,7 @@ } div#userActionsBox { - width: 14em; + width: 15em; text-align: right; } @@ -445,20 +571,6 @@ padding-right: 2em; } -/* download box XXX move to its own file? */ -div.downloadBoxTitle{ - background : #8fbc8f; - font-weight: bold; -} - -div.downloadBox{ - font-weight: bold; -} - -div.downloadBox div.sideBoxBody{ - background : #eefed9; -} - /**************/ /* navigation */ /**************/ @@ -565,7 +677,7 @@ div#appMsg { margin-bottom: %(defaultLayoutMargin)s; - border: 1px solid %(actionBoxTitleBgColor)s; + border: 1px solid %(incontextBoxTitleBgColor)s; } .message { @@ -578,7 +690,7 @@ padding-left: 25px; background: %(msgBgColor)s url("critical.png") 2px center no-repeat; color: %(errorMsgColor)s; - border: 1px solid %(actionBoxTitleBgColor)s; + border: 1px solid %(incontextBoxTitleBgColor)s; } /* search-associate message */ @@ -733,7 +845,7 @@ input.button{ margin: 1em 1em 0px 0px; border: 1px solid %(buttonBorderColor)s; - border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s; + border-color: %(buttonBorderColor)s %(incontextBoxTitleBgColor)s %(incontextBoxTitleBgColor)s %(buttonBorderColor)s; } /* FileItemInnerView jquery.treeview.css */ @@ -753,74 +865,20 @@ ul.startup li, ul.section li { - margin-left:0px -} - -ul.boxListing { - margin: 0px; - padding: 0px 3px; -} - -ul.boxListing li, -ul.boxListing ul li { - margin: 0px; - padding: 0px; - background-image: none; -} - -ul.boxListing ul { - padding: 1px 3px; -} - -ul.boxListing a { - color: %(defaultColor)s; - display:block; - padding: 1px 9px 1px 3px; -} - -ul.boxListing .selected { - color: %(aColor)s; - font-weight: bold; -} - -ul.boxListing a.boxMenu:hover { - border-top: medium none; - background: %(sideBoxBodyBgColor)s %(bulletDownImg)s; -} - -ul.boxListing a.boxBookmark { - padding-left: 3px; - background-image: none; - background:#fff; + margin-left: 0px } ul.simple li, -ul.boxListing ul li , .popupWrapper ul li { background: transparent url("bullet_orange.png") no-repeat 0% 6px; } -ul.boxListing a.boxBookmark:hover, -ul.boxListing a:hover, -ul.boxListing ul li a:hover { - text-decoration: none; - background: %(sideBoxBodyBg)s; -} - -ul.boxListing ul li a:hover{ - background-color: transparent; -} - -ul.boxListing ul li a { - padding: 1px 3px 0px 10px; -} - ul.simple li { padding-left: 8px; } .popupWrapper ul { - padding:0.2em 0.3em; + padding: 0.2em 0.3em; margin-bottom: 0px; } @@ -853,20 +911,12 @@ .validateButton { margin: 1em 1em 0px 0px; border: 1px solid %(buttonBorderColor)s; - border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s; + border-color: %(buttonBorderColor)s %(incontextBoxTitleBgColor)s %(incontextBoxTitleBgColor)s %(buttonBorderColor)s; background: %(buttonBgColor)s url("button.png") bottom left repeat-x; } /********************************/ -/* placement of alt. view icons */ -/********************************/ - -.otherView { - float: right; -} - -/********************************/ -/* rest releted classes */ +/* rest related classes */ /********************************/ img.align-right { diff -r f76599a96238 -r 65a619eb31c4 web/data/cubicweb.form.css --- a/web/data/cubicweb.form.css Wed Aug 25 09:43:12 2010 +0200 +++ b/web/data/cubicweb.form.css Wed Aug 25 10:01:11 2010 +0200 @@ -229,6 +229,6 @@ margin: 1em 1em 0px 0px; border-width: 1px; border-style: solid; - border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s; + border-color: %(buttonBorderColor)s %(incontextBoxBodyBgColor)s %(incontextBoxBodyBgColor)s %(buttonBorderColor)s; background: %(buttonBgColor)s %(buttonBgImg)s; } diff -r f76599a96238 -r 65a619eb31c4 web/data/cubicweb.login.css --- a/web/data/cubicweb.login.css Wed Aug 25 09:43:12 2010 +0200 +++ b/web/data/cubicweb.login.css Wed Aug 25 10:01:11 2010 +0200 @@ -30,7 +30,7 @@ margin-left: -14em; width: 28em; background: #fff; - border: 2px solid %(actionBoxTitleBgColor)s; + border: 2px solid %(incontextBoxBodyBgColor)s; padding-bottom: 0.5em; text-align: center; } @@ -80,7 +80,7 @@ .loginButton { border: 1px solid #edecd2; - border-color: #edecd2 %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s #edecd2; + border-color: #edecd2 %(incontextBoxBodyBgColor)s %(incontextBoxBodyBgColor)s #edecd2; margin: 2px 0px 0px; background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; } diff -r f76599a96238 -r 65a619eb31c4 web/data/cubicweb.old.css --- a/web/data/cubicweb.old.css Wed Aug 25 09:43:12 2010 +0200 +++ b/web/data/cubicweb.old.css Wed Aug 25 10:01:11 2010 +0200 @@ -22,7 +22,8 @@ font-family: Verdana, sans-serif; } -h1 { +h1, +.vtitle { font-size: 188%; margin: 0.2em 0px 0.3em; border-bottom: 1px solid #000; @@ -243,10 +244,6 @@ } /* Popup on login box and userActionBox */ -div.popupWrapper{ - position:relative; - z-index:100; -} div.popup { position: absolute; @@ -303,18 +300,23 @@ div#rqlinput { border: 1px solid #cfceb7; margin-bottom: 8px; - padding: 3px; + padding: 1px; background: #cfceb7; + width: 100%; +} + +input#rql { + width: 99%; } -input#rql{ - width: 95%; +input.rqlsubmit{ + display: block; + width: 20px; + height: 20px; + background: %(buttonBgColor)s url("go.png") 50% 50% no-repeat; + vertical-align: bottom; } - -/* boxes */ -div.navboxes { - margin-top: 8px; -} +/* old boxes, deprecated */ div.boxFrame { width: 100%; @@ -324,25 +326,17 @@ padding-top: 0px; padding-bottom: 0.2em; font: bold 100% Georgia; - overflow: hidden; color: #fff; background: #ff9900 url("search.png") left bottom repeat-x; } -div.searchBoxFrame div.boxTitle, -div.greyBoxFrame div.boxTitle { - background: #cfceb7; -} - div.boxTitle span, div.sideBoxTitle span { padding: 0px 5px; white-space: nowrap; } -div.sideBoxTitle span, -div.searchBoxFrame div.boxTitle span, -div.greyBoxFrame div.boxTitle span { +div.sideBoxTitle span { color: #222211; } @@ -356,85 +350,6 @@ border-top: none; } -ul.boxListing { - margin: 0px; - padding: 0px 3px; -} - -ul.boxListing li, -ul.boxListing ul li { - display: inline; - margin: 0px; - padding: 0px; - background-image: none; -} - -ul.boxListing ul { - margin: 0px 0px 0px 7px; - padding: 1px 3px; -} - -ul.boxListing a { - color: #000; - display: block; - padding: 1px 9px 1px 3px; -} - -ul.boxListing .selected { - color: #FF4500; - font-weight: bold; -} - -ul.boxListing a.boxBookmark:hover, -ul.boxListing a:hover, -ul.boxListing ul li a:hover { - text-decoration: none; - background: #eeedd9; - color: #111100; -} - -ul.boxListing a.boxMenu:hover { - background: #eeedd9 url(puce_down.png) no-repeat scroll 98% 6px; - cursor:pointer; - border-top:medium none; - } -a.boxMenu { - background: transparent url("puce_down.png") 98% 6px no-repeat; - display: block; - padding: 1px 9px 1px 3px; -} - - -a.popupMenu { - background: transparent url("puce_down_black.png") 2% 6px no-repeat; - padding-left: 2em; -} - -ul.boxListing ul li a:hover { - background: #eeedd9 url("bullet_orange.png") 0% 6px no-repeat; -} - -a.boxMenu:hover { - background: #eeedd9 url("puce_down.png") 98% 6px no-repeat; - cursor: pointer; -} - -ul.boxListing a.boxBookmark { - padding-left: 3px; - background-image:none; - background:#fff; -} - -ul.boxListing ul li a { - background: #fff url("bullet_orange.png") 0% 6px no-repeat; - padding: 1px 3px 0px 10px; -} - -div.searchBoxFrame div.boxContent { - padding: 4px 4px 3px; - background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; -} - div.shadow{ height: 14px; background: url("shadow.gif") no-repeat top right; @@ -474,16 +389,164 @@ padding-right: 1em; } -input.rqlsubmit{ - background: #fffff8 url("go.png") 50% 50% no-repeat; - width: 20px; - height: 20px; - margin: 0px; +/* boxes */ + +div.navboxes { + padding-top: 0.5em; +} + +div.boxTitle { + overflow: hidden; + font-weight: bold; +} + +div.boxTitle span { + padding: 0px 0.5em; + white-space: nowrap; +} + +div.boxBody { + padding: 3px 3px; + border-top: none; + background-color: %(leftrightBoxBodyBgColor)s; +} + +div.boxBody a { + color: %(leftrightBoxBodyColor)s; +} + +div.boxBody a:hover { + text-decoration: none; + cursor: pointer; + background-color: %(leftrightBoxBodyHoverBgColor)s; +} + +/* boxes contextual customization */ + +.contextFreeBox div.boxTitle { + background: %(contextFreeBoxTitleBg)s; + color: %(contextFreeBoxTitleColor)s; +} + +.contextualBox div.boxTitle { + background: %(contextualBoxTitleBg)s; + color: %(contextualBoxTitleColor)s; +} + +.primaryRight div.boxTitle { + background: %(incontextBoxTitleBg)s; + color: %(incontextBoxTitleColor)s; +} + +.primaryRight div.boxBody { + padding: 0.2em 5px; + background: %(incontextBoxBodyBgColor)s; +} + +.primaryRight div.boxBody a { + color: %(incontextBoxBodyColor)s; +} + +.primaryRight div.boxBody a:hover { + background-color: %(incontextBoxBodyHoverBgColor)s; +} + +.primaryRight div.boxFooter { + margin-bottom: 1em; +} + +#navColumnLeft div.boxFooter, #navColumnRight div.boxFooter{ + height: 14px; + background: url("shadow.gif") no-repeat top right; +} + +/* boxes lists and menus */ + +ul.boxListing { + margin: 0; + padding: 0; } -input#norql{ - width:13em; - margin-right: 2px; +ul.boxListing ul { + padding: 1px 3px; +} + +ul.boxListing a { + color: %(defaultColor)s; + display: block; + padding: 1px 3px; +} + +ul.boxListing li { + margin: 0px; + padding: 0px; + background-image: none; +} + +ul.boxListing ul li { + margin: 0px; + padding-left: 1em; +} + +ul.boxListing ul li a { + padding-left: 10px; + background-image: url("bullet_orange.png"); + background-repeat: no-repeat; + background-position: 0 6px; +} + +ul.boxListing .selected { + color: %(aColor)s; + font-weight: bold; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s; +} + +a.boxMenu, +ul.boxListing a.boxMenu{ + display: block; + padding: 1px 3px; + background: transparent %(bulletDownImg)s; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s %(bulletDownImg)s; +} + +a.boxMenu:hover { + cursor: pointer; +} + +a.popupMenu { + background: transparent url("puce_down_black.png") 2% 6px no-repeat; + padding-left: 2em; +} + + +/* custom boxes */ + +.search_box div.boxBody { + padding: 4px 4px 3px; + background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; +} + +.bookmarks_box ul.boxListing div a { + background: #fff; + display: inline; + padding: 0; +} + +.download_box div.boxTitle { + background : #8fbc8f !important; +} + +.download_box div.boxBody { + background : #eefed9; + vertical-align: center; } /* user actions menu */ @@ -503,20 +566,6 @@ padding-right: 2em; } -/* download box XXX move to its own file? */ -div.downloadBoxTitle{ - background : #8FBC8F; - font-weight: bold; -} - -div.downloadBox{ - font-weight: bold; -} - -div.downloadBox div.sideBoxBody{ - background : #EEFED9; -} - /**************/ /* navigation */ /**************/ @@ -852,11 +901,3 @@ border-color:#edecd2 #cfceb7 #cfceb7 #edecd2; background: #fffff8 url("button.png") bottom left repeat-x; } - -/********************************/ -/* placement of alt. view icons */ -/********************************/ - -.otherView { - float: right; -} diff -r f76599a96238 -r 65a619eb31c4 web/data/cubicweb.tableview.css --- a/web/data/cubicweb.tableview.css Wed Aug 25 09:43:12 2010 +0200 +++ b/web/data/cubicweb.tableview.css Wed Aug 25 10:01:11 2010 +0200 @@ -6,7 +6,7 @@ font-weight: bold; background: #ebe8d9 url("button.png") repeat-x; padding: 0.3em; - border-bottom: 1px solid %(actionBoxTitleBgColor)s; + border-bottom: 1px solid %(incontextBoxBodyBgColor)s; text-align: left; } diff -r f76599a96238 -r 65a619eb31c4 web/data/incontextBoxHeader.png Binary file web/data/incontextBoxHeader.png has changed diff -r f76599a96238 -r 65a619eb31c4 web/data/uiprops.py --- a/web/data/uiprops.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/data/uiprops.py Wed Aug 25 10:01:11 2010 +0200 @@ -103,18 +103,36 @@ pageContentPadding = '1em' pageMinHeight = '800px' -# boxes -boxTitleBg = lazystr('%(headerBgColor)s url("boxHeader.png") repeat-x 50%% 50%%') -boxBodyBgColor = '#efefde' +# boxes ######################################################################## + +# title of contextFree / contextual boxes +contextFreeBoxTitleBgColor = '#CFCEB7' +contextFreeBoxTitleBg = lazystr('%(contextFreeBoxTitleBgColor)s url("contextFreeBoxHeader.png") repeat-x 50%% 50%%') +contextFreeBoxTitleColor = '#000' + +contextualBoxTitleBgColor = '#FF9900' +contextualBoxTitleBg = lazystr('%(contextualBoxTitleBgColor)s url("contextualBoxHeader.png") repeat-x 50%% 50%%') +contextualBoxTitleColor = '#FFF' -# action, search, sideBoxes -actionBoxTitleBgColor = '#cfceb7' -actionBoxTitleBg = lazystr('%(actionBoxTitleBgColor)s url("actionBoxHeader.png") repeat-x 50%% 50%%') -sideBoxBodyBgColor = '#f8f8ee' -sideBoxBodyBg = lazystr('%(sideBoxBodyBgColor)s') -sideBoxBodyColor = '#555544' +# title of 'incontext' boxes (eg displayed insinde the primary view) +incontextBoxTitleBgColor = lazystr('%(contextFreeBoxTitleBgColor)s') +incontextBoxTitleBg = lazystr('%(incontextBoxTitleBgColor)s url("incontextBoxHeader.png") repeat-x 50%% 50%%') +incontextBoxTitleColor = '#000' -# table listing & co +# body of boxes in the left or right column (whatever contextFree / contextual) +leftrightBoxBodyBgColor = '#FFF' +leftrightBoxBodyBg = lazystr('%(leftrightBoxBodyBgColor)s') +leftrightBoxBodyColor = '#black' +leftrightBoxBodyHoverBgColor = '#EEEDD9' + +# body of 'incontext' boxes (eg displayed insinde the primary view) +incontextBoxBodyBgColor = '#f8f8ee' +incontextBoxBodyBg = lazystr('%(incontextBoxBodyBgColor)s') +incontextBoxBodyColor = '#555544' +incontextBoxBodyHoverBgColor = lazystr('%(incontextBoxBodyBgColor)s') + + +# table listing & co ########################################################### listingBorderColor = '#ccc' listingHeaderBgColor = '#efefef' listingHihligthedBgColor = '#fbfbfb' diff -r f76599a96238 -r 65a619eb31c4 web/htmlwidgets.py --- a/web/htmlwidgets.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/htmlwidgets.py Wed Aug 25 10:01:11 2010 +0200 @@ -182,7 +182,10 @@ toggle_action(ident), self.link_class, self.label)) self._begin_menu(ident) for item in self.items: - item.render(self.w) + if hasattr(item, 'render'): + item.render(self.w) + else: + self.w(u'
  • %s
  • ' % item) self._end_menu() if self.isitem: self.w(u'') diff -r f76599a96238 -r 65a619eb31c4 web/views/ajaxedit.py --- a/web/views/ajaxedit.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/ajaxedit.py Wed Aug 25 10:01:11 2010 +0200 @@ -15,21 +15,19 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Set of views allowing edition of entities/relations using ajax +"""Set of views allowing edition of entities/relations using ajax""" -""" __docformat__ = "restructuredtext en" from cubicweb import role +from cubicweb.view import View from cubicweb.selectors import match_form_params, match_kwargs -from cubicweb.web.box import EditRelationBoxTemplate +from cubicweb.web.box import EditRelationMixIn, EditRelationBoxTemplate -class AddRelationView(EditRelationBoxTemplate): - """base class for view which let add entities linked - by a given relation +class AddRelationView(EditRelationMixIn, View): + """base class for view which let add entities linked by a given relation - subclasses should define at least id, rtype and target - class attributes. + subclasses should define at least id, rtype and target class attributes. """ __registry__ = 'views' __regid__ = 'xaddrelation' @@ -38,7 +36,7 @@ cw_property_defs = {} # don't want to inherit this from Box expected_kwargs = form_params = ('rtype', 'target') - build_js = EditRelationBoxTemplate.build_reload_js_call + build_js = EditRelationMixIn.build_reload_js_call def cell_call(self, row, col, rtype=None, target=None, etype=None): self.rtype = rtype or self._cw.form['rtype'] @@ -53,13 +51,13 @@ etypes = rschema.subjects(entity.e_schema) if len(etypes) == 1: self.etype = etypes[0] - self.w(u'
    ' % self.__regid__) + self.w(u'
    ' % self.domid) self.w(u'

    %s

    ' % self._cw._('relation %(relname)s of %(ent)s') % {'relname': rschema.display_name(self._cw, role(self)), 'ent': entity.view('incontext')}) self.w(u'
      ') for boxitem in self.unrelated_boxitems(entity): - boxitem.render(self.w) + self.w('' % botitem) self.w(u'
    ') def unrelated_entities(self, entity): @@ -74,11 +72,4 @@ ordermethod='fetch_order') self.pagination(self._cw, rset, w=self.w) return rset.entities() - # in other cases, use vocabulary functions - entities = [] - # XXX to update for 3.2 - for _, eid in entity.vocabulary(self.rtype, role(self)): - if eid is not None: - rset = self._cw.eid_rset(eid) - entities.append(rset.get_entity(0, 0)) - return entities + super(AddRelationView, self).unrelated_entities(self) diff -r f76599a96238 -r 65a619eb31c4 web/views/basecomponents.py --- a/web/views/basecomponents.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/basecomponents.py Wed Aug 25 10:01:11 2010 +0200 @@ -148,8 +148,7 @@ self.w(u'
    \n' % (toggle_action('appMsg'), (msgs and ' ' or 'hidden'))) for msg in msgs: - self.w(u'
    %s
    ' % ( - self.div_id(), msg)) + self.w(u'
    %s
    ' % (self.domid, msg)) self.w(u'
    ') diff -r f76599a96238 -r 65a619eb31c4 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/basecontrollers.py Wed Aug 25 10:01:11 2010 +0200 @@ -26,7 +26,7 @@ from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError, AuthenticationError, typed_eid) -from cubicweb.utils import json, json_dumps +from cubicweb.utils import UStringIO, json, json_dumps from cubicweb.selectors import authenticated_user, anonymous_user, match_form_params from cubicweb.mail import format_mail from cubicweb.web import Redirect, RemoteCallFailed, DirectResponse @@ -345,14 +345,19 @@ def _call_view(self, view, paginate=False, **kwargs): divid = self._cw.form.get('divid', 'pageContent') # we need to call pagination before with the stream set - stream = view.set_stream() + try: + stream = view.set_stream() + except AttributeError: + stream = UStringIO() + kwargs['w'] = stream.write + assert not paginate if paginate: if divid == 'pageContent': # mimick main template behaviour stream.write(u'
    ') vtitle = self._cw.form.get('vtitle') if vtitle: - stream.write(u'

    %s

    \n' % vtitle) + stream.write(u'
    %s
    \n' % vtitle) view.paginate() if divid == 'pageContent': stream.write(u'
    ') diff -r f76599a96238 -r 65a619eb31c4 web/views/basetemplates.py --- a/web/views/basetemplates.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/basetemplates.py Wed Aug 25 10:01:11 2010 +0200 @@ -127,7 +127,7 @@ w(u'
    \n') vtitle = self._cw.form.get('vtitle') if vtitle: - w(u'

    %s

    \n' % xml_escape(vtitle)) + w(u'
    %s
    \n' % xml_escape(vtitle)) # display entity type restriction component etypefilter = self._cw.vreg['components'].select_or_none( 'etypenavigation', self._cw, rset=self.cw_rset) @@ -191,6 +191,7 @@ boxes = list(self._cw.vreg['boxes'].poss_visible_objects( self._cw, rset=self.cw_rset, view=view, context=context)) if boxes: + getlayout = self._cw.vreg['components'].select self.w(u'') -class BookmarksBox(box.UserRQLBoxTemplate): +class BookmarksBox(box.Box): """display a box containing all user's bookmarks""" __regid__ = 'bookmarks_box' + + title = _('bookmarks') order = 40 - title = _('bookmarks') rql = ('Any B,T,P ORDERBY lower(T) ' 'WHERE B is Bookmark,B title T, B path P, B bookmarked_by U, ' 'U eid %(x)s') - etype = 'Bookmark' - rtype = 'bookmarked_by' + def init_rendering(self): + ueid = self._cw.user.eid + self.bookmarks_rset = self._cw.execute(self.rql, {'x': ueid}) + rschema = self._cw.vreg.schema.rschema('bookmarked_by') + eschema = self._cw.vreg.schema.eschema('Bookmark') + self.can_delete = rschema.has_perm(self._cw, 'delete', toeid=ueid) + self.can_edit = (eschema.has_perm(self._cw, 'add') and + rschema.has_perm(self._cw, 'add', toeid=ueid)) + if not self.bookmarks_rset and not self.can_edit: + raise box.EmptyComponent() + self.items = [] - def call(self, **kwargs): + def render_body(self, w): + ueid = self._cw.user.eid req = self._cw - ueid = req.user.eid - try: - rset = req.execute(self.rql, {'x': ueid}) - except Unauthorized: - # can't access to something in the query, forget this box - return - box = BoxWidget(req._(self.title), self.__regid__) - box.listing_class = 'sideBox' - rschema = self._cw.vreg.schema.rschema(self.rtype) - eschema = self._cw.vreg.schema.eschema(self.etype) - candelete = rschema.has_perm(req, 'delete', toeid=ueid) - if candelete: + if self.can_delete: req.add_js('cubicweb.ajax.js') - else: - dlink = None - for bookmark in rset.entities(): - label = '%s' % (xml_escape(bookmark.action_url()), - xml_escape(bookmark.title)) - if candelete: + for bookmark in self.bookmarks_rset.entities(): + label = self.build_link(bookmark.title, bookmark.action_url()) + if self.can_delete: dlink = u'[-]' % ( bookmark.eid, _('delete this bookmark')) - label = '%s %s' % (dlink, label) - box.append(RawBoxItem(label)) - if eschema.has_perm(req, 'add') and rschema.has_perm(req, 'add', toeid=ueid): - boxmenu = BoxMenu(req._('manage bookmarks')) + label = '
    %s %s
    ' % (dlink, label) + self.append(label) + if self.can_edit: + menu = BoxMenu(req._('manage bookmarks')) linkto = 'bookmarked_by:%s:subject' % ueid # use a relative path so that we can move the instance without # loosing bookmarks path = req.relative_path() - # XXX if vtitle specified in params, extract it and use it as default value - # for bookmark's title + # XXX if vtitle specified in params, extract it and use it as + # default value for bookmark's title url = req.vreg['etypes'].etype_class('Bookmark').cw_create_url( req, __linkto=linkto, path=path) - boxmenu.append(self.mk_action(req._('bookmark this page'), url, - category='manage', id='bookmark')) - if rset: + menu.append(self.build_link(req._('bookmark this page'), url)) + if self.bookmarks_rset: if req.user.is_in_group('managers'): bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, U eid %s' % ueid - erset = rset + erset = self.bookmarks_rset else: # we can't edit shared bookmarks we don't own bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, B owned_by U, U eid %(x)s' @@ -130,11 +125,10 @@ build_descr=False) bookmarksrql %= {'x': ueid} if erset: - url = self._cw.build_url(vid='muledit', rql=bookmarksrql) - boxmenu.append(self.mk_action(self._cw._('edit bookmarks'), url, category='manage')) + url = req.build_url(vid='muledit', rql=bookmarksrql) + menu.append(self.build_link(req._('edit bookmarks'), url)) url = req.user.absolute_url(vid='xaddrelation', rtype='bookmarked_by', target='subject') - boxmenu.append(self.mk_action(self._cw._('pick existing bookmarks'), url, category='manage')) - box.append(boxmenu) - if not box.is_empty(): - box.render(self.w) + menu.append(self.build_link(req._('pick existing bookmarks'), url)) + self.append(menu) + self.render_items(w) diff -r f76599a96238 -r 65a619eb31c4 web/views/boxes.py --- a/web/views/boxes.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/boxes.py Wed Aug 25 10:01:11 2010 +0200 @@ -18,10 +18,11 @@ """Generic boxes for CubicWeb web client: * actions box -* possible views box +* search box -additional (disabled by default) boxes +Additional boxes (disabled by default): * schema box +* possible views box * startup views box """ @@ -31,42 +32,41 @@ from warnings import warn from logilab.mtconverter import xml_escape +from logilab.common.deprecation import class_deprecated -from cubicweb.selectors import match_user_groups, non_final_entity +from cubicweb import Unauthorized +from cubicweb.selectors import (match_user_groups, match_context, match_kwargs, + non_final_entity, nonempty_rset) from cubicweb.view import EntityView from cubicweb.schema import display_name -from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem -from cubicweb.web.box import BoxTemplate +from cubicweb.web import box, htmlwidgets +# XXX bw compat, some cubes import this class from here +BoxTemplate = box.BoxTemplate +BoxHtml = htmlwidgets.BoxHtml -class EditBox(BoxTemplate): # XXX rename to ActionsBox +class EditBox(box.Box): # XXX rename to ActionsBox """ box with all actions impacting the entity displayed: edit, copy, delete change state, add related entities """ __regid__ = 'edit_box' - __select__ = BoxTemplate.__select__ & non_final_entity() + __select__ = box.Box.__select__ & non_final_entity() title = _('actions') order = 2 + contextual = True - def call(self, view=None, **kwargs): + def init_rendering(self): + super(EditBox, self).init_rendering() _ = self._cw._ - title = _(self.title) - if self.cw_rset: - etypes = self.cw_rset.column_types(0) - if len(etypes) == 1: - plural = self.cw_rset.rowcount > 1 and 'plural' or '' - etypelabel = display_name(self._cw, iter(etypes).next(), plural) - title = u'%s - %s' % (title, etypelabel.lower()) - box = BoxWidget(title, self.__regid__, _class="greyBoxFrame") self._menus_in_order = [] self._menus_by_id = {} # build list of actions actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset, - view=view) + **self.cw_extra_kwargs) other_menu = self._get_menu('moreactions', _('more actions')) - for category, defaultmenu in (('mainactions', box), + for category, defaultmenu in (('mainactions', self), ('moreactions', other_menu), ('addrelated', None)): for action in actions.get(category, ()): @@ -81,16 +81,28 @@ menu = defaultmenu action.fill_menu(self, menu) # if we've nothing but actions in the other_menu, add them directly into the box - if box.is_empty() and len(self._menus_by_id) == 1 and not other_menu.is_empty(): - box.items = other_menu.items - other_menu.items = [] + if not self.items and len(self._menus_by_id) == 1 and not other_menu.is_empty(): + self.items = other_menu.items else: # ensure 'more actions' menu appears last self._menus_in_order.remove(other_menu) self._menus_in_order.append(other_menu) - for submenu in self._menus_in_order: - self.add_submenu(box, submenu) - if not box.is_empty(): - box.render(self.w) + for submenu in self._menus_in_order: + self.add_submenu(self, submenu) + if not self.items: + raise box.EmptyComponent() + + def render_title(self, w): + title = self._cw._(self.title) + if self.cw_rset: + etypes = self.cw_rset.column_types(0) + if len(etypes) == 1: + plural = self.cw_rset.rowcount > 1 and 'plural' or '' + etypelabel = display_name(self._cw, iter(etypes).next(), plural) + title = u'%s - %s' % (title, etypelabel.lower()) + w(title) + + def render_body(self, w): + self.render_items(w) def _get_menu(self, id, title=None, label_prefix=None): try: @@ -98,7 +110,7 @@ except KeyError: if title is None: title = self._cw._(id) - self._menus_by_id[id] = menu = BoxMenu(title) + self._menus_by_id[id] = menu = htmlwidgets.BoxMenu(title) menu.label_prefix = label_prefix self._menus_in_order.append(menu) return menu @@ -108,19 +120,22 @@ if len(submenu.items) == 1 and not appendanyway: boxlink = submenu.items[0] if submenu.label_prefix: - boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label) + # XXX iirk + if hasattr(boxlink, 'label'): + boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label) + else: + submenu.items[0] = u'%s %s' % (submenu.label_prefix, boxlink) box.append(boxlink) elif submenu.items: box.append(submenu) elif appendanyway: - box.append(RawBoxItem(xml_escape(submenu.label))) + box.append(xml_escape(submenu.label)) -class SearchBox(BoxTemplate): +class SearchBox(box.Box): """display a box with a simple search form""" __regid__ = 'search_box' - visible = True # enabled by default title = _('search') order = 0 formdef = u"""
    @@ -130,74 +145,123 @@ - -
    """ + + """ - def call(self, view=None, **kwargs): - req = self._cw - if req.form.pop('__fromsearchbox', None): - rql = req.form.get('rql', '') + def render_title(self, w): + w(u"""%s""" + % self._cw._(self.title)) + + def render_body(self, w): + if self._cw.form.pop('__fromsearchbox', None): + rql = self._cw.form.get('rql', '') else: rql = '' - form = self.formdef % (req.build_url('view'), req.next_tabindex(), - xml_escape(rql), req.next_tabindex()) - title = u"""%s""" % req._(self.title) - box = BoxWidget(title, self.__regid__, _class="searchBoxFrame", islist=False, escape=False) - box.append(BoxHtml(form)) - box.render(self.w) + w(self.formdef % (self._cw.build_url('view'), self._cw.next_tabindex(), + xml_escape(rql), self._cw.next_tabindex())) # boxes disabled by default ################################################### -class PossibleViewsBox(BoxTemplate): +class PossibleViewsBox(box.Box): """display a box containing links to all possible views""" __regid__ = 'possible_views_box' - __select__ = BoxTemplate.__select__ & match_user_groups('users', 'managers') visible = False title = _('possible views') order = 10 - def call(self, **kwargs): - box = BoxWidget(self._cw._(self.title), self.__regid__) - views = [v for v in self._cw.vreg['views'].possible_views(self._cw, - rset=self.cw_rset) - if v.category != 'startupview'] - for category, views in self.sort_actions(views): - menu = BoxMenu(category) + def init_rendering(self): + self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw, + rset=self.cw_rset) + if v.category != 'startupview'] + if not self.views: + raise box.EmptyComponent() + self.items = [] + + def render_body(self, w): + for category, views in box.sort_by_category(self.views): + menu = htmlwidgets.BoxMenu(category) for view in views: menu.append(self.box_action(view)) - box.append(menu) - if not box.is_empty(): - box.render(self.w) + self.append(menu) + self.render_items(w) -class StartupViewsBox(BoxTemplate): +class StartupViewsBox(PossibleViewsBox): """display a box containing links to all startup views""" __regid__ = 'startup_views_box' + visible = False # disabled by default title = _('startup views') order = 70 - def call(self, **kwargs): - box = BoxWidget(self._cw._(self.title), self.__regid__) - for view in self._cw.vreg['views'].possible_views(self._cw, None): - if view.category == 'startupview': - box.append(self.box_action(view)) - if not box.is_empty(): - box.render(self.w) + def init_rendering(self): + self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw) + if v.category == 'startupview'] + if not self.views: + raise box.EmptyComponent() + self.items = [] -# helper classes ############################################################## +class RsetBox(box.Box): + """helper view class to display an rset in a sidebox""" + __select__ = nonempty_rset() & match_kwargs('title', 'vid') + __regid__ = 'rsetbox' + cw_property_defs = {} + context = 'incontext' + + @property + def domid(self): + return super(RsetBox, self).domid + unicode(abs(id(self))) + def render_title(self, w): + w(self.cw_extra_kwargs['title']) + + def render_body(self, w): + self._cw.view(self.cw_extra_kwargs['vid'], self.cw_rset, w=w) + + # helper classes ############################################################## class SideBoxView(EntityView): """helper view class to display some entities in a sidebox""" + __metaclass__ = class_deprecated + __deprecation_warning__ = 'SideBoxView is deprecated, use RsetBox instead' + __regid__ = 'sidebox' - def call(self, boxclass='sideBox', title=u''): + def call(self, **kwargs): """display a list of entities by calling their view""" - if title: - self.w(u'
    %s
    ' % title) - self.w(u'
    ' % boxclass) - self.wview('autolimited', self.cw_rset, **self.cw_extra_kwargs) - self.w(u'
    \n
    \n') + box = self._cw.vreg['boxes'].select('rsetbox', self._cw, rset=self.cw_rset, + vid='autolimited', title=title, + **self.cw_extra_kwargs) + box.render(self.w) + + +class ContextualBoxLayout(box.Layout): + __select__ = match_context('incontext', 'left', 'right') & box.contextual() + # predefined class in cubicweb.css: contextualBox | contextFreeBox + # XXX: navigationBox | actionBox + cssclass = 'contextualBox' + + def render(self, w): + view = self.cw_extra_kwargs['view'] + try: + view.init_rendering() + except Unauthorized, ex: + self.warning("can't render %s: %s", view, ex) + return + except box.EmptyComponent: + return + w(u'
    ' % (self.cssclass, view.cssclass, + view.domid)) + w(u'
    ') + view.render_title(w) + w(u'
    \n
    ') + view.render_body(w) + # boxFooter div is a CSS place holder (for shadow for example) + w(u'
    \n') + + +class ContextFreeBoxLayout(ContextualBoxLayout): + __select__ = match_context('incontext', 'left', 'right') & ~box.contextual() + cssclass = 'contextFreeBox' diff -r f76599a96238 -r 65a619eb31c4 web/views/facets.py --- a/web/views/facets.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/facets.py Wed Aug 25 10:01:11 2010 +0200 @@ -25,7 +25,7 @@ from cubicweb.selectors import (non_final_entity, multi_lines_rset, match_context_prop, yes, relation_possible) from cubicweb.utils import json_dumps -from cubicweb.web.box import BoxTemplate +from cubicweb.web import box from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet, prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst, _prepare_vocabulary_rqlst) @@ -38,13 +38,13 @@ return 0 -class FilterBox(BoxTemplate): +class FilterBox(box.Box): """filter results of a query""" __regid__ = 'filter_box' __select__ = (((non_final_entity() & multi_lines_rset()) | contextview_selector() ) & match_context_prop()) - context = 'left' + context = 'left' # XXX doesn't support 'incontext', only 'left' or 'right' title = _('boxes_filter_box') visible = True # functionality provided by the search box by default order = 1 @@ -61,7 +61,8 @@ """ return {} - def _get_context(self, view): + def _get_context(self): + view = self.cw_extra_kwargs.get('view') context = getattr(view, 'filter_box_context_info', lambda: None)() if context: rset, vid, divid, paginate = context @@ -71,14 +72,15 @@ paginate = view and view.paginable return rset, vid, divid, paginate - def call(self, view=None): + def render(self, w, **kwargs): req = self._cw req.add_js( self.needs_js ) req.add_css( self.needs_css) if self.roundcorners: req.html_headers.add_onload('jQuery(".facet").corner("tl br 10px");') - rset, vid, divid, paginate = self._get_context(view) - if rset.rowcount < 2: # XXX done by selectors, though maybe necessary when rset has been hijacked + rset, vid, divid, paginate = self._get_context() + # XXX done by selectors, though maybe necessary when rset has been hijacked + if rset.rowcount < 2: return rqlst = rset.syntax_tree() # union not yet supported @@ -97,7 +99,6 @@ return if vid is None: vid = req.form.get('vid') - w = self.w if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'): w(self.bookmark_link(rset)) w(u'
    ' % ( @@ -110,7 +111,7 @@ hiddens[param] = req.form[param] filter_hiddens(w, **hiddens) for wdg in widgets: - wdg.render(w=self.w) + wdg.render(w=w) w(u'\n
    \n') def bookmark_link(self, rset): diff -r f76599a96238 -r 65a619eb31c4 web/views/idownloadable.py --- a/web/views/idownloadable.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/idownloadable.py Wed Aug 25 10:01:11 2010 +0200 @@ -42,22 +42,25 @@ w(u'%s %s' % (xml_escape(entity.cw_adapt_to('IDownloadable').download_url()), req.uiprops['DOWNLOAD_ICON'], - _('download icon'), xml_escape(label or entity.dc_title()))) + req._('download icon'), xml_escape(label or entity.dc_title()))) w(u'%s
    ' % footer) w(u'
    \n') -class DownloadBox(box.EntityBoxTemplate): +class DownloadBox(box.EntityBox): __regid__ = 'download_box' # no download box for images - # XXX primary_view selector ? - __select__ = (one_line_rset() & match_context_prop() - & adaptable('IDownloadable') & ~has_mimetype('image/')) + __select__ = (box.EntityBox.__select__ & + adaptable('IDownloadable') & ~has_mimetype('image/')) + order = 10 + title = _('download') - def cell_call(self, row, col, title=None, label=None, **kwargs): - entity = self.cw_rset.get_entity(row, col) - download_box(self.w, entity, title, label) + def render_body(self, w): + w(u'%s %s' + % (xml_escape(self.entity.cw_adapt_to('IDownloadable').download_url()), + self._cw.uiprops['DOWNLOAD_ICON'], + self._cw._('download icon'), xml_escape(self.entity.dc_title()))) class DownloadView(EntityView): diff -r f76599a96238 -r 65a619eb31c4 web/views/primary.py --- a/web/views/primary.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/primary.py Wed Aug 25 10:01:11 2010 +0200 @@ -194,42 +194,45 @@ try: label, rset, vid, dispctrl = box except ValueError: - warn('[3.5] box views should now be defined as a 4-uple (label, rset, vid, dispctrl), ' - 'please update %s' % self.__class__.__name__, - DeprecationWarning) label, rset, vid = box dispctrl = {} + warn('[3.10] box views should now be a RsetBox instance, ' + 'please update %s' % self.__class__.__name__, + DeprecationWarning) self.w(u'') else: - try: - box.render(w=self.w, row=self.cw_row) - except NotImplementedError: - # much probably a context insensitive box, which only implements - # .call() and not cell_call() + try: + box.render(w=self.w, row=self.cw_row) + except NotImplementedError: + # much probably a context insensitive box, which only + # implements .call() and not cell_call() + # XXX shouldn't occurs with the new box system box.render(w=self.w) def _prepare_side_boxes(self, entity): sideboxes = [] + boxesreg = self._cw.vreg['boxes'] for rschema, tschemas, role, dispctrl in self._section_def(entity, 'sideboxes'): rset = self._relation_rset(entity, rschema, role, dispctrl) if not rset: continue label = self._rel_label(entity, rschema, role, dispctrl) - vid = dispctrl.get('vid', 'sidebox') - sideboxes.append( (label, rset, vid, dispctrl) ) - sideboxes += self._cw.vreg['boxes'].poss_visible_objects( - self._cw, rset=self.cw_rset, row=self.cw_row, view=self, - context='incontext') + vid = dispctrl.get('vid', 'autolimited') + box = boxesreg.select('rsetbox', self._cw, rset=rset, + vid=vid, title=label, dispctrl=dispctrl, + context='incontext') + sideboxes.append(box) + sideboxes += boxesreg.poss_visible_objects( + self._cw, rset=self.cw_rset, row=self.cw_row, view=self, + context='incontext') # XXX since we've two sorted list, it may be worth using bisect def get_order(x): - if isinstance(x, tuple): - # x is a view box (label, rset, vid, dispctrl) - # default to 1000 so view boxes occurs after component boxes - return x[-1].get('order', 1000) - # x is a component box - return x.cw_propval('order') + if 'order' in x.cw_property_defs: + return x.cw_propval('order') + # default to 9999 so view boxes occurs after component boxes + return x.cw_extra_kwargs.get('dispctrl', {}).get('order', 9999) return sorted(sideboxes, key=get_order) def _section_def(self, entity, where): diff -r f76599a96238 -r 65a619eb31c4 web/views/treeview.py --- a/web/views/treeview.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/treeview.py Wed Aug 25 10:01:11 2010 +0200 @@ -110,7 +110,7 @@ __regid__ = 'treeview' itemvid = 'treeitemview' subvid = 'oneline' - css_classes = 'treeview widget' + cssclass = 'treeview widget' title = _('tree view') def _init_params(self, subvid, treeid, initial_load, initial_thru_ajax, morekwargs): @@ -144,7 +144,7 @@ if toplevel: self._init_headers(treeid, toplevel_thru_ajax) ulid = ' id="tree-%s"' % treeid - self.w(u'' % (ulid, self.css_classes)) + self.w(u'' % (ulid, self.cssclass)) # XXX force sorting on x.sortvalue() (which return dc_title by default) # we need proper ITree & co specification to avoid this. # (pb when type ambiguity at the other side of the tree relation, @@ -171,7 +171,7 @@ """specific version of the treeview to display file trees """ __regid__ = 'filetree' - css_classes = 'treeview widget filetree' + cssclass = 'treeview widget filetree' title = _('file tree view') def call(self, subvid=None, treeid=None, initial_load=True, **kwargs): diff -r f76599a96238 -r 65a619eb31c4 web/views/xmlrss.py --- a/web/views/xmlrss.py Wed Aug 25 09:43:12 2010 +0200 +++ b/web/views/xmlrss.py Wed Aug 25 10:01:11 2010 +0200 @@ -148,25 +148,25 @@ return entity.cw_adapt_to('IFeed').rss_feed_url() -class RSSIconBox(box.BoxTemplate): +class RSSIconBox(box.Box): """just display the RSS icon on uniform result set""" __regid__ = 'rss' - __select__ = (box.BoxTemplate.__select__ + __select__ = (box.Box.__select__ & appobject_selectable('components', 'rss_feed_url')) visible = False order = 999 - def call(self, **kwargs): + def render(self, w, **kwargs): try: rss = self._cw.uiprops['RSS_LOGO'] except KeyError: self.error('missing RSS_LOGO external resource') return urlgetter = self._cw.vreg['components'].select('rss_feed_url', self._cw, - rset=self.cw_rset) + rset=self.cw_rset) url = urlgetter.feed_url() - self.w(u'rss\n' % (xml_escape(url), rss)) + w(u'rss\n' % (xml_escape(url), rss)) class RSSView(XMLView):