# HG changeset patch # User Sylvain Thénault # Date 1286577482 -7200 # Node ID de95bbed878148dd03da3005df2043a11844692a # Parent c8a5ac2d1eaaf96fb90352d5101fc3ecf8de5913 [components] refactor main template header: make it much more flexible by using CtxComponent * rename userlink class (not private) with somewhat bw compat * make some cosmetic adjustements on log-in/registration components * update to component api, though this may breaks some sub-classes (for application customizing the breadcrumbs component for instance) diff -r c8a5ac2d1eaa -r de95bbed8781 vregistry.py --- a/vregistry.py Sat Oct 09 00:05:52 2010 +0200 +++ b/vregistry.py Sat Oct 09 00:38:02 2010 +0200 @@ -195,18 +195,6 @@ select_object = deprecated('[3.6] use select_or_none instead of select_object' )(select_or_none) - def selectable(self, oid, *args, **kwargs): - """return all appobjects having the given oid that are - selectable against the given context, in score order - """ - objects = [] - for appobject in self[oid]: - score = appobject.__select__(appobject, *args, **kwargs) - if score > 0: - objects.append((score, appobject)) - return [obj(*args, **kwargs) - for _score, obj in sorted(objects)] - def possible_objects(self, *args, **kwargs): """return an iterator on possible objects in this registry for the given context diff -r c8a5ac2d1eaa -r de95bbed8781 web/data/cubicweb.css --- a/web/data/cubicweb.css Sat Oct 09 00:05:52 2010 +0200 +++ b/web/data/cubicweb.css Sat Oct 09 00:38:02 2010 +0200 @@ -222,6 +222,7 @@ table#header { background: %(headerBgColor)s url("banner.png") repeat-x top left; text-align: left; + width: 100%; } table#header td { @@ -232,6 +233,15 @@ color: %(defaultColor)s; } +table#header td#headtext { + float: left; +} + +table#header td#header-right { + padding-top: 1em; + float: right; +} + table#header img#logo{ vertical-align: middle; } @@ -242,10 +252,6 @@ white-space: nowrap; } -table#header td#headtext { - width: 100%; -} - /* Popup on login box and userActionBox */ div.popupWrapper { position: relative; diff -r c8a5ac2d1eaa -r de95bbed8781 web/data/cubicweb.login.css --- a/web/data/cubicweb.login.css Sat Oct 09 00:05:52 2010 +0200 +++ b/web/data/cubicweb.login.css Sat Oct 09 00:38:02 2010 +0200 @@ -78,6 +78,9 @@ width: 12em; } +/* This is seriously debatable + These buttons render much more clearly as standard/default buttons + */ .loginButton { border: 1px solid #edecd2; border-color: #edecd2 %(incontextBoxBodyBgColor)s %(incontextBoxBodyBgColor)s #edecd2; diff -r c8a5ac2d1eaa -r de95bbed8781 web/data/cubicweb.old.css --- a/web/data/cubicweb.old.css Sat Oct 09 00:05:52 2010 +0200 +++ b/web/data/cubicweb.old.css Sat Oct 09 00:38:02 2010 +0200 @@ -229,6 +229,7 @@ table#header { background: #ff7700 url("banner.png") left top repeat-x; text-align: left; + width: 100%; } table#header td { @@ -236,17 +237,22 @@ } table#header a { -color: #000; + color: #000; +} + +table#header td#headtext { + float: left; +} + +table#header td#header-right { + padding-top: 1em; + float: right; } span#appliName { - font-weight: bold; - color: #000; - white-space: nowrap; -} - -table#header td#headtext { - width: 100%; + font-weight: bold; + color: #000; + white-space: nowrap; } /* FIXME appear with 4px width in IE6 */ diff -r c8a5ac2d1eaa -r de95bbed8781 web/test/unittest_breadcrumbs.py --- a/web/test/unittest_breadcrumbs.py Sat Oct 09 00:05:52 2010 +0200 +++ b/web/test/unittest_breadcrumbs.py Sat Oct 09 00:38:02 2010 +0200 @@ -31,8 +31,10 @@ self.assertEqual(f2.view('breadcrumbs'), 'chi&ld' % f2.eid) childrset = f2.as_rset() - ibc = self.vreg['components'].select('breadcrumbs', self.request(), rset=childrset) - self.assertEqual(ibc.render(), + ibc = self.vreg['ctxcomponents'].select('breadcrumbs', self.request(), rset=childrset) + l = [] + ibc.render(l.append) + self.assertEqual(''.join(l), """ > folder_plural > par&ent >  chi&ld""" % (f1.eid, f2.eid)) diff -r c8a5ac2d1eaa -r de95bbed8781 web/views/basecomponents.py --- a/web/views/basecomponents.py Sat Oct 09 00:05:52 2010 +0200 +++ b/web/views/basecomponents.py Sat Oct 09 00:38:02 2010 +0200 @@ -72,70 +72,95 @@ self.w(u'') -class ApplLogo(component.Component): - """build the instance logo, usually displayed in the header""" - __regid__ = 'logo' - cw_property_defs = VISIBLE_PROP_DEF - # don't want user to hide this component using an cwproperty - site_wide = True - def call(self): - self.w(u'' - % (self._cw.base_url(), self._cw.uiprops['LOGO'])) - - -class ApplHelp(component.Component): - """build the help button, usually displayed in the header""" - __regid__ = 'help' - cw_property_defs = VISIBLE_PROP_DEF - def call(self): - self.w(u' ' - % (self._cw.build_url(_restpath='doc/main'), - self._cw._(u'help'),)) - - -class _UserLink(component.Component): +class HeaderComponent(component.CtxComponent): # XXX rename properly along with related context """if the user is the anonymous user, build a link to login else display a menu with user'action (preference, logout, etc...) """ __abstract__ = True - __regid__ = 'loggeduserlink' - cw_property_defs = VISIBLE_PROP_DEF + cw_property_defs = component.override_ctx( + component.CtxComponent, + vocabulary=['header-left', 'header-center', 'header-right']) # don't want user to hide this component using an cwproperty site_wide = True + context = _('header-center') + + +class ApplLogo(HeaderComponent): + """build the instance logo, usually displayed in the header""" + __regid__ = 'logo' + order = -1 + + def render(self, w): + w(u'' + % (self._cw.base_url(), self._cw.uiprops['LOGO'])) -class CookieAnonUserLink(_UserLink): - __select__ = (_UserLink.__select__ & anonymous_user() +class ApplicationName(HeaderComponent): + """display the instance name""" + __regid__ = 'appliname' + context = _('header-center') + + def render(self, w): + title = self._cw.property_value('ui.site-title') + if title: + w(u'%s' % ( + self._cw.base_url(), xml_escape(title))) + + +class CookieLoginComponent(HeaderComponent): + __regid__ = 'anonuserlink' + __select__ = (HeaderComponent.__select__ & anonymous_user() & configuration_values('auth-mode', 'cookie')) + context = 'header-right' loginboxid = 'popupLoginBox' + _html = u"""[%s]""" + + def render(self, w): + # XXX bw compat, though should warn about subclasses redefining call + self.w = w + self.call() def call(self): - w = self.w - w(self._cw._('anonymous')) - w(u"""[%s]""" - % (self.loginboxid, self._cw._('i18n_login_popup'))) + self.w(self._html % (self._cw._('login / password'), + self.loginboxid, self._cw._('i18n_login_popup'))) self.wview('logform', rset=self.cw_rset, id=self.loginboxid, klass='hidden', title=False, showmessage=False) -AnonUserLink = class_renamed('AnonUserLink', CookieAnonUserLink) -class HTTPAnonUserLink(_UserLink): - __select__ = (_UserLink.__select__ & anonymous_user() +class HTTPLoginComponent(CookieLoginComponent): + __select__ = (HeaderComponent.__select__ & anonymous_user() & configuration_values('auth-mode', 'http')) - def call(self): - w = self.w - w(self._cw._('anonymous')) + def render(self, w): # this redirects to the 'login' controller which in turn # will raise a 401/Unauthorized - w(u' [%s]' - % (self._cw.build_url('login'), self._cw._('login'))) + req = self._cw + w(u'[%s]' + % (req._('login / password'), req.build_url('login'), req._('login'))) + + +_UserLink = class_renamed('_UserLink', HeaderComponent) +AnonUserLink = class_renamed('AnonUserLink', CookieLoginComponent) +AnonUserLink.__abstract__ = True +AnonUserLink.__select__ &= yes(1) + -class UserLink(_UserLink): - __select__ = _UserLink.__select__ & authenticated_user() +class AnonUserStatusLink(HeaderComponent): + __regid__ = 'userstatus' + __select__ = HeaderComponent.__select__ & anonymous_user() + context = _('header-right') + order = HeaderComponent.order - 10 - def call(self): + def render(self, w): + w(u'%s' % self._cw._('anonymous')) + + +class AuthenticatedUserStatus(AnonUserStatusLink): + __select__ = HeaderComponent.__select__ & authenticated_user() + + def render(self, w): # display useractions and siteactions actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset) box = MenuWidget('', 'userActionsBox', _class='', islist=False) @@ -149,7 +174,7 @@ for action in actions.get('siteactions', ()): menu.append(BoxLink(action.url(), self._cw._(action.title), action.html_class())) - box.render(w=self.w) + box.render(w=w) class ApplicationMessage(component.Component): @@ -171,20 +196,6 @@ self.w(u'') -class ApplicationName(component.Component): - """display the instance name""" - __regid__ = 'appliname' - cw_property_defs = VISIBLE_PROP_DEF - # don't want user to hide this component using an cwproperty - site_wide = True - - def call(self): - title = self._cw.property_value('ui.site-title') - if title: - self.w(u'%s' % ( - self._cw.base_url(), xml_escape(title))) - - class EtypeRestrictionComponent(component.Component): """displays the list of entity types contained in the resultset to be able to filter accordingly. diff -r c8a5ac2d1eaa -r de95bbed8781 web/views/basetemplates.py --- a/web/views/basetemplates.py Sat Oct 09 00:05:52 2010 +0200 +++ b/web/views/basetemplates.py Sat Oct 09 00:38:02 2010 +0200 @@ -338,27 +338,16 @@ """build the top menu with authentification info and the rql box""" w = self.w w(u'\n') - w(u'\n') - # appliname and breadcrumbs - w(u'') - # logged user and help - login_components = self._cw.vreg['components'].selectable( - 'loggeduserlink', self._cw, rset=self.cw_rset) - for comp in login_components: - w(u'') w(u'\n') diff -r c8a5ac2d1eaa -r de95bbed8781 web/views/ibreadcrumbs.py --- a/web/views/ibreadcrumbs.py Sat Oct 09 00:05:52 2010 +0200 +++ b/web/views/ibreadcrumbs.py Sat Oct 09 00:38:02 2010 +0200 @@ -25,12 +25,13 @@ from logilab.mtconverter import xml_escape #from cubicweb.interfaces import IBreadCrumbs +from cubicweb import tags, uilib +from cubicweb.entity import Entity from cubicweb.selectors import (is_instance, one_line_rset, adaptable, one_etype_rset, multi_lines_rset, any_rset) -from cubicweb.view import EntityView, Component, EntityAdapter +from cubicweb.view import EntityView, EntityAdapter +from cubicweb.web.views import basecomponents # don't use AnyEntity since this may cause bug with isinstance() due to reloading -from cubicweb.entity import Entity -from cubicweb import tags, uilib # ease bw compat @@ -90,84 +91,82 @@ return path -class BreadCrumbEntityVComponent(Component): +class BreadCrumbEntityVComponent(basecomponents.HeaderComponent): __regid__ = 'breadcrumbs' - __select__ = one_line_rset() & adaptable('IBreadCrumbs') - - cw_property_defs = { - _('visible'): dict(type='Boolean', default=True, - help=_('display the component or not')), - } - # title = _('ctxcomponents_breadcrumbs') - # help = _('ctxcomponents_breadcrumbs_description') + __select__ = (basecomponents.HeaderComponent.__select__ + & one_line_rset() & adaptable('IBreadCrumbs')) + order = basecomponents.ApplicationName.order + 1 separator = u' > ' link_template = u'%s' + first_separator = True - def call(self, view=None, first_separator=True): + def render(self, w): entity = self.cw_rset.get_entity(0, 0) adapter = ibreadcrumb_adapter(entity) + view = self.cw_extra_kwargs.get('view') path = adapter.breadcrumbs(view) if path: - self.open_breadcrumbs() - if first_separator: - self.w(self.separator) - self.render_breadcrumbs(entity, path) - self.close_breadcrumbs() + self.open_breadcrumbs(w) + if self.first_separator: + w(self.separator) + self.render_breadcrumbs(w, entity, path) + self.close_breadcrumbs(w) - def open_breadcrumbs(self): - self.w(u'') + def open_breadcrumbs(self, w): + w(u'') - def close_breadcrumbs(self): - self.w(u'') + def close_breadcrumbs(self, w): + w(u'') - def render_breadcrumbs(self, contextentity, path): + def render_breadcrumbs(self, w, contextentity, path): root = path.pop(0) if isinstance(root, Entity): - self.w(self.link_template % (self._cw.build_url(root.__regid__), + w(self.link_template % (self._cw.build_url(root.__regid__), root.dc_type('plural'))) - self.w(self.separator) - self.wpath_part(root, contextentity, not path) + w(self.separator) + self.wpath_part(w, root, contextentity, not path) for i, parent in enumerate(path): - self.w(self.separator) - self.w(u"\n") - self.wpath_part(parent, contextentity, i == len(path) - 1) + w(self.separator) + w(u"\n") + self.wpath_part(w, parent, contextentity, i == len(path) - 1) - def wpath_part(self, part, contextentity, last=False): # XXX deprecates last argument? + def wpath_part(self, w, part, contextentity, last=False): # XXX deprecates last argument? if isinstance(part, Entity): - self.w(part.view('breadcrumbs')) + w(part.view('breadcrumbs')) elif isinstance(part, tuple): url, title = part textsize = self._cw.property_value('navigation.short-line-size') - self.w(self.link_template % ( + w(self.link_template % ( xml_escape(url), xml_escape(uilib.cut(title, textsize)))) else: textsize = self._cw.property_value('navigation.short-line-size') - self.w(uilib.cut(unicode(part), textsize)) + w(uilib.cut(unicode(part), textsize)) class BreadCrumbETypeVComponent(BreadCrumbEntityVComponent): - __select__ = multi_lines_rset() & one_etype_rset() & \ - adaptable('IBreadCrumbs') + __select__ = (basecomponents.HeaderComponent.__select__ + & multi_lines_rset() & one_etype_rset() + & adaptable('IBreadCrumbs')) - def render_breadcrumbs(self, contextentity, path): + def render_breadcrumbs(self, w, contextentity, path): # XXX hack: only display etype name or first non entity path part root = path.pop(0) if isinstance(root, Entity): - self.w(u'%s' % (self._cw.build_url(root.__regid__), - root.dc_type('plural'))) + w(u'%s' % (self._cw.build_url(root.__regid__), + root.dc_type('plural'))) else: - self.wpath_part(root, contextentity, not path) + self.wpath_part(w, root, contextentity, not path) class BreadCrumbAnyRSetVComponent(BreadCrumbEntityVComponent): - __select__ = any_rset() + __select__ = basecomponents.HeaderComponent.__select__ & any_rset() - def call(self, view=None, first_separator=True): - self.w(u'') - if first_separator: - self.w(self.separator) - self.w(self._cw._('search')) - self.w(u'') + def render(self, w): + w(u'') + if self.first_separator: + w(self.separator) + w(self._cw._('search')) + w(u'') class BreadCrumbView(EntityView):