"""Set of HTML automatic forms to create, delete, copy or edit a single entityor a list of entities of the same type: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"_=unicodefromcopyimportcopyfromsimplejsonimportdumpsfromlogilab.mtconverterimportxml_escapefromcubicweb.selectorsimport(match_kwargs,one_line_rset,non_final_entity,specified_etype_implements,yes)fromcubicweb.utilsimportmake_uidfromcubicweb.viewimportEntityViewfromcubicweb.commonimporttagsfromcubicweb.webimportINTERNAL_FIELD_VALUE,stdmsgs,eid_param,uicfgfromcubicweb.web.formimportFormViewMixIn,FieldNotFoundfromcubicweb.web.formfieldsimportguess_fieldfromcubicweb.web.formwidgetsimportButton,SubmitButton,ResetButtonfromcubicweb.web.viewsimportformsdefrelation_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(dumps(eid)))returnu'[<a class="handle" href="%s" id="handle%s">%s</a>]'%(js,nodeid,label)classDeleteConfForm(forms.CompositeForm):id='deleteconf'__select__=non_final_entity()domid='deleteconf'copy_nav_params=Trueform_buttons=[Button(stdmsgs.YES,cwaction='delete'),Button(stdmsgs.NO,cwaction='cancel')]@propertydefaction(self):returnself.build_url('edit')def__init__(self,*args,**kwargs):super(DeleteConfForm,self).__init__(*args,**kwargs)done=set()forentityinself.rset.entities():ifentity.eidindone:continuedone.add(entity.eid)subform=self.vreg['forms'].select('base',self.req,entity=entity,mainform=False)self.form_add_subform(subform)classDeleteConfFormView(FormViewMixIn,EntityView):"""form used to confirm deletion of some entities"""id='deleteconf'title=_('delete')# don't use navigation, all entities asked to be deleted should be displayed# else we will only delete the displayed pageneed_navigation=Falsedefcall(self,onsubmit=None):"""ask for confirmation before real deletion"""req,w=self.req,self.w_=req._w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'%_('this action is not reversible!'))# XXX above message should have style of a warningw(u'<h4>%s</h4>\n'%_('Do you want to delete the following element(s) ?'))form=self.vreg['forms'].select(self.id,req,rset=self.rset,onsubmit=onsubmit)w(u'<ul>\n')forentityinself.rset.entities():# don't use outofcontext view or any other that may contain inline edition formw(u'<li>%s</li>'%tags.a(entity.view('textoutofcontext'),href=entity.absolute_url()))w(u'</ul>\n')w(form.form_render())classClickAndEditFormView(FormViewMixIn,EntityView):"""form used to permit ajax edition of a relation or attribute of an entity in a view, if logged user have the permission to edit it. (double-click on the field to see an appropriate edition widget). """id='doreledit'__select__=non_final_entity()&match_kwargs('rtype')# FIXME editableField class could be toggleable from userprefs# add metadata to allow edition of metadata attributes (not considered by# edition form by default)attrcategories=('primary','secondary','metadata')_onclick=u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"_defaultlandingzone=(u'<img title="%(msg)s" ''src="data/accessories-text-editor.png" ''alt="%(msg)s"/>')_landingzonemsg=_('click to edit this field')# default relation vids according to cardinality_one_rvid='incontext'_many_rvid='csv'def_compute_best_vid(self,eschema,rschema,role):ifeschema.cardinality(rschema,role)in'+*':returnself._many_rvidreturnself._one_rviddef_build_landing_zone(self,lzone):returnlzoneorself._defaultlandingzone%{'msg':xml_escape(self.req._(self._landingzonemsg))}def_build_renderer(self,entity,rtype,role):returnself.vreg['formrenderers'].select('base',self.req,entity=entity,display_label=False,display_help=False,display_fields=[(rtype,role)],table_class='',button_bar_class='buttonbar',display_progress_div=False)def_build_form(self,entity,rtype,role,formid,default,onsubmit,reload,extradata=None,**formargs):divid='d%s'%make_uid('%s-%s'%(rtype,entity.eid))event_data={'divid':divid,'eid':entity.eid,'rtype':rtype,'reload':dumps(reload),'default':default}ifextradata:event_data.update(extradata)onsubmit%=event_datacancelclick="hideInlineEdit(%s,\'%s\',\'%s\')"%(entity.eid,rtype,divid)form=self.vreg['forms'].select(formid,self.req,entity=entity,domid='%s-form'%divid,cssstyle='display: none',onsubmit=onsubmit,action='#',form_buttons=[SubmitButton(),Button(stdmsgs.BUTTON_CANCEL,onclick=cancelclick)],**formargs)form.event_data=event_datareturnformdefcell_call(self,row,col,rtype=None,role='subject',reload=False,# controls reloading the whole page after changervid=None,# vid to be applied to other side of rtype (non final relations only)default=None,# default valuelanding_zone=None# prepend value with a separate html element to click onto# (esp. needed when values are links)):"""display field to edit entity's `rtype` relation on click"""assertrtypeassertrolein('subject','object')ifdefaultisNone:default=xml_escape(self.req._('<no value>'))entity=self.entity(row,col)rschema=entity.schema.rschema(rtype)lzone=self._build_landing_zone(landing_zone)# compute value, checking perms, build formifrschema.is_final():onsubmit=("return inlineValidateAttributeForm('%(rtype)s', '%(eid)s', '%(divid)s', ""%(reload)s, '%(default)s');")form=self._build_form(entity,rtype,role,'edition',default,onsubmit,reload,attrcategories=self.attrcategories)ifnotself.should_edit_attribute(entity,rschema,role,form):returnvalue=entity.printable_value(rtype)ordefaultself.attribute_form(lzone,value,form,self._build_renderer(entity,rtype,role))else:ifrvidisNone:rvid=self._compute_best_vid(entity.e_schema,rschema,role)ifnotself.should_edit_relation(entity,rschema,role,rvid):returnrset=entity.related(rtype,role)ifrset:value=self.view(rvid,rset)else:value=defaultonsubmit=("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', ""'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")form=self._build_form(entity,rtype,role,'base',default,onsubmit,reload,dict(vid=rvid,role=role,lzone=lzone))field=guess_field(entity.e_schema,entity.schema.rschema(rtype),role)form.append_field(field)self.relation_form(lzone,value,form,self._build_renderer(entity,rtype,role))defshould_edit_attribute(self,entity,rschema,role,form):rtype=str(rschema)ttype=rschema.targets(entity.id,role)[0]afs=uicfg.autoform_section.etype_get(entity.id,rtype,role,ttype)ifnot(afsinself.attrcategoriesandentity.has_perm('update')):self.w(entity.printable_value(rtype))returnFalsetry:field=form.field_by_name(rtype,role)exceptFieldNotFound:self.w(entity.printable_value(rtype))returnFalsereturnTruedefshould_edit_relation(self,entity,rschema,role,rvid):if((role=='subject'andnotrschema.has_perm(self.req,'add',fromeid=entity.eid))or(role=='object'andnotrschema.has_perm(self.req,'add',toeid=entity.eid))):self.wview(rvid,entity.related(str(rschema),role),'null')returnFalsereturnTruedefattribute_form(self,lzone,value,form,renderer):"""div (class=field) +-xxx div | +-xxx div (class=editableField) | | +-landing zone | +-value-xxx div | +-value +-form-xxx div """w=self.ww(u'<div class="field">')w(u'<div id="%s" style="display: inline">'%form.event_data['divid'])w(tags.div(lzone,klass='editableField',onclick=self._onclick%form.event_data))w(u'<div id="value-%s" style="display: inline">%s</div>'%(form.event_data['divid'],value))w(u'</div>')w(form.form_render(renderer=renderer))w(u'</div>')defrelation_form(self,lzone,value,form,renderer):"""xxx-reledit div (class=field) +-xxx div (class="editableField") | +-landing zone +-value +-form-xxx div """w=self.ww(u'<div id="%s-reledit" class="field">'%form.event_data['divid'])w(tags.div(lzone,klass='editableField',id=form.event_data['divid'],onclick=self._onclick%form.event_data))w(value)w(form.form_render(renderer=renderer))w(u'</div>')classAutoClickAndEditFormView(ClickAndEditFormView):"""same as ClickAndEditFormView but checking if the view *should* be applied by checking uicfg configuration and composite relation property. """id='reledit'defshould_edit_relation(self,entity,rschema,role,rvid):eschema=entity.e_schemartype=str(rschema)# XXX check autoform_section. what if 'generic'?dispctrl=uicfg.primaryview_display_ctrl.etype_get(eschema,rtype,role)vid=dispctrl.get('vid','reledit')ifvid!='reledit':# reledit explicitly disabledself.wview(vid,entity.related(rtype,role),'null')returnFalseifeschema.role_rproperty(role,rschema,'composite')==role:self.wview(rvid,entity.related(rtype,role),'null')returnFalsereturnsuper(AutoClickAndEditFormView,self).should_edit_relation(entity,rschema,role,rvid)classEditionFormView(FormViewMixIn,EntityView):"""display primary entity edition form"""id='edition'# add yes() so it takes precedence over deprecated views in baseforms,# though not baseforms based customized view__select__=one_line_rset()&non_final_entity()&yes()title=_('edition')defcell_call(self,row,col,**kwargs):entity=self.complete_entity(row,col)self.render_form(entity)defrender_form(self,entity):"""fetch and render the form"""self.form_title(entity)form=self.vreg['forms'].select('edition',self.req,rset=entity.rset,row=entity.row,col=entity.col,entity=entity,submitmsg=self.submited_message())self.init_form(form,entity)self.w(form.form_render(formvid=u'edition'))definit_form(self,form,entity):"""customize your form before rendering here"""passdefform_title(self,entity):"""the form view title"""ptitle=self.req._(self.title)self.w(u'<div class="formTitle"><span>%s%s</span></div>'%(entity.dc_type(),ptitleand'(%s)'%ptitle))defsubmited_message(self):"""return the message that will be displayed on successful edition"""returnself.req._('entity edited')classCreationFormView(EditionFormView):"""display primary entity creation form"""id='creation'__select__=specified_etype_implements('Any')&yes()title=_('creation')defcall(self,**kwargs):"""creation view for an entity"""etype=kwargs.pop('etype',self.req.form.get('etype'))try:entity=self.vreg['etypes'].etype_class(etype)(self.req)except:self.w(self.req._('no such entity type %s')%etype)else:self.initialize_varmaker()entity.eid=self.varmaker.next()self.render_form(entity)defform_title(self,entity):"""the form view title"""if'__linkto'inself.req.form:ifisinstance(self.req.form['__linkto'],list):# XXX which one should be considered (case: add a ticket to a# version in jpl)rtype,linkto_eid,role=self.req.form['__linkto'][0].split(':')else:rtype,linkto_eid,role=self.req.form['__linkto'].split(':')linkto_rset=self.req.eid_rset(linkto_eid)linkto_type=linkto_rset.description[0][0]ifrole=='subject':title=self.req.__('creating %s (%s%s%s%%(linkto)s)'%(entity.e_schema,entity.e_schema,rtype,linkto_type))else:title=self.req.__('creating %s (%s%%(linkto)s %s%s)'%(entity.e_schema,linkto_type,rtype,entity.e_schema))msg=title%{'linkto':self.view('incontext',linkto_rset)}self.w(u'<div class="formTitle notransform"><span>%s</span></div>'%msg)else:super(CreationFormView,self).form_title(entity)defurl(self):"""return the url associated with this view"""returnself.create_url(self.req.form.get('etype'))defsubmited_message(self):"""return the message that will be displayed on successful edition"""returnself.req._('entity created')classCopyFormView(EditionFormView):"""display primary entity creation form initialized with values from another entity """id='copy'defrender_form(self,entity):"""fetch and render the form"""# make a copy of entity to avoid altering the entity in the# request's cache.entity.complete()self.newentity=copy(entity)self.copying=entityself.initialize_varmaker()self.newentity.eid=self.varmaker.next()self.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'%self.req._('Please note that this is only a shallow copy'))super(CopyFormView,self).render_form(self.newentity)delself.newentitydefinit_form(self,form,entity):"""customize your form before rendering here"""super(CopyFormView,self).init_form(form,entity)ifentity.eid==self.newentity.eid:form.form_add_hidden(eid_param('__cloned_eid',entity.eid),self.copying.eid)forrschema,_,roleinform.relations_by_category(form.attrcategories,'add'):ifnotrschema.is_final():# ensure relation cache is filedrset=self.copying.related(rschema,role)self.newentity.set_related_cache(rschema,role,rset)defsubmited_message(self):"""return the message that will be displayed on successful edition"""returnself.req._('entity copied')classTableEditForm(forms.CompositeForm):id='muledit'domid='entityForm'onsubmit="return validateForm('%s', null);"%domidform_buttons=[SubmitButton(_('validate modifications on selected items')),ResetButton(_('revert changes'))]def__init__(self,req,rset,**kwargs):kwargs.setdefault('__redirectrql',rset.printable_rql())super(TableEditForm,self).__init__(req,rset,**kwargs)forrowinxrange(len(self.rset)):form=self.vreg['forms'].select('edition',self.req,rset=self.rset,row=row,attrcategories=('primary',),mainform=False)# XXX rely on the EntityCompositeFormRenderer to put the eid inputform.remove_field(form.field_by_name('eid'))self.form_add_subform(form)classTableEditFormView(FormViewMixIn,EntityView):id='muledit'__select__=EntityView.__select__&yes()title=_('multiple edit')defcall(self,**kwargs):"""a view to edit multiple entities of the same type the first column should be the eid """#self.form_title(entity)form=self.vreg['forms'].select(self.id,self.req,rset=self.rset)self.w(form.form_render())classInlineEntityEditionFormView(FormViewMixIn,EntityView):id='inline-edition'__select__=non_final_entity()&match_kwargs('peid','rtype')removejs="removeInlinedEntity('%s', '%s', '%s')"defcall(self,**kwargs):"""redefine default call() method to avoid automatic insertions of <div class="section"> between each row of the resultset """rset=self.rsetforiinxrange(len(rset)):self.wview(self.id,rset,row=i,**kwargs)defcell_call(self,row,col,peid,rtype,role='subject',**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(row,col)divonclick="restoreInlinedEntity('%s', '%s', '%s')"%(peid,rtype,entity.eid)self.render_form(entity,peid,rtype,role,divonclick=divonclick)defrender_form(self,entity,peid,rtype,role,**kwargs):"""fetch and render the form"""form=self.vreg['forms'].select('edition',self.req,entity=entity,form_renderer_id='inline',mainform=False,copy_nav_params=False)self.add_hiddens(form,entity,peid,rtype,role)divid='%s-%s-%s'%(peid,rtype,entity.eid)title=self.schema.rschema(rtype).display_name(self.req,role)removejs=self.removejs%(peid,rtype,entity.eid)countkey='%s_count'%rtypetry:self.req.data[countkey]+=1except:self.req.data[countkey]=1self.w(form.form_render(divid=divid,title=title,removejs=removejs,counter=self.req.data[countkey],**kwargs))defadd_hiddens(self,form,entity,peid,rtype,role):# to ease overriding (see cubes.vcsfile.views.forms for instance)ifself.keep_entity(form,entity,peid,rtype):ifentity.has_eid():rval=entity.eidelse:rval=INTERNAL_FIELD_VALUEform.form_add_hidden('edit%s-%s:%s'%(role[0],rtype,peid),rval)form.form_add_hidden(name='%s:%s'%(rtype,peid),value=entity.eid,id='rel-%s-%s-%s'%(peid,rtype,entity.eid))defkeep_entity(self,form,entity,peid,rtype):ifnotentity.has_eid():returnTrue# are we regenerating form because of a validation error ?ifform.form_previous_values:cdvalues=self.req.list_form_param(eid_param(rtype,peid),form.form_previous_values)ifunicode(entity.eid)notincdvalues:returnFalsereturnTrueclassInlineEntityCreationFormView(InlineEntityEditionFormView):id='inline-creation'__select__=(match_kwargs('peid','rtype')&specified_etype_implements('Any'))removejs="removeInlineForm('%s', '%s', '%s')"defcall(self,etype,peid,rtype,role='subject',**kwargs):""" :param etype: the entity type being created in the inline form :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 """try:entity=self.vreg['etypes'].etype_class(etype)(self.req,None,None)except:self.w(self.req._('no such entity type %s')%etype)returnself.initialize_varmaker()entity.eid=self.varmaker.next()self.render_form(entity,peid,rtype,role)