"""The automatic entity form.: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"_=unicodefromlogilab.common.decoratorsimporticlassmethod,cachedfromcubicwebimporttyped_eidfromcubicweb.webimportstdmsgs,uicfgfromcubicweb.webimportform,formwidgetsasfwdgsfromcubicweb.web.formfieldsimportguess_fieldfromcubicweb.web.viewsimportforms,editforms_afs=uicfg.autoform_sectionclassAutomaticEntityForm(forms.EntityFieldsForm):"""base automatic form to edit any entity. Designed to be fully generated from schema but highly configurable through: * uicfg (autoform_* relation tags) * various standard form parameters * overriding You can also easily customise it by adding/removing fields in AutomaticEntityForm instances or by inheriting from it. """__regid__='edition'cwtarget='eformframe'cssclass='entityForm'copy_nav_params=Trueform_buttons=[fwdgs.SubmitButton(),fwdgs.Button(stdmsgs.BUTTON_APPLY,cwaction='apply'),fwdgs.Button(stdmsgs.BUTTON_CANCEL,cwaction='cancel')]# for attributes selection when searching in uicfg.autoform_sectionformtype='main'# set this to a list of [(relation, role)] if you want to explictily tell# which relations should be editeddisplay_fields=None# class attributes below are actually stored in the uicfg module since we# don't want them to be reloadedrfields=uicfg.autoform_fieldrfields_kwargs=uicfg.autoform_field_kwargs# class methods mapping schema relations to fields in the form ############@iclassmethoddeffield_by_name(cls_or_self,name,role='subject',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(AutomaticEntityForm,cls_or_self).field_by_name(name,role)exceptform.FieldNotFound:ifeschemaisNoneornotnameincls_or_self._cw.schema:raiserschema=cls_or_self._cw.schema.rschema(name)# XXX use a sample target type. Document this.tschemas=rschema.targets(eschema,role)fieldcls=cls_or_self.rfields.etype_get(eschema,rschema,role,tschemas[0])kwargs=cls_or_self.rfields_kwargs.etype_get(eschema,rschema,role,tschemas[0])ifkwargsisNone:kwargs={}iffieldcls:ifnotisinstance(fieldcls,type):returnfieldcls# already and instancereturnfieldcls(name=name,role=role,eidparam=True,**kwargs)field=guess_field(eschema,rschema,role,eidparam=True,**kwargs)iffieldisNone:raisereturnfield# base automatic entity form methods #######################################def__init__(self,*args,**kwargs):super(AutomaticEntityForm,self).__init__(*args,**kwargs)entity=self.edited_entityifentity.has_eid():entity.complete()forrtype,roleinself.editable_attributes():try:self.field_by_name(str(rtype),role)continue# explicitly specifiedexceptform.FieldNotFound:# has to be guessedtry:field=self.field_by_name(str(rtype),role,eschema=entity.e_schema)self.fields.append(field)exceptform.FieldNotFound:# meta attribute such as <attr>_formatcontinueself.maxrelitems=self._cw.property_value('navigation.related-limit')self.force_display=bool(self._cw.form.get('__force_display'))@propertydefrelated_limit(self):ifself.force_display:returnNonereturnself.maxrelitems+1@propertydefform_needs_multipart(self):"""true if the form needs enctype=multipart/form-data"""returnself._subform_needs_multipart()defbuild_context(self,rendervalues=None):super(AutomaticEntityForm,self).build_context(rendervalues)forforminself.inlined_forms():form.build_context(rendervalues)def_subform_needs_multipart(self,_tested=None):if_testedisNone:_tested=set()ifsuper(AutomaticEntityForm,self).form_needs_multipart:returnTrue# take a look at inlined forms to check (recursively) if they# need multipart handling.# XXX: this is very suboptimal because inlined forms will be# selected / instantiated twice : here and during form rendering.# Potential solutions:# -> use subforms for inlined forms to get easiser access# -> use a simple onload js function to check if there is# a input type=file in the form# -> generate the <form> node when the content is rendered# and we know the correct enctype (formrenderer's w attribute# is not a StringIO)forformviewinself.inlined_form_views():ifformview.form:ifhasattr(formview.form,'_subform_needs_multipart'):needs_multipart=formview.form._subform_needs_multipart(_tested)else:needs_multipart=formview.form.form_needs_multipartifneeds_multipart:returnTruereturnFalsedefaction(self):"""return the form's action attribute. Default to validateform if not explicitly overriden. """try:returnself._actionexceptAttributeError:returnself._cw.build_url('validateform')defset_action(self,value):"""override default action"""self._action=valueaction=property(action,set_action)# methods mapping edited entity relations to fields in the form ############def_relations_by_section(self,section,permission='add',strict=False):"""return a list of (relation schema, target schemas, role) matching given category(ies) and permission """return_afs.relations_by_section(self.edited_entity,self.formtype,section,permission,strict)defeditable_attributes(self,strict=False):"""return a list of (relation schema, role) to edit for the entity"""ifself.display_fieldsisnotNone:returnself.display_fields# XXX we should simply put eid in the generated section, no?return[(rtype,role)forrtype,_,roleinself._relations_by_section('attributes',strict=strict)ifrtype!='eid']defeditable_relations(self):"""return a sorted list of (relation's label, relation'schema, role) for relations in the 'relations' section """result=[]forrschema,_,roleinself._relations_by_section('relations',strict=True):result.append((rschema.display_name(self.edited_entity._cw,role,self.edited_entity.__regid__),rschema,role))returnsorted(result)definlined_relations(self):"""return a list of (relation schema, target schemas, role) matching given category(ies) and permission """returnself._relations_by_section('inlined')# generic relations modifier ###############################################defrelations_table(self):"""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 """entity=self.edited_entitypending_deletes=self._cw.get_pending_deletes(entity.eid)forlabel,rschema,roleinself.editable_relations():relatedrset=entity.related(rschema,role,limit=self.related_limit)ifrschema.has_perm(self._cw,'delete'):toggleable_rel_link_func=editforms.toggleable_relation_linkelse:toggleable_rel_link_func=lambdax,y,z:u''related=[]forrowinxrange(relatedrset.rowcount):nodeid=editforms.relation_id(entity.eid,rschema,role,relatedrset[row][0])ifnodeidinpending_deletes:status=u'pendingDelete'label='+'else:status=u''label='x'dellink=toggleable_rel_link_func(entity.eid,nodeid,label)eview=self._cw.view('oneline',relatedrset,row=row)related.append((nodeid,dellink,status,eview))yield(rschema,role,related)defrestore_pending_inserts(self,cell=False):"""used to restore edition page as it was before clicking on 'search for <some entity type>' """eid=self.edited_entity.eidcell=celland"div_insert_"or"tr"pending_inserts=set(self._cw.get_pending_inserts(eid))forpendingidinpending_inserts:eidfrom,rtype,eidto=pendingid.split(':')iftyped_eid(eidfrom)==eid:# subjectlabel=display_name(self._cw,rtype,'subject',self.edited_entity.__regid__)reid=eidtoelse:label=display_name(self._cw,rtype,'object',self.edited_entity.__regid__)reid=eidfromjscall="javascript: cancelPendingInsert('%s', '%s', null, %s);" \%(pendingid,cell,eid)rset=self._cw.eid_rset(reid)eview=self._cw.view('text',rset,row=0)# XXX find a clean way to handle basketsifrset.description[0][0]=='Basket':eview='%s (%s)'%(eview,display_name(self._cw,'Basket'))yieldrtype,pendingid,jscall,label,reid,eview# inlined forms support ####################################################@cacheddefinlined_form_views(self):"""compute and return list of inlined form views (hosting the inlined form object) """allformviews=[]entity=self.edited_entityforrschema,ttypes,roleinself.inlined_relations():# show inline forms only if there's one possible target type# for rschemaiflen(ttypes)!=1:self.warning('entity related by the %s relation should have ''inlined form but there is multiple target types, ''dunno what to do',rschema)continuettype=ttypes[0].typeifself.should_inline_relation_form(rschema,ttype,role):formviews=list(self.inline_edition_form_view(rschema,ttype,role))ifrole=='subject':card=rschema.rproperty(entity.e_schema,ttype,'cardinality')[0]else:card=rschema.rproperty(ttype,entity.e_schema,'cardinality')[1]# there is no related entity and we need at least one: we need to# display one explicit inline-creation viewifself.should_display_inline_creation_form(rschema,formviews,card):formviews+=self.inline_creation_form_view(rschema,ttype,role)# we can create more than one related entity, we thus display a link# to add new related entitiesifself.should_display_add_new_relation_link(rschema,formviews,card):addnewlink=self.vreg['views'].select('inline-addnew-link',self.req,etype=ttype,rtype=rschema,role=role,peid=self.edited_entity.eid,pform=self,card=card)formviews.append(addnewlink)allformviews+=formviewsreturnallformviewsdefinlined_forms(self):forformviewinself.inlined_form_views():ifformview.form:# may be None for the addnew_link artefact formyieldformview.formdefshould_inline_relation_form(self,rschema,targettype,role):"""return true if the given relation with entity has role and a targettype target should be inlined At this point we now relation has inlined_attributes tag (eg is returned by `inlined_relations()`. Overrides this for more finer control. """returnTruedefshould_display_inline_creation_form(self,rschema,existant,card):"""return true if a creation form should be inlined by default true if there is no related entity and we need at least one """returnnotexistantandcardin'1+'orself._cw.form.has_key('force_%s_display'%rschema)defshould_display_add_new_relation_link(self,rschema,existant,card):"""return true if we should add a link to add a new creation form (through ajax call) by default true if there is no related entity or if the relation has multiple cardinality """returnnotexistantorcardin'+*'defshould_hide_add_new_relation_link(self,rschema,card):"""return true if once an inlined creation form is added, the 'add new' link should be hidden by default true if the relation has single cardinality """returncardin'1?'definline_edition_form_view(self,rschema,ttype,role):"""yield inline form views for already related entities through the given relation """entity=self.edited_entityrelated=entity.has_eid()andentity.related(rschema,role)ifrelated:vvreg=self._cw.vreg['views']# display inline-edition view for all existing related entitiesfori,relentityinenumerate(related.entities()):ifrelentity.has_perm('update'):yieldvvreg.select('inline-edition',self._cw,rset=related,row=i,col=0,rtype=rschema,role=role,peid=entity.eid,pform=self)definline_creation_form_view(self,rschema,ttype,role):"""yield inline form views to a newly related (hence created) entity through the given relation """yieldself._cw.vreg['views'].select('inline-creation',self._cw,etype=ttype,rtype=rschema,role=role,peid=self.edited_entity.eid,pform=self)defetype_relation_field(etype,rtype,role='subject'):eschema=AutomaticEntityForm.schema.eschema(etype)returnAutomaticEntityForm.field_by_name(rtype,role,eschema)## default form ui configuration ##############################################_afs=uicfg.autoform_section# use primary and not generated for eid since it has to be an hidden_afs.tag_attribute(('*','eid'),'main','attributes')_afs.tag_attribute(('*','eid'),'muledit','attributes')_afs.tag_attribute(('*','description'),'main','attributes')_afs.tag_attribute(('*','creation_date'),'main','metadata')_afs.tag_attribute(('*','modification_date'),'main','metadata')_afs.tag_attribute(('*','cwuri'),'main','metadata')_afs.tag_attribute(('*','has_text'),'main','hidden')_afs.tag_subject_of(('*','in_state','*'),'main','hidden')_afs.tag_subject_of(('*','owned_by','*'),'main','metadata')_afs.tag_subject_of(('*','created_by','*'),'main','metadata')_afs.tag_subject_of(('*','require_permission','*'),'main','hidden')_afs.tag_subject_of(('*','by_transition','*'),'main','attributes')_afs.tag_subject_of(('*','by_transition','*'),'muledit','attributes')_afs.tag_object_of(('*','by_transition','*'),'main','hidden')_afs.tag_object_of(('*','from_state','*'),'main','hidden')_afs.tag_object_of(('*','to_state','*'),'main','hidden')_afs.tag_subject_of(('*','wf_info_for','*'),'main','attributes')_afs.tag_subject_of(('*','wf_info_for','*'),'muledit','attributes')_afs.tag_object_of(('*','wf_info_for','*'),'main','hidden')_afs.tag_subject_of(('*','for_user','*'),'main','hidden')_afs.tag_object_of(('*','for_user','*'),'main','hidden')_afs.tag_subject_of(('CWPermission','require_group','*'),'main','attributes')_afs.tag_subject_of(('CWPermission','require_group','*'),'muledit','attributes')_afs.tag_attribute(('CWEType','final'),'main','hidden')_afs.tag_attribute(('CWRType','final'),'main','hidden')_afs.tag_attribute(('CWUser','firstname'),'main','attributes')_afs.tag_attribute(('CWUser','surname'),'main','attributes')_afs.tag_attribute(('CWUser','last_login_time'),'main','metadata')_afs.tag_subject_of(('CWUser','in_group','*'),'main','attributes')_afs.tag_subject_of(('CWUser','in_group','*'),'muledit','attributes')_afs.tag_object_of(('*','bookmarked_by','CWUser'),'main','metadata')_afs.tag_attribute(('Bookmark','path'),'main','attributes')_afs.tag_attribute(('Bookmark','path'),'muledit','attributes')_afs.tag_subject_of(('*','primary_email','*'),'main','relations')_afs.tag_subject_of(('*','use_email','*'),'main','inlined')_afs.tag_subject_of(('CWRelation','relation_type','*'),'main','inlined')_afs.tag_subject_of(('CWRelation','from_entity','*'),'main','inlined')_afs.tag_subject_of(('CWRelation','to_entity','*'),'main','inlined')uicfg.autoform_field_kwargs.tag_attribute(('RQLExpression','expression'),{'widget':fwdgs.TextInput})uicfg.autoform_field_kwargs.tag_attribute(('Bookmark','path'),{'widget':fwdgs.TextInput})uicfg.autoform_field_kwargs.tag_subject_of(('TrInfo','wf_info_for','*'),{'widget':fwdgs.HiddenInput})