# copyright 2003-2012 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"_=unicodefromwarningsimportwarnfromlogilab.common.deprecationimportdeprecatedfromcubicwebimport(NoSelectableObject,ObjectNotFound,ValidationError,AuthenticationError,typed_eid,UndoTransactionException,Forbidden)fromcubicweb.utilsimportjson_dumpsfromcubicweb.predicatesimport(authenticated_user,anonymous_user,match_form_params)fromcubicweb.webimportRedirect,RemoteCallFailedfromcubicweb.web.controllerimportController,append_url_paramsfromcubicweb.web.viewsimportvid_from_rsetimportcubicweb.transactionastx@deprecated('[3.15] jsonize is deprecated, use AjaxFunction appobjects instead')defjsonize(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__returnwrapper@deprecated('[3.15] xhtmlize is deprecated, use AjaxFunction appobjects instead')defxhtmlize(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__returnwrapper@deprecated('[3.15] check_pageid is deprecated, use AjaxFunction appobjects instead')defcheck_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)classLoginControllerForAuthed(Controller):__regid__='login'__select__=~anonymous_user()defpublish(self,rset=None):"""log in the instance"""path=self._cw.form.get('postlogin_path','')# redirect expect an url, not a path. Also path may contains a query# string, hence should not be given to _cw.build_url()raiseRedirect(self._cw.base_url()+path)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:txuuid=req.cnx.commit()# ValidationError may be raised 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:iftxuuidisnotNone:req.data['last_undoable_transaction']=txuuid# 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.ajax_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)classJSonController(Controller):__regid__='json'defpublish(self,rset=None):warn('[3.15] JSONController is deprecated, use AjaxController instead',DeprecationWarning)ajax_controller=self._cw.vreg['controllers'].select('ajax',self._cw,appli=self.appli)returnajax_controller.publish(rset)# XXX move to massmailingclassMailBugReportController(Controller):__regid__='reportbug'__select__=match_form_params('description')defpublish(self,rset=None):req=self._cwdesc=req.form['description']# The description is generated and signed by cubicweb itself, check# description's signature so we don't want to send spam heresign=req.form.get('__signature','')ifnot(signandreq.vreg.config.check_text_sign(desc,sign)):raiseForbidden('Invalid content')self.sendmail(req.vreg.config['submit-mail'],req._('%s error report')%req.vreg.config.appid,desc)raiseRedirect(req.build_url(__message=req._('bug report sent')))classUndoController(Controller):__regid__='undo'__select__=authenticated_user()&match_form_params('txuuid')defpublish(self,rset=None):txuuid=self._cw.form['txuuid']try:self._cw.cnx.undo_transaction(txuuid)exceptUndoTransactionException,exc:errors=exc.errors#This will cause a rollback in main_publishraiseValidationError(None,{None:'\n'.join(errors)})else:self.redirect()# Will raise Redirectdefredirect(self,msg=None):req=self._cwmsg=msgorreq._("transaction undone")self._return_to_lastpage(dict(_cwmsgid=req.set_redirect_message(msg)))