"""generic table view, including filtering abilities:organization: Logilab:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"fromsimplejsonimportdumpsfromlogilab.mtconverterimportxml_escapefromcubicweb.selectorsimportnonempty_rset,match_form_paramsfromcubicweb.utilsimportmake_uidfromcubicweb.viewimportEntityView,AnyRsetViewfromcubicwebimporttagsfromcubicweb.uilibimporttoggle_action,limitsize,htmlescapefromcubicweb.webimportjsonizefromcubicweb.web.htmlwidgetsimport(TableWidget,TableColumn,MenuWidget,PopupBoxMenu,BoxLink)fromcubicweb.web.facetimportprepare_facets_rqlst,filter_hiddensclassTableView(AnyRsetView):__regid__='table'title=_('table')finalview='final'defform_filter(self,divid,displaycols,displayactions,displayfilter,hidden=True):rqlst=self.cw_rset.syntax_tree()# union not yet supportediflen(rqlst.children)!=1:return()rqlst.save_state()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,context='tablefilter',filtered_variable=mainvar)]wdgs=[wdgforwdginwdgsifwdgisnotNone]rqlst.recover()ifwdgs:self._generate_form(divid,baserql,wdgs,hidden,vidargs={'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 occurs when a context eid is given """self._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)self.w(u'<form method="post" cubicweb:facetargs="%s" action="">'%xml_escape(dumps([divid,'table',False,vidargs])))self.w(u'<fieldset id="%sForm" class="%s">'%(divid,hiddenand'hidden'or''))self.w(u'<input type="hidden" name="divid" value="%s" />'%divid)self.w(u'<input type="hidden" name="fromformfilter" value="1" />')filter_hiddens(self.w,facets=','.join(wdg.facet.__regid__forwdginfwidgets),baserql=baserql)self.w(u'<table class="filter">\n')self.w(u'<tr>\n')forwdginfwidgets:self.w(u'<td>')wdg.render(w=self.w)self.w(u'</td>\n')self.w(u'</tr>\n')self.w(u'</table>\n')self.w(u'</fieldset>\n')self.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):ifdisplaycolsisNone:if'displaycols'inself._cw.form:displaycols=[int(idx)foridxinself._cw.form['displaycols']]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):"""Dumps 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:ifdisplayfilterisNoneand'displayfilter'inreq.form:displayfilter=Trueifreq.form['displayfilter']=='shown':hidden=FalseifdisplayactionsisNoneand'displayactions'inreq.form:displayactions=Truedisplaycols=self.displaycols(displaycols)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)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))self.w(u' cubicweb:displayactions="1">')# close <div tagelse:self.w(u'>')# close <div tag# render actions menuifactions:self.render_actions(divid,actions)# render tabletable=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')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.external_resource('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==mainindex: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]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]ifvalisnotNoneandnotself._cw.vreg.schema.eschema(etype).final:e=self.cw_rset.get_entity(row,col)e.view(cellvidor'outofcontext',w=self.w)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()&match_form_params('actualrql')# should not be displayed in possible view since it expects some specific# parameterstitle=Nonedefcall(self,title=None,subvid=None,headers=None,divid=None,displaycols=None,displayactions=None,mainindex=None):"""Dumps a table displaying a composite query"""actrql=self._cw.form['actualrql']self._cw.ensure_ro_rql(actrql)displaycols=self.displaycols(displaycols)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,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'