[rql2sql] fix bad sql generated when outer joining 'identity' relation and lhs var comes from a subquery. Closes #3099418
The generated SQL was attempting to access table.cw_eid which doesn't exist.
# copyright 2003-2013 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/>.""".. autodocstring:: cubicweb.web.views.autoform::AutomaticEntityFormConfiguration through uicfg```````````````````````````It is possible to manage which and how an entity's attributes and relationswill be edited in the various contexts where the automatic entity form is usedby using proper uicfg tags.The details of the uicfg syntax can be found in the :ref:`uicfg` chapter.Possible relation tags that apply to entity forms are detailled below.They are all in the :mod:`cubicweb.web.uicfg` module.Attributes/relations display location^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^``autoform_section`` specifies where to display a relation in form for a givenform type. :meth:`tag_attribute`, :meth:`tag_subject_of` and:meth:`tag_object_of` methods for this relation tag expect two argumentsadditionally to the relation key: a `formtype` and a `section`.`formtype` may be one of:* 'main', the main entity form (e.g. the one you get when creating or editing an entity)* 'inlined', the form for an entity inlined into another form* 'muledit', the table form when editing multiple entities of the same typesection may be one of:* 'hidden', don't display (not even in a hidden input)* 'attributes', display in the attributes section* 'relations', display in the relations section, using the generic relation selector combobox (available in main form only, and not usable for attributes)* 'inlined', display target entity of the relation into an inlined form (available in main form only, and not for attributes)By default, mandatory relations are displayed in the 'attributes' section,others in 'relations' section.Change default fields^^^^^^^^^^^^^^^^^^^^^Use ``autoform_field`` to replace the default field class to use for a relationor attribute. You can put either a field class or instance as value (put a classwhenether it's possible)... Warning:: `autoform_field_kwargs` should usually be used instead of `autoform_field`. If you put a field instance into `autoform_field`, `autoform_field_kwargs` values for this relation will be ignored.Customize field options^^^^^^^^^^^^^^^^^^^^^^^In order to customize field options (see :class:`~cubicweb.web.formfields.Field`for a detailed list of options), use `autoform_field_kwargs`. This rtag takesa dictionary as arguments, that will be given to the field's contructor.You can then put in that dictionary any arguments supported by the fieldclass. For instance:.. sourcecode:: python # Change the content of the combobox. Here `ticket_done_in_choices` is a # function which returns a list of elements to populate the combobox autoform_field_kwargs.tag_subject_of(('Ticket', 'done_in', '*'), {'sort': False, 'choices': ticket_done_in_choices}) # Force usage of a TextInput widget for the expression attribute of # RQLExpression entities autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'), {'widget': fw.TextInput}).. note:: the widget argument can be either a class or an instance (the later case being convenient to pass the Widget specific initialisation options)Overriding permissions^^^^^^^^^^^^^^^^^^^^^^The `autoform_permissions_overrides` rtag provides a way to by-pass securitychecking for dark-corner case where it can't be verified properly... More about inlined forms.. Controlling the generic relation fields"""__docformat__="restructuredtext en"_=unicodefromwarningsimportwarnfromlogilab.mtconverterimportxml_escapefromlogilab.common.decoratorsimporticlassmethod,cachedfromlogilab.common.deprecationimportdeprecatedfromcubicwebimportneg_role,uilibfromcubicweb.schemaimportdisplay_namefromcubicweb.viewimportEntityViewfromcubicweb.predicatesimport(match_kwargs,match_form_params,non_final_entity,specified_etype_implements)fromcubicweb.utilsimportjson_dumpsfromcubicweb.webimport(stdmsgs,eid_param,formasf,formwidgetsasfw,formfieldsasff)fromcubicweb.web.viewsimportuicfg,formsfromcubicweb.web.views.ajaxcontrollerimportajaxfunc# inlined form handling ########################################################classInlinedFormField(ff.Field):def__init__(self,view=None,**kwargs):kwargs.setdefault('label',None)# don't add eidparam=True since this field doesn't actually hold the# relation value (the subform does) hence should not be listed in# _cw_entity_fieldssuper(InlinedFormField,self).__init__(name=view.rtype,role=view.role,**kwargs)self.view=viewdefrender(self,form,renderer):"""render this field, which is part of form, using the given form renderer """view=self.viewi18nctx='inlined:%s.%s.%s'%(form.edited_entity.e_schema,view.rtype,view.role)returnu'<div class="inline-%s-%s-slot">%s</div>'%(view.rtype,view.role,view.render(i18nctx=i18nctx,row=view.cw_row,col=view.cw_col))defform_init(self,form):"""method called before by build_context to trigger potential field initialization requiring the form instance """ifself.view.form:self.view.form.build_context(form.formvalues)@propertydefneeds_multipart(self):ifself.view.form:# take a look at inlined forms to check (recursively) if they need# multipart handling.returnself.view.form.needs_multipartreturnFalsedefhas_been_modified(self,form):returnFalsedefprocess_posted(self,form):pass# handled by the subformclassInlineEntityEditionFormView(f.FormViewMixIn,EntityView):""" :attr peid: the parent entity's eid hosting the inline form :attr rtype: the relation bridging `etype` and `peid` :attr role: the role played by the `peid` in the relation :attr pform: the parent form where this inlined form is being displayed """__regid__='inline-edition'__select__=non_final_entity()&match_kwargs('peid','rtype')_select_attrs=('peid','rtype','role','pform','etype')removejs="removeInlinedEntity('%s', '%s', '%s')"# make pylint happypeid=rtype=role=pform=etype=Nonedef__init__(self,*args,**kwargs):forattrinself._select_attrs:# don't pop attributes from kwargs, so the end-up in# self.cw_extra_kwargs which is then passed to the edition form (see# the .form method)setattr(self,attr,kwargs.get(attr))super(InlineEntityEditionFormView,self).__init__(*args,**kwargs)def_entity(self):assertself.cw_rowisnotNone,selfreturnself.cw_rset.get_entity(self.cw_row,self.cw_col)@property@cacheddefform(self):entity=self._entity()form=self._cw.vreg['forms'].select('edition',self._cw,entity=entity,formtype='inlined',form_renderer_id='inline',copy_nav_params=False,mainform=False,parent_form=self.pform,**self.cw_extra_kwargs)ifself.pformisNone:form.restore_previous_post(form.session_key())#assert form.parent_formself.add_hiddens(form,entity)returnformdefcell_call(self,row,col,i18nctx,**kwargs):""" :param peid: the parent entity's eid hosting the inline form :param rtype: the relation bridging `etype` and `peid` :param role: the role played by the `peid` in the relation """entity=self._entity()divonclick="restoreInlinedEntity('%s', '%s', '%s')"%(self.peid,self.rtype,entity.eid)self.render_form(i18nctx,divonclick=divonclick,**kwargs)def_get_removejs(self):""" Don't display the remove link in edition form if the cardinality is 1. Handled in InlineEntityCreationFormView for creation form. """entity=self._entity()ifisinstance(self.peid,int):pentity=self._cw.entity_from_eid(self.peid)petype=pentity.e_schema.typerdef=entity.e_schema.rdef(self.rtype,neg_role(self.role),petype)card=rdef.role_cardinality(self.role)ifcard=='1':# don't display remove linkreturnNonereturnself.removejsandself.removejs%(self.peid,self.rtype,entity.eid)defrender_form(self,i18nctx,**kwargs):"""fetch and render the form"""entity=self._entity()divid='%s-%s-%s'%(self.peid,self.rtype,entity.eid)title=self.form_title(entity,i18nctx)removejs=self._get_removejs()countkey='%s_count'%self.rtypetry:self._cw.data[countkey]+=1exceptKeyError:self._cw.data[countkey]=1self.form.render(w=self.w,divid=divid,title=title,removejs=removejs,i18nctx=i18nctx,counter=self._cw.data[countkey],**kwargs)defform_title(self,entity,i18nctx):returnself._cw.pgettext(i18nctx,entity.cw_etype)defadd_hiddens(self,form,entity):"""to ease overriding (see cubes.vcsfile.views.forms for instance)"""iid='rel-%s-%s-%s'%(self.peid,self.rtype,entity.eid)# * str(self.rtype) in case it's a schema object# * neged_role() since role is the for parent entity, we want the role# of the inlined entityform.add_hidden(name=str(self.rtype),value=self.peid,role=neg_role(self.role),eidparam=True,id=iid)defkeep_entity(self,form,entity):ifnotentity.has_eid():returnTrue# are we regenerating form because of a validation error ?ifform.form_previous_values:cdvalues=self._cw.list_form_param(eid_param(self.rtype,self.peid),form.form_previous_values)ifunicode(entity.eid)notincdvalues:returnFalsereturnTrueclassInlineEntityCreationFormView(InlineEntityEditionFormView):""" :attr etype: the entity type being created in the inline form """__regid__='inline-creation'__select__=(match_kwargs('peid','petype','rtype')&specified_etype_implements('Any'))_select_attrs=InlineEntityEditionFormView._select_attrs+('petype',)# make pylint happypetype=None@propertydefremovejs(self):entity=self._entity()rdef=entity.e_schema.rdef(self.rtype,neg_role(self.role),self.petype)card=rdef.role_cardinality(self.role)# when one is adding an inline entity for a relation of a single card,# the 'add a new xxx' link disappears. If the user then cancel the addition,# we have to make this link appears back. This is done by giving add new link# id to removeInlineForm.ifcard=='?':divid="addNew%s%s%s:%s"%(self.etype,self.rtype,self.role,self.peid)return"removeInlineForm('%%s', '%%s', '%s', '%%s', '%s')"%(self.role,divid)elifcardin'+*':return"removeInlineForm('%%s', '%%s', '%s', '%%s')"%self.role# don't do anything for card == '1'@cacheddef_entity(self):try:cls=self._cw.vreg['etypes'].etype_class(self.etype)exceptException:self.w(self._cw._('no such entity type %s')%self.etype)returnentity=cls(self._cw)entity.eid=self._cw.varmaker.next()returnentitydefcall(self,i18nctx,**kwargs):self.render_form(i18nctx,**kwargs)classInlineAddNewLinkView(InlineEntityCreationFormView):""" :attr card: the cardinality of the relation according to role of `peid` """__regid__='inline-addnew-link'__select__=(match_kwargs('peid','petype','rtype')&specified_etype_implements('Any'))_select_attrs=InlineEntityCreationFormView._select_attrs+('card',)card=None# make pylint happyform=None# no actual form wrappeddefcall(self,i18nctx,**kwargs):self._cw.set_varmaker()divid="addNew%s%s%s:%s"%(self.etype,self.rtype,self.role,self.peid)self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'%divid)js="addInlineCreationForm('%s', '%s', '%s', '%s', '%s', '%s')"%(self.peid,self.petype,self.etype,self.rtype,self.role,i18nctx)ifself.pform.should_hide_add_new_relation_link(self.rtype,self.card):js="toggleVisibility('%s'); %s"%(divid,js)__=self._cw.pgettextself.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'%(self.rtype,self.peid,js,__(i18nctx,'add a %s'%self.etype)))self.w(u'</div>')# generic relations handling ##################################################defrelation_id(eid,rtype,role,reid):"""return an identifier for a relation between two entities"""ifrole=='subject':returnu'%s:%s:%s'%(eid,rtype,reid)returnu'%s:%s:%s'%(reid,rtype,eid)deftoggleable_relation_link(eid,nodeid,label='x'):"""return javascript snippet to delete/undelete a relation between two entities """js=u"javascript: togglePendingDelete('%s', %s);"%(nodeid,xml_escape(json_dumps(eid)))returnu'[<a class="handle" href="%s" id="handle%s">%s</a>]'%(js,nodeid,label)defget_pending_inserts(req,eid=None):"""shortcut to access req's pending_insert entry This is where are stored relations being added while editing an entity. This used to be stored in a temporary cookie. """pending=req.session.data.get('pending_insert',())return['%s:%s:%s'%(subj,rel,obj)forsubj,rel,objinpendingifeidisNoneoreidin(subj,obj)]defget_pending_deletes(req,eid=None):"""shortcut to access req's pending_delete entry This is where are stored relations being removed while editing an entity. This used to be stored in a temporary cookie. """pending=req.session.data.get('pending_delete',())return['%s:%s:%s'%(subj,rel,obj)forsubj,rel,objinpendingifeidisNoneoreidin(subj,obj)]defparse_relations_descr(rdescr):"""parse a string describing some relations, in the form subjeids:rtype:objeids where subjeids and objeids are eids separeted by a underscore return an iterator on (subject eid, relation type, object eid) found """forrstrinrdescr:subjs,rtype,objs=rstr.split(':')forsubjinsubjs.split('_'):forobjinobjs.split('_'):yieldint(subj),rtype,int(obj)defdelete_relations(req,rdefs):"""delete relations from the repository"""# FIXME convert to using the syntax subject:relation:eidsexecute=req.executeforsubj,rtype,objinparse_relations_descr(rdefs):rql='DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s'%rtypeexecute(rql,{'x':subj,'y':obj})req.set_message(req._('relations deleted'))definsert_relations(req,rdefs):"""insert relations into the repository"""execute=req.executeforsubj,rtype,objinparse_relations_descr(rdefs):rql='SET X %s Y where X eid %%(x)s, Y eid %%(y)s'%rtypeexecute(rql,{'x':subj,'y':obj})# ajax edition helpers ########################################################@ajaxfunc(output_type='xhtml',check_pageid=True)definline_creation_form(self,peid,petype,ttype,rtype,role,i18nctx):view=self._cw.vreg['views'].select('inline-creation',self._cw,etype=ttype,rtype=rtype,role=role,peid=peid,petype=petype)returnself._call_view(view,i18nctx=i18nctx)@ajaxfunc(output_type='json')defvalidate_form(self,action,names,values):returnself.validate_form(action,names,values)@ajaxfuncdefcancel_edition(self,errorurl):"""cancelling edition from javascript We need to clear associated req's data : - errorurl - pending insertions / deletions """self._cw.cancel_edition(errorurl)def_add_pending(req,eidfrom,rel,eidto,kind):key='pending_%s'%kindpendings=req.session.data.setdefault(key,set())pendings.add((int(eidfrom),rel,int(eidto)))def_remove_pending(req,eidfrom,rel,eidto,kind):key='pending_%s'%kindpendings=req.session.data[key]pendings.remove((int(eidfrom),rel,int(eidto)))@ajaxfunc(output_type='json')defremove_pending_insert(self,(eidfrom,rel,eidto)):_remove_pending(self._cw,eidfrom,rel,eidto,'insert')@ajaxfunc(output_type='json')defadd_pending_inserts(self,tripletlist):foreidfrom,rel,eidtointripletlist:_add_pending(self._cw,eidfrom,rel,eidto,'insert')@ajaxfunc(output_type='json')defremove_pending_delete(self,(eidfrom,rel,eidto)):_remove_pending(self._cw,eidfrom,rel,eidto,'delete')@ajaxfunc(output_type='json')defadd_pending_delete(self,(eidfrom,rel,eidto)):_add_pending(self._cw,eidfrom,rel,eidto,'delete')classGenericRelationsWidget(fw.FieldWidget):defrender(self,form,field,renderer):stream=[]w=stream.appendreq=form._cw_=req.___=_eid=form.edited_entity.eidw(u'<table id="relatedEntities">')forrschema,role,relatedinfield.relations_table(form):# already linked entitiesifrelated:label=rschema.display_name(req,role,context=form.edited_entity.cw_etype)w(u'<tr><th class="labelCol">%s</th>'%label)w(u'<td>')w(u'<ul>')forviewparamsinrelated:w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'%(viewparams[1],viewparams[0],viewparams[2],viewparams[3]))ifnotform.force_displayandform.maxrelitems<len(related):link=(u'<span class="invisible">''[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]''</span>'%_('view all'))w(u'<li class="invisible">%s</li>'%link)w(u'</ul>')w(u'</td>')w(u'</tr>')pendings=list(field.restore_pending_inserts(form))ifnotpendings:w(u'<tr><th> </th><td> </td></tr>')else:forrowinpendings:# soon to be linked to entitiesw(u'<tr id="tr%s">'%row[1])w(u'<th>%s</th>'%row[3])w(u'<td>')w(u'<a class="handle" title="%s" href="%s">[x]</a>'%(_('cancel this insert'),row[2]))w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'%(row[1],row[4],xml_escape(row[5])))w(u'</td>')w(u'</tr>')w(u'<tr id="relationSelectorRow_%s" class="separator">'%eid)w(u'<th class="labelCol">')w(u'<select id="relationSelector_%s" tabindex="%s" ''onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'%(eid,req.next_tabindex(),xml_escape(json_dumps(eid))))w(u'<option value="">%s</option>'%_('select a relation'))fori18nrtype,rschema,roleinfield.relations:# more entities to link tow(u'<option value="%s_%s">%s</option>'%(rschema,role,i18nrtype))w(u'</select>')w(u'</th>')w(u'<td id="unrelatedDivs_%s"></td>'%eid)w(u'</tr>')w(u'</table>')return'\n'.join(stream)classGenericRelationsField(ff.Field):widget=GenericRelationsWidgetdef__init__(self,relations,name='_cw_generic_field',**kwargs):assertrelationskwargs['eidparam']=Truesuper(GenericRelationsField,self).__init__(name,**kwargs)self.relations=relationsdefprocess_posted(self,form):todelete=get_pending_deletes(form._cw)iftodelete:delete_relations(form._cw,todelete)toinsert=get_pending_inserts(form._cw)iftoinsert:insert_relations(form._cw,toinsert)return()defrelations_table(self,form):"""yiels 3-tuples (rtype, role, 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=form.edited_entitypending_deletes=get_pending_deletes(form._cw,entity.eid)forlabel,rschema,roleinself.relations:related=[]ifentity.has_eid():rset=entity.related(rschema,role,limit=form.related_limit)ifrole=='subject':haspermkwargs={'fromeid':entity.eid}else:haspermkwargs={'toeid':entity.eid}ifrschema.has_perm(form._cw,'delete',**haspermkwargs):toggleable_rel_link_func=toggleable_relation_linkelse:toggleable_rel_link_func=lambdax,y,z:u''forrowinxrange(rset.rowcount):nodeid=relation_id(entity.eid,rschema,role,rset[row][0])ifnodeidinpending_deletes:status,label=u'pendingDelete','+'else:status,label=u'','x'dellink=toggleable_rel_link_func(entity.eid,nodeid,label)eview=form._cw.view('oneline',rset,row=row)related.append((nodeid,dellink,status,eview))yield(rschema,role,related)defrestore_pending_inserts(self,form):"""used to restore edition page as it was before clicking on 'search for <some entity type>' """entity=form.edited_entitypending_inserts=set(get_pending_inserts(form._cw,form.edited_entity.eid))forpendingidinpending_inserts:eidfrom,rtype,eidto=pendingid.split(':')pendingid='id'+pendingidifint(eidfrom)==entity.eid:# subjectlabel=display_name(form._cw,rtype,'subject',entity.cw_etype)reid=eidtoelse:label=display_name(form._cw,rtype,'object',entity.cw_etype)reid=eidfromjscall="javascript: cancelPendingInsert('%s', 'tr', null, %s);" \%(pendingid,entity.eid)rset=form._cw.eid_rset(reid)eview=form._cw.view('text',rset,row=0)yieldrtype,pendingid,jscall,label,reid,eviewclassUnrelatedDivs(EntityView):__regid__='unrelateddivs'__select__=match_form_params('relation')defcell_call(self,row,col):entity=self.cw_rset.get_entity(row,col)relname,role=self._cw.form.get('relation').rsplit('_',1)rschema=self._cw.vreg.schema.rschema(relname)hidden='hidden'inself._cw.formis_cell='is_cell'inself._cw.formself.w(self.build_unrelated_select_div(entity,rschema,role,is_cell=is_cell,hidden=hidden))defbuild_unrelated_select_div(self,entity,rschema,role,is_cell=False,hidden=True):options=[]divid='div%s_%s_%s'%(rschema.type,role,entity.eid)selectid='select%s_%s_%s'%(rschema.type,role,entity.eid)ifrschema.symmetricorrole=='subject':targettypes=rschema.objects(entity.e_schema)etypes='/'.join(sorted(etype.display_name(self._cw)foretypeintargettypes))else:targettypes=rschema.subjects(entity.e_schema)etypes='/'.join(sorted(etype.display_name(self._cw)foretypeintargettypes))etypes=uilib.cut(etypes,self._cw.property_value('navigation.short-line-size'))options.append('<option>%s%s</option>'%(self._cw._('select a'),etypes))options+=self._get_select_options(entity,rschema,role)options+=self._get_search_options(entity,rschema,role,targettypes)relname,role=self._cw.form.get('relation').rsplit('_',1)returnu"""\<div class="%s" id="%s"> <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">%s </select></div>"""%(hiddenand'hidden'or'',divid,selectid,xml_escape(json_dumps(entity.eid)),is_celland'true'or'null',relname,'\n'.join(options))def_get_select_options(self,entity,rschema,role):"""add options to search among all entities of each possible type"""options=[]pending_inserts=get_pending_inserts(self._cw,entity.eid)rtype=rschema.typeform=self._cw.vreg['forms'].select('edition',self._cw,entity=entity)field=form.field_by_name(rschema,role,entity.e_schema)limit=self._cw.property_value('navigation.combobox-limit')# NOTE: expect 'limit' arg on choices method of relation fieldforeview,reidinfield.vocabulary(form,limit=limit):ifreidisNone:ifeview:# skip blank valueoptions.append('<option class="separator">-- %s --</option>'%xml_escape(eview))elifreid!=ff.INTERNAL_FIELD_VALUE:optionid=relation_id(entity.eid,rtype,role,reid)ifoptionidnotinpending_inserts:# prefix option's id with letters to make valid XHTML wiseoptions.append('<option id="id%s" value="%s">%s</option>'%(optionid,reid,xml_escape(eview)))returnoptionsdef_get_search_options(self,entity,rschema,role,targettypes):"""add options to search among all entities of each possible type"""options=[]_=self._cw._foreschemaintargettypes:mode='%s:%s:%s:%s'%(role,entity.eid,rschema.type,eschema)url=self._cw.build_url(entity.rest_path(),vid='search-associate',__mode=mode)options.append((eschema.display_name(self._cw),'<option value="%s">%s%s</option>'%(xml_escape(url),_('Search for'),eschema.display_name(self._cw))))return[oforl,oinsorted(options)]# The automatic entity form ####################################################classAutomaticEntityForm(forms.EntityFieldsForm):"""AutomaticEntityForm is an automagic form to edit any entity. It is designed to be fully generated from schema but highly configurable through uicfg. Of course, as for other forms, you can also customise it by specifying various standard form parameters on selection, overriding, or adding/removing fields in selected instances. """__regid__='edition'cwtarget='eformframe'cssclass='entityForm'copy_nav_params=Trueform_buttons=[fw.SubmitButton(),fw.Button(stdmsgs.BUTTON_APPLY,cwaction='apply'),fw.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# action on the form tag_default_form_action_path='validateform'# pre 3.8.3 compatdefset_action(self,action):self._action=actiondefget_action(self):try:returnself._actionexceptAttributeError:returnself._cw.build_url(self._default_form_action_path)action=property(deprecated('[3.9] use form.form_action()')(get_action),set_action)@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(AutomaticEntityForm,cls_or_self).field_by_name(name,role,eschema)exceptf.FieldNotFound:ifname=='_cw_generic_field'andnotisinstance(cls_or_self,type):returncls_or_self._generic_relations_field()raise# base automatic entity form methods #######################################def__init__(self,*args,**kwargs):super(AutomaticEntityForm,self).__init__(*args,**kwargs)self.uicfg_afs=self._cw.vreg['uicfg'].select('autoform_section',self._cw,entity=self.edited_entity)entity=self.edited_entityifentity.has_eid():entity.complete()forrtype,roleinself.editable_attributes():try:self.field_by_name(str(rtype),role)continue# explicitly specifiedexceptf.FieldNotFound:# has to be guessedtry:field=self.field_by_name(str(rtype),role,eschema=entity.e_schema)self.fields.append(field)exceptf.FieldNotFound:# meta attribute such as <attr>_formatcontinueifself.fieldsets_in_order:fsio=list(self.fieldsets_in_order)else:fsio=[None]self.fieldsets_in_order=fsio# add fields for relation whose target should have an inline formforformviewinself.inlined_form_views():field=self._inlined_form_view_field(formview)self.fields.append(field)ifnotfield.fieldsetinfsio:fsio.append(field.fieldset)ifself.formtype=='main':# add the generic relation field if necessaryifentity.has_eid()and(self.display_fieldsisNoneor'_cw_generic_field'inself.display_fields):try:field=self.field_by_name('_cw_generic_field')exceptf.FieldNotFound:# no editable relationpasselse:self.fields.append(field)ifnotfield.fieldsetinfsio:fsio.append(field.fieldset)self.maxrelitems=self._cw.property_value('navigation.related-limit')self.force_display=bool(self._cw.form.get('__force_display'))fnum=len(self.fields)self.fields.sort(key=lambdaf:f.orderisNoneandfnumorf.order)@propertydefrelated_limit(self):ifself.force_display:returnNonereturnself.maxrelitems+1# autoform specific fields #################################################def_generic_relations_field(self):srels_by_cat=self.editable_relations()ifnotsrels_by_cat:raisef.FieldNotFound('_cw_generic_field')fieldset=u'%s :'%self._cw.__('This %s'%self.edited_entity.e_schema)fieldset=fieldset.capitalize()returnGenericRelationsField(self.editable_relations(),fieldset=fieldset,label=None)def_inlined_form_view_field(self,view):# XXX allow more customizationkwargs=self.uicfg_affk.etype_get(self.edited_entity.e_schema,view.rtype,view.role,view.etype)ifkwargsisNone:kwargs={}returnInlinedFormField(view=view,**kwargs)# 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 """returnself.uicfg_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:schema=self._cw.vreg.schemareturn[(schema[rtype],role)forrtype,roleinself.display_fields]ifself.edited_entity.has_eid()andnotself.edited_entity.cw_has_perm('update'):return[]# XXX we should simply put eid in the generated section, no?return[(rtype,role)forrtype,_,roleinself._relations_by_section('attributes','update',strict)]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.cw_etype),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')# inlined forms control ####################################################definlined_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)continuetschema=ttypes[0]ttype=tschema.typeformviews=list(self.inline_edition_form_view(rschema,ttype,role))card=rschema.role_rdef(entity.e_schema,ttype,role).role_cardinality(role)# 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):rdef=entity.e_schema.rdef(rschema,role,ttype)ifentity.has_eid():ifrole=='subject':rdefkwargs={'fromeid':entity.eid}else:rdefkwargs={'toeid':entity.eid}else:rdefkwargs={}if(tschema.has_perm(self._cw,'add')andrdef.has_perm(self._cw,'add',**rdefkwargs)):addnewlink=self._cw.vreg['views'].select('inline-addnew-link',self._cw,etype=ttype,rtype=rschema,role=role,card=card,peid=self.edited_entity.eid,petype=self.edited_entity.e_schema,pform=self)formviews.append(addnewlink)allformviews+=formviewsreturnallformviewsdefshould_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+'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.cw_has_perm('update'):yieldvvreg.select('inline-edition',self._cw,rset=related,row=i,col=0,etype=ttype,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,petype=self.edited_entity.e_schema,pform=self)## 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(('*','has_text'),'main','hidden')_AFS.tag_subject_of(('*','in_state','*'),'main','hidden')forrtypein('creation_date','modification_date','cwuri','owned_by','created_by','cw_source'):_AFS.tag_subject_of(('*',rtype,'*'),'main','metadata')_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_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_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')_AFFK=uicfg.autoform_field_kwargs_AFFK.tag_attribute(('RQLExpression','expression'),{'widget':fw.TextInput})_AFFK.tag_subject_of(('TrInfo','wf_info_for','*'),{'widget':fw.HiddenInput})defregistration_callback(vreg):globaletype_relation_fielddefetype_relation_field(etype,rtype,role='subject'):try:eschema=vreg.schema.eschema(etype)returnAutomaticEntityForm.field_by_name(rtype,role,eschema)except(KeyError,f.FieldNotFound):# catch KeyError raised when etype/rtype not found in schemaAutomaticEntityForm.error('field for %s%s may not be found in schema'%(rtype,role))returnNonevreg.register_all(globals().itervalues(),__name__)