[testlib] simplify code by using a class attribute
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""Set of HTML generic base views:* noresult, final* primary, sidebox* oneline, incontext, outofcontext, text* list"""__docformat__="restructuredtext en"_=unicodefromdatetimeimporttimedeltafromwarningsimportwarnfromrqlimportnodesfromlogilab.mtconverterimportTransformError,xml_escape,xml_escapefromcubicwebimportNoSelectableObject,tagsfromcubicweb.selectorsimportyes,empty_rset,one_etype_rset,match_kwargsfromcubicweb.schemaimportdisplay_namefromcubicweb.viewimportEntityView,AnyRsetView,Viewfromcubicweb.uilibimportcut,printable_valuefromcubicweb.web.viewsimportcalendarclassNullView(AnyRsetView):"""default view when no result has been found"""__regid__='null'__select__=yes()defcall(self,**kwargs):passcell_call=callclassNoResultView(View):"""default view when no result has been found"""__select__=empty_rset()__regid__='noresult'defcall(self,**kwargs):self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'%self._cw._('No result matching query'))classFinalView(AnyRsetView):"""display values without any transformation (i.e. get a number for entities) """__regid__='final'# record generated i18n catalog messages_('%d years')_('%d months')_('%d weeks')_('%d days')_('%d hours')_('%d minutes')_('%d seconds')_('%d years')_('%d months')_('%d weeks')_('%d days')_('%d hours')_('%d minutes')_('%d seconds')defcell_call(self,row,col,props=None,format='text/html'):etype=self.cw_rset.description[row][col]value=self.cw_rset.rows[row][col]ifvalueisNone:self.w(u'')returnifetype=='String':entity,rtype=self.cw_rset.related_entity(row,col)ifentityisnotNone:# yes !self.w(entity.printable_value(rtype,value,format=format))returnelifetypein('Time','Interval'):ifetype=='Interval'andisinstance(value,(int,long)):# `date - date`, unlike `datetime - datetime` gives an int# (number of days), not a timedelta# XXX should rql be fixed to return Int instead of Interval in# that case? that would be probably the proper fix but we# loose information on the way...value=timedelta(days=value)# value is DateTimeDelta but we have no idea about what is the# reference date here, so we can only approximate years and monthsifformat=='text/html':space=' 'else:space=' 'ifvalue.days>730:# 2 yearsself.w(self._cw.__('%%d%syears'%space)%(value.days//365))elifvalue.days>60:# 2 monthsself.w(self._cw.__('%%d%smonths'%space)%(value.days//30))elifvalue.days>14:# 2 weeksself.w(self._cw.__('%%d%sweeks'%space)%(value.days//7))elifvalue.days>2:self.w(self._cw.__('%%d%sdays'%space)%int(value.days))elifvalue.seconds>3600:self.w(self._cw.__('%%d%shours'%space)%int(value.seconds//3600))elifvalue.seconds>=120:self.w(self._cw.__('%%d%sminutes'%space)%int(value.seconds//60))else:self.w(self._cw.__('%%d%sseconds'%space)%int(value.seconds))returnself.wdata(printable_value(self._cw,etype,value,props))# XXX deprecatedclassSecondaryView(EntityView):__regid__='secondary'title=_('secondary')defcell_call(self,row,col,**kwargs):"""the secondary view for an entity secondary = icon + view(oneline) """entity=self.cw_rset.get_entity(row,col)self.w(u' ')self.wview('oneline',self.cw_rset,row=row,col=col)classOneLineView(EntityView):__regid__='oneline'title=_('oneline')defcell_call(self,row,col,**kwargs):"""the one line view for an entity: linked text view """entity=self.cw_rset.get_entity(row,col)self.w(u'<a href="%s">'%xml_escape(entity.absolute_url()))self.w(xml_escape(self._cw.view('text',self.cw_rset,row=row,col=col)))self.w(u'</a>')classTextView(EntityView):"""the simplest text view for an entity"""__regid__='text'title=_('text')content_type='text/plain'defcall(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.cw_rsetifrsetisNone:raiseNotImplementedError,selfforiinxrange(len(rset)):self.wview(self.__regid__,rset,row=i,**kwargs)iflen(rset)>1:self.w(u"\n")defcell_call(self,row,col=0,**kwargs):entity=self.cw_rset.get_entity(row,col)self.w(cut(entity.dc_title(),self._cw.property_value('navigation.short-line-size')))classMetaDataView(EntityView):"""paragraph view of some metadata"""__regid__='metadata'show_eid=Truedefcell_call(self,row,col):_=self._cw._entity=self.cw_rset.get_entity(row,col)self.w(u'<div>')ifself.show_eid:self.w(u'%s #%s - '%(entity.dc_type(),entity.eid))ifentity.modification_date!=entity.creation_date:self.w(u'<span>%s</span> '%_('latest update on'))self.w(u'<span class="value">%s</span>, '%self._cw.format_date(entity.modification_date))# entities from external source may not have a creation date (eg ldap)ifentity.creation_date:self.w(u'<span>%s</span> '%_('created on'))self.w(u'<span class="value">%s</span>'%self._cw.format_date(entity.creation_date))ifentity.creator:self.w(u' <span>%s</span> '%_('by'))self.w(u'<span class="value">%s</span>'%entity.creator.name())self.w(u'</div>')classInContextTextView(TextView):__regid__='textincontext'title=None# not listed as a possible viewdefcell_call(self,row,col):entity=self.cw_rset.get_entity(row,col)self.w(entity.dc_title())classOutOfContextTextView(InContextTextView):__regid__='textoutofcontext'defcell_call(self,row,col):entity=self.cw_rset.get_entity(row,col)self.w(entity.dc_long_title())classInContextView(EntityView):__regid__='incontext'defcell_call(self,row,col):entity=self.cw_rset.get_entity(row,col)desc=cut(entity.dc_description(),50)self.w(u'<a href="%s" title="%s">'%(xml_escape(entity.absolute_url()),xml_escape(desc)))self.w(xml_escape(self._cw.view('textincontext',self.cw_rset,row=row,col=col)))self.w(u'</a>')classOutOfContextView(EntityView):__regid__='outofcontext'defcell_call(self,row,col):entity=self.cw_rset.get_entity(row,col)desc=cut(entity.dc_description(),50)self.w(u'<a href="%s" title="%s">'%(xml_escape(entity.absolute_url()),xml_escape(desc)))self.w(xml_escape(self._cw.view('textoutofcontext',self.cw_rset,row=row,col=col)))self.w(u'</a>')# list views ##################################################################classListView(EntityView):__regid__='list'title=_('list')item_vid='listitem'defcall(self,klass=None,title=None,subvid=None,listid=None,**kwargs):"""display a list of entities by calling their <item_vid> view :param listid: the DOM id to use for the root element """# XXX much of the behaviour here should probably be outside this viewifsubvidisNoneand'subvid'inself._cw.form:subvid=self._cw.form.pop('subvid')# consume itiflistid:listid=u' id="%s"'%listidelse:listid=u''iftitle:self.w(u'<div%s class="%s"><h4>%s</h4>\n'%(listid,klassor'section',title))self.w(u'<ul>\n')else:self.w(u'<ul%s class="%s">\n'%(listid,klassor'section'))foriinxrange(self.cw_rset.rowcount):self.cell_call(row=i,col=0,vid=subvid,**kwargs)self.w(u'</ul>\n')iftitle:self.w(u'</div>\n')defcell_call(self,row,col=0,vid=None,**kwargs):self.w(u'<li>')self.wview(self.item_vid,self.cw_rset,row=row,col=col,vid=vid,**kwargs)self.w(u'</li>\n')classListItemView(EntityView):__regid__='listitem'@propertydefredirect_vid(self):ifself._cw.search_state[0]=='normal':return'outofcontext'return'outofcontext-search'defcell_call(self,row,col,vid=None,**kwargs):ifnotvid:vid=self.redirect_vidtry:self.wview(vid,self.cw_rset,row=row,col=col,**kwargs)exceptNoSelectableObject:ifvid==self.redirect_vid:raiseself.wview(self.redirect_vid,self.cw_rset,row=row,col=col,**kwargs)classSimpleListView(ListItemView):"""list without bullets"""__regid__='simplelist'redirect_vid='incontext'defcall(self,subvid=None,**kwargs):"""display a list of entities by calling their <item_vid> view :param listid: the DOM id to use for the root element """ifsubvidisNoneand'vid'inkwargs:warn("should give a 'subvid' argument instead of 'vid'",DeprecationWarning,stacklevel=2)else:kwargs['vid']=subvidreturnsuper(SimpleListView,self).call(**kwargs)classSameETypeListView(EntityView):"""list of entities of the same type, when asked explicitly for same etype list view (for instance, display gallery if only images) """__regid__='sameetypelist'__select__=EntityView.__select__&one_etype_rset()item_vid='sameetypelistitem'@propertydeftitle(self):etype=iter(self.cw_rset.column_types(0)).next()returndisplay_name(self._cw,etype,form='plural')defcall(self,**kwargs):"""display a list of entities by calling their <item_vid> view"""showtitle=kwargs.pop('showtitle',not'vtitle'inself._cw.form)ifshowtitle:self.w(u'<h1>%s</h1>'%self.title)super(SameETypeListView,self).call(**kwargs)defcell_call(self,row,col=0,**kwargs):self.wview(self.item_vid,self.cw_rset,row=row,col=col,**kwargs)classSameETypeListItemView(EntityView):__regid__='sameetypelistitem'defcell_call(self,row,col,**kwargs):self.wview('listitem',self.cw_rset,row=row,col=col,**kwargs)classCSVView(SimpleListView):__regid__='csv'redirect_vid='incontext'defcall(self,subvid=None,**kwargs):ifsubvidisNoneand'vid'inkwargs:warn("[3.9] should give a 'subvid' argument instead of 'vid'",DeprecationWarning,stacklevel=2)else:kwargs['vid']=subvidrset=self.cw_rsetforiinxrange(len(rset)):self.cell_call(i,0,**kwargs)ifi<rset.rowcount-1:self.w(u", ")classTreeItemView(ListItemView):__regid__='treeitem'defcell_call(self,row,col):self.wview('incontext',self.cw_rset,row=row,col=col)classTextSearchResultView(EntityView):"""this view is used to display full-text search It tries to highlight part of data where the search word appears. XXX: finish me (fixed line width, fixed number of lines, CSS, etc.) """__regid__='tsearch'defcell_call(self,row,col,**kwargs):entity=self.cw_rset.complete_entity(row,col)self.w(entity.view('incontext'))searched=self.cw_rset.searched_text()ifsearchedisNone:returnsearched=searched.lower()highlighted='<b>%s</b>'%searchedforattrinentity.e_schema.indexable_attributes():try:value=xml_escape(entity.printable_value(attr,format='text/plain').lower())exceptTransformError,ex:continueexcept:continueifsearchedinvalue:contexts=[]forctxinvalue.split(searched):iflen(ctx)>30:contexts.append(u'...'+ctx[-30:])else:contexts.append(ctx)value=u'\n'+highlighted.join(contexts)self.w(value.replace('\n','<br/>'))classTooltipView(EntityView):"""A entity view used in a tooltip"""__regid__='tooltip'defcell_call(self,row,col):self.wview('oneline',self.cw_rset,row=row,col=col)# XXX bw compatfromlogilab.common.deprecationimportclass_movedtry:fromcubicweb.web.views.tableviewimportTableViewTableView=class_moved(TableView)exceptImportError:pass# gae has no tableview module (yet)fromcubicweb.web.viewsimportboxes,xmlrss,primaryPrimaryView=class_moved(primary.PrimaryView)SideBoxView=class_moved(boxes.SideBoxView)XmlView=class_moved(xmlrss.XMLView)XmlItemView=class_moved(xmlrss.XMLItemView)XmlRsetView=class_moved(xmlrss.XMLRsetView)RssView=class_moved(xmlrss.RSSView)RssItemView=class_moved(xmlrss.RSSItemView)classGroupByView(EntityView):"""grouped view of a result set. The `group_key` method return the group key of an entities (a string or tuple of string). For each group, display a link to entities of this group by generating url like <basepath>/<key> or <basepath>/<key item 1>/<key item 2>. """__abstrack__=True__select__=EntityView.__select__&match_kwargs('basepath')entity_attribute=Nonereversed=Falsedefindex_url(self,basepath,key,**kwargs):ifisinstance(key,(list,tuple)):key='/'.join(key)returnself._cw.build_url('%s/%s'%(basepath,key),**kwargs)defindex_link(self,basepath,key,items):url=self.index_url(basepath,key)ifisinstance(key,(list,tuple)):key=' '.join(key)returntags.a(key,href=url)defgroup_key(self,entity,**kwargs):value=getattr(entity,self.entity_attribute)ifcallable(value):value=value()returnvaluedefcall(self,basepath,maxentries=None,**kwargs):index={}forentityinself.cw_rset.entities():index.setdefault(self.group_key(entity,**kwargs),[]).append(entity)displayed=sorted(index)ifself.reversed:displayed=reversed(displayed)ifmaxentriesisNone:needmore=Falseelse:needmore=len(index)>maxentriesdisplayed=tuple(displayed)[:maxentries]w=self.ww(u'<ul class="boxListing">')forkeyindisplayed:w(u'<li>%s</li>\n'%self.index_link(basepath,key,index[key]))ifneedmore:url=self._cw.build_url('view',vid=self.__regid__,rql=self.cw_rset.printable_rql())w(u'<li>%s</li>\n'%tags.a(u'[%s]'%self._cw._('see more'),href=url))w(u'</ul>\n')classArchiveView(GroupByView):"""archive view of a result set. Links to months are built using a basepath parameters, eg using url like <basepath>/<year>/<month> """__regid__='cw.archive.by_date'entity_attribute='creation_date'reversed=Truedefgroup_key(self,entity,**kwargs):value=super(ArchiveView,self).group_key(entity,**kwargs)return'%04d'%value.year,'%02d'%value.monthdefindex_link(self,basepath,key,items):"""represent a single month entry"""year,month=keylabel=u'%s%s [%s]'%(self._cw._(calendar.MONTHNAMES[int(month)-1]),year,len(items))etypes=set(entity.__regid__forentityinitems)vtitle='%s%s'%(', '.join(display_name(self._cw,etype,'plural')foretypeinetypes),label)title=self._cw._('archive for %(month)s/%(year)s')%{'month':month,'year':year}url=self.index_url(basepath,key,vtitle=vtitle)returntags.a(label,href=url,title=title)classAuthorView(GroupByView):"""author view of a result set. Links to month are built using a basepath parameters, eg using url like <basepath>/<author> """__regid__='cw.archive.by_author'entity_attribute='creator'defgroup_key(self,entity,**kwargs):value=super(AuthorView,self).group_key(entity,**kwargs)ifvalue:returnvalue.loginreturnvaluedefindex_link(self,basepath,key,items):label=u'%s [%s]'%(key,len(items))etypes=set(entity.__regid__forentityinitems)vtitle=self._cw._('%(etype)s by %(author)s')%{'etype':', '.join(display_name(self._cw,etype,'plural')foretypeinetypes),'author':label}url=self.index_url(basepath,key,vtitle=vtitle)title=self._cw._('archive for %(author)s')%{'author':key}returntags.a(label,href=url,title=title)