[view, plot] deprecate flot and encourage usage of the jqplot cube (closes #1625218)
# copyright 2003-2011 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 base controllers, which are directly plugged into the applicationobject to handle publication."""__docformat__="restructuredtext en"_=unicodefromlogilab.common.dateimportstrptimefromlogilab.common.deprecationimportdeprecatedfromcubicwebimport(NoSelectableObject,ObjectNotFound,ValidationError,AuthenticationError,typed_eid)fromcubicweb.utilsimportUStringIO,json,json_dumpsfromcubicweb.uilibimportexc_messagefromcubicweb.selectorsimportauthenticated_user,anonymous_user,match_form_paramsfromcubicweb.mailimportformat_mailfromcubicweb.webimportRedirect,RemoteCallFailed,DirectResponsefromcubicweb.web.controllerimportControllerfromcubicweb.web.viewsimportvid_from_rset,formrendererstry:fromcubicweb.webimportfacetasfacetbaseexceptImportError:# gaefacetbase=Nonedefjsonize(func):"""decorator to sets correct content_type and calls `json_dumps` on results """defwrapper(self,*args,**kwargs):self._cw.set_content_type('application/json')returnjson_dumps(func(self,*args,**kwargs))wrapper.__name__=func.__name__returnwrapperdefxhtmlize(func):"""decorator to sets correct content_type and calls `xmlize` on results"""defwrapper(self,*args,**kwargs):self._cw.set_content_type(self._cw.html_content_type())result=func(self,*args,**kwargs)return''.join((self._cw.document_surrounding_div(),result.strip(),u'</div>'))wrapper.__name__=func.__name__returnwrapperdefcheck_pageid(func):"""decorator which checks the given pageid is found in the user's session data """defwrapper(self,*args,**kwargs):data=self._cw.session.data.get(self._cw.pageid)ifdataisNone:raiseRemoteCallFailed(self._cw._('pageid-not-found'))returnfunc(self,*args,**kwargs)returnwrapperclassLoginController(Controller):__regid__='login'__select__=anonymous_user()defpublish(self,rset=None):"""log in the instance"""ifself._cw.vreg.config['auth-mode']=='http':# HTTP authenticationraiseAuthenticationError()else:# Cookie authenticationreturnself.appli.need_login_content(self._cw)classLogoutController(Controller):__regid__='logout'defpublish(self,rset=None):"""logout from the instance"""returnself.appli.session_handler.logout(self._cw,self.goto_url())defgoto_url(self):# * in http auth mode, url will be ignored# * in cookie mode redirecting to the index view is enough : either# anonymous connection is allowed and the page will be displayed or# we'll be redirected to the login formmsg=self._cw._('you have been logged out')# force base_url so on dual http/https configuration, we generate an url# on the http version of the sitereturnself._cw.build_url('view',vid='loggedout',base_url=self._cw.vreg.config['base-url'])classViewController(Controller):"""standard entry point : - build result set - select and call main template """__regid__='view'template='main-template'defpublish(self,rset=None):"""publish a request, returning an encoded string"""view,rset=self._select_view_and_rset(rset)self.add_to_breadcrumbs(view)self.validate_cache(view)template=self.appli.main_template_id(self._cw)returnself._cw.vreg['views'].main_template(self._cw,template,rset=rset,view=view)def_select_view_and_rset(self,rset):req=self._cwifrsetisNoneandnothasattr(req,'_rql_processed'):req._rql_processed=Trueifreq.cnx:rset=self.process_rql()else:rset=Nonevid=req.form.get('vid')orvid_from_rset(req,rset,self._cw.vreg.schema)try:view=self._cw.vreg['views'].select(vid,req,rset=rset)exceptObjectNotFound:self.warning("the view %s could not be found",vid)req.set_message(req._("The view %s could not be found")%vid)vid=vid_from_rset(req,rset,self._cw.vreg.schema)view=self._cw.vreg['views'].select(vid,req,rset=rset)exceptNoSelectableObject:ifrset:req.set_message(req._("The view %s can not be applied to this query")%vid)else:req.set_message(req._("You have no access to this view or it can not ""be used to display the current data."))vid=req.form.get('fallbackvid')orvid_from_rset(req,rset,req.vreg.schema)view=req.vreg['views'].select(vid,req,rset=rset)returnview,rsetdefadd_to_breadcrumbs(self,view):# update breadcrumbs **before** validating cache, unless the view# specifies explicitly it should not be added to breadcrumb or the# view is a binary viewifview.add_to_breadcrumbsandnotview.binary:self._cw.update_breadcrumbs()defexecute_linkto(self,eid=None):"""XXX __linkto parameter may cause security issue defined here since custom application controller inheriting from this one use this method? """req=self._cwifnot'__linkto'inreq.form:returnifeidisNone:eid=typed_eid(req.form['eid'])forlinktoinreq.list_form_param('__linkto',pop=True):rtype,eids,target=linkto.split(':')asserttargetin('subject','object')eids=eids.split('_')iftarget=='subject':rql='SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s'%rtypeelse:rql='SET Y %s X WHERE X eid %%(x)s, Y eid %%(y)s'%rtypeforteidineids:req.execute(rql,{'x':eid,'y':typed_eid(teid)})def_validation_error(req,ex):req.cnx.rollback()# XXX necessary to remove existant validation error?# imo (syt), it's not necessaryreq.session.data.pop(req.form.get('__errorurl'),None)foreid=ex.entityeidmap=req.data.get('eidmap',{})forvar,eidineidmap.items():ifforeid==eid:foreid=varbreakreturn(foreid,ex.errors)def_validate_form(req,vreg):# XXX should use the `RemoteCallFailed` mechanismtry:ctrl=vreg['controllers'].select('edit',req=req)exceptNoSelectableObject:return(False,{None:req._('not authorized')},None)try:ctrl.publish(None)exceptValidationError,ex:return(False,_validation_error(req,ex),ctrl._edited_entity)exceptRedirect,ex:try:req.cnx.commit()# ValidationError may be raise on commitexceptValidationError,ex:return(False,_validation_error(req,ex),ctrl._edited_entity)exceptException,ex:req.cnx.rollback()req.exception('unexpected error while validating form')return(False,str(ex).decode('utf-8'),ctrl._edited_entity)else:# complete entity: it can be used in js callbacks where we might# want every possible informationifctrl._edited_entity:ctrl._edited_entity.complete()return(True,ex.location,ctrl._edited_entity)exceptException,ex:req.cnx.rollback()req.exception('unexpected error while validating form')return(False,str(ex).decode('utf-8'),ctrl._edited_entity)return(False,'???',None)classFormValidatorController(Controller):__regid__='validateform'defresponse(self,domid,status,args,entity):callback=str(self._cw.form.get('__onsuccess','null'))errback=str(self._cw.form.get('__onfailure','null'))cbargs=str(self._cw.form.get('__cbargs','null'))self._cw.set_content_type('text/html')jsargs=json_dumps((status,args,entity))return"""<script type="text/javascript"> window.parent.handleFormValidationResponse('%s', %s, %s, %s, %s);</script>"""%(domid,callback,errback,jsargs,cbargs)defpublish(self,rset=None):self._cw.json_request=True# XXX unclear why we have a separated controller here vs# js_validate_form on the json controllerstatus,args,entity=_validate_form(self._cw,self._cw.vreg)domid=self._cw.form.get('__domid','entityForm').encode(self._cw.encoding)returnself.response(domid,status,args,entity)defoptional_kwargs(extraargs):ifextraargsisNone:return{}# we receive unicode keys which is not supported by the **syntaxreturndict((str(key),value)forkey,valueinextraargs.iteritems())classJSonController(Controller):__regid__='json'defpublish(self,rset=None):"""call js_* methods. Expected form keys: :fname: the method name without the js_ prefix :args: arguments list (json) note: it's the responsability of js_* methods to set the correct response content type """self._cw.json_request=Truetry:fname=self._cw.form['fname']func=getattr(self,'js_%s'%fname)exceptKeyError:raiseRemoteCallFailed('no method specified')exceptAttributeError:raiseRemoteCallFailed('no %s method'%fname)# no <arg> attribute means the callback takes no argumentargs=self._cw.form.get('arg',())ifnotisinstance(args,(list,tuple)):args=(args,)try:args=[json.loads(arg)forarginargs]exceptValueError,exc:self.exception('error while decoding json arguments for js_%s: %s (err: %s)',fname,args,exc)raiseRemoteCallFailed(exc_message(exc,self._cw.encoding))try:result=func(*args)except(RemoteCallFailed,DirectResponse):raiseexceptException,exc:self.exception('an exception occurred while calling js_%s(%s): %s',fname,args,exc)raiseRemoteCallFailed(exc_message(exc,self._cw.encoding))ifresultisNone:return''# get unicode on @htmlize methods, encoded string on @jsonize methodselifisinstance(result,unicode):returnresult.encode(self._cw.encoding)returnresultdef_rebuild_posted_form(self,names,values,action=None):form={}forname,valueinzip(names,values):# remove possible __action_xxx inputsifname.startswith('__action'):ifactionisNone:# strip '__action_' to get the actual action nameaction=name[9:]continue# form.setdefault(name, []).append(value)ifnameinform:curvalue=form[name]ifisinstance(curvalue,list):curvalue.append(value)else:form[name]=[curvalue,value]else:form[name]=value# simulate click on __action_%s button to help the controllerifaction:form['__action_%s'%action]=u'whatever'returnformdef_exec(self,rql,args=None,rocheck=True):"""json mode: execute RQL and return resultset as json"""rql=rql.strip()ifrql.startswith('rql:'):rql=rql[4:]ifrocheck:self._cw.ensure_ro_rql(rql)try:returnself._cw.execute(rql,args)exceptException,ex:self.exception("error in _exec(rql=%s): %s",rql,ex)returnNonereturnNonedef_call_view(self,view,paginate=False,**kwargs):divid=self._cw.form.get('divid')# we need to call pagination before with the stream settry:stream=view.set_stream()exceptAttributeError:stream=UStringIO()kwargs['w']=stream.writeassertnotpaginateifdivid=='pageContent':# ensure divid isn't reused by the view (e.g. table view)delself._cw.form['divid']# mimick main template behaviourstream.write(u'<div id="pageContent">')vtitle=self._cw.form.get('vtitle')ifvtitle:stream.write(u'<h1 class="vtitle">%s</h1>\n'%vtitle)paginate=Trueifpaginate:view.paginate()ifdivid=='pageContent':stream.write(u'<div id="contentmain">')view.render(**kwargs)extresources=self._cw.html_headers.getvalue(skiphead=True)ifextresources:stream.write(u'<div class="ajaxHtmlHead">\n')# XXX use a widget ?stream.write(extresources)stream.write(u'</div>\n')ifdivid=='pageContent':stream.write(u'</div></div>')returnstream.getvalue()@xhtmlizedefjs_view(self):# XXX try to use the page-content templatereq=self._cwrql=req.form.get('rql')ifrql:rset=self._exec(rql)elif'eid'inreq.form:rset=self._cw.eid_rset(req.form['eid'])else:rset=Nonevid=req.form.get('vid')orvid_from_rset(req,rset,self._cw.vreg.schema)try:view=self._cw.vreg['views'].select(vid,req,rset=rset)exceptNoSelectableObject:vid=req.form.get('fallbackvid','noresult')view=self._cw.vreg['views'].select(vid,req,rset=rset)self.validate_cache(view)returnself._call_view(view,paginate=req.form.pop('paginate',False))@xhtmlizedefjs_prop_widget(self,propkey,varname,tabindex=None):"""specific method for CWProperty handling"""entity=self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)entity.eid=varnameentity['pkey']=propkeyform=self._cw.vreg['forms'].select('edition',self._cw,entity=entity)form.build_context()vfield=form.field_by_name('value')renderer=formrenderers.FormRenderer(self._cw)returnvfield.render(form,renderer,tabindex=tabindex) \+renderer.render_help(form,vfield)@xhtmlizedefjs_component(self,compid,rql,registry='components',extraargs=None):ifrql:rset=self._exec(rql)else:rset=None# XXX while it sounds good, addition of the try/except below cause pb:# when filtering using facets return an empty rset, the edition box# isn't anymore selectable, as expected. The pb is that with the# try/except below, we see a "an error occurred" message in the ui, while# we don't see it without it. Proper fix would probably be to deal with# this by allowing facet handling code to tell to js_component that such# error is expected and should'nt be reported.#try:comp=self._cw.vreg[registry].select(compid,self._cw,rset=rset,**optional_kwargs(extraargs))#except NoSelectableObject:# raise RemoteCallFailed('unselectable')returnself._call_view(comp,**optional_kwargs(extraargs))@xhtmlizedefjs_render(self,registry,oid,eid=None,selectargs=None,renderargs=None):ifeidisnotNone:rset=self._cw.eid_rset(eid)# XXX set row=0elifself._cw.form.get('rql'):rset=self._cw.execute(self._cw.form['rql'])else:rset=Noneview=self._cw.vreg[registry].select(oid,self._cw,rset=rset,**optional_kwargs(selectargs))returnself._call_view(view,**optional_kwargs(renderargs))@check_pageid@xhtmlizedefjs_inline_creation_form(self,peid,petype,ttype,rtype,role,i18nctx):view=self._cw.vreg['views'].select('inline-creation',self._cw,etype=ttype,rtype=rtype,role=role,peid=peid,petype=petype)returnself._call_view(view,i18nctx=i18nctx)@jsonizedefjs_validate_form(self,action,names,values):returnself.validate_form(action,names,values)defvalidate_form(self,action,names,values):self._cw.form=self._rebuild_posted_form(names,values,action)return_validate_form(self._cw,self._cw.vreg)@xhtmlizedefjs_reledit_form(self):req=self._cwargs=dict((x,req.form[x])forxin('formid','rtype','role','reload','action'))rset=req.eid_rset(typed_eid(self._cw.form['eid']))try:args['reload']=json.loads(args['reload'])exceptValueError:# not true/false, an absolute urlassertargs['reload'].startswith('http')view=req.vreg['views'].select('reledit',req,rset=rset,rtype=args['rtype'])returnself._call_view(view,**args)@jsonizedefjs_i18n(self,msgids):"""returns the translation of `msgid`"""return[self._cw._(msgid)formsgidinmsgids]@jsonizedefjs_format_date(self,strdate):"""returns the formatted date for `msgid`"""date=strptime(strdate,'%Y-%m-%d %H:%M:%S')returnself._cw.format_date(date)@jsonizedefjs_external_resource(self,resource):"""returns the URL of the external resource named `resource`"""returnself._cw.uiprops[resource]@check_pageid@jsonizedefjs_user_callback(self,cbname):page_data=self._cw.session.data.get(self._cw.pageid,{})try:cb=page_data[cbname]exceptKeyError:returnNonereturncb(self._cw)iffacetbaseisnotNone:@jsonizedefjs_filter_build_rql(self,names,values):form=self._rebuild_posted_form(names,values)self._cw.form=formbuilder=facetbase.FilterRQLBuilder(self._cw)returnbuilder.build_rql()@jsonizedefjs_filter_select_content(self,facetids,rql,mainvar):# Union unsupported yetselect=self._cw.vreg.parse(self._cw,rql).children[0]filtered_variable=facetbase.get_filtered_variable(select,mainvar)facetbase.prepare_select(select,filtered_variable)update_map={}forfacetidinfacetids:facet=facetbase.get_facet(self._cw,facetid,select,filtered_variable)update_map[facetid]=facet.possible_values()returnupdate_mapdefjs_unregister_user_callback(self,cbname):self._cw.unregister_callback(self._cw.pageid,cbname)defjs_unload_page_data(self):self._cw.session.data.pop(self._cw.pageid,None)defjs_cancel_edition(self,errorurl):"""cancelling edition from javascript We need to clear associated req's data : - errorurl - pending insertions / deletions """self._cw.cancel_edition(errorurl)defjs_delete_bookmark(self,beid):rql='DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'self._cw.execute(rql,{'b':typed_eid(beid),'u':self._cw.user.eid})defjs_node_clicked(self,treeid,nodeeid):"""add/remove eid in treestate cookie"""fromcubicweb.web.views.treeviewimporttreecookienamecookies=self._cw.get_cookie()statename=treecookiename(treeid)treestate=cookies.get(statename)iftreestateisNone:self._cw.set_cookie(statename,nodeeid)else:marked=set(filter(None,treestate.value.split(':')))ifnodeeidinmarked:marked.remove(nodeeid)else:marked.add(nodeeid)self._cw.set_cookie(statename,':'.join(marked))@jsonize@deprecated("[3.13] use jQuery.cookie(cookiename, cookievalue, {path: '/'}) in js land instead")defjs_set_cookie(self,cookiename,cookievalue):cookiename,cookievalue=str(cookiename),str(cookievalue)self._cw.set_cookie(cookiename,cookievalue)# relations edition stuff ##################################################def_add_pending(self,eidfrom,rel,eidto,kind):key='pending_%s'%kindpendings=self._cw.session.data.setdefault(key,set())pendings.add((typed_eid(eidfrom),rel,typed_eid(eidto)))def_remove_pending(self,eidfrom,rel,eidto,kind):key='pending_%s'%kindpendings=self._cw.session.data[key]pendings.remove((typed_eid(eidfrom),rel,typed_eid(eidto)))defjs_remove_pending_insert(self,(eidfrom,rel,eidto)):self._remove_pending(eidfrom,rel,eidto,'insert')defjs_add_pending_inserts(self,tripletlist):foreidfrom,rel,eidtointripletlist:self._add_pending(eidfrom,rel,eidto,'insert')defjs_remove_pending_delete(self,(eidfrom,rel,eidto)):self._remove_pending(eidfrom,rel,eidto,'delete')defjs_add_pending_delete(self,(eidfrom,rel,eidto)):self._add_pending(eidfrom,rel,eidto,'delete')# XXX move to massmailingclassMailBugReportController(Controller):__regid__='reportbug'__select__=match_form_params('description')defpublish(self,rset=None):body=self._cw.form['description']self.sendmail(self._cw.config['submit-mail'],self._cw._('%s error report')%self._cw.config.appid,body)url=self._cw.build_url(__message=self._cw._('bug report sent'))raiseRedirect(url)classUndoController(Controller):__regid__='undo'__select__=authenticated_user()&match_form_params('txuuid')defpublish(self,rset=None):txuuid=self._cw.form['txuuid']errors=self._cw.cnx.undo_transaction(txuuid)ifnoterrors:self.redirect()raiseValidationError(None,{None:'\n'.join(errors)})defredirect(self,msg=None):req=self._cwmsg=msgorreq._("transaction undone")breadcrumbs=req.session.data.get('breadcrumbs',None)ifbreadcrumbsisnotNoneandlen(breadcrumbs)>1:url=req.rebuild_url(breadcrumbs[-2],__message=msg)else:url=req.build_url(__message=msg)raiseRedirect(url)