[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)
--- 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
--- 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;
--- 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;
--- 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 */
--- 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'),
'<a href="http://testing.fr/cubicweb/folder/%s" title="">chi&ld</a>' % 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),
"""<span id="breadcrumbs" class="pathbar"> > <a href="http://testing.fr/cubicweb/Folder">folder_plural</a> > <a href="http://testing.fr/cubicweb/folder/%s" title="">par&ent</a> > 
<a href="http://testing.fr/cubicweb/folder/%s" title="">chi&ld</a></span>""" % (f1.eid, f2.eid))
--- 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'</form></div>')
-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'<a href="%s"><img id="logo" src="%s" alt="logo"/></a>'
- % (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'<a href="%s" class="help" title="%s"> </a>'
- % (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'<a href="%s"><img id="logo" src="%s" alt="logo"/></a>'
+ % (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'<span id="appliName"><a href="%s">%s</a></span>' % (
+ 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"""[<a class="logout" title="%s" href="javascript:
+cw.htmlhelpers.popupLoginBox('%s', '__login');">%s</a>]"""
+
+ 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"""[<a class="logout" href="javascript: cw.htmlhelpers.popupLoginBox('%s', '__login');">%s</a>]"""
- % (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' [<a class="logout" href="%s">%s</a>]'
- % (self._cw.build_url('login'), self._cw._('login')))
+ req = self._cw
+ w(u'[<a class="logout" title="%s" href="%s">%s</a>]'
+ % (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'<span class="caption">%s</span>' % 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'</div>')
-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'<span id="appliName"><a href="%s">%s</a></span>' % (
- 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.
--- 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'<table id="header"><tr>\n')
- w(u'<td id="firstcolumn">')
- logo = self._cw.vreg['components'].select_or_none(
- 'logo', self._cw, rset=self.cw_rset)
- if logo and logo.cw_propval('visible'):
- logo.render(w=w)
- w(u'</td>\n')
- # appliname and breadcrumbs
- w(u'<td id="headtext">')
- for cid in self.main_cell_components:
- comp = self._cw.vreg['components'].select_or_none(
- cid, self._cw, rset=self.cw_rset)
- if comp and comp.cw_propval('visible'):
- comp.render(w=self.w)
- w(u'</td>')
- # 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'<td>\n')
- if comp.cw_propval('visible'):
+ for colid, context in (('firstcolumn', 'header-left'),
+ ('headtext', 'header-center'),
+ ('header-right', 'header-right'),
+ ):
+ w(u'<td id="%s">' % colid)
+ components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=view, context=context)
+ for comp in components:
comp.render(w=w)
+ w(u' ')
w(u'</td>')
w(u'</tr></table>\n')
--- 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'<a href="%s">%s</a>'
+ 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'<span id="breadcrumbs" class="pathbar">')
+ def open_breadcrumbs(self, w):
+ w(u'<span id="breadcrumbs" class="pathbar">')
- def close_breadcrumbs(self):
- self.w(u'</span>')
+ def close_breadcrumbs(self, w):
+ w(u'</span>')
- 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'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
- root.dc_type('plural')))
+ w(u'<a href="%s">%s</a>' % (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'<span id="breadcrumbs" class="pathbar">')
- if first_separator:
- self.w(self.separator)
- self.w(self._cw._('search'))
- self.w(u'</span>')
+ def render(self, w):
+ w(u'<span id="breadcrumbs" class="pathbar">')
+ if self.first_separator:
+ w(self.separator)
+ w(self._cw._('search'))
+ w(u'</span>')
class BreadCrumbView(EntityView):