# -*- coding: utf-8 -*-"""Set of base controllers, which are directly plugged into the applicationobject to handle publication.:organization: Logilab:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr"""__docformat__="restructuredtext en"fromsmtplibimportSMTPimportsimplejsonfrommx.DateTime.ParserimportDateFromStringfromlogilab.common.decoratorsimportcachedfromcubicwebimportNoSelectableObject,ValidationError,typed_eidfromcubicweb.common.selectorsimportyes_selectorfromcubicweb.common.mailimportformat_mailfromcubicweb.common.viewimportSTRICT_DOCTYPE,CW_XHTML_EXTENSIONSfromcubicweb.webimportExplicitLogin,Redirect,RemoteCallFailedfromcubicweb.web.controllerimportControllerfromcubicweb.web.viewsimportvid_from_rsettry:fromcubicweb.web.facetimport(FilterRQLBuilder,get_facet,prepare_facets_rqlst)HAS_SEARCH_RESTRICTION=TrueexceptImportError:# gaeHAS_SEARCH_RESTRICTION=FalseclassLoginController(Controller):id='login'defpublish(self,rset=None):"""log in the application"""ifself.config['auth-mode']=='http':# HTTP authenticationraiseExplicitLogin()else:# Cookie authenticationreturnself.appli.need_login_content(self.req)classLogoutController(Controller):id='logout'defpublish(self,rset=None):"""logout from the application"""returnself.appli.session_handler.logout(self.req)classViewController(Controller):id='view'template='main'defpublish(self,rset=None):"""publish a request, returning an encoded string"""self.req.update_search_state()template=self.req.property_value('ui.main-template')iftemplatenotinself.vreg.registry('templates'):template=self.templatereturnself.vreg.main_template(self.req,template,rset=rset)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.reqifnot'__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)},('x','y'))classFormValidatorController(Controller):id='validateform'defpublish(self,rset=None):vreg=self.vregtry:ctrl=vreg.select(vreg.registry_objects('controllers','edit'),req=self.req,appli=self.appli)exceptNoSelectableObject:status,args=(False,{None:self.req._('not authorized')})else:try:ctrl.publish(None,fromjson=True)exceptValidationError,err:status,args=self.validation_error(err)exceptRedirect,err:try:self.req.cnx.commit()# ValidationError may be raise on commitexceptValidationError,err:status,args=self.validation_error(err)else:status,args=(True,err.location)exceptException,err:self.req.cnx.rollback()self.exception('unexpected error in validateform')try:status,args=(False,self.req._(unicode(err)))exceptUnicodeError:status,args=(False,repr(err))else:status,args=(False,'???')self.req.set_content_type('text/html')jsarg=simplejson.dumps((status,args))return"""<script type="text/javascript"> window.parent.handleFormValidationResponse('entityForm', null, %s);</script>"""%simplejson.dumps((status,args))defvalidation_error(self,err):self.req.cnx.rollback()try:eid=err.entity.eidexceptAttributeError:eid=err.entityreturn(False,(eid,err.errors))defxmlize(source):head=u'<?xml version="1.0"?>\n'+STRICT_DOCTYPE%CW_XHTML_EXTENSIONSreturnhead+u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>'%source.strip()defjsonize(func):"""sets correct content_type and calls `simplejson.dumps` on results """defwrapper(self,*args,**kwargs):self.req.set_content_type('application/json')result=func(self,*args,**kwargs)returnsimplejson.dumps(result)returnwrapperdefcheck_pageid(func):"""decorator which checks the given pageid is found in the user's session data """defwrapper(self,*args,**kwargs):data=self.req.get_session_data(self.req.pageid)ifdataisNone:raiseRemoteCallFailed(self.req._('pageid-not-found'))returnfunc(self,*args,**kwargs)returnwrapperclassJSonController(Controller):id='json'template='main'defpublish(self,rset=None):mode=self.req.form.get('mode','html')self.req.pageid=self.req.form.get('pageid')try:func=getattr(self,'%s_exec'%mode)exceptAttributeError,ex:self.error('json controller got an unknown mode %r',mode)self.error('\t%s',ex)result=u''else:try:result=func(rset)exceptRemoteCallFailed:raiseexceptException,ex:self.exception('an exception occured on json request(rset=%s): %s',rset,ex)raiseRemoteCallFailed(repr(ex))returnresult.encode(self.req.encoding)def_exec(self,rql,args=None,eidkey=None,rocheck=True):"""json mode: execute RQL and return resultset as json"""ifrocheck:self.ensure_ro_rql(rql)try:returnself.req.execute(rql,args,eidkey)exceptException,ex:self.exception("error in _exec(rql=%s): %s",rql,ex)returnNonereturnNone@jsonizedefjson_exec(self,rset=None):"""json mode: execute RQL and return resultset as json"""rql=self.req.form.get('rql')ifrsetisNoneandrql:rset=self._exec(rql)returnrsetandrset.rowsor[]def_set_content_type(self,vobj,data):"""sets req's content type according to vobj's content type (and xmlize data if needed) """content_type=vobj.content_typeifcontent_type=='application/xhtml+xml':self.req.set_content_type(content_type)returnxmlize(data)returndatadefhtml_exec(self,rset=None):"""html mode: execute query and return the view as HTML"""req=self.reqrql=req.form.get('rql')ifrsetisNoneandrql:rset=self._exec(rql)vid=req.form.get('vid')orvid_from_rset(req,rset,self.schema)try:view=self.vreg.select_view(vid,req,rset)exceptNoSelectableObject:vid=req.form.get('fallbackvid','noresult')view=self.vreg.select_view(vid,req,rset)divid=req.form.get('divid','pageContent')# we need to call pagination before with the stream setstream=view.set_stream()ifreq.form.get('paginate'):ifdivid=='pageContent':# mimick main template behaviourstream.write(u'<div id="pageContent">')vtitle=self.req.form.get('vtitle')ifvtitle:w(u'<h1 class="vtitle">%s</h1>\n'%vtitle)view.pagination(req,rset,view.w,notview.need_navigation)ifdivid=='pageContent':stream.write(u'<div id="contentmain">')view.dispatch()ifreq.form.get('paginate')anddivid=='pageContent':stream.write(u'</div></div>')source=stream.getvalue()returnself._set_content_type(view,source)defrawremote_exec(self,rset=None):"""like remote_exec but doesn't change content type"""# no <arg> attribute means the callback takes no argumentargs=self.req.form.get('arg',())ifnotisinstance(args,(list,tuple)):args=(args,)fname=self.req.form['fname']args=[simplejson.loads(arg)forarginargs]try:func=getattr(self,'js_%s'%fname)exceptAttributeError:self.exception('rawremote_exec fname=%s',fname)returnu""returnfunc(*args)remote_exec=jsonize(rawremote_exec)def_rebuild_posted_form(self,names,values,action=None):form={}forname,valueinzip(names,values):# remove possible __action_xxx inputsifname.startswith('__action'):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'returnformdefjs_validate_form(self,action,names,values):# XXX this method (and correspoding js calls) should use the new# `RemoteCallFailed` mechansimself.req.form=self._rebuild_posted_form(names,values,action)vreg=self.vregtry:ctrl=vreg.select(vreg.registry_objects('controllers','edit'),req=self.req)exceptNoSelectableObject:return(False,{None:self.req._('not authorized')})try:ctrl.publish(None,fromjson=True)exceptValidationError,err:self.req.cnx.rollback()ifnoterr.entityorisinstance(err.entity,(long,int)):eid=err.entityelse:eid=err.entity.eidreturn(False,(eid,err.errors))exceptRedirect,err:return(True,err.location)exceptException,err:self.req.cnx.rollback()self.exception('unexpected error in js_validateform')return(False,self.req._(str(err)))return(False,'???')defjs_edit_field(self,action,names,values,rtype,eid):success,args=self.js_validate_form(action,names,values)ifsuccess:rset=self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N'%rtype,{'x':eid},'x')entity=rset.get_entity(0,0)return(success,args,entity.printable_value(rtype))else:return(success,args,None)defjs_rql(self,rql):rset=self._exec(rql)returnrsetandrset.rowsor[]defjs_i18n(self,msgids):"""returns the translation of `msgid`"""return[self.req._(msgid)formsgidinmsgids]defjs_format_date(self,strdate):"""returns the formatted date for `msgid`"""date=DateFromString(strdate)returnself.format_date(date)defjs_external_resource(self,resource):"""returns the URL of the external resource named `resource`"""returnself.req.external_resource(resource)defjs_prop_widget(self,propkey,varname,tabindex=None):"""specific method for EProperty handling"""w=self.vreg.property_value_widget(propkey,req=self.req)entity=self.vreg.etype_class('EProperty')(self.req,None,None)entity.eid=varnameself.req.form['value']=self.vreg.property_info(propkey)['default']returnw.edit_render(entity,tabindex,includehelp=True)defjs_component(self,compid,rql,registry='components',extraargs=None):ifrql:rset=self._exec(rql)else:rset=Nonecomp=self.vreg.select_object(registry,compid,self.req,rset)ifextraargsisNone:extraargs={}else:# we receive unicode keys which is not supported by the **syntaxextraargs=dict((str(key),value)forkey,valueinextraargs.items())extraargs=extraargsor{}print'extraargs =',extraargsreturnself._set_content_type(comp,comp.dispatch(**extraargs))@check_pageiddefjs_user_callback(self,cbname):page_data=self.req.get_session_data(self.req.pageid,{})try:cb=page_data[cbname]exceptKeyError:returnNonereturncb(self.req)defjs_unregister_user_callback(self,cbname):self.req.unregister_callback(self.req.pageid,cbname)defjs_unload_page_data(self):self.req.del_session_data(self.req.pageid)defjs_cancel_edition(self,errorurl):"""cancelling edition from javascript We need to clear associated req's data : - errorurl - pending insertions / deletions """self.req.cancel_edition(errorurl)@check_pageiddefjs_inline_creation_form(self,peid,ptype,ttype,rtype,role):view=self.vreg.select_view('inline-creation',self.req,None,etype=ttype,ptype=ptype,peid=peid,rtype=rtype,role=role)source=view.dispatch(etype=ttype,ptype=ptype,peid=peid,rtype=rtype,role=role)returnself._set_content_type(view,source)defjs_remove_pending_insert(self,(eidfrom,rel,eidto)):self._remove_pending(eidfrom,rel,eidto,'insert')defjs_add_pending_insert(self,(eidfrom,rel,eidto)):self._add_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')ifHAS_SEARCH_RESTRICTION:defjs_filter_build_rql(self,names,values):form=self._rebuild_posted_form(names,values)self.req.form=formbuilder=FilterRQLBuilder(self.req)returnbuilder.build_rql()defjs_filter_select_content(self,facetids,rql):rqlst=self.vreg.parse(self.req,rql)# XXX Union unsupported yetmainvar=prepare_facets_rqlst(rqlst)[0]update_map={}forfacetidinfacetids:facet=get_facet(self.req,facetid,rqlst.children[0],mainvar)update_map[facetid]=facet.possible_values()returnupdate_mapdefjs_delete_bookmark(self,beid):try:rql='DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'self.req.execute(rql,{'b':typed_eid(beid),'u':self.req.user.eid})exceptException,ex:self.exception(unicode(ex))returnself.req._('Problem occured')def_add_pending(self,eidfrom,rel,eidto,kind):key='pending_%s'%kindpendings=self.req.get_session_data(key,set())pendings.add((typed_eid(eidfrom),rel,typed_eid(eidto)))self.req.set_session_data(key,pendings)def_remove_pending(self,eidfrom,rel,eidto,kind):key='pending_%s'%kindtry:pendings=self.req.get_session_data(key)pendings.remove((typed_eid(eidfrom),rel,typed_eid(eidto)))except:self.exception('while removing pending eids')else:self.req.set_session_data(key,pendings)defjs_add_and_link_new_entity(self,etype_to,rel,eid_to,etype_from,value_from):# create a new entityeid_from=self.req.execute('INSERT %s T : T name "%s"'%(etype_from,value_from))[0][0]# link the new entity to the main entityrql='SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s'%{'rel':rel,'eid_to':eid_to,'eid_from':eid_from}returneid_fromclassSendMailController(Controller):id='sendmail'require_groups=('managers','users')defrecipients(self):"""returns an iterator on email's recipients as entities"""eids=self.req.form['recipient']# make sure we have a list even though only one recipient was specifiedifisinstance(eids,basestring):eids=(eids,)rql='Any X WHERE X eid in (%s)'%(','.join(eids))rset=self.req.execute(rql)forentityinrset.entities():entity.complete()# XXX really?yieldentity@property@cacheddefsmtp(self):mailhost,port=self.config['smtp-host'],self.config['smtp-port']try:returnSMTP(mailhost,port)exceptException,ex:self.exception("can't connect to smtp server %s:%s (%s)",mailhost,port,ex)url=self.build_url(__message=self.req._('could not connect to the SMTP server'))raiseRedirect(url)defsendmail(self,recipient,subject,body):helo_addr='%s <%s>'%(self.config['sender-name'],self.config['sender-addr'])msg=format_mail({'email':self.req.user.get_email(),'name':self.req.user.dc_title(),},[recipient],body,subject)self.smtp.sendmail(helo_addr,[recipient],msg.as_string())defpublish(self,rset=None):# XXX this allow anybody with access to an cubicweb application to use it as a mail relaybody=self.req.form['mailbody']subject=self.req.form['mailsubject']forrecipientinself.recipients():text=body%recipient.as_email_context()self.sendmail(recipient.get_email(),subject,text)# breadcrumbs = self.req.get_session_data('breadcrumbs', None)url=self.build_url(__message=self.req._('emails successfully sent'))raiseRedirect(url)classMailBugReportController(SendMailController):id='reportbug'__selectors__=(yes_selector,)defpublish(self,rset=None):body=self.req.form['description']self.sendmail(self.config['submit-mail'],_('%s error report')%self.config.appid,body)url=self.build_url(__message=self.req._('bug report sent'))raiseRedirect(url)