[pkg] Set version to 3.22.2.dev0
So that cubes used in test dependencies do not install a released CubicWeb.
# copyright 2003-2014 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/>."""Base form classes-----------------.. Note: Form is the glue that bind a context to a set of fields, and is rendered using a form renderer. No display is actually done here, though you'll find some attributes of form that are used to control the rendering process.Besides the automagic form we'll see later, there are roughly two mainform classes in |cubicweb|:.. autoclass:: cubicweb.web.views.forms.FieldsForm.. autoclass:: cubicweb.web.views.forms.EntityFieldsFormAs you have probably guessed, choosing between them is easy. Simply ask you thequestion 'I am editing an entity or not?'. If the answer is yes, use:class:`EntityFieldsForm`, else use :class:`FieldsForm`.Actually there exists a third form class:.. autoclass:: cubicweb.web.views.forms.CompositeFormbut you'll use this one rarely."""__docformat__="restructuredtext en"importtimeimportinspectfromsiximporttext_typefromlogilab.commonimportdictattr,tempattrfromlogilab.common.decoratorsimporticlassmethod,cachedfromlogilab.common.textutilsimportsplitstripfromcubicwebimportValidationError,neg_rolefromcubicweb.predicatesimportnon_final_entity,match_kwargs,one_line_rsetfromcubicweb.webimportRequestError,ProcessFormErrorfromcubicweb.webimportformfromcubicweb.web.viewsimportuicfgfromcubicweb.web.formfieldsimportguess_fieldclassFieldsForm(form.Form):"""This is the base class for fields based forms. **Attributes** The following attributes may be either set on subclasses or given on form selection to customize the generated form: :attr:`needs_js` sequence of javascript files that should be added to handle this form (through :meth:`~cubicweb.web.request.Request.add_js`) :attr:`needs_css` sequence of css files that should be added to handle this form (through :meth:`~cubicweb.web.request.Request.add_css`) :attr:`domid` value for the "id" attribute of the <form> tag :attr:`action` value for the "action" attribute of the <form> tag :attr:`onsubmit` value for the "onsubmit" attribute of the <form> tag :attr:`cssclass` value for the "class" attribute of the <form> tag :attr:`cssstyle` value for the "style" attribute of the <form> tag :attr:`cwtarget` value for the "target" attribute of the <form> tag :attr:`redirect_path` relative to redirect to after submitting the form :attr:`copy_nav_params` flag telling if navigation parameters should be copied back in hidden inputs :attr:`form_buttons` sequence of form control (:class:`~cubicweb.web.formwidgets.Button` widgets instances) :attr:`form_renderer_id` identifier of the form renderer to use to render the form :attr:`fieldsets_in_order` sequence of fieldset names , to control order :attr:`autocomplete` set to False to add 'autocomplete=off' in the form open tag **Generic methods** .. automethod:: cubicweb.web.form.Form.field_by_name(name, role=None) .. automethod:: cubicweb.web.form.Form.fields_by_name(name, role=None) **Form construction methods** .. automethod:: cubicweb.web.form.Form.remove_field(field) .. automethod:: cubicweb.web.form.Form.append_field(field) .. automethod:: cubicweb.web.form.Form.insert_field_before(field, name, role=None) .. automethod:: cubicweb.web.form.Form.insert_field_after(field, name, role=None) .. automethod:: cubicweb.web.form.Form.add_hidden(name, value=None, **kwargs) **Form rendering methods** .. automethod:: cubicweb.web.views.forms.FieldsForm.render **Form posting methods** Once a form is posted, you can retrieve the form on the controller side and use the following methods to ease processing. For "simple" forms, this should looks like : .. sourcecode :: python form = self._cw.vreg['forms'].select('myformid', self._cw) posted = form.process_posted() # do something with the returned dictionary Notice that form related to entity edition should usually use the `edit` controller which will handle all the logic for you. .. automethod:: cubicweb.web.views.forms.FieldsForm.process_posted .. automethod:: cubicweb.web.views.forms.FieldsForm.iter_modified_fields """__regid__='base'# attributes overrideable by subclasses or through __init__needs_js=('cubicweb.ajax.js','cubicweb.edition.js',)needs_css=('cubicweb.form.css',)action=Nonecssclass=Nonecssstyle=Nonecwtarget=Noneredirect_path=Noneform_buttons=Noneform_renderer_id='default'fieldsets_in_order=Noneautocomplete=True@propertydefneeds_multipart(self):"""true if the form needs enctype=multipart/form-data"""returnany(field.needs_multipartforfieldinself.fields)def_get_onsubmit(self):try:returnself._onsubmitexceptAttributeError:return"return freezeFormButtons('%(domid)s');"%dictattr(self)def_set_onsubmit(self,value):self._onsubmit=valueonsubmit=property(_get_onsubmit,_set_onsubmit)defadd_media(self):"""adds media (CSS & JS) required by this widget"""ifself.needs_js:self._cw.add_js(self.needs_js)ifself.needs_css:self._cw.add_css(self.needs_css)defrender(self,formvalues=None,renderer=None,**kwargs):"""Render this form, using the `renderer` given as argument or the default according to :attr:`form_renderer_id`. The rendered form is returned as a unicode string. `formvalues` is an optional dictionary containing values that will be considered as field's value. Extra keyword arguments will be given to renderer's :meth:`render` method. """w=kwargs.pop('w',None)self.build_context(formvalues)ifrendererisNone:renderer=self.default_renderer()renderer.render(w,self,kwargs)defdefault_renderer(self):returnself._cw.vreg['formrenderers'].select(self.form_renderer_id,self._cw,rset=self.cw_rset,row=self.cw_row,col=self.cw_color0)formvalues=Nonedefbuild_context(self,formvalues=None):"""build form context values (the .context attribute which is a dictionary with field instance as key associated to a dictionary containing field 'name' (qualified), 'id', 'value' (for display, always a string). """ifself.formvaluesisnotNone:return# already builtself.formvalues=formvaluesor{}# use a copy in case fields are modified while context is built (eg# __linkto handling for instance)forfieldinself.fields[:]:forfieldinfield.actual_fields(self):field.form_init(self)# store used field in an hidden input for later usage by a controllerfields=set()eidfields=set()forfieldinself.fields:iffield.eidparam:eidfields.add(field.role_name())eliffield.namenotinself.control_fields:fields.add(field.role_name())iffields:self.add_hidden('_cw_fields',u','.join(fields))ifeidfields:self.add_hidden('_cw_entity_fields',u','.join(eidfields),eidparam=True)_default_form_action_path='edit'defform_action(self):action=self.actionifactionisNone:returnself._cw.build_url(self._default_form_action_path)returnaction# controller form processing methods #######################################defiter_modified_fields(self,editedfields=None,entity=None):"""return a generator on field that has been modified by the posted form. """ifeditedfieldsisNone:try:editedfields=self._cw.form['_cw_fields']exceptKeyError:raiseRequestError(self._cw._('no edited fields specified'))entityform=entityandlen(inspect.getargspec(self.field_by_name))==4# XXXforeditedfieldinsplitstrip(editedfields):try:name,role=editedfield.split('-')exceptException:name=editedfieldrole=Noneifentityform:field=self.field_by_name(name,role,eschema=entity.e_schema)else:field=self.field_by_name(name,role)iffield.has_been_modified(self):yieldfielddefprocess_posted(self):"""use this method to process the content posted by a simple form. it will return a dictionary with field names as key and typed value as associated value. """withtempattr(self,'formvalues',{}):# init fields value cacheerrors=[]processed={}forfieldinself.iter_modified_fields():try:forfield,valueinfield.process_posted(self):processed[field.role_name()]=valueexceptProcessFormErrorasexc:errors.append((field,exc))iferrors:errors=dict((f.role_name(),text_type(ex))forf,exinerrors)raiseValidationError(None,errors)returnprocessedclassEntityFieldsForm(FieldsForm):"""This class is designed for forms used to edit some entities. It should handle for you all the underlying stuff necessary to properly work with the generic :class:`~cubicweb.web.views.editcontroller.EditController`. """__regid__='base'__select__=(match_kwargs('entity')|(one_line_rset()&non_final_entity()))domid='entityForm'uicfg_aff=uicfg.autoform_fielduicfg_affk=uicfg.autoform_field_kwargs@iclassmethoddeffield_by_name(cls_or_self,name,role=None,eschema=None):"""return field with the given name and role. If field is not explicitly defined for the form but `eclass` is specified, guess_field will be called. """try:returnsuper(EntityFieldsForm,cls_or_self).field_by_name(name,role)exceptform.FieldNotFound:ifeschemaisNoneorroleisNoneornotnameineschema.schema:raiserschema=eschema.schema.rschema(name)# XXX use a sample target type. Document this.tschemas=rschema.targets(eschema,role)fieldcls=cls_or_self.uicfg_aff.etype_get(eschema,rschema,role,tschemas[0])kwargs=cls_or_self.uicfg_affk.etype_get(eschema,rschema,role,tschemas[0])ifkwargsisNone:kwargs={}iffieldcls:ifnotisinstance(fieldcls,type):returnfieldcls# already and instancereturnfieldcls(name=name,role=role,eidparam=True,**kwargs)ifisinstance(cls_or_self,type):req=Noneelse:req=cls_or_self._cwfield=guess_field(eschema,rschema,role,req=req,eidparam=True,**kwargs)iffieldisNone:raisereturnfielddef__init__(self,_cw,rset=None,row=None,col=None,**kwargs):try:self.edited_entity=kwargs.pop('entity')exceptKeyError:self.edited_entity=rset.complete_entity(rowor0,color0)msg=kwargs.pop('submitmsg',None)super(EntityFieldsForm,self).__init__(_cw,rset,row,col,**kwargs)self.uicfg_aff=self._cw.vreg['uicfg'].select('autoform_field',self._cw,entity=self.edited_entity)self.uicfg_affk=self._cw.vreg['uicfg'].select('autoform_field_kwargs',self._cw,entity=self.edited_entity)self.add_hidden('__type',self.edited_entity.cw_etype,eidparam=True)self.add_hidden('eid',self.edited_entity.eid)self.add_generation_time()# mainform default to true in parent, hence default to Trueifkwargs.get('mainform',True)orkwargs.get('mainentity',False):self.add_hidden(u'__maineid',self.edited_entity.eid)# If we need to directly attach the new object to another oneif'__linkto'inself._cw.form:ifmsg:msg='%s%s'%(msg,self._cw._('and linked'))else:msg=self._cw._('entity linked')ifmsg:msgid=self._cw.set_redirect_message(msg)self.add_hidden('_cwmsgid',msgid)defadd_generation_time(self):# use %f to prevent (unlikely) display in exponential formatself.add_hidden('__form_generation_time','%.6f'%time.time(),eidparam=True)defadd_linkto_hidden(self):"""add the __linkto hidden field used to directly attach the new object to an existing other one when the relation between those two is not already present in the form. Warning: this method must be called only when all form fields are setup """for(rtype,role),eidsinself.linked_to.items():# if the relation is already setup by a form field, do not add it# in a __linkto hidden to avoid setting it twice in the controllertry:self.field_by_name(rtype,role)exceptform.FieldNotFound:foreidineids:self.add_hidden('__linkto','%s:%s:%s'%(rtype,eid,role))defrender(self,*args,**kwargs):self.add_linkto_hidden()returnsuper(EntityFieldsForm,self).render(*args,**kwargs)@property@cacheddeflinked_to(self):linked_to={}# case where this is an embeded creation formtry:eid=int(self.cw_extra_kwargs['peid'])except(KeyError,ValueError):# When parent is being created, its eid is not numeric (e.g. 'A')# hence ValueError.passelse:ltrtype=self.cw_extra_kwargs['rtype']ltrole=neg_role(self.cw_extra_kwargs['role'])linked_to[(ltrtype,ltrole)]=[eid]# now consider __linkto if the current form is the main formtry:self.field_by_name('__maineid')exceptform.FieldNotFound:returnlinked_toforlinktoinself._cw.list_form_param('__linkto'):ltrtype,eid,ltrole=linkto.split(':')linked_to.setdefault((ltrtype,ltrole),[]).append(int(eid))returnlinked_todefsession_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_keyisnotNone:returnself.force_session_key# XXX if this is a json request, suppose we should redirect to the# entity primary viewifself._cw.ajax_requestandself.edited_entity.has_eid():return'%s#%s'%(self.edited_entity.absolute_url(),self.domid)# XXX we should not consider some url parameters that may lead to# different url after a validation errorreturn'%s#%s'%(self._cw.url(),self.domid)defdefault_renderer(self):returnself._cw.vreg['formrenderers'].select(self.form_renderer_id,self._cw,rset=self.cw_rset,row=self.cw_row,col=self.cw_col,entity=self.edited_entity)defshould_display_add_new_relation_link(self,rschema,existant,card):returnFalse# controller side method (eg POST reception handling)defactual_eid(self,eid):# should be either an int (existant entity) or a variable (to be# created entity)asserteidoreid==0,repr(eid)# 0 is a valid eidtry:returnint(eid)exceptValueError:try:returnself._cw.data['eidmap'][eid]exceptKeyError:self._cw.data['eidmap'][eid]=NonereturnNonedefeditable_relations(self):return()classCompositeFormMixIn(object):__regid__='composite'form_renderer_id=__regid__def__init__(self,*args,**kwargs):super(CompositeFormMixIn,self).__init__(*args,**kwargs)self.forms=[]defadd_subform(self,subform):"""mark given form as a subform and append it"""subform.parent_form=selfself.forms.append(subform)defbuild_context(self,formvalues=None):super(CompositeFormMixIn,self).build_context(formvalues)forforminself.forms:form.build_context(formvalues)classCompositeForm(CompositeFormMixIn,FieldsForm):"""Form composed of sub-forms. Typical usage is edition of multiple entities at once. """classCompositeEntityForm(CompositeFormMixIn,EntityFieldsForm):pass# XXX why is this class necessary?