web/views/basetemplates.py
changeset 0 b97547f5f1fa
child 16 a70ece4d9d1a
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 # -*- coding: utf-8 -*-
       
     2 """default templates for CubicWeb web client
       
     3 
       
     4 :organization: Logilab
       
     5 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     7 """
       
     8 __docformat__ = "restructuredtext en"
       
     9 
       
    10 from logilab.mtconverter import html_escape
       
    11 
       
    12 from cubicweb import NoSelectableObject, ObjectNotFound
       
    13 from cubicweb.common.view import Template, MainTemplate,  NOINDEX, NOFOLLOW
       
    14 from cubicweb.common.selectors import nfentity_selector
       
    15 from cubicweb.common.utils import make_uid
       
    16 
       
    17 from cubicweb.web.views.baseviews import vid_from_rset
       
    18 
       
    19 # main templates ##############################################################
       
    20 
       
    21 
       
    22 class LogInOutTemplate(MainTemplate):
       
    23     
       
    24     def call(self):
       
    25         self.set_request_content_type()
       
    26         w = self.w
       
    27         self.write_doctype()
       
    28         lang = self.req.lang
       
    29         self.template_header('text/html', self.req._('login_action'))
       
    30         w(u'<body>\n')
       
    31         self.content(w)
       
    32         w(u'</body>')
       
    33 
       
    34     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
       
    35         w = self.whead
       
    36         # explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
       
    37         w(u'<base href="%s"></base>' % html_escape(self.req.base_url()))
       
    38         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
       
    39           % (content_type, self.req.encoding))
       
    40         w(NOINDEX)
       
    41         w(NOFOLLOW)
       
    42         w(u'\n'.join(additional_headers) + u'\n')
       
    43         self.template('htmlheader', rset=self.rset)
       
    44         w(u'<title>%s</title>\n' % html_escape(page_title))
       
    45         
       
    46 
       
    47 class LogInTemplate(LogInOutTemplate):
       
    48     id = 'login'
       
    49     title = 'log in'
       
    50 
       
    51     def content(self, w):
       
    52         self.template('logform', rset=self.rset, id='loginBox', klass='')
       
    53         
       
    54 
       
    55 class LoggedOutTemplate(LogInOutTemplate):
       
    56     id = 'loggedout'
       
    57     title = 'logged out'
       
    58 
       
    59     def content(self, w):
       
    60         msg = self.req._('you have been logged out')
       
    61         w(u'<h1 class="noborder">%s</h1>\n' % msg)
       
    62         if self.config['anonymous-user']:
       
    63             indexurl = self.build_url('view', vid='index', __message=msg)
       
    64             w(u'<p><a href="%s">%s</a><p>' % (
       
    65                 html_escape(indexurl),
       
    66                 self.req._('go back to the index page')))
       
    67 
       
    68         
       
    69 class TheMainTemplate(MainTemplate):
       
    70     """default main template :
       
    71     
       
    72     - call header / footer templates
       
    73     - build result set
       
    74     - guess and call an appropriate view through the view manager
       
    75     """
       
    76     id = 'main'
       
    77 
       
    78     def _select_view_and_rset(self):
       
    79         req = self.req
       
    80         if self.rset is None and not hasattr(req, '_rql_processed'):
       
    81             req._rql_processed = True
       
    82             rset = self.process_rql(req.form.get('rql'))
       
    83         else:
       
    84             rset = self.rset
       
    85         # handle special "method" param when necessary
       
    86         # XXX this should probably not be in the template (controller ?), however:
       
    87         #     * we need to have the displayed rset
       
    88         #     * we don't want to handle it in each view
       
    89         if rset and rset.rowcount == 1 and '__method' in req.form:
       
    90             entity = rset.get_entity(0, 0)
       
    91             try:
       
    92                 method = getattr(entity, req.form.pop('__method'))
       
    93                 method()
       
    94             except Exception, ex:
       
    95                 self.exception('while handling __method')
       
    96                 req.set_message(req._("error while handling __method: %s") % req._(ex))
       
    97         vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
       
    98         try:
       
    99             view = self.vreg.select_view(vid, req, rset)
       
   100         except ObjectNotFound:
       
   101             self.warning("the view %s could not be found", vid)
       
   102             req.set_message(req._("The view %s could not be found") % vid)
       
   103             vid = vid_from_rset(req, rset, self.schema)
       
   104             view = self.vreg.select_view(vid, req, rset)
       
   105         except NoSelectableObject:
       
   106             if rset:
       
   107                 req.set_message(req._("The view %s can not be applied to this query") % vid)
       
   108             else:
       
   109                 req.set_message(req._("You have no access to this view or it's not applyable to current data"))
       
   110             self.warning("the view %s can not be applied to this query", vid)
       
   111             vid = vid_from_rset(req, rset, self.schema)
       
   112             view = self.vreg.select_view(vid, req, rset)
       
   113         return view, rset
       
   114     
       
   115     def call(self):
       
   116         view, rset = self._select_view_and_rset()
       
   117         req = self.req
       
   118         # update breadcrumps **before** validating cache, unless the view
       
   119         # specifies explicitly it should not be added to breadcrumb or the
       
   120         # view is a binary view
       
   121         if view.add_to_breadcrumbs and not view.binary:
       
   122             req.update_breadcrumbs()
       
   123         view.set_http_cache_headers()
       
   124         req.validate_cache()
       
   125         with_templates = not view.binary and view.templatable and \
       
   126                          not req.form.has_key('__notemplate')
       
   127         if not with_templates:
       
   128             view.set_request_content_type()
       
   129             self.set_stream(templatable=False)
       
   130         else:
       
   131             self.set_request_content_type()
       
   132             content_type = self.content_type
       
   133             self.template_header(content_type, view)
       
   134         if view.binary:
       
   135             # have to replace our unicode stream using view's binary stream
       
   136             view.dispatch()
       
   137             assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream
       
   138             self._stream = view._stream
       
   139         else:
       
   140             view.dispatch(w=self.w)
       
   141         if with_templates:
       
   142             self.template_footer(view)
       
   143 
       
   144             
       
   145     def process_rql(self, rql):
       
   146         """execute rql if specified"""
       
   147         if rql:
       
   148             self.ensure_ro_rql(rql)
       
   149             if not isinstance(rql, unicode):
       
   150                 rql = unicode(rql, self.req.encoding)
       
   151             pp = self.vreg.select_component('magicsearch', self.req)
       
   152             self.rset = pp.process_query(rql, self.req)
       
   153             return self.rset
       
   154         return None
       
   155 
       
   156     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
       
   157         page_title = page_title or view.page_title()
       
   158         additional_headers = additional_headers or view.html_headers()
       
   159         self.template_html_header(content_type, page_title, additional_headers)
       
   160         self.template_body_header(view)
       
   161         # display entity type restriction component
       
   162         etypefilter = self.vreg.select_component('etypenavigation',
       
   163                                                  self.req, self.rset)
       
   164         if etypefilter and etypefilter.propval('visible'):
       
   165             etypefilter.dispatch(w=self.w)
       
   166         self.pagination(self.req, self.rset, self.w, not view.need_navigation)
       
   167         self.w(u'<div id="contentmain">\n')
       
   168     
       
   169     def template_html_header(self, content_type, page_title, additional_headers=()):
       
   170         w = self.whead
       
   171         lang = self.req.lang
       
   172         self.write_doctype()
       
   173         w(u'<base href="%s" />' % html_escape(self.req.base_url()))
       
   174         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
       
   175           % (content_type, self.req.encoding))
       
   176         w(u'\n'.join(additional_headers) + u'\n')
       
   177         self.template('htmlheader', rset=self.rset)
       
   178         if page_title:
       
   179             w(u'<title>%s</title>\n' % html_escape(page_title))
       
   180 
       
   181     def template_body_header(self, view):
       
   182         w = self.w
       
   183         w(u'<body>\n')
       
   184         self.template('header', rset=self.rset, view=view)
       
   185         w(u'<div id="page"><table width="100%" border="0" id="mainLayout"><tr>\n')
       
   186         self.nav_column(view, 'left')
       
   187         w(u'<td id="contentcol">\n')
       
   188         rqlcomp = self.vreg.select_component('rqlinput', self.req, self.rset)
       
   189         if rqlcomp:
       
   190             rqlcomp.dispatch(w=self.w, view=view)
       
   191         msgcomp = self.vreg.select_component('applmessages', self.req, self.rset)
       
   192         if msgcomp:
       
   193             msgcomp.dispatch(w=self.w)
       
   194         self.content_header(view)
       
   195         w(u'<div id="pageContent">\n')
       
   196         vtitle = self.req.form.get('vtitle')
       
   197         if vtitle:
       
   198             w(u'<h1 class="vtitle">%s</h1>\n' % vtitle)
       
   199             
       
   200     def template_footer(self, view=None):
       
   201         self.w(u'</div>\n') # close id=contentmain
       
   202         self.w(u'</div>\n') # closes id=pageContent
       
   203         self.content_footer(view)
       
   204         self.w(u'</td>\n')
       
   205         self.nav_column(view, 'right')
       
   206         self.w(u'</tr></table></div>\n')
       
   207         self.template('footer', rset=self.rset)
       
   208         self.w(u'</body>')
       
   209 
       
   210     def nav_column(self, view, context):
       
   211         boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset,
       
   212                                                  view=view, context=context))
       
   213         if boxes:
       
   214             self.w(u'<td class="navcol"><div class="navboxes">\n')
       
   215             for box in boxes:
       
   216                 box.dispatch(w=self.w, view=view)
       
   217             self.w(u'</div></td>\n')
       
   218 
       
   219     def content_header(self, view=None):
       
   220         """by default, display informal messages in content header"""
       
   221         self.template('contentheader', rset=self.rset, view=view)
       
   222             
       
   223     def content_footer(self, view=None):
       
   224         self.template('contentfooter', rset=self.rset, view=view)
       
   225 
       
   226 
       
   227 class ErrorTemplate(TheMainTemplate):
       
   228     """fallback template if an internal error occured during displaying the
       
   229     main template. This template may be called for authentication error,
       
   230     which means that req.cnx and req.user may not be set.
       
   231     """
       
   232     id = 'error'
       
   233     
       
   234     def call(self):
       
   235         """display an unexpected error"""
       
   236         self.set_request_content_type()
       
   237         self.req.reset_headers()
       
   238         view = self.vreg.select_view('error', self.req, self.rset)
       
   239         self.template_header(self.content_type, view, self.req._('an error occured'),
       
   240                              [NOINDEX, NOFOLLOW])
       
   241         view.dispatch(w=self.w)
       
   242         self.template_footer(view)
       
   243     
       
   244     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
       
   245         w = self.whead
       
   246         lang = self.req.lang
       
   247         self.write_doctype()
       
   248         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
       
   249           % (content_type, self.req.encoding))
       
   250         w(u'\n'.join(additional_headers))
       
   251         self.template('htmlheader', rset=self.rset)
       
   252         w(u'<title>%s</title>\n' % html_escape(page_title))
       
   253         self.w(u'<body>\n')
       
   254 
       
   255     def template_footer(self, view=None):
       
   256         self.w(u'</body>')
       
   257 
       
   258 
       
   259 class SimpleMainTemplate(TheMainTemplate):
       
   260 
       
   261     id = 'main-no-top'
       
   262     
       
   263     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
       
   264         page_title = page_title or view.page_title()
       
   265         additional_headers = additional_headers or view.html_headers()
       
   266         whead = self.whead
       
   267         lang = self.req.lang
       
   268         self.write_doctype()
       
   269         whead(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
       
   270               % (content_type, self.req.encoding))
       
   271         whead(u'\n'.join(additional_headers) + u'\n')
       
   272         self.template('htmlheader', rset=self.rset)
       
   273         w = self.w
       
   274         w(u'<title>%s</title>\n' % html_escape(page_title))
       
   275         w(u'<body>\n')
       
   276         w(u'<div id="page">')
       
   277         w(u'<table width="100%" height="100%" border="0"><tr>\n')
       
   278         w(u'<td class="navcol">\n')
       
   279         self.topleft_header()
       
   280         boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset,
       
   281                                                  view=view, context='left'))
       
   282         if boxes:
       
   283             w(u'<div class="navboxes">\n')
       
   284             for box in boxes:
       
   285                 box.dispatch(w=w)
       
   286             self.w(u'</div>\n')
       
   287         w(u'</td>')
       
   288         w(u'<td id="contentcol" rowspan="2">')
       
   289         w(u'<div id="pageContent">\n')
       
   290         vtitle = self.req.form.get('vtitle')
       
   291         if vtitle:
       
   292             w(u'<h1 class="vtitle">%s</h1>' % (vtitle))
       
   293             
       
   294     def topleft_header(self):
       
   295         self.w(u'<table id="header"><tr>\n')
       
   296         self.w(u'<td>')
       
   297         self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
       
   298         self.w(u'</td>\n')
       
   299         self.w(u'</tr></table>\n')
       
   300 
       
   301 # page parts templates ########################################################
       
   302 
       
   303 class HTMLHeader(Template):
       
   304     """default html headers"""
       
   305     id = 'htmlheader'
       
   306     
       
   307     def call(self, **kwargs):
       
   308         self.favicon()
       
   309         self.stylesheets()
       
   310         self.javascripts()
       
   311         self.alternates()
       
   312         self.pageid()
       
   313 
       
   314     def favicon(self):
       
   315         favicon = self.req.external_resource('FAVICON', None)
       
   316         if favicon:
       
   317             self.whead(u'<link rel="shortcut icon" href="%s"/>\n' % favicon)
       
   318             
       
   319     def stylesheets(self):
       
   320         req = self.req
       
   321         add_css = req.add_css
       
   322         for css in req.external_resource('STYLESHEETS'):
       
   323             add_css(css, localfile=False)
       
   324         for css in req.external_resource('STYLESHEETS_PRINT'):
       
   325             add_css(css, u'print', localfile=False)
       
   326         for css in req.external_resource('IE_STYLESHEETS'):
       
   327             add_css(css, localfile=False, ieonly=True)
       
   328         
       
   329     def javascripts(self):
       
   330         for jscript in self.req.external_resource('JAVASCRIPTS'):
       
   331             self.req.add_js(jscript, localfile=False)
       
   332             
       
   333     def alternates(self):
       
   334         # nfentity_selector is used by the rss icon box as well
       
   335         if nfentity_selector(self, self.req, self.rset):
       
   336             url = self.build_url(rql=self.limited_rql(), vid='rss')
       
   337             self.whead(u'<link rel="alternate" type="application/rss+xml" title="RSS feed" href="%s"/>\n'
       
   338                    % html_escape(url))
       
   339 
       
   340     def pageid(self):
       
   341         req = self.req
       
   342         pid = make_uid(id(req))
       
   343         req.pageid = pid
       
   344         req.html_headers.define_var('pageid', pid);
       
   345 
       
   346 
       
   347 class HTMLPageHeader(Template):
       
   348     """default html page header"""
       
   349     id = 'header'
       
   350     
       
   351     def call(self, view, **kwargs):
       
   352         self.main_header(view)
       
   353         self.w(u'''
       
   354   <div id="stateheader">''')
       
   355         self.state_header()
       
   356         self.w(u'''
       
   357   </div>
       
   358   ''')
       
   359         
       
   360     def main_header(self, view):
       
   361         """build the top menu with authentification info and the rql box"""
       
   362         self.w(u'<table id="header"><tr>\n')
       
   363         self.w(u'<td id="firstcolumn">')
       
   364         self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
       
   365         self.w(u'</td>\n')
       
   366         # appliname and breadcrumbs
       
   367         self.w(u'<td id="headtext">')
       
   368         comp = self.vreg.select_component('appliname', self.req, self.rset)
       
   369         if comp and comp.propval('visible'):
       
   370             comp.dispatch(w=self.w)
       
   371         comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
       
   372         if comp and comp.propval('visible'):
       
   373             comp.dispatch(w=self.w, view=view)
       
   374         self.w(u'</td>')
       
   375         # logged user and help
       
   376         self.w(u'<td>\n')
       
   377         comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
       
   378         comp.dispatch(w=self.w)
       
   379         self.w(u'</td><td>')
       
   380         helpcomp = self.vreg.select_component('help', self.req, self.rset)
       
   381         if helpcomp: # may not be available if Card is not defined in the schema
       
   382             helpcomp.dispatch(w=self.w)
       
   383         self.w(u'</td>')
       
   384         # lastcolumn
       
   385         self.w(u'<td id="lastcolumn">')
       
   386         self.w(u'</td>\n')
       
   387         self.w(u'</tr></table>\n')
       
   388         self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
       
   389                       title=False, message=False)
       
   390         
       
   391     def state_header(self):
       
   392         state = self.req.search_state
       
   393         if state[0] == 'normal':
       
   394             return
       
   395         _ = self.req._
       
   396         value = self.view('oneline', self.req.eid_rset(state[1][1]))
       
   397         msg = ' '.join((_("searching for"),
       
   398                         display_name(self.req, state[1][3]),
       
   399                         _("to associate with"), value,
       
   400                         _("by relation"), '"', 
       
   401                         display_name(self.req, state[1][2], state[1][0]),
       
   402                         '"'))
       
   403         return self.w(u'<div class="stateMessage">%s</div>' % msg)
       
   404 
       
   405 
       
   406 
       
   407 class HTMLPageFooter(Template):
       
   408     """default html page footer: include logo if any, and close the HTML body
       
   409     """
       
   410     id = 'footer'
       
   411     
       
   412     def call(self, **kwargs):
       
   413         req = self.req
       
   414         self.w(u'<div class="footer">')
       
   415         # XXX Take object from the registry if in there? would be
       
   416         #     better anyway
       
   417         from cubicweb.web.views.wdoc import ChangeLogView
       
   418         self.w(u'<a href="%s">%s</a> | ' % (req.build_url('changelog'),
       
   419                                             req._(ChangeLogView.title).lower()))
       
   420         self.w(u'<a href="%s">%s</a> | ' % (req.build_url('doc/about'),
       
   421                                             req._('about this site')))
       
   422         self.w(u'© 2001-2008 <a href="http://www.logilab.fr">Logilab S.A.</a>')
       
   423         self.w(u'</div>')
       
   424 
       
   425 
       
   426 class HTMLContentHeader(Template):
       
   427     """default html page content header:
       
   428     * include message component if selectable for this request
       
   429     * include selectable content navigation components
       
   430     """
       
   431     id = 'contentheader'
       
   432     
       
   433     def call(self, view, **kwargs):
       
   434         """by default, display informal messages in content header"""
       
   435         components = self.vreg.possible_vobjects('contentnavigation',
       
   436                                                  self.req, self.rset,
       
   437                                                  view=view, context='navtop')
       
   438         if components:
       
   439             self.w(u'<div id="contentheader">')
       
   440             for comp in components:
       
   441                 comp.dispatch(w=self.w, view=view)
       
   442             self.w(u'</div><div class="clear"></div>')
       
   443 
       
   444 
       
   445 class HTMLContentFooter(Template):
       
   446     """default html page content footer: include selectable content navigation
       
   447     components
       
   448     """
       
   449     id = 'contentfooter'
       
   450     
       
   451     def call(self, view, **kwargs):
       
   452         components = self.vreg.possible_vobjects('contentnavigation',
       
   453                                                  self.req, self.rset,
       
   454                                                  view=view, context='navbottom')
       
   455         if components:
       
   456             self.w(u'<div id="contentfooter">')
       
   457             for comp in components:
       
   458                 comp.dispatch(w=self.w, view=view)
       
   459             self.w(u'</div>')
       
   460 
       
   461 
       
   462 class LogFormTemplate(Template):
       
   463     id = 'logform'
       
   464     title = 'log in'
       
   465 
       
   466     def call(self, id, klass, title=True, message=True):
       
   467         self.req.add_css('cubicweb.login.css')
       
   468         self.w(u'<div id="%s" class="%s">' % (id, klass))
       
   469         if title:
       
   470             self.w(u'<div id="loginTitle">%s</div>'
       
   471                    % self.req.property_value('ui.site-title'))
       
   472         self.w(u'<div id="loginContent">\n')        
       
   473 
       
   474         if message:
       
   475             self.display_message()
       
   476         if self.config['auth-mode'] == 'http':
       
   477             # HTTP authentication
       
   478             pass
       
   479         else:
       
   480             # Cookie authentication
       
   481             self.login_form(id)
       
   482         self.w(u'</div></div>\n')
       
   483 
       
   484     def display_message(self):
       
   485         message = self.req.message
       
   486         if message:
       
   487             self.w(u'<div class="simpleMessage">%s</div>\n' % message)
       
   488                      
       
   489     def login_form(self, id):
       
   490         _ = self.req._
       
   491         self.w(u'<form method="post" action="%s" id="login_form">\n'
       
   492                % html_escape(login_form_url(self.config, self.req)))
       
   493         self.w(u'<table>\n')
       
   494         self.w(u'<tr>\n')
       
   495         self.w(u'<td><label for="__login">%s</label></td>' % _('login'))
       
   496         self.w(u'<td><input name="__login" id="__login" class="data" type="text" /></td>')
       
   497         self.w(u'</tr><tr>\n')
       
   498         self.w(u'<td><label for="__password" >%s</label></td>' % _('password'))
       
   499         self.w(u'<td><input name="__password" id="__password" class="data" type="password" /></td>\n')
       
   500         self.w(u'</tr><tr>\n')
       
   501         self.w(u'<td>&nbsp;</td><td><input type="submit" class="loginButton right" value="%s" />\n</td>' % _('log in'))
       
   502         self.w(u'</tr>\n')
       
   503         self.w(u'</table>\n')
       
   504         self.w(u'</form>\n')
       
   505         # XXX doesn't seem to work, rewrite this
       
   506         self.w(u'''<script type="text/javascript">if(document.getElementById("%s").className != "hidden")
       
   507                    {$('login_form').__login.focus()}</script>''' % id)
       
   508 
       
   509     
       
   510 def login_form_url(config, req):
       
   511     if req.https:
       
   512         return req.url()
       
   513     if config.get('https-url'):
       
   514         return req.url().replace(req.base_url(), config['https-url'])
       
   515     return req.url()
       
   516