[c-c instance commands] keyboard interrupt should stop the command, not jump to the next instance. Closes #1794850
# copyright 2003-2011 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/>."""edit entity attributes/relations from any view, without going to the entityform"""__docformat__="restructuredtext en"_=unicodeimportcopyfromwarningsimportwarnfromlogilab.mtconverterimportxml_escapefromlogilab.common.deprecationimportdeprecated,class_renamedfromlogilab.common.decoratorsimportcachedfromcubicwebimportneg_rolefromcubicweb.schemaimportdisplay_namefromcubicweb.utilsimportjson_dumpsfromcubicweb.selectorsimportnon_final_entity,match_kwargsfromcubicweb.viewimportEntityViewfromcubicweb.webimportuicfg,stdmsgsfromcubicweb.web.formimportFieldNotFoundfromcubicweb.web.formwidgetsimportButton,SubmitButtonclass_DummyForm(object):__slots__=('event_args',)defform_render(self,**_args):returnu''defrender(self,*_args,**_kwargs):returnu''defappend_field(self,*args):passdefadd_hidden(self,*args):passrctrl=uicfg.reledit_ctrlclassAutoClickAndEditFormView(EntityView):__regid__='reledit'__select__=non_final_entity()&match_kwargs('rtype')# ui side continuations_onclick=(u"cw.reledit.loadInlineEditionForm('%(formid)s', %(eid)s, '%(rtype)s', '%(role)s', ""'%(divid)s', %(reload)s, '%(vid)s', '%(action)s');")_cancelclick="cw.reledit.cleanupAfterCancel('%s')"# ui side actions/buttons_addzone=u'<img title="%(msg)s" src="%(logo)s" alt="%(msg)s"/>'_addmsg=_('click to add a value')_addlogo='plus.png'_deletezone=u'<img title="%(msg)s" src="%(logo)s" alt="%(msg)s"/>'_deletemsg=_('click to delete this value')_deletelogo='cancel.png'_editzone=u'<img title="%(msg)s" src="%(logo)s" alt="%(msg)s"/>'_editzonemsg=_('click to edit this field')_editlogo='pen_icon.png'# renderer_form_renderer_id='base'defcell_call(self,row,col,rtype=None,role='subject',reload=False,# controls reloading the whole page after change# boolean, eid (to redirect), or# function taking the subject entity & returning a boolean or an eidrvid=None,# vid to be applied to other side of rtype (non final relations only)default_value=None,formid='base',action=None):"""display field to edit entity's `rtype` relation on click"""assertrtypeself._cw.add_css('cubicweb.form.css')self._cw.add_js(('cubicweb.reledit.js','cubicweb.edition.js','cubicweb.ajax.js'))self.entity=self.cw_rset.get_entity(row,col)rschema=self._cw.vreg.schema[rtype]self._rules=rctrl.etype_get(self.entity.e_schema.type,rschema.type,role,'*')ifrvidisnotNoneordefault_valueisnotNone:warn('[3.9] specifying rvid/default_value on select is deprecated, ''reledit_ctrl rtag to control this'%self,DeprecationWarning)reload=self._compute_reload(rschema,role,reload)divid=self._build_divid(rtype,role,self.entity.eid)ifrschema.final:self._handle_attribute(rschema,role,divid,reload,action)else:ifself._is_composite():self._handle_composite(rschema,role,divid,reload,formid,action)else:self._handle_relation(rschema,role,divid,reload,formid,action)def_handle_attribute(self,rschema,role,divid,reload,action):value=self.entity.printable_value(rschema.type)ifnotself._should_edit_attribute(rschema):self.w(value)returnform,renderer=self._build_form(self.entity,rschema,role,divid,'base',reload,action)value=valueorself._compute_default_value(rschema,role)self.view_form(divid,value,form,renderer)def_compute_formid_value(self,rschema,role,rvid,formid):related_rset=self.entity.related(rschema.type,role)ifrelated_rset:value=self._cw.view(rvid,related_rset)else:value=self._compute_default_value(rschema,role)ifnotself._should_edit_relation(rschema,role):returnNone,valuereturnformid,valuedef_handle_relation(self,rschema,role,divid,reload,formid,action):rvid=self._rules.get('rvid','autolimited')formid,value=self._compute_formid_value(rschema,role,rvid,formid)ifformidisNone:returnself.w(value)form,renderer=self._build_form(self.entity,rschema,role,divid,formid,reload,action,dict(vid=rvid))self.view_form(divid,value,form,renderer)def_handle_composite(self,rschema,role,divid,reload,formid,action):# this is for attribute-like composites (1 target type, 1 related entity at most, for now)entity=self.entityrelated_rset=entity.related(rschema.type,role)add_related=self._may_add_related(related_rset,rschema,role)edit_related=self._may_edit_related_entity(related_rset,rschema,role)delete_related=edit_relatedandself._may_delete_related(related_rset,rschema,role)rvid=self._rules.get('rvid','autolimited')formid,value=self._compute_formid_value(rschema,role,rvid,formid)ifformidisNoneornot(edit_relatedoradd_related):# till we learn to handle cases where not (edit_related or add_related)self.w(value)returnform,renderer=self._build_form(entity,rschema,role,divid,formid,reload,action,dict(vid=rvid))self.view_form(divid,value,form,renderer,edit_related,add_related,delete_related)@cacheddef_compute_ttypes(self,rschema,role):dual_role=neg_role(role)returngetattr(rschema,'%ss'%dual_role)()def_compute_reload(self,rschema,role,reload):ctrl_reload=self._rules.get('reload',reload)ifcallable(ctrl_reload):ctrl_reload=ctrl_reload(self.entity)ifisinstance(ctrl_reload,int)andctrl_reload>1:# not True/Falsectrl_reload=self._cw.build_url(ctrl_reload)returnctrl_reloaddef_compute_default_value(self,rschema,role):default=self._rules.get('novalue_label')ifdefaultisNone:ifself._rules.get('novalue_include_rtype'):default=self._cw._('<%s not specified>')%display_name(self._cw,rschema.type,role)else:default=self._cw._('<not specified>')returnxml_escape(default)def_is_composite(self):returnself._rules.get('edit_target')=='related'def_may_add_related(self,related_rset,rschema,role):""" ok for attribute-like composite entities """ttypes=self._compute_ttypes(rschema,role)iflen(ttypes)>1:# many etypes: learn how to do itreturnFalserdef=rschema.role_rdef(self.entity.e_schema,ttypes[0],role)card=rdef.role_cardinality(role)ifrelated_rsetorcardnotin'?1':returnFalseifrole=='subject':kwargs={'fromeid':self.entity.eid}else:kwargs={'toeid':self.entity.eid}returnrdef.has_perm(self._cw,'add',**kwargs)def_may_edit_related_entity(self,related_rset,rschema,role):""" controls the edition of the related entity """ttypes=self._compute_ttypes(rschema,role)iflen(ttypes)>1orlen(related_rset.rows)!=1:returnFalseifself.entity.e_schema.rdef(rschema,role).role_cardinality(role)notin'?1':returnFalsereturnrelated_rset.get_entity(0,0).cw_has_perm('update')def_may_delete_related(self,related_rset,rschema,role):# we assume may_edit_related, only 1 related entityifnotrelated_rset:returnFalserentity=related_rset.get_entity(0,0)entity=self.entityifrole=='subject':kwargs={'fromeid':entity.eid,'toeid':rentity.eid}else:kwargs={'fromeid':rentity.eid,'toeid':entity.eid}# NOTE: should be sufficient given a well built schema/securityreturnrschema.has_perm(self._cw,'delete',**kwargs)def_build_zone(self,zonedef,msg,logo):returnzonedef%{'msg':xml_escape(self._cw._(msg)),'logo':xml_escape(self._cw.data_url(logo))}def_build_edit_zone(self):returnself._build_zone(self._editzone,self._editzonemsg,self._editlogo)def_build_delete_zone(self):returnself._build_zone(self._deletezone,self._deletemsg,self._deletelogo)def_build_add_zone(self):returnself._build_zone(self._addzone,self._addmsg,self._addlogo)def_build_divid(self,rtype,role,entity_eid):""" builds an id for the root div of a reledit widget """return'%s-%s-%s'%(rtype,role,entity_eid)def_build_args(self,entity,rtype,role,formid,reload,action,extradata=None):divid=self._build_divid(rtype,role,entity.eid)event_args={'divid':divid,'eid':entity.eid,'rtype':rtype,'formid':formid,'reload':json_dumps(reload),'action':action,'role':role,'vid':u''}ifextradata:event_args.update(extradata)returnevent_argsdef_prepare_form(self,entity,rschema,role,action):assertactionin('edit_rtype','edit_related','add','delete'),actionifaction=='edit_rtype':returnFalse,entitylabel=Trueifactionin('edit_related','delete'):edit_entity=entity.related(rschema,role).get_entity(0,0)elifaction=='add':add_etype=self._compute_ttypes(rschema,role)[0]_new_entity=self._cw.vreg['etypes'].etype_class(add_etype)(self._cw)_new_entity.eid=self._cw.varmaker.next()edit_entity=_new_entity# XXX see forms.py ~ 276 and entities.linked_to method# is there another way ?self._cw.form['__linkto']='%s:%s:%s'%(rschema,entity.eid,neg_role(role))assertedit_entityreturnlabel,edit_entitydef_build_renderer(self,related_entity,display_label):returnself._cw.vreg['formrenderers'].select(self._form_renderer_id,self._cw,entity=related_entity,display_label=display_label,table_class='attributeForm'ifdisplay_labelelse'',display_help=False,button_bar_class='buttonbar',display_progress_div=False)def_build_form(self,entity,rschema,role,divid,formid,reload,action,extradata=None,**formargs):rtype=rschema.typeevent_args=self._build_args(entity,rtype,role,formid,reload,action,extradata)ifnotaction:form=_DummyForm()form.event_args=event_argsreturnform,Nonelabel,edit_entity=self._prepare_form(entity,rschema,role,action)cancelclick=self._cancelclick%dividform=self._cw.vreg['forms'].select(formid,self._cw,rset=edit_entity.as_rset(),entity=edit_entity,domid='%s-form'%divid,formtype='inlined',action=self._cw.build_url('validateform',__onsuccess='window.parent.cw.reledit.onSuccess'),cwtarget='eformframe',cssclass='releditForm',**formargs)# pass reledit argumentsforpname,pvalueinevent_args.iteritems():form.add_hidden('__reledit|'+pname,pvalue)# handle buttonsifform.form_buttons:# edition, deleteform_buttons=[]forbuttoninform.form_buttons:ifnotbutton.label.endswith('apply'):ifbutton.label.endswith('cancel'):button=copy.deepcopy(button)button.cwaction=Nonebutton.onclick=cancelclickform_buttons.append(button)form.form_buttons=form_buttonselse:# baseform.form_buttons=[SubmitButton(),Button(stdmsgs.BUTTON_CANCEL,onclick=cancelclick)]form.event_args=event_argsifformid=='base':field=form.field_by_name(rtype,role,entity.e_schema)form.append_field(field)returnform,self._build_renderer(edit_entity,label)def_should_edit_attribute(self,rschema):entity=self.entityrdef=entity.e_schema.rdef(rschema)# check permissionsifnotentity.cw_has_perm('update'):returnFalserdef=entity.e_schema.rdef(rschema)returnrdef.has_perm(self._cw,'update',eid=entity.eid)should_edit_attributes=deprecated('[3.9] should_edit_attributes is deprecated,'' use _should_edit_attribute instead',_should_edit_attribute)def_should_edit_relation(self,rschema,role):eeid=self.entity.eidperm_args={'fromeid':eeid}ifrole=='subject'else{'toeid':eeid}returnrschema.has_perm(self._cw,'add',**perm_args)should_edit_relations=deprecated('[3.9] should_edit_relations is deprecated,'' use _should_edit_relation instead',_should_edit_relation)def_open_form_wrapper(self,divid,value,form,renderer,_edit_related,_add_related,_delete_related):w=self.ww(u'<div id="%(id)s-reledit" onmouseout="%(out)s" onmouseover="%(over)s" class="%(css)s">'%{'id':divid,'css':'releditField','out':"jQuery('#%s').addClass('hidden')"%divid,'over':"jQuery('#%s').removeClass('hidden')"%divid})w(u'<div id="%s-value" class="editableFieldValue">'%divid)w(value)w(u'</div>')form.render(w=w,renderer=renderer)w(u'<div id="%s" class="editableField hidden">'%divid)def_edit_action(self,divid,args,edit_related,add_related,_delete_related):# XXX disambiguate wrt edit_relatedifnotadd_related:# currently, excludes editionw=self.wargs['formid']='edition'ifedit_relatedelse'base'args['action']='edit_related'ifedit_relatedelse'edit_rtype'w(u'<div id="%s-update" class="editableField" onclick="%s" title="%s">'%(divid,xml_escape(self._onclick%args),self._cw._(self._editzonemsg)))w(self._build_edit_zone())w(u'</div>')def_add_action(self,divid,args,_edit_related,add_related,_delete_related):ifadd_related:w=self.wargs['formid']='edition'args['action']='add'w(u'<div id="%s-add" class="editableField" onclick="%s" title="%s">'%(divid,xml_escape(self._onclick%args),self._cw._(self._addmsg)))w(self._build_add_zone())w(u'</div>')def_del_action(self,divid,args,_edit_related,_add_related,delete_related):ifdelete_related:w=self.wargs['formid']='deleteconf'args['action']='delete'w(u'<div id="%s-delete" class="editableField" onclick="%s" title="%s">'%(divid,xml_escape(self._onclick%args),self._cw._(self._deletemsg)))w(self._build_delete_zone())w(u'</div>')def_close_form_wrapper(self):self.w(u'</div>')self.w(u'</div>')defview_form(self,divid,value,form=None,renderer=None,edit_related=False,add_related=False,delete_related=False):self._open_form_wrapper(divid,value,form,renderer,edit_related,add_related,delete_related)args=form.event_args.copy()self._edit_action(divid,args,edit_related,add_related,delete_related)self._add_action(divid,args,edit_related,add_related,delete_related)self._del_action(divid,args,edit_related,add_related,delete_related)self._close_form_wrapper()ClickAndEditFormView=class_renamed('ClickAndEditFormView',AutoClickAndEditFormView)