view.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4702 d9e51820d0c2
child 5377 84d14ddfae13
equal deleted inserted replaced
4563:c25da7573ebd 4985:02b52bf9f5f8
    19 
    19 
    20 from rql import nodes
    20 from rql import nodes
    21 
    21 
    22 from cubicweb import NotAnEntity
    22 from cubicweb import NotAnEntity
    23 from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset
    23 from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset
    24 from cubicweb.selectors import require_group_compat, accepts_compat
       
    25 from cubicweb.appobject import AppObject
    24 from cubicweb.appobject import AppObject
    26 from cubicweb.utils import UStringIO, HTMLStream
    25 from cubicweb.utils import UStringIO, HTMLStream
    27 from cubicweb.schema import display_name
    26 from cubicweb.schema import display_name
    28 
    27 
    29 # robots control
    28 # robots control
    90     * if the view is not templatable, it should set the `content_type` class
    89     * if the view is not templatable, it should set the `content_type` class
    91       attribute to the correct MIME type (text/xhtml by default)
    90       attribute to the correct MIME type (text/xhtml by default)
    92     * the `category` attribute may be used in the interface to regroup related
    91     * the `category` attribute may be used in the interface to regroup related
    93       objects together
    92       objects together
    94 
    93 
    95     At instantiation time, the standard `req`, `rset`, and `cursor`
    94     At instantiation time, the standard `_cw`, and `cw_rset` attributes are
    96     attributes are added and the `w` attribute will be set at rendering
    95     added and the `w` attribute will be set at rendering time to a write
    97     time to a write function to use.
    96     function to use.
    98     """
    97     """
    99     __registry__ = 'views'
    98     __registry__ = 'views'
   100     registered = require_group_compat(AppObject.registered)
       
   101 
    99 
   102     templatable = True
   100     templatable = True
   103     # content_type = 'application/xhtml+xml' # text/xhtml'
   101     # content_type = 'application/xhtml+xml' # text/xhtml'
   104     binary = False
   102     binary = False
   105     add_to_breadcrumbs = True
   103     add_to_breadcrumbs = True
   117                  % self.__class__, DeprecationWarning)
   115                  % self.__class__, DeprecationWarning)
   118             return self.need_navigation
   116             return self.need_navigation
   119         return True
   117         return True
   120 
   118 
   121     def __init__(self, req=None, rset=None, **kwargs):
   119     def __init__(self, req=None, rset=None, **kwargs):
   122         super(View, self).__init__(req, rset, **kwargs)
   120         super(View, self).__init__(req, rset=rset, **kwargs)
   123         self.w = None
   121         self.w = None
   124 
   122 
   125     @property
   123     @property
   126     def content_type(self):
   124     def content_type(self):
   127         return self.req.html_content_type()
   125         return self._cw.html_content_type()
   128 
   126 
   129     def set_stream(self, w=None):
   127     def set_stream(self, w=None):
   130         if self.w is not None:
   128         if self.w is not None:
   131             return
   129             return
   132         if w is None:
   130         if w is None:
   165         view_func(**context)
   163         view_func(**context)
   166         # return stream content if we have created it
   164         # return stream content if we have created it
   167         if stream is not None:
   165         if stream is not None:
   168             return self._stream.getvalue()
   166             return self._stream.getvalue()
   169 
   167 
   170     dispatch = deprecated('.dispatch is deprecated, use .render')(render)
   168     def tal_render(self, template, variables):
       
   169         """render a precompiled page template with variables in the given
       
   170         dictionary as context
       
   171         """
       
   172         from cubicweb.ext.tal import CubicWebContext
       
   173         context = CubicWebContext()
       
   174         context.update({'self': self, 'rset': self.cw_rset, '_' : self._cw._,
       
   175                         'req': self._cw, 'user': self._cw.user})
       
   176         context.update(variables)
       
   177         output = UStringIO()
       
   178         template.expand(context, output)
       
   179         return output.getvalue()
       
   180 
       
   181     dispatch = deprecated('[3.4] .dispatch is deprecated, use .render')(render)
   171 
   182 
   172     # should default .call() method add a <div classs="section"> around each
   183     # should default .call() method add a <div classs="section"> around each
   173     # rset item
   184     # rset item
   174     add_div_section = True
   185     add_div_section = True
   175 
   186 
   178         other rows of the result set and call the same view on the
   189         other rows of the result set and call the same view on the
   179         particular row
   190         particular row
   180 
   191 
   181         Views applicable on None result sets have to override this method
   192         Views applicable on None result sets have to override this method
   182         """
   193         """
   183         rset = self.rset
   194         rset = self.cw_rset
   184         if rset is None:
   195         if rset is None:
   185             raise NotImplementedError, self
   196             raise NotImplementedError, (self, "an rset is required")
   186         wrap = self.templatable and len(rset) > 1 and self.add_div_section
   197         wrap = self.templatable and len(rset) > 1 and self.add_div_section
   187         # XXX propagate self.extra_kwars?
   198         # XXX propagate self.extra_kwars?
   188         for i in xrange(len(rset)):
   199         for i in xrange(len(rset)):
   189             if wrap:
   200             if wrap:
   190                 self.w(u'<div class="section">')
   201                 self.w(u'<div class="section">')
   191             self.wview(self.id, rset, row=i, **kwargs)
   202             self.wview(self.__regid__, rset, row=i, **kwargs)
   192             if wrap:
   203             if wrap:
   193                 self.w(u"</div>")
   204                 self.w(u"</div>")
   194 
   205 
   195     def cell_call(self, row, col, **kwargs):
   206     def cell_call(self, row, col, **kwargs):
   196         """the view is called for a particular result set cell"""
   207         """the view is called for a particular result set cell"""
   204         if not getattr(self, 'title', None):
   215         if not getattr(self, 'title', None):
   205             return False
   216             return False
   206         return True
   217         return True
   207 
   218 
   208     def is_primary(self):
   219     def is_primary(self):
   209         return self.extra_kwargs.get('is_primary', self.id == 'primary')
   220         return self.cw_extra_kwargs.get('is_primary', self.__regid__ == 'primary')
   210 
   221 
   211     def url(self):
   222     def url(self):
   212         """return the url associated with this view. Should not be
   223         """return the url associated with this view. Should not be
   213         necessary for non linkable views, but a default implementation
   224         necessary for non linkable views, but a default implementation
   214         is provided anyway.
   225         is provided anyway.
   215         """
   226         """
   216         rset = self.rset
   227         rset = self.cw_rset
   217         if rset is None:
   228         if rset is None:
   218             return self.build_url('view', vid=self.id)
   229             return self._cw.build_url('view', vid=self.__regid__)
   219         coltypes = rset.column_types(0)
   230         coltypes = rset.column_types(0)
   220         if len(coltypes) == 1:
   231         if len(coltypes) == 1:
   221             etype = iter(coltypes).next()
   232             etype = iter(coltypes).next()
   222             if not self.schema.eschema(etype).final:
   233             if not self._cw.vreg.schema.eschema(etype).final:
   223                 if len(rset) == 1:
   234                 if len(rset) == 1:
   224                     entity = rset.get_entity(0, 0)
   235                     entity = rset.get_entity(0, 0)
   225                     return entity.absolute_url(vid=self.id)
   236                     return entity.absolute_url(vid=self.__regid__)
   226             # don't want to generate /<etype> url if there is some restriction
   237             # don't want to generate /<etype> url if there is some restriction
   227             # on something else than the entity type
   238             # on something else than the entity type
   228             restr = rset.syntax_tree().children[0].where
   239             restr = rset.syntax_tree().children[0].where
   229             # XXX norestriction is not correct here. For instance, in cases like
   240             # XXX norestriction is not correct here. For instance, in cases like
   230             # "Any P,N WHERE P is Project, P name N" norestriction should equal
   241             # "Any P,N WHERE P is Project, P name N" norestriction should equal
   231             # True
   242             # True
   232             norestriction = (isinstance(restr, nodes.Relation) and
   243             norestriction = (isinstance(restr, nodes.Relation) and
   233                              restr.is_types_restriction())
   244                              restr.is_types_restriction())
   234             if norestriction:
   245             if norestriction:
   235                 return self.build_url(etype.lower(), vid=self.id)
   246                 return self._cw.build_url(etype.lower(), vid=self.__regid__)
   236         return self.build_url('view', rql=rset.printable_rql(), vid=self.id)
   247         return self._cw.build_url('view', rql=rset.printable_rql(), vid=self.__regid__)
   237 
   248 
   238     def set_request_content_type(self):
   249     def set_request_content_type(self):
   239         """set the content type returned by this view"""
   250         """set the content type returned by this view"""
   240         self.req.set_content_type(self.content_type)
   251         self._cw.set_content_type(self.content_type)
   241 
   252 
   242     # view utilities ##########################################################
   253     # view utilities ##########################################################
   243 
   254 
   244     def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs):
   255     def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs):
   245         """shortcut to self.view method automatically passing self.w as argument
   256         """shortcut to self.view method automatically passing self.w as argument
   246         """
   257         """
   247         self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
   258         self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
   248 
   259 
   249     # XXX Template bw compat
   260     # XXX Template bw compat
   250     template = deprecated('.template is deprecated, use .view')(wview)
   261     template = deprecated('[3.4] .template is deprecated, use .view')(wview)
   251 
   262 
   252     def whead(self, data):
   263     def whead(self, data):
   253         self.req.html_headers.write(data)
   264         self._cw.html_headers.write(data)
   254 
   265 
   255     def wdata(self, data):
   266     def wdata(self, data):
   256         """simple helper that escapes `data` and writes into `self.w`"""
   267         """simple helper that escapes `data` and writes into `self.w`"""
   257         self.w(xml_escape(data))
   268         self.w(xml_escape(data))
   258 
   269 
   266 
   277 
   267     def page_title(self):
   278     def page_title(self):
   268         """returns a title according to the result set - used for the
   279         """returns a title according to the result set - used for the
   269         title in the HTML header
   280         title in the HTML header
   270         """
   281         """
   271         vtitle = self.req.form.get('vtitle')
   282         vtitle = self._cw.form.get('vtitle')
   272         if vtitle:
   283         if vtitle:
   273             return self.req._(vtitle)
   284             return self._cw._(vtitle)
   274         # class defined title will only be used if the resulting title doesn't
   285         # class defined title will only be used if the resulting title doesn't
   275         # seem clear enough
   286         # seem clear enough
   276         vtitle = getattr(self, 'title', None) or u''
   287         vtitle = getattr(self, 'title', None) or u''
   277         if vtitle:
   288         if vtitle:
   278             vtitle = self.req._(vtitle)
   289             vtitle = self._cw._(vtitle)
   279         rset = self.rset
   290         rset = self.cw_rset
   280         if rset and rset.rowcount:
   291         if rset and rset.rowcount:
   281             if rset.rowcount == 1:
   292             if rset.rowcount == 1:
   282                 try:
   293                 try:
   283                     entity = self.complete_entity(0)
   294                     entity = rset.complete_entity(0, 0)
   284                     # use long_title to get context information if any
   295                     # use long_title to get context information if any
   285                     clabel = entity.dc_long_title()
   296                     clabel = entity.dc_long_title()
   286                 except NotAnEntity:
   297                 except NotAnEntity:
   287                     clabel = display_name(self.req, rset.description[0][0])
   298                     clabel = display_name(self._cw, rset.description[0][0])
   288                     clabel = u'%s (%s)' % (clabel, vtitle)
   299                     clabel = u'%s (%s)' % (clabel, vtitle)
   289             else :
   300             else :
   290                 etypes = rset.column_types(0)
   301                 etypes = rset.column_types(0)
   291                 if len(etypes) == 1:
   302                 if len(etypes) == 1:
   292                     etype = iter(etypes).next()
   303                     etype = iter(etypes).next()
   293                     clabel = display_name(self.req, etype, 'plural')
   304                     clabel = display_name(self._cw, etype, 'plural')
   294                 else :
   305                 else :
   295                     clabel = u'#[*] (%s)' % vtitle
   306                     clabel = u'#[*] (%s)' % vtitle
   296         else:
   307         else:
   297             clabel = vtitle
   308             clabel = vtitle
   298         return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
   309         return u'%s (%s)' % (clabel, self._cw.property_value('ui.site-title'))
   299 
   310 
   300     def output_url_builder( self, name, url, args ):
   311     def output_url_builder( self, name, url, args ):
   301         self.w(u'<script language="JavaScript"><!--\n' \
   312         self.w(u'<script language="JavaScript"><!--\n' \
   302                u'function %s( %s ) {\n' % (name, ','.join(args) ) )
   313                u'function %s( %s ) {\n' % (name, ','.join(args) ) )
   303         url_parts = url.split("%s")
   314         url_parts = url.split("%s")
   308                 self.w(u'+"%s"' % part)
   319                 self.w(u'+"%s"' % part)
   309         self.w('\n document.window.href=url;\n')
   320         self.w('\n document.window.href=url;\n')
   310         self.w('}\n-->\n</script>\n')
   321         self.w('}\n-->\n</script>\n')
   311 
   322 
   312     def create_url(self, etype, **kwargs):
   323     def create_url(self, etype, **kwargs):
   313         """return the url of the entity creation form for a given entity type"""
   324         """ return the url of the entity creation form for a given entity type"""
   314         return self.req.build_url('add/%s' % etype, **kwargs)
   325         return self._cw.build_url('add/%s' % etype, **kwargs)
   315 
   326 
   316     def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False):
   327     def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False):
   317         """read-only field"""
   328         """read-only field"""
   318         if w is None:
   329         if w is None:
   319             w = self.w
   330             w = self.w
   321             w(u'<tr class="entityfield">')
   332             w(u'<tr class="entityfield">')
   322         else:
   333         else:
   323             w(u'<div class="entityfield">')
   334             w(u'<div class="entityfield">')
   324         if show_label and label:
   335         if show_label and label:
   325             if tr:
   336             if tr:
   326                 label = display_name(self.req, label)
   337                 label = display_name(self._cw, label)
   327             if table:
   338             if table:
   328                 w(u'<th>%s</th>' % label)
   339                 w(u'<th>%s</th>' % label)
   329             else:
   340             else:
   330                 w(u'<span>%s</span> ' % label)
   341                 w(u'<span>%s</span> ' % label)
   331         if table:
   342         if table:
   341 # concrete views base classes #################################################
   352 # concrete views base classes #################################################
   342 
   353 
   343 class EntityView(View):
   354 class EntityView(View):
   344     """base class for views applying on an entity (i.e. uniform result set)"""
   355     """base class for views applying on an entity (i.e. uniform result set)"""
   345     __select__ = non_final_entity()
   356     __select__ = non_final_entity()
   346     registered = accepts_compat(View.registered)
       
   347 
       
   348     category = 'entityview'
   357     category = 'entityview'
   349 
   358 
   350 
   359 
   351 class StartupView(View):
   360 class StartupView(View):
   352     """base class for views which doesn't need a particular result set to be
   361     """base class for views which doesn't need a particular result set to be
   353     displayed (so they can always be displayed !)
   362     displayed (so they can always be displayed !)
   354     """
   363     """
   355     __select__ = none_rset()
   364     __select__ = none_rset()
   356     registered = require_group_compat(View.registered)
       
   357 
   365 
   358     category = 'startupview'
   366     category = 'startupview'
   359 
   367 
   360     def html_headers(self):
   368     def html_headers(self):
   361         """return a list of html headers (eg something to be inserted between
   369         """return a list of html headers (eg something to be inserted between
   373     __select__ = none_rset() | non_final_entity()
   381     __select__ = none_rset() | non_final_entity()
   374 
   382 
   375     default_rql = None
   383     default_rql = None
   376 
   384 
   377     def __init__(self, req, rset=None, **kwargs):
   385     def __init__(self, req, rset=None, **kwargs):
   378         super(EntityStartupView, self).__init__(req, rset, **kwargs)
   386         super(EntityStartupView, self).__init__(req, rset=rset, **kwargs)
   379         if rset is None:
   387         if rset is None:
   380             # this instance is not in the "entityview" category
   388             # this instance is not in the "entityview" category
   381             self.category = 'startupview'
   389             self.category = 'startupview'
   382 
   390 
   383     def startup_rql(self):
   391     def startup_rql(self):
   386 
   394 
   387     def call(self, **kwargs):
   395     def call(self, **kwargs):
   388         """override call to execute rql returned by the .startup_rql method if
   396         """override call to execute rql returned by the .startup_rql method if
   389         necessary
   397         necessary
   390         """
   398         """
   391         if self.rset is None:
   399         rset = self.cw_rset
   392             self.rset = self.req.execute(self.startup_rql())
   400         if rset is None:
   393         rset = self.rset
   401             rset = self.cw_rset = self._cw.execute(self.startup_rql())
   394         for i in xrange(len(rset)):
   402         for i in xrange(len(rset)):
   395             self.wview(self.id, rset, row=i, **kwargs)
   403             self.wview(self.__regid__, rset, row=i, **kwargs)
   396 
   404 
   397 
   405 
   398 class AnyRsetView(View):
   406 class AnyRsetView(View):
   399     """base class for views applying on any non empty result sets"""
   407     """base class for views applying on any non empty result sets"""
   400     __select__ = nonempty_rset()
   408     __select__ = nonempty_rset()
   401 
   409 
   402     category = 'anyrsetview'
   410     category = 'anyrsetview'
   403 
   411 
   404     def columns_labels(self, mainindex=0, tr=True):
   412     def columns_labels(self, mainindex=0, tr=True):
   405         if tr:
   413         if tr:
   406             translate = lambda val, req=self.req: display_name(req, val)
   414             translate = lambda val, req=self._cw: display_name(req, val)
   407         else:
   415         else:
   408             translate = lambda val: val
   416             translate = lambda val: val
   409         # XXX [0] because of missing Union support
   417         # XXX [0] because of missing Union support
   410         rqlstdescr = self.rset.syntax_tree().get_description(mainindex,
   418         rqlstdescr = self.cw_rset.syntax_tree().get_description(mainindex,
   411                                                              translate)[0]
   419                                                                 translate)[0]
   412         labels = []
   420         labels = []
   413         for colindex, label in enumerate(rqlstdescr):
   421         for colindex, label in enumerate(rqlstdescr):
   414             # compute column header
   422             # compute column header
   415             if label == 'Any': # find a better label
   423             if label == 'Any': # find a better label
   416                 label = ','.join(translate(et)
   424                 label = ','.join(translate(et)
   417                                  for et in self.rset.column_types(colindex))
   425                                  for et in self.cw_rset.column_types(colindex))
   418             labels.append(label)
   426             labels.append(label)
   419         return labels
   427         return labels
   420 
   428 
   421 
   429 
   422 # concrete template base classes ##############################################
   430 # concrete template base classes ##############################################
   424 class MainTemplate(View):
   432 class MainTemplate(View):
   425     """main template are primary access point to render a full HTML page.
   433     """main template are primary access point to render a full HTML page.
   426     There is usually at least a regular main template and a simple fallback
   434     There is usually at least a regular main template and a simple fallback
   427     one to display error if the first one failed
   435     one to display error if the first one failed
   428     """
   436     """
   429     registered = require_group_compat(View.registered)
       
   430 
   437 
   431     @property
   438     @property
   432     def doctype(self):
   439     def doctype(self):
   433         if self.req.xhtml_browser():
   440         if self._cw.xhtml_browser():
   434             return STRICT_DOCTYPE
   441             return STRICT_DOCTYPE
   435         return STRICT_DOCTYPE_NOEXT
   442         return STRICT_DOCTYPE_NOEXT
   436 
   443 
   437     def set_stream(self, w=None):
   444     def set_stream(self, w=None):
   438         if self.w is not None:
   445         if self.w is not None:
   439             return
   446             return
   440         if w is None:
   447         if w is None:
   441             if self.binary:
   448             if self.binary:
   442                 self._stream = stream = StringIO()
   449                 self._stream = stream = StringIO()
   443             else:
   450             else:
   444                 self._stream = stream = HTMLStream(self.req)
   451                 self._stream = stream = HTMLStream(self._cw)
   445             w = stream.write
   452             w = stream.write
   446         else:
   453         else:
   447             stream = None
   454             stream = None
   448         self.w = w
   455         self.w = w
   449         return stream
   456         return stream
   464 
   471 
   465     def user_callback(self, cb, args, msg=None, nonify=False):
   472     def user_callback(self, cb, args, msg=None, nonify=False):
   466         """register the given user callback and return an url to call it ready to be
   473         """register the given user callback and return an url to call it ready to be
   467         inserted in html
   474         inserted in html
   468         """
   475         """
   469         self.req.add_js('cubicweb.ajax.js')
   476         self._cw.add_js('cubicweb.ajax.js')
   470         if nonify:
   477         if nonify:
   471             _cb = cb
   478             _cb = cb
   472             def cb(*args):
   479             def cb(*args):
   473                 _cb(*args)
   480                 _cb(*args)
   474         cbname = self.req.register_onetime_callback(cb, *args)
   481         cbname = self._cw.register_onetime_callback(cb, *args)
   475         return self.build_js(cbname, xml_escape(msg or ''))
   482         return self.build_js(cbname, xml_escape(msg or ''))
   476 
   483 
   477     def build_update_js_call(self, cbname, msg):
   484     def build_update_js_call(self, cbname, msg):
   478         rql = self.rset.printable_rql()
   485         rql = self.cw_rset.printable_rql()
   479         return "javascript:userCallbackThenUpdateUI('%s', '%s', %s, %s, '%s', '%s')" % (
   486         return "javascript:userCallbackThenUpdateUI('%s', '%s', %s, %s, '%s', '%s')" % (
   480             cbname, self.id, dumps(rql), dumps(msg),
   487             cbname, self.id, dumps(rql), dumps(msg),
   481             self.__registry__, self.div_id())
   488             self.__registry__, self.div_id())
   482 
   489 
   483     def build_reload_js_call(self, cbname, msg):
   490     def build_reload_js_call(self, cbname, msg):
   491 
   498 
   492 class Component(ReloadableMixIn, View):
   499 class Component(ReloadableMixIn, View):
   493     """base class for components"""
   500     """base class for components"""
   494     __registry__ = 'components'
   501     __registry__ = 'components'
   495     __select__ = yes()
   502     __select__ = yes()
   496     property_defs = {}
       
   497 
   503 
   498     # XXX huummm, much probably useless
   504     # XXX huummm, much probably useless
   499     htmlclass = 'mainRelated'
   505     htmlclass = 'mainRelated'
   500     def div_class(self):
   506     def div_class(self):
   501         return '%s %s' % (self.htmlclass, self.id)
   507         return '%s %s' % (self.htmlclass, self.__regid__)
   502     # XXX a generic '%s%s' % (self.id, self.__registry__.capitalize()) would probably be nicer
   508 
       
   509     # XXX a generic '%s%s' % (self.__regid__, self.__registry__.capitalize()) would probably be nicer
   503     def div_id(self):
   510     def div_id(self):
   504         return '%sComponent' % self.id
   511         return '%sComponent' % self.__regid__