[basetemplates] breadcrumbs component context sticks to ApplicationName
# 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/>."""generic table view, including filtering abilities"""__docformat__="restructuredtext en"_=unicodefromlogilab.mtconverterimportxml_escapefromcubicweb.selectorsimportnonempty_rset,match_form_paramsfromcubicweb.utilsimportmake_uid,json_dumpsfromcubicweb.viewimportEntityView,AnyRsetViewfromcubicwebimporttagsfromcubicweb.uilibimporttoggle_action,limitsize,htmlescapefromcubicweb.webimportjsonizefromcubicweb.web.htmlwidgetsimport(TableWidget,TableColumn,MenuWidget,PopupBoxMenu,BoxLink)fromcubicweb.web.facetimportprepare_facets_rqlst,filter_hiddensclassTableView(AnyRsetView):"""The table view accepts any non-empty rset. It uses introspection on the result set to compute column names and the proper way to display the cells. It is however highly configurable and accepts a wealth of options. """__regid__='table'title=_('table')finalview='final'defform_filter(self,divid,displaycols,displayactions,displayfilter,paginate,hidden=True):rqlst=self.cw_rset.syntax_tree()# union not yet supportediflen(rqlst.children)!=1:return()rqlst=rqlst.copy()self._cw.vreg.rqlhelper.annotate(rqlst)mainvar,baserql=prepare_facets_rqlst(rqlst,self.cw_rset.args)wdgs=[facet.get_widget()forfacetinself._cw.vreg['facets'].poss_visible_objects(self._cw,rset=self.cw_rset,rqlst=rqlst.children[0],context='tablefilter',filtered_variable=mainvar)]wdgs=[wdgforwdginwdgsifwdgisnotNone]ifwdgs:self._generate_form(divid,baserql,wdgs,hidden,vidargs={'paginate':paginate,'displaycols':displaycols,'displayactions':displayactions,'displayfilter':displayfilter})returnself.show_hide_actions(divid,nothidden)return()def_generate_form(self,divid,baserql,fwidgets,hidden=True,vidargs={}):"""display a form to filter table's content. This should only occur when a context eid is given """w=self.wself._cw.add_css('cubicweb.facets.css')self._cw.add_js(('cubicweb.ajax.js','cubicweb.facets.js'))# drop False / None values from vidargsvidargs=dict((k,v)fork,vinvidargs.iteritems()ifv)w(u'<form method="post" cubicweb:facetargs="%s" action="">'%xml_escape(json_dumps([divid,self.__regid__,False,vidargs])))w(u'<fieldset id="%sForm" class="%s">'%(divid,hiddenand'hidden'or''))w(u'<input type="hidden" name="divid" value="%s" />'%divid)w(u'<input type="hidden" name="fromformfilter" value="1" />')filter_hiddens(w,facets=','.join(wdg.facet.__regid__forwdginfwidgets),baserql=baserql)w(u'<table class="filter">\n')w(u'<tr>\n')forwdginfwidgets:w(u'<td>')wdg.render(w=w)w(u'</td>\n')w(u'</tr>\n')w(u'</table>\n')w(u'</fieldset>\n')w(u'</form>\n')defmain_var_index(self):"""returns the index of the first non-attribute variable among the RQL selected variables """eschema=self._cw.vreg.schema.eschemafori,etypeinenumerate(self.cw_rset.description[0]):try:ifnoteschema(etype).final:returniexceptKeyError:# XXX possible?continuereturnNonedefdisplaycols(self,displaycols,headers):ifdisplaycolsisNone:if'displaycols'inself._cw.form:displaycols=[int(idx)foridxinself._cw.form['displaycols']]elifheadersisnotNone:displaycols=range(len(headers))else:displaycols=range(len(self.cw_rset.syntax_tree().children[0].selection))returndisplaycolsdefcall(self,title=None,subvid=None,displayfilter=None,headers=None,displaycols=None,displayactions=None,actions=(),divid=None,cellvids=None,cellattrs=None,mainindex=None,paginate=False,page_size=None):"""Produces a table displaying a composite query :param title: title added before table :param subvid: cell view :param displayfilter: filter that selects rows to display :param headers: columns' titles """req=self._cwreq.add_js('jquery.tablesorter.js')req.add_css(('cubicweb.tablesorter.css','cubicweb.tableview.css'))# compute label first since the filter form may remove some necessary# information from the rql syntax treeifmainindexisNone:mainindex=self.main_var_index()computed_labels=self.columns_labels(mainindex)hidden=Trueifnotsubvidand'subvid'inreq.form:subvid=req.form.pop('subvid')divid=dividorreq.form.get('divid')or'rs%s'%make_uid(id(self.cw_rset))actions=list(actions)ifmainindexisNone:displayfilter,displayactions=False,Falseelse:ifdisplayfilterisNoneandreq.form.get('displayfilter'):displayfilter=Trueifreq.form['displayfilter']=='shown':hidden=FalseifdisplayactionsisNoneandreq.form.get('displayactions'):displayactions=Truedisplaycols=self.displaycols(displaycols,headers)fromformfilter='fromformfilter'inreq.form# if fromformfilter is true, this is an ajax call and we only want to# replace the inner div, so don't regenerate everything under the if# belowifnotfromformfilter:self.w(u'<div class="section">')ifnottitleand'title'inreq.form:title=req.form['title']iftitle:self.w(u'<h2 class="tableTitle">%s</h2>\n'%title)ifdisplayfilter:actions+=self.form_filter(divid,displaycols,displayfilter,displayactions,paginate)elifdisplayfilter:actions+=self.show_hide_actions(divid,True)self.w(u'<div id="%s">'%divid)ifdisplayactions:actionsbycat=self._cw.vreg['actions'].possible_actions(req,self.cw_rset)foractioninactionsbycat.get('mainactions',()):foractioninaction.actual_actions():actions.append((action.url(),req._(action.title),action.html_class(),None))# render actions menuifactions:self.render_actions(divid,actions)# render tableifpaginate:self.divid=divid# XXX iirk (see usage in page_navigation_url)self.paginate(page_size=page_size,show_all_option=False)table=TableWidget(self)forcolumninself.get_columns(computed_labels,displaycols,headers,subvid,cellvids,cellattrs,mainindex):table.append_column(column)table.render(self.w)self.w(u'</div>\n')ifnotfromformfilter:self.w(u'</div>\n')defpage_navigation_url(self,navcomp,path,params):ifhasattr(self,'divid'):params['divid']=self.dividparams['vid']=self.__regid__returnnavcomp.ajax_page_url(**params)defshow_hide_actions(self,divid,currentlydisplayed=False):showhide=u';'.join(toggle_action('%s%s'%(divid,what))[11:]forwhatin('Form','Show','Hide','Actions'))showhide='javascript:'+showhideshowlabel=self._cw._('show filter form')hidelabel=self._cw._('hide filter form')ifcurrentlydisplayed:return[(showhide,showlabel,'hidden','%sShow'%divid),(showhide,hidelabel,None,'%sHide'%divid)]return[(showhide,showlabel,None,'%sShow'%divid),(showhide,hidelabel,'hidden','%sHide'%divid)]defrender_actions(self,divid,actions):box=MenuWidget('','tableActionsBox',_class='',islist=False)label=tags.img(src=self._cw.uiprops['PUCE_DOWN'],alt=xml_escape(self._cw._('action(s) on this selection')))menu=PopupBoxMenu(label,isitem=False,link_class='actionsBox',ident='%sActions'%divid)box.append(menu)forurl,label,klass,identinactions:menu.append(BoxLink(url,label,klass,ident=ident,escape=True))box.render(w=self.w)self.w(u'<div class="clear"/>')defget_columns(self,computed_labels,displaycols,headers,subvid,cellvids,cellattrs,mainindex):columns=[]eschema=self._cw.vreg.schema.eschemaforcolindex,labelinenumerate(computed_labels):ifcolindexnotindisplaycols:continue# compute column headerifheadersisnotNone:label=headers[displaycols.index(colindex)]ifcolindex==mainindexandlabelisnotNone:label+=' (%s)'%self.cw_rset.rowcountcolumn=TableColumn(label,colindex)coltype=self.cw_rset.description[0][colindex]# compute column cell view (if coltype is None, it's a left outer# join, use the default non final subvid)ifcellvidsandcolindexincellvids:column.append_renderer(cellvids[colindex],colindex)elifcoltypeisnotNoneandeschema(coltype).final:column.append_renderer(self.finalview,colindex)else:column.append_renderer(subvidor'incontext',colindex)ifcellattrsandcolindexincellattrs:forname,valueincellattrs[colindex].iteritems():column.add_attr(name,value)# add columncolumns.append(column)returncolumnsdefrender_cell(self,cellvid,row,col,w):self._cw.view('cell',self.cw_rset,row=row,col=col,cellvid=cellvid,w=w)defget_rows(self):returnself.cw_rset@htmlescape@jsonize@limitsize(10)defsortvalue(self,row,col):# XXX it might be interesting to try to limit value's# length as much as possible (e.g. by returning the 10# first characters of a string)val=self.cw_rset[row][col]ifvalisNone:returnu''etype=self.cw_rset.description[row][col]ifetypeisNone:returnu''ifself._cw.vreg.schema.eschema(etype).final:entity,rtype=self.cw_rset.related_entity(row,col)ifentityisNone:returnval# remove_html_tags() ?returnentity.sortvalue(rtype)entity=self.cw_rset.get_entity(row,col)returnentity.sortvalue()classEditableTableView(TableView):__regid__='editable-table'finalview='editable-final'title=_('editable-table')classCellView(EntityView):__regid__='cell'__select__=nonempty_rset()defcell_call(self,row,col,cellvid=None):""" :param row, col: indexes locating the cell value in view's result set :param cellvid: cell view (defaults to 'outofcontext') """etype,val=self.cw_rset.description[row][col],self.cw_rset[row][col]ifvalisnotNoneandetypeisnotNoneandnotself._cw.vreg.schema.eschema(etype).final:self.wview(cellvidor'outofcontext',self.cw_rset,row=row,col=col)elifvalisNone:# This is usually caused by a left outer join and in that case,# regular views will most certainly fail if they don't have# a real eidself.wview('final',self.cw_rset,row=row,col=col)else:self.wview(cellvidor'final',self.cw_rset,'null',row=row,col=col)classInitialTableView(TableView):"""same display as table view but consider two rql queries : * the default query (ie `rql` form parameter), which is only used to select this view and to build the filter form. This query should have the same structure as the actual without actual restriction (but link to restriction variables) and usually with a limit for efficiency (limit set to 2 is advised) * the actual query (`actualrql` form parameter) whose results will be displayed with default restrictions set """__regid__='initialtable'__select__=nonempty_rset()# should not be displayed in possible view since it expects some specific# parameterstitle=Nonedefcall(self,title=None,subvid=None,headers=None,divid=None,paginate=False,displaycols=None,displayactions=None,mainindex=None):"""Dumps a table displaying a composite query"""try:actrql=self._cw.form['actualrql']exceptKeyError:actrql=self.cw_rset.printable_rql()else:self._cw.ensure_ro_rql(actrql)displaycols=self.displaycols(displaycols,headers)ifdisplayactionsisNoneand'displayactions'inself._cw.form:displayactions=TrueifdividisNoneand'divid'inself._cw.form:divid=self._cw.form['divid']self.w(u'<div class="section">')ifnottitleand'title'inself._cw.form:# pop title so it's not displayed by the table view as welltitle=self._cw.form.pop('title')iftitle:self.w(u'<h2>%s</h2>\n'%title)ifmainindexisNone:mainindex=self.main_var_index()ifmainindexisnotNone:actions=self.form_filter(divid,displaycols,displayactions,displayfilter=True,paginate=paginate,hidden=True)else:actions=()ifnotsubvidand'subvid'inself._cw.form:subvid=self._cw.form.pop('subvid')self._cw.view('table',self._cw.execute(actrql),'noresult',w=self.w,displayfilter=False,subvid=subvid,displayactions=displayactions,displaycols=displaycols,actions=actions,headers=headers,divid=divid)self.w(u'</div>\n')classEditableInitialTableTableView(InitialTableView):__regid__='editable-initialtable'finalview='editable-final'classEntityAttributesTableView(EntityView):"""This table displays entity attributes in a table and allow to set a specific method to help building cell content for each attribute as well as column header. Table will render entity cell by using the appropriate build_COLNAME_cell methods if defined otherwise cell content will be entity.COLNAME. Table will render column header using the method header_for_COLNAME if defined otherwise COLNAME will be used. """__abstract__=Truecolumns=()table_css="listing"css_files=()defcall(self,columns=None):ifself.css_files:self._cw.add_css(self.css_files)_=self._cw._self.columns=columnsorself.columnssample=self.cw_rset.get_entity(0,0)self.w(u'<table class="%s">'%self.table_css)self.table_header(sample)self.w(u'<tbody>')forrowinxrange(self.cw_rset.rowcount):self.cell_call(row=row,col=0)self.w(u'</tbody>')self.w(u'</table>')defcell_call(self,row,col):_=self._cw._entity=self.cw_rset.get_entity(row,col)infos={}forcolinself.columns:meth=getattr(self,'build_%s_cell'%col,None)# find the build method or try to find matching attributeifmeth:content=meth(entity)else:content=entity.printable_value(col)infos[col]=contentself.w(u"""<tr onmouseover="$(this).addClass('highlighted');" onmouseout="$(this).removeClass('highlighted')">""")line=u''.join(u'<td>%%(%s)s</td>'%colforcolinself.columns)self.w(line%infos)self.w(u'</tr>\n')deftable_header(self,sample):"""builds the table's header"""self.w(u'<thead><tr>')forcolumninself.columns:meth=getattr(self,'header_for_%s'%column,None)ifmeth:colname=meth(sample)else:colname=self._cw._(column)self.w(u'<th>%s</th>'%xml_escape(colname))self.w(u'</tr></thead>\n')