diff -r 000000000000 -r b97547f5f1fa web/views/basetemplates.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/basetemplates.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +"""default templates for CubicWeb web client + +:organization: Logilab +:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from logilab.mtconverter import html_escape + +from cubicweb import NoSelectableObject, ObjectNotFound +from cubicweb.common.view import Template, MainTemplate, NOINDEX, NOFOLLOW +from cubicweb.common.selectors import nfentity_selector +from cubicweb.common.utils import make_uid + +from cubicweb.web.views.baseviews import vid_from_rset + +# main templates ############################################################## + + +class LogInOutTemplate(MainTemplate): + + def call(self): + self.set_request_content_type() + w = self.w + self.write_doctype() + lang = self.req.lang + self.template_header('text/html', self.req._('login_action')) + w(u'\n') + self.content(w) + w(u'') + + def template_header(self, content_type, view=None, page_title='', additional_headers=()): + w = self.whead + # explictly close the tag to avoid IE 6 bugs while browsing DOM + w(u'' % html_escape(self.req.base_url())) + w(u'\n' + % (content_type, self.req.encoding)) + w(NOINDEX) + w(NOFOLLOW) + w(u'\n'.join(additional_headers) + u'\n') + self.template('htmlheader', rset=self.rset) + w(u'%s\n' % html_escape(page_title)) + + +class LogInTemplate(LogInOutTemplate): + id = 'login' + title = 'log in' + + def content(self, w): + self.template('logform', rset=self.rset, id='loginBox', klass='') + + +class LoggedOutTemplate(LogInOutTemplate): + id = 'loggedout' + title = 'logged out' + + def content(self, w): + msg = self.req._('you have been logged out') + w(u'

%s

\n' % msg) + if self.config['anonymous-user']: + indexurl = self.build_url('view', vid='index', __message=msg) + w(u'

%s

' % ( + html_escape(indexurl), + self.req._('go back to the index page'))) + + +class TheMainTemplate(MainTemplate): + """default main template : + + - call header / footer templates + - build result set + - guess and call an appropriate view through the view manager + """ + id = 'main' + + def _select_view_and_rset(self): + req = self.req + if self.rset is None and not hasattr(req, '_rql_processed'): + req._rql_processed = True + rset = self.process_rql(req.form.get('rql')) + else: + rset = self.rset + # handle special "method" param when necessary + # XXX this should probably not be in the template (controller ?), however: + # * we need to have the displayed rset + # * we don't want to handle it in each view + if rset and rset.rowcount == 1 and '__method' in req.form: + entity = rset.get_entity(0, 0) + try: + method = getattr(entity, req.form.pop('__method')) + method() + except Exception, ex: + self.exception('while handling __method') + req.set_message(req._("error while handling __method: %s") % req._(ex)) + vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema) + try: + view = self.vreg.select_view(vid, req, rset) + except ObjectNotFound: + self.warning("the view %s could not be found", vid) + req.set_message(req._("The view %s could not be found") % vid) + vid = vid_from_rset(req, rset, self.schema) + view = self.vreg.select_view(vid, req, rset) + except NoSelectableObject: + if rset: + req.set_message(req._("The view %s can not be applied to this query") % vid) + else: + req.set_message(req._("You have no access to this view or it's not applyable to current data")) + self.warning("the view %s can not be applied to this query", vid) + vid = vid_from_rset(req, rset, self.schema) + view = self.vreg.select_view(vid, req, rset) + return view, rset + + def call(self): + view, rset = self._select_view_and_rset() + req = self.req + # update breadcrumps **before** validating cache, unless the view + # specifies explicitly it should not be added to breadcrumb or the + # view is a binary view + if view.add_to_breadcrumbs and not view.binary: + req.update_breadcrumbs() + view.set_http_cache_headers() + req.validate_cache() + with_templates = not view.binary and view.templatable and \ + not req.form.has_key('__notemplate') + if not with_templates: + view.set_request_content_type() + self.set_stream(templatable=False) + else: + self.set_request_content_type() + content_type = self.content_type + self.template_header(content_type, view) + if view.binary: + # have to replace our unicode stream using view's binary stream + view.dispatch() + assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream + self._stream = view._stream + else: + view.dispatch(w=self.w) + if with_templates: + self.template_footer(view) + + + def process_rql(self, rql): + """execute rql if specified""" + if rql: + self.ensure_ro_rql(rql) + if not isinstance(rql, unicode): + rql = unicode(rql, self.req.encoding) + pp = self.vreg.select_component('magicsearch', self.req) + self.rset = pp.process_query(rql, self.req) + return self.rset + return None + + def template_header(self, content_type, view=None, page_title='', additional_headers=()): + page_title = page_title or view.page_title() + additional_headers = additional_headers or view.html_headers() + self.template_html_header(content_type, page_title, additional_headers) + self.template_body_header(view) + # display entity type restriction component + etypefilter = self.vreg.select_component('etypenavigation', + self.req, self.rset) + if etypefilter and etypefilter.propval('visible'): + etypefilter.dispatch(w=self.w) + self.pagination(self.req, self.rset, self.w, not view.need_navigation) + self.w(u'

\n') + + def template_html_header(self, content_type, page_title, additional_headers=()): + w = self.whead + lang = self.req.lang + self.write_doctype() + w(u'' % html_escape(self.req.base_url())) + w(u'\n' + % (content_type, self.req.encoding)) + w(u'\n'.join(additional_headers) + u'\n') + self.template('htmlheader', rset=self.rset) + if page_title: + w(u'%s\n' % html_escape(page_title)) + + def template_body_header(self, view): + w = self.w + w(u'\n') + self.template('header', rset=self.rset, view=view) + w(u'
\n') + self.nav_column(view, 'left') + w(u'\n') + self.nav_column(view, 'right') + self.w(u'
\n') + rqlcomp = self.vreg.select_component('rqlinput', self.req, self.rset) + if rqlcomp: + rqlcomp.dispatch(w=self.w, view=view) + msgcomp = self.vreg.select_component('applmessages', self.req, self.rset) + if msgcomp: + msgcomp.dispatch(w=self.w) + self.content_header(view) + w(u'
\n') + vtitle = self.req.form.get('vtitle') + if vtitle: + w(u'

%s

\n' % vtitle) + + def template_footer(self, view=None): + self.w(u'
\n') # close id=contentmain + self.w(u'\n') # closes id=pageContent + self.content_footer(view) + self.w(u'
\n') + self.template('footer', rset=self.rset) + self.w(u'') + + def nav_column(self, view, context): + boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset, + view=view, context=context)) + if boxes: + self.w(u'\n') + + def content_header(self, view=None): + """by default, display informal messages in content header""" + self.template('contentheader', rset=self.rset, view=view) + + def content_footer(self, view=None): + self.template('contentfooter', rset=self.rset, view=view) + + +class ErrorTemplate(TheMainTemplate): + """fallback template if an internal error occured during displaying the + main template. This template may be called for authentication error, + which means that req.cnx and req.user may not be set. + """ + id = 'error' + + def call(self): + """display an unexpected error""" + self.set_request_content_type() + self.req.reset_headers() + view = self.vreg.select_view('error', self.req, self.rset) + self.template_header(self.content_type, view, self.req._('an error occured'), + [NOINDEX, NOFOLLOW]) + view.dispatch(w=self.w) + self.template_footer(view) + + def template_header(self, content_type, view=None, page_title='', additional_headers=()): + w = self.whead + lang = self.req.lang + self.write_doctype() + w(u'\n' + % (content_type, self.req.encoding)) + w(u'\n'.join(additional_headers)) + self.template('htmlheader', rset=self.rset) + w(u'%s\n' % html_escape(page_title)) + self.w(u'\n') + + def template_footer(self, view=None): + self.w(u'') + + +class SimpleMainTemplate(TheMainTemplate): + + id = 'main-no-top' + + def template_header(self, content_type, view=None, page_title='', additional_headers=()): + page_title = page_title or view.page_title() + additional_headers = additional_headers or view.html_headers() + whead = self.whead + lang = self.req.lang + self.write_doctype() + whead(u'\n' + % (content_type, self.req.encoding)) + whead(u'\n'.join(additional_headers) + u'\n') + self.template('htmlheader', rset=self.rset) + w = self.w + w(u'%s\n' % html_escape(page_title)) + w(u'\n') + w(u'
') + w(u'\n') + w(u'') + w(u'
') + w(u'
\n') + vtitle = self.req.form.get('vtitle') + if vtitle: + w(u'

%s

' % (vtitle)) + + def topleft_header(self): + self.w(u'\n') + self.w(u'\n') + self.w(u'\n') + +# page parts templates ######################################################## + +class HTMLHeader(Template): + """default html headers""" + id = 'htmlheader' + + def call(self, **kwargs): + self.favicon() + self.stylesheets() + self.javascripts() + self.alternates() + self.pageid() + + def favicon(self): + favicon = self.req.external_resource('FAVICON', None) + if favicon: + self.whead(u'\n' % favicon) + + def stylesheets(self): + req = self.req + add_css = req.add_css + for css in req.external_resource('STYLESHEETS'): + add_css(css, localfile=False) + for css in req.external_resource('STYLESHEETS_PRINT'): + add_css(css, u'print', localfile=False) + for css in req.external_resource('IE_STYLESHEETS'): + add_css(css, localfile=False, ieonly=True) + + def javascripts(self): + for jscript in self.req.external_resource('JAVASCRIPTS'): + self.req.add_js(jscript, localfile=False) + + def alternates(self): + # nfentity_selector is used by the rss icon box as well + if nfentity_selector(self, self.req, self.rset): + url = self.build_url(rql=self.limited_rql(), vid='rss') + self.whead(u'\n' + % html_escape(url)) + + def pageid(self): + req = self.req + pid = make_uid(id(req)) + req.pageid = pid + req.html_headers.define_var('pageid', pid); + + +class HTMLPageHeader(Template): + """default html page header""" + id = 'header' + + def call(self, view, **kwargs): + self.main_header(view) + self.w(u''' +
''') + self.state_header() + self.w(u''' +
+ ''') + + def main_header(self, view): + """build the top menu with authentification info and the rql box""" + self.w(u'\n') + self.w(u'\n') + # appliname and breadcrumbs + self.w(u'') + # logged user and help + self.w(u'') + # lastcolumn + self.w(u'\n') + self.w(u'\n') + self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden', + title=False, message=False) + + def state_header(self): + state = self.req.search_state + if state[0] == 'normal': + return + _ = self.req._ + value = self.view('oneline', self.req.eid_rset(state[1][1])) + msg = ' '.join((_("searching for"), + display_name(self.req, state[1][3]), + _("to associate with"), value, + _("by relation"), '"', + display_name(self.req, state[1][2], state[1][0]), + '"')) + return self.w(u'
%s
' % msg) + + + +class HTMLPageFooter(Template): + """default html page footer: include logo if any, and close the HTML body + """ + id = 'footer' + + def call(self, **kwargs): + req = self.req + self.w(u'') + + +class HTMLContentHeader(Template): + """default html page content header: + * include message component if selectable for this request + * include selectable content navigation components + """ + id = 'contentheader' + + def call(self, view, **kwargs): + """by default, display informal messages in content header""" + components = self.vreg.possible_vobjects('contentnavigation', + self.req, self.rset, + view=view, context='navtop') + if components: + self.w(u'
') + for comp in components: + comp.dispatch(w=self.w, view=view) + self.w(u'
') + + +class HTMLContentFooter(Template): + """default html page content footer: include selectable content navigation + components + """ + id = 'contentfooter' + + def call(self, view, **kwargs): + components = self.vreg.possible_vobjects('contentnavigation', + self.req, self.rset, + view=view, context='navbottom') + if components: + self.w(u'
') + for comp in components: + comp.dispatch(w=self.w, view=view) + self.w(u'
') + + +class LogFormTemplate(Template): + id = 'logform' + title = 'log in' + + def call(self, id, klass, title=True, message=True): + self.req.add_css('cubicweb.login.css') + self.w(u'
' % (id, klass)) + if title: + self.w(u'
%s
' + % self.req.property_value('ui.site-title')) + self.w(u'
\n') + + if message: + self.display_message() + if self.config['auth-mode'] == 'http': + # HTTP authentication + pass + else: + # Cookie authentication + self.login_form(id) + self.w(u'
\n') + + def display_message(self): + message = self.req.message + if message: + self.w(u'
%s
\n' % message) + + def login_form(self, id): + _ = self.req._ + self.w(u'
\n' + % html_escape(login_form_url(self.config, self.req))) + self.w(u'\n') + self.w(u'\n') + self.w(u'' % _('login')) + self.w(u'') + self.w(u'\n') + self.w(u'' % _('password')) + self.w(u'\n') + self.w(u'\n') + self.w(u'' % _('log in')) + self.w(u'\n') + self.w(u'
 \n
\n') + self.w(u'
\n') + # XXX doesn't seem to work, rewrite this + self.w(u'''''' % id) + + +def login_form_url(config, req): + if req.https: + return req.url() + if config.get('https-url'): + return req.url().replace(req.base_url(), config['https-url']) + return req.url() +