"""abstract form classes for CubicWeb web client: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"fromsimplejsonimportdumpsfromlogilab.mtconverterimporthtml_escapefromcubicwebimporttyped_eidfromcubicweb.common.selectorsimportmatch_form_paramsfromcubicweb.common.registerersimportaccepts_registererfromcubicweb.common.viewimportNOINDEX,NOFOLLOW,View,EntityView,AnyRsetViewfromcubicweb.webimportstdmsgsfromcubicweb.web.httpcacheimportNoHTTPCacheManagerfromcubicweb.web.controllerimportredirect_paramsdefrelation_id(eid,rtype,target,reid):iftarget=='subject':returnu'%s:%s:%s'%(eid,rtype,reid)returnu'%s:%s:%s'%(reid,rtype,eid)classFormMixIn(object):"""abstract form mix-in"""category='form'controller='edit'domid='entityForm'http_cache_manager=NoHTTPCacheManageradd_to_breadcrumbs=Falseskip_relations=set()def__init__(self,req,rset):super(FormMixIn,self).__init__(req,rset)self.maxrelitems=self.req.property_value('navigation.related-limit')self.maxcomboitems=self.req.property_value('navigation.combobox-limit')self.force_display=notnotreq.form.get('__force_display')# get validation session data which may have been previously set.# deleting validation errors here breaks form reloading (errors are# no more available), they have to be deleted by application's publish# method on successful commitformurl=req.url()forminfo=req.get_session_data(formurl)ifforminfo:req.data['formvalues']=forminfo['values']req.data['formerrors']=errex=forminfo['errors']req.data['displayederrors']=set()# if some validation error occured on entity creation, we have to# get the original variable name from its attributed eidforeid=errex.entityforvar,eidinforminfo['eidmap'].items():ifforeid==eid:errex.eid=varbreakelse:errex.eid=foreiddefhtml_headers(self):"""return a list of html headers (eg something to be inserted between <head> and </head> of the returned page by default forms are neither indexed nor followed """return[NOINDEX,NOFOLLOW]deflinkable(self):"""override since forms are usually linked by an action, so we don't want them to be listed by appli.possible_views """returnFalse@propertydeflimit(self):ifself.force_display:returnNonereturnself.maxrelitems+1defneed_multipart(self,entity,categories=('primary','secondary')):"""return a boolean indicating if form's enctype should be multipart """forrschema,_,xinentity.relations_by_category(categories):ifentity.get_widget(rschema,x).need_multipart:returnTrue# let's find if any of our inlined entities needs multipartforrschema,targettypes,xinentity.relations_by_category('inlineview'):assertlen(targettypes)==1, \"I'm not able to deal with several targets and inlineview"ttype=targettypes[0]inlined_entity=self.vreg.etype_class(ttype)(self.req,None,None)forirschema,_,xininlined_entity.relations_by_category(categories):ifinlined_entity.get_widget(irschema,x).need_multipart:returnTruereturnFalsedeferror_message(self):"""return formatted error message This method should be called once inlined field errors has been consumed """errex=self.req.data.get('formerrors')# get extra errorsiferrexisnotNone:errormsg=self.req._('please correct the following errors:')displayed=self.req.data['displayederrors']errors=sorted((field,err)forfield,errinerrex.errors.items()ifnotfieldindisplayed)iferrors:iflen(errors)>1:templstr='<li>%s</li>\n'else:templstr=' %s\n'forfield,errinerrors:iffieldisNone:errormsg+=templstr%errelse:errormsg+=templstr%'%s: %s'%(self.req._(field),err)iflen(errors)>1:errormsg='<ul>%s</ul>'%errormsgreturnu'<div class="errorMessage">%s</div>'%errormsgreturnu''defrestore_pending_inserts(self,entity,cell=False):"""used to restore edition page as it was before clicking on 'search for <some entity type>' """eid=entity.eidcell=celland"div_insert_"or"tr"pending_inserts=set(self.req.get_pending_inserts(eid))forpendingidinpending_inserts:eidfrom,rtype,eidto=pendingid.split(':')iftyped_eid(eidfrom)==entity.eid:# subjectlabel=display_name(self.req,rtype,'subject')reid=eidtoelse:label=display_name(self.req,rtype,'object')reid=eidfromjscall="javascript: cancelPendingInsert('%s', '%s', null, %s);" \%(pendingid,cell,eid)rset=self.req.eid_rset(reid)eview=self.view('text',rset,row=0)# XXX find a clean way to handle basketsifrset.description[0][0]=='Basket':eview='%s (%s)'%(eview,display_name(self.req,'Basket'))yieldrtype,pendingid,jscall,label,reid,eviewdefforce_display_link(self):return(u'<span class="invisible">'u'[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'u'</span>'%self.req._('view all'))defrelations_table(self,entity):"""yiels 3-tuples (rtype, target, related_list) where <related_list> itself a list of : - node_id (will be the entity element's DOM id) - appropriate javascript's togglePendingDelete() function call - status 'pendingdelete' or '' - oneline view of related entity """eid=entity.eidpending_deletes=self.req.get_pending_deletes(eid)# XXX (adim) : quick fix to get Folder relationsforlabel,rschema,targetinentity.srelations_by_category(('generic','metadata'),'add'):ifrschemainself.skip_relations:continuerelatedrset=entity.related(rschema,target,limit=self.limit)toggable_rel_link=self.toggable_relation_link_func(rschema)related=[]forrowinxrange(relatedrset.rowcount):nodeid=relation_id(eid,rschema,target,relatedrset[row][0])ifnodeidinpending_deletes:status=u'pendingDelete'label='+'else:status=u''label='x'dellink=toggable_rel_link(eid,nodeid,label)eview=self.view('oneline',relatedrset,row=row)related.append((nodeid,dellink,status,eview))yield(rschema,target,related)deftoggable_relation_link_func(self,rschema):ifnotrschema.has_perm(self.req,'delete'):returnlambdax,y,z:u''returntoggable_relation_linkdefredirect_url(self,entity=None):"""return a url to use as next direction if there are some information specified in current form params, else return the result the reset_url method which should be defined in concrete classes """rparams=redirect_params(self.req.form)ifrparams:returnself.build_url('view',**rparams)returnself.reset_url(entity)defreset_url(self,entity):raiseNotImplementedError('implement me in concrete classes')BUTTON_STR=u'<input class="validateButton" type="submit" name="%s" value="%s" tabindex="%s"/>'ACTION_SUBMIT_STR=u'<input class="validateButton" type="button" onclick="postForm(\'%s\', \'%s\', \'%s\')" value="%s" tabindex="%s"/>'defbutton_ok(self,label=None,tabindex=None):label=self.req._(labelorstdmsgs.BUTTON_OK).capitalize()returnself.BUTTON_STR%('defaultsubmit',label,tabindexor2)defbutton_apply(self,label=None,tabindex=None):label=self.req._(labelorstdmsgs.BUTTON_APPLY).capitalize()returnself.ACTION_SUBMIT_STR%('__action_apply',label,self.domid,label,tabindexor3)defbutton_delete(self,label=None,tabindex=None):label=self.req._(labelorstdmsgs.BUTTON_DELETE).capitalize()returnself.ACTION_SUBMIT_STR%('__action_delete',label,self.domid,label,tabindexor3)defbutton_cancel(self,label=None,tabindex=None):label=self.req._(labelorstdmsgs.BUTTON_CANCEL).capitalize()returnself.ACTION_SUBMIT_STR%('__action_cancel',label,self.domid,label,tabindexor4)defbutton_reset(self,label=None,tabindex=None):label=self.req._(labelorstdmsgs.BUTTON_CANCEL).capitalize()returnu'<input class="validateButton" type="reset" value="%s" tabindex="%s"/>'%(label,tabindexor4)deftoggable_relation_link(eid,nodeid,label='x'):js=u"javascript: togglePendingDelete('%s', %s);"%(nodeid,html_escape(dumps(eid)))returnu'[<a class="handle" href="%s" id="handle%s">%s</a>]'%(js,nodeid,label)classForm(FormMixIn,View):"""base class for forms. Apply by default according to request form parameters specified using the `form_params` class attribute which should list necessary parameters in the form to be accepted. """__registerer__=accepts_registerer__select__=classmethod(match_form_params)form_params=()classEntityForm(FormMixIn,EntityView):"""base class for forms applying on an entity (i.e. uniform result set) """classAnyRsetForm(FormMixIn,AnyRsetView):"""base class for forms applying on any empty result sets """