"""abstract form classes for CubicWeb web client:organization: Logilab:copyright: 2001-2010 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"fromwarningsimportwarnfromlogilab.common.decoratorsimporticlassmethodfromlogilab.common.deprecationimportdeprecatedfromcubicweb.appobjectimportAppObjectfromcubicweb.viewimportNOINDEX,NOFOLLOWfromcubicwebimporttagsfromcubicweb.webimportstdmsgs,httpcache,formfieldsclassFormViewMixIn(object):"""abstract form view mix-in"""category='form'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###############################################################################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(AppObject):__metaclass__=metafieldsform__registry__='forms'parent_form=Noneforce_session_key=Nonedef__init__(self,req,rset,**kwargs):super(Form,self).__init__(req,rset=rset,**kwargs)self.restore_previous_post(self.session_key())@propertydefroot_form(self):"""return the root form"""ifself.parent_formisNone:returnselfreturnself.parent_form.root_form@propertydefform_valerror(self):"""the validation error exception if any"""ifself.parent_formisNone:returnself._form_valerrorreturnself.parent_form.form_valerror@propertydefform_previous_values(self):"""previously posted values (on validation error)"""ifself.parent_formisNone:returnself._form_previous_valuesreturnself.parent_form.form_previous_values@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=None):"""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,role)@iclassmethoddeffields_by_name(cls_or_self,name,role=None):"""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)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 """ifself.force_session_keyisNone:return'%s#%s'%(self._cw.url(),self.domid)returnself.force_session_keydefrestore_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._cw.get_session_data(sessionkey,pop=True)ifforminfo:self._form_previous_values=forminfo['values']self._form_valerror=forminfo['error']# 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=Nonedeffield_error(self,field):"""return field's error if specified in current validation exception"""ifself.form_valerror:iffield.eidparamandself.edited_entity.eid!=self.form_valerror.eid:returnNonetry:returnself.form_valerror.errors.pop(field.role_name())exceptKeyError:iffield.roleandfield.nameinself.form_valerror:warn('%s: errors key of attribute/relation should be suffixed by "-<role>"'%self.form_valerror.__class__,DeprecationWarning)returnself.form_valerror.errors.pop(field.name)returnNonedefremaining_errors(self):returnsorted(self.form_valerror.errors.items())@deprecated('[3.6] use form.field_error and/or new renderer.render_error method')defform_field_error(self,field):"""return validation error for widget's field, if any"""err=self.field_error(field)iferr:returnu'<span class="error">%s</span>'%errreturnu''