"""abstract views and templates classes 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"fromcStringIOimportStringIOfromlogilab.mtconverterimporthtml_escapefromcubicwebimportNotAnEntity,NoSelectableObjectfromcubicweb.common.registerersimportaccepts_registerer,priority_registererfromcubicweb.common.selectorsimport(chainfirst,match_user_group,accept,nonempty_rset,empty_rset,none_rset)fromcubicweb.common.appobjectimportAppRsetObject,ComponentMixInfromcubicweb.common.utilsimportUStringIO,HTMLStream_=unicode# robots controlNOINDEX=u'<meta name="ROBOTS" content="NOINDEX" />'NOFOLLOW=u'<meta name="ROBOTS" content="NOFOLLOW" />'CW_XHTML_EXTENSIONS='''[ <!ATTLIST html xmlns:cubicweb CDATA #FIXED \'http://www.logilab.org/2008/cubicweb\' ><!ENTITY % coreattrs "id ID #IMPLIED class CDATA #IMPLIED style CDATA #IMPLIED title CDATA #IMPLIED cubicweb:sortvalue CDATA #IMPLIED cubicweb:target CDATA #IMPLIED cubicweb:limit CDATA #IMPLIED cubicweb:type CDATA #IMPLIED cubicweb:loadtype CDATA #IMPLIED cubicweb:wdgtype CDATA #IMPLIED cubicweb:initfunc CDATA #IMPLIED cubicweb:inputid CDATA #IMPLIED cubicweb:tindex CDATA #IMPLIED cubicweb:inputname CDATA #IMPLIED cubicweb:value CDATA #IMPLIED cubicweb:required CDATA #IMPLIED cubicweb:accesskey CDATA #IMPLIED cubicweb:maxlength CDATA #IMPLIED cubicweb:variables CDATA #IMPLIED cubicweb:displayactions CDATA #IMPLIED cubicweb:fallbackvid CDATA #IMPLIED cubicweb:vid CDATA #IMPLIED cubicweb:rql CDATA #IMPLIED cubicweb:actualrql CDATA #IMPLIED cubicweb:rooteid CDATA #IMPLIED cubicweb:dataurl CDATA #IMPLIED cubicweb:size CDATA #IMPLIED cubicweb:tlunit CDATA #IMPLIED cubicweb:loadurl CDATA #IMPLIED cubicweb:uselabel CDATA #IMPLIED "> ] '''TRANSITIONAL_DOCTYPE=u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n'STRICT_DOCTYPE=u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'classView(AppRsetObject):"""abstract view class, used as base for every renderable object such as views, templates, some components...web A view is instantiated to render a [part of a] result set. View subclasses may be parametred using the following class attributes: * `templatable` indicates if the view may be embeded in a main template or if it has to be rendered standalone (i.e. XML for instance) * if the view is not templatable, it should set the `content_type` class attribute to the correct MIME type (text/xhtml by default) * the `category` attribute may be used in the interface to regroup related objects together At instantiation time, the standard `req`, `rset`, and `cursor` attributes are added and the `w` attribute will be set at rendering time to a write function to use. """__registry__='views'templatable=Trueneed_navigation=True# content_type = 'application/xhtml+xml' # text/xhtml'binary=Falseadd_to_breadcrumbs=Truecategory='view'def__init__(self,req,rset):super(View,self).__init__(req,rset)self.w=None@propertydefcontent_type(self):ifself.req.xhtml_browser():return'application/xhtml+xml'return'text/html'defset_stream(self,w=None):ifself.wisnotNone:returnifwisNone:ifself.binary:self._stream=stream=StringIO()else:self._stream=stream=UStringIO()w=stream.writeelse:stream=Noneself.w=wreturnstream# main view interface #####################################################defdispatch(self,w=None,**context):"""called to render a view object for a result set. This method is a dispatched to an actual method selected according to optional row and col parameters, which are locating a particular row or cell in the result set: * if row [and col] are specified, `cell_call` is called * if none of them is supplied, the view is considered to apply on the whole result set (which may be None in this case), `call` is called """row,col=context.get('row'),context.get('col')ifrowisnotNone:context.setdefault('col',0)view_func=self.cell_callelse:view_func=self.callstream=self.set_stream(w)# stream = self.set_stream(context)view_func(**context)# return stream content if we have created itifstreamisnotNone:returnself._stream.getvalue()# should default .call() method add a <div classs="section"> around each# rset itemadd_div_section=Truedefcall(self,**kwargs):"""the view is called for an entire result set, by default loop other rows of the result set and call the same view on the particular row Views applicable on None result sets have to override this method """rset=self.rsetifrsetisNone:raiseNotImplementedError,selfwrap=self.templatableandlen(rset)>1andself.add_div_sectionforiinxrange(len(rset)):ifwrap:self.w(u'<div class="section">')self.wview(self.id,rset,row=i,**kwargs)ifwrap:self.w(u"</div>")defcell_call(self,row,col,**kwargs):"""the view is called for a particular result set cell"""raiseNotImplementedError,selfdeflinkable(self):"""return True if the view may be linked in a menu by default views without title are not meant to be displayed """ifnotgetattr(self,'title',None):returnFalsereturnTruedefis_primary(self):returnself.id=='primary'defurl(self):"""return the url associated with this view. Should not be necessary for non linkable views, but a default implementation is provided anyway. """try:returnself.build_url(vid=self.id,rql=self.req.form['rql'])exceptKeyError:returnself.build_url(vid=self.id)defset_request_content_type(self):"""set the content type returned by this view"""self.req.set_content_type(self.content_type)# view utilities ##########################################################defview(self,__vid,rset,__fallback_vid=None,**kwargs):"""shortcut to self.vreg.render method avoiding to pass self.req"""try:view=self.vreg.select_view(__vid,self.req,rset,**kwargs)exceptNoSelectableObject:if__fallback_vidisNone:raiseview=self.vreg.select_view(__fallback_vid,self.req,rset,**kwargs)returnview.dispatch(**kwargs)defwview(self,__vid,rset,__fallback_vid=None,**kwargs):"""shortcut to self.view method automatically passing self.w as argument """self.view(__vid,rset,__fallback_vid,w=self.w,**kwargs)defwhead(self,data):self.req.html_headers.write(data)defwdata(self,data):"""simple helper that escapes `data` and writes into `self.w`"""self.w(html_escape(data))defaction(self,actionid,row=0):"""shortcut to get action object with id `actionid`"""returnself.vreg.select_action(actionid,self.req,self.rset,row=row)defaction_url(self,actionid,label=None,row=0):"""simple method to be able to display `actionid` as a link anywhere """action=self.vreg.select_action(actionid,self.req,self.rset,row=row)ifaction:label=labelorself.req._(action.title)returnu'<a href="%s">%s</a>'%(html_escape(action.url()),label)returnu''defhtml_headers(self):"""return a list of html headers (eg something to be inserted between <head> and </head> of the returned page by default return a meta tag to disable robot indexation of the page """return[NOINDEX]defpage_title(self):"""returns a title according to the result set - used for the title in the HTML header """vtitle=self.req.form.get('vtitle')ifvtitle:returnself.req._(vtitle)# class defined title will only be used if the resulting title doesn't# seem clear enoughvtitle=getattr(self,'title',None)oru''ifvtitle:vtitle=self.req._(vtitle)rset=self.rsetifrsetandrset.rowcount:ifrset.rowcount==1:try:entity=self.complete_entity(0)# use long_title to get context information if anyclabel=entity.dc_long_title()exceptNotAnEntity:clabel=display_name(self.req,rset.description[0][0])clabel=u'%s (%s)'%(clabel,vtitle)else:etypes=rset.column_types(0)iflen(etypes)==1:etype=iter(etypes).next()clabel=display_name(self.req,etype,'plural')else:clabel=u'#[*] (%s)'%vtitleelse:clabel=vtitlereturnu'%s (%s)'%(clabel,self.req.property_value('ui.site-title'))defoutput_url_builder(self,name,url,args):self.w(u'<script language="JavaScript"><!--\n' \u'function %s( %s ) {\n'%(name,','.join(args)))url_parts=url.split("%s")self.w(u' url="%s"'%url_parts[0])forarg,partinzip(args,url_parts[1:]):self.w(u'+str(%s)'%arg)ifpart:self.w(u'+"%s"'%part)self.w('\n document.window.href=url;\n')self.w('}\n-->\n</script>\n')defcreate_url(self,etype,**kwargs):""" return the url of the entity creation form for a given entity type"""returnself.req.build_url('add/%s'%etype,**kwargs)# concrete views base classes #################################################classEntityView(View):"""base class for views applying on an entity (i.e. uniform result set) """__registerer__=accepts_registerer__selectors__=(accept,)category='entityview'deffield(self,label,value,row=True,show_label=True,w=None,tr=True):""" read-only field """ifwisNone:w=self.wifrow:w(u'<div class="row">')ifshow_label:iftr:label=display_name(self.req,label)w(u'<span class="label">%s</span>'%label)w(u'<div class="field">%s</div>'%value)ifrow:w(u'</div>')classStartupView(View):"""base class for views which doesn't need a particular result set to be displayed (so they can always be displayed !) """__registerer__=priority_registerer__selectors__=(match_user_group,none_rset)require_groups=()category='startupview'defurl(self):"""return the url associated with this view. We can omit rql here"""returnself.build_url('view',vid=self.id)defhtml_headers(self):"""return a list of html headers (eg something to be inserted between <head> and </head> of the returned page by default startup views are indexed """return[]classEntityStartupView(EntityView):"""base class for entity views which may also be applied to None result set (usually a default rql is provided by the view class) """__registerer__=accepts_registerer__selectors__=(chainfirst(none_rset,accept),)default_rql=Nonedef__init__(self,req,rset):super(EntityStartupView,self).__init__(req,rset)ifrsetisNone:# this instance is not in the "entityview" categoryself.category='startupview'defstartup_rql(self):"""return some rql to be executedif the result set is None"""returnself.default_rqldefcall(self,**kwargs):"""override call to execute rql returned by the .startup_rql method if necessary """ifself.rsetisNone:self.rset=self.req.execute(self.startup_rql())rset=self.rsetforiinxrange(len(rset)):self.wview(self.id,rset,row=i,**kwargs)defurl(self):"""return the url associated with this view. We can omit rql if we are on a result set on which we do not apply. """ifnotself.__select__(self.req,self.rset):returnself.build_url(vid=self.id)returnsuper(EntityStartupView,self).url()classAnyRsetView(View):"""base class for views applying on any non empty result sets"""__registerer__=priority_registerer__selectors__=(nonempty_rset,)category='anyrsetview'defcolumns_labels(self,tr=True):iftr:translate=display_nameelse:translate=lambdareq,val:valrqlstdescr=self.rset.syntax_tree().get_description()[0]# XXX missing Union supportlabels=[]forcolindex,attrinenumerate(rqlstdescr):# compute column headerifcolindex==0orattr=='Any':# find a better labellabel=','.join(translate(self.req,et)foretinself.rset.column_types(colindex))else:label=translate(self.req,attr)labels.append(label)returnlabelsclassEmptyRsetView(View):"""base class for views applying on any empty result sets"""__registerer__=priority_registerer__selectors__=(empty_rset,)# concrete template base classes ##############################################classTemplate(View):"""a template is almost like a view, except that by default a template is only used globally (i.e. no result set adaptation) """__registry__='templates'__registerer__=priority_registerer__selectors__=(match_user_group,)require_groups=()deftemplate(self,oid,**kwargs):"""shortcut to self.registry.render method on the templates registry"""w=kwargs.pop('w',self.w)self.vreg.render('templates',oid,self.req,w=w,**kwargs)classMainTemplate(Template):"""main template are primary access point to render a full HTML page. There is usually at least a regular main template and a simple fallback one to display error if the first one failed """base_doctype=STRICT_DOCTYPE@propertydefdoctype(self):ifself.req.xhtml_browser():returnself.base_doctype%CW_XHTML_EXTENSIONSreturnself.base_doctype%''defset_stream(self,w=None,templatable=True):iftemplatableandself.wisnotNone:returnifwisNone:ifself.binary:self._stream=stream=StringIO()elifnottemplatable:# not templatable means we're using a non-html view, we don't# want the HTMLStream stuff to interfere during data generationself._stream=stream=UStringIO()else:self._stream=stream=HTMLStream(self.req)w=stream.writeelse:stream=Noneself.w=wreturnstreamdefwrite_doctype(self,xmldecl=True):assertisinstance(self._stream,HTMLStream)self._stream.doctype=self.doctypeifnotxmldecl:self._stream.xmldecl=u''# viewable components base classes ############################################classVComponent(ComponentMixIn,View):"""base class for displayable components"""property_defs={'visible':dict(type='Boolean',default=True,help=_('display the component or not')),}classSingletonVComponent(VComponent):"""base class for displayable unique components"""__registerer__=priority_registerer