"""abstract form classes for CubicWeb web client: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"fromlogilab.common.decoratorsimporticlassmethodfromcubicweb.appobjectimportAppObjectfromcubicweb.viewimportNOINDEX,NOFOLLOWfromcubicweb.commonimporttagsfromcubicweb.webimportstdmsgs,httpcache,formfieldsclassFormViewMixIn(object):"""abstract form view mix-in"""category='form'controller='edit'http_cache_manager=httpcache.NoHTTPCacheManageradd_to_breadcrumbs=Falsedefhtml_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# XXX should disappearclassFormMixIn(object):"""abstract form mix-in XXX: you should inherit from this FIRST (obscure pb with super call) """defsession_key(self):"""return the key that may be used to store / retreive data about a previous post which failed because of a validation error """return'%s#%s'%(self.req.url(),self.domid)def__init__(self,req,rset,**kwargs):super(FormMixIn,self).__init__(req,rset,**kwargs)self.restore_previous_post(self.session_key())defrestore_previous_post(self,sessionkey):# 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 commitforminfo=self.req.get_session_data(sessionkey,pop=True)ifforminfo:# XXX remove req.data assigment once cw.web.widget is killedself.req.data['formvalues']=self.form_previous_values=forminfo['values']self.req.data['formerrors']=self.form_valerror=forminfo['errors']self.req.data['displayederrors']=self.form_displayed_errors=set()# if some validation error occured on entity creation, we have to# get the original variable name from its attributed eidforeid=self.form_valerror.entityforvar,eidinforminfo['eidmap'].items():ifforeid==eid:self.form_valerror.eid=varbreakelse:self.form_valerror.eid=foreidelse:self.form_previous_values={}self.form_valerror=None# XXX deprecated with new form system. Should disappeardomid='entityForm'category='form'controller='edit'http_cache_manager=httpcache.NoHTTPCacheManageradd_to_breadcrumbs=Falsedefhtml_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 """returnFalsedefbutton(self,label,klass='validateButton',tabindex=None,**kwargs):iftabindexisNone:tabindex=self.req.next_tabindex()returntags.input(value=label,klass=klass,**kwargs)defaction_button(self,label,onclick=None,__action=None,**kwargs):ifonclickisNone:onclick="postForm('__action_%s', \'%s\', \'%s\')"%(__action,label,self.domid)returnself.button(label,onclick=onclick,**kwargs)defbutton_ok(self,label=None,type='submit',name='defaultsubmit',**kwargs):label=self.req._(labelorstdmsgs.BUTTON_OK).capitalize()returnself.button(label,name=name,type=type,**kwargs)defbutton_apply(self,label=None,type='button',**kwargs):label=self.req._(labelorstdmsgs.BUTTON_APPLY).capitalize()returnself.action_button(label,__action='apply',type=type,**kwargs)defbutton_delete(self,label=None,type='button',**kwargs):label=self.req._(labelorstdmsgs.BUTTON_DELETE).capitalize()returnself.action_button(label,__action='delete',type=type,**kwargs)defbutton_cancel(self,label=None,type='button',**kwargs):label=self.req._(labelorstdmsgs.BUTTON_CANCEL).capitalize()returnself.action_button(label,__action='cancel',type=type,**kwargs)defbutton_reset(self,label=None,type='reset',name='__action_cancel',**kwargs):label=self.req._(labelorstdmsgs.BUTTON_CANCEL).capitalize()returnself.button(label,type=type,**kwargs)defneed_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')orself.form_valerror# get extra errorsiferrexisnotNone:errormsg=self.req._('please correct the following errors:')displayed=self.req.data.get('displayederrors')orself.form_displayed_errorserrors=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''###############################################################################classmetafieldsform(type):"""metaclass for FieldsForm to retrieve fields defined as class attributes and put them into a single ordered list: '_fields_'. """def__new__(mcs,name,bases,classdict):allfields=[]forbaseinbases:ifhasattr(base,'_fields_'):allfields+=base._fields_clsfields=(itemforiteminclassdict.items()ifisinstance(item[1],formfields.Field))forfieldname,fieldinsorted(clsfields,key=lambdax:x[1].creation_rank):ifnotfield.name:field.set_name(fieldname)allfields.append(field)classdict['_fields_']=allfieldsreturnsuper(metafieldsform,mcs).__new__(mcs,name,bases,classdict)classFieldNotFound(Exception):"""raised by field_by_name when a field with the given name has not been found """classForm(FormMixIn,AppObject):__metaclass__=metafieldsform__registry__='forms'parent_form=None@propertydefroot_form(self):"""return the root form"""ifself.parent_formisNone:returnselfreturnself.parent_form.root_form@iclassmethoddef_fieldsattr(cls_or_self):ifisinstance(cls_or_self,type):fields=cls_or_self._fields_else:fields=cls_or_self.fieldsreturnfields@iclassmethoddeffield_by_name(cls_or_self,name,role='subject'):"""return field with the given name and role. Raise FieldNotFound if the field can't be found. """forfieldincls_or_self._fieldsattr():iffield.name==nameandfield.role==role:returnfieldraiseFieldNotFound(name)@iclassmethoddeffields_by_name(cls_or_self,name,role='subject'):"""return a list of fields with the given name and role"""return[fieldforfieldincls_or_self._fieldsattr()iffield.name==nameandfield.role==role]@iclassmethoddefremove_field(cls_or_self,field):"""remove a field from form class or instance"""cls_or_self._fieldsattr().remove(field)@iclassmethoddefappend_field(cls_or_self,field):"""append a field to form class or instance"""cls_or_self._fieldsattr().append(field)@iclassmethoddefinsert_field_before(cls_or_self,new_field,name,role='subject'):field=cls_or_self.field_by_name(name,role)fields=cls_or_self._fieldsattr()fields.insert(fields.index(field),new_field)@iclassmethoddefinsert_field_after(cls_or_self,new_field,name,role='subject'):field=cls_or_self.field_by_name(name,role)fields=cls_or_self._fieldsattr()fields.insert(fields.index(field)+1,new_field)