nicer mainvars/expression handling when initializing rql constraints
"""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"fromcopyimportcopyfromsimplejsonimportdumpsfromlogilab.mtconverterimportxml_escapefromlogilab.common.decoratorsimportcachedfromcubicweb.selectorsimport(specified_etype_implements,accepts_etype_compat,non_final_entity,match_kwargs,one_line_rset)fromcubicweb.viewimportView,EntityViewfromcubicweb.webimportINTERNAL_FIELD_VALUE,eid_paramfromcubicweb.web.controllerimportNAV_FORM_PARAMETERSfromcubicweb.web.widgetsimportcheckbox,InputWidget,ComboBoxWidgetfromcubicweb.web.formimportFormMixInfromcubicweb.web.views.autoformimportAutomaticEntityForm_=unicodeclassEditionForm(FormMixIn,EntityView):"""primary entity edition form When generating a new attribute_input, the editor will look for a method named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the name of the attribute being edited. You may use this feature to compute dynamic default values such as the 'tomorrow' date or the user's login being connected """id='edition'__select__=one_line_rset()&non_final_entity()title=_('edition')controller='edit'skip_relations=set()EDITION_BODY=u'''\%(errormsg)s<form id="%(formid)s" class="entityForm" cubicweb:target="eformframe" method="post" onsubmit="%(onsubmit)s" enctype="%(enctype)s" action="%(action)s">%(title)s <div id="progress">%(inprogress)s</div> <div class="iformTitle"><span>%(mainattrs_label)s</span></div> <div class="formBody"><fieldset>%(base)s%(attrform)s%(relattrform)s</fieldset>%(relform)s </div> <table width="100%%"> <tbody> <tr><td align="center">%(validate)s </td><td style="align: right; width: 50%%;">%(apply)s%(cancel)s </td></tr> </tbody> </table></form>'''defcell_call(self,row,col,**kwargs):self.req.add_js(('cubicweb.ajax.js',))entity=self.complete_entity(row,col)self.edit_form(entity,kwargs)defedit_form(self,entity,kwargs):varmaker=self.req.get_page_data('rql_varmaker')ifvarmakerisNone:varmaker=self.req.varmakerself.req.set_page_data('rql_varmaker',varmaker)self.varmaker=varmakerself.w(self.EDITION_BODY%self.form_context(entity,kwargs))defform_context(self,entity,kwargs):"""returns the dictionnary used to fill the EDITION_BODY template If you create your own edition form, you can probably just override `EDITION_BODY` and `form_context` """ifself.need_multipart(entity):enctype='multipart/form-data'else:enctype='application/x-www-form-urlencoded'self._hiddens=[]ifentity.eidisNone:entity.eid=self.varmaker.next()# XXX (hack) action_title might need __linkto req's original value# and widgets such as DynamicComboWidget might change it# so we need to compute title before calling atttributes_formformtitle=self.action_title(entity)# be sure to call .*_form first so tabindexes are correct and inlined# fields errors are consumedifnotentity.has_eid()orentity.has_perm('update'):attrform=self.attributes_form(entity,kwargs)else:attrform=''inlineform=self.inline_entities_form(entity,kwargs)relform=self.relations_form(entity,kwargs)vindex=self.req.next_tabindex()aindex=self.req.next_tabindex()cindex=self.req.next_tabindex()self.add_hidden_web_behaviour_params(entity)_=self.req._return{'formid':self.domid,'onsubmit':self.on_submit(entity),'enctype':enctype,'errormsg':self.error_message(),'action':self.build_url('validateform'),'eids':entity.has_eid()and[entity.eid]or[],'inprogress':_('validating...'),'title':formtitle,'mainattrs_label':_('main informations'),'reseturl':self.redirect_url(entity),'attrform':attrform,'relform':relform,'relattrform':inlineform,'base':self.base_form(entity,kwargs),'validate':self.button_ok(tabindex=vindex),'apply':self.button_apply(tabindex=aindex),'cancel':self.button_cancel(tabindex=cindex),}@propertydefformid(self):returnself.iddefaction_title(self,entity):"""form's title"""ptitle=self.req._(self.title)returnu'<div class="formTitle"><span>%s%s</span></div>'%(entity.dc_type(),ptitleand'(%s)'%ptitle)defbase_form(self,entity,kwargs):output=[]forname,value,iidinself._hiddens:ifisinstance(value,basestring):value=xml_escape(value)ifiid:output.append(u'<input id="%s" type="hidden" name="%s" value="%s" />'%(iid,name,value))else:output.append(u'<input type="hidden" name="%s" value="%s" />'%(name,value))returnu'\n'.join(output)defadd_hidden_web_behaviour_params(self,entity):"""inserts hidden params controlling how errors and redirection should be handled """req=self.reqself._hiddens.append((u'__maineid',entity.eid,u''))self._hiddens.append((u'__errorurl',req.url(),u'errorurl'))self._hiddens.append((u'__form_id',self.formid,u''))forparaminNAV_FORM_PARAMETERS:value=req.form.get(param)ifvalue:self._hiddens.append((param,value,u''))msg=self.submited_message()# If we need to directly attach the new object to another oneforlinktoinreq.list_form_param('__linkto'):self._hiddens.append(('__linkto',linkto,''))msg='%s%s'%(msg,self.req._('and linked'))self._hiddens.append(('__message',msg,''))defattributes_form(self,entity,kwargs,include_eid=True):"""create a form to edit entity's attributes"""html=[]w=html.appendeid=entity.eidwdg=entity.get_widgetlines=(wdg(rschema,x)forrschema,xinself.editable_attributes(entity))ifinclude_eid:self._hiddens.append(('eid',entity.eid,''))self._hiddens.append((eid_param('__type',eid),entity.e_schema,''))w(u'<table id="%s" class="%s" style="width:100%%;">'%(kwargs.get('tab_id','entityForm%s'%eid),kwargs.get('tab_class','attributeForm')))forwidgetinlines:w(u'<tr>\n<th class="labelCol">%s</th>'%widget.render_label(entity))error=widget.render_error(entity)iferror:w(u'<td class="error" style="width:100%;">')else:w(u'<td style="width:100%;">')iferror:w(error)w(widget.edit_render(entity))w(widget.render_help(entity))w(u'</td>\n</tr>')w(u'</table>')returnu'\n'.join(html)defeditable_attributes(self,entity):# XXX both (add, delete)return[(rschema,x)forrschema,_,xinentity.relations_by_category(('primary','secondary'),'add')ifrschema!='eid']defrelations_form(self,entity,kwargs):srels_by_cat=entity.srelations_by_category(('generic','metadata'),'add')ifnotsrels_by_cat:returnu''req=self.req_=self.req.___=_label=u'%s :'%__('This %s'%entity.e_schema).capitalize()eid=entity.eidhtml=[]w=html.appendw(u'<fieldset class="subentity">')w(u'<legend class="iformTitle">%s</legend>'%label)w(u'<table id="relatedEntities">')forrowinself.relations_table(entity):# already linked entitiesifrow[2]:w(u'<tr><th class="labelCol">%s</th>'%row[0].display_name(req,row[1]))w(u'<td>')w(u'<ul>')forviewparamsinrow[2]:w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'%(viewparams[1],viewparams[0],viewparams[2],viewparams[3]))ifnotself.force_displayandself.maxrelitems<len(row[2]):w(u'<li class="invisible">%s</li>'%self.force_display_link())w(u'</ul>')w(u'</td>')w(u'</tr>')pendings=list(self.restore_pending_inserts(entity))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'<span>%s</span>'%_('add relation'))w(u'<select id="relationSelector_%s" tabindex="%s" onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'%(eid,req.next_tabindex(),xml_escape(dumps(eid))))w(u'<option value="">%s</option>'%_('select a relation'))fori18nrtype,rschema,targetinsrels_by_cat:# more entities to link tow(u'<option value="%s_%s">%s</option>'%(rschema,target,i18nrtype))w(u'</select>')w(u'</th>')w(u'<td id="unrelatedDivs_%s"></td>'%eid)w(u'</tr>')w(u'</table>')w(u'</fieldset>')return'\n'.join(html)definline_entities_form(self,entity,kwargs):"""create a form to edit entity's inlined relations"""result=[]_=self.req._forrschema,targettypes,xinentity.relations_by_category('inlineview','add'):# show inline forms only if there's one possible target type# for rschemaiflen(targettypes)!=1:self.warning('entity related by the %s relation should have ''inlined form but there is multiple target types, ''dunno what to do',rschema)continuetargettype=targettypes[0].typeifself.should_inline_relation_form(entity,rschema,targettype,x):result.append(u'<div id="inline%sslot">'%rschema)existant=entity.has_eid()andentity.related(rschema)ifexistant:# display inline-edition view for all existing related entitiesresult.append(self.view('inline-edition',existant,ptype=entity.e_schema,peid=entity.eid,rtype=rschema,role=x,**kwargs))ifx=='subject':card=rschema.rproperty(entity.e_schema,targettype,'cardinality')[0]else:card=rschema.rproperty(targettype,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_relation_form(rschema,existant,card):result.append(self.view('inline-creation',None,etype=targettype,peid=entity.eid,ptype=entity.e_schema,rtype=rschema,role=x,**kwargs))# we can create more than one related entity, we thus display a link# to add new related entitiesifself.should_display_add_inline_relation_link(rschema,existant,card):divid="addNew%s%s%s:%s"%(targettype,rschema,x,entity.eid)result.append(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'%divid)js="addInlineCreationForm('%s', '%s', '%s', '%s', '%s')"%(entity.eid,entity.e_schema,targettype,rschema,x)ifcardin'1?':js="toggleVisibility('%s'); %s"%(divid,js)result.append(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'%(rschema,entity.eid,js,self.req.__('add a %s'%targettype)))result.append(u'</div>')result.append(u'</div>')return'\n'.join(result)# should_* method extracted to allow overridingdefshould_inline_relation_form(self,entity,rschema,targettype,role):returnAutomaticEntityForm.rinlined.etype_get(entity.id,rschema,role,targettype)defshould_display_inline_relation_form(self,rschema,existant,card):returnnotexistantandcardin'1+'defshould_display_add_inline_relation_link(self,rschema,existant,card):returnnotexistantorcardin'+*'defreset_url(self,entity):returnentity.absolute_url()defon_submit(self,entity):returnu'return freezeFormButtons(\'%s\')'%(self.domid)defsubmited_message(self):returnself.req._('element edited')classCreationForm(EditionForm):__select__=specified_etype_implements('Any')# XXX bw compat, use View.registered since we don't want accept_compat# wrapper set in EntityViewregistered=accepts_etype_compat(View.registered)id='creation'title=_('creation')defcall(self,**kwargs):"""creation view for an entity"""self.req.add_js(('cubicweb.ajax.js',))self.initialize_varmaker()etype=kwargs.pop('etype',self.req.form.get('etype'))try:entity=self.vreg.etype_class(etype)(self.req,None,None)except:self.w(self.req._('no such entity type %s')%etype)else:entity.eid=self.varmaker.next()self.edit_form(entity,kwargs)defaction_title(self,entity):"""custom form title if creating a entity with __linkto"""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)}returnu'<div class="formTitle notransform"><span>%s</span></div>'%msgelse:returnsuper(CreationForm,self).action_title(entity)@propertydefformid(self):return'edition'defrelations_form(self,entity,kwargs):returnu''defreset_url(self,entity=None):returnself.build_url(self.req.form.get('etype','').lower())defsubmited_message(self):returnself.req._('element created')defurl(self):"""return the url associated with this view"""returnself.create_url(self.req.form.get('etype'))classInlineFormMixIn(object):@cacheddefcard(self,etype):returnself.rschema.rproperty(self.parent_schema,etype,'cardinality')[0]defaction_title(self,entity):returnself.rschema.display_name(self.req,self.role)defadd_hidden_web_behaviour_params(self,entity):passdefedit_form(self,entity,ptype,peid,rtype,role='subject',**kwargs):self.rschema=self.schema.rschema(rtype)self.role=roleself.parent_schema=self.schema.eschema(ptype)self.parent_eid=peidsuper(InlineFormMixIn,self).edit_form(entity,kwargs)defshould_inline_relation_form(self,entity,rschema,targettype,role):ifrschema==self.rschema:returnFalsereturnAutomaticEntityForm.rinlined.etype_get(entity.id,rschema,role,targettype)@cacheddefkeep_entity(self,entity):req=self.req# are we regenerating form because of a validation error ?erroneous_post=req.data.get('formvalues')iferroneous_post:cdvalues=req.list_form_param('%s:%s'%(self.rschema,self.parent_eid),erroneous_post)ifunicode(entity.eid)notincdvalues:returnFalsereturnTruedefform_context(self,entity,kwargs):ctx=super(InlineFormMixIn,self).form_context(entity,kwargs)_=self.req._local_ctx={'createmsg':self.req.__('add a %s'%entity.e_schema),'so':self.role[0],# 's' for subject, 'o' for object'eid':entity.eid,'rtype':self.rschema,'parenteid':self.parent_eid,'parenttype':self.parent_schema,'etype':entity.e_schema,'novalue':INTERNAL_FIELD_VALUE,'removemsg':self.req.__('remove this %s'%entity.e_schema),'notice':self.req._('click on the box to cancel the deletion'),}ctx.update(local_ctx)returnctxclassCopyEditionForm(EditionForm):id='copy'title=_('copy edition')defcell_call(self,row,col,**kwargs):self.req.add_js(('cubicweb.ajax.js',))entity=self.complete_entity(row,col,skip_bytes=True)# make a copy of entity to avoid altering the entity in the# request's cache.self.newentity=copy(entity)self.copying=self.newentity.eidself.newentity.eid=Noneself.edit_form(self.newentity,kwargs)delself.newentitydefaction_title(self,entity):"""form's title"""msg=super(CopyEditionForm,self).action_title(entity)returnmsg+(u'<script type="text/javascript">updateMessage("%s");</script>\n'%self.req._('Please note that this is only a shallow copy'))# XXX above message should have style of a warning@propertydefformid(self):return'edition'defrelations_form(self,entity,kwargs):returnu''defreset_url(self,entity):returnself.build_url('view',rql='Any X WHERE X eid %s'%self.copying)defattributes_form(self,entity,kwargs,include_eid=True):# we don't want __clone_eid on inlined edited entitiesifentity.eid==self.newentity.eid:self._hiddens.append((eid_param('__cloned_eid',entity.eid),self.copying,''))returnEditionForm.attributes_form(self,entity,kwargs,include_eid)defsubmited_message(self):returnself.req._('element copied')classTableEditForm(FormMixIn,EntityView):id='muledit'title=_('multiple edit')EDITION_BODY=u'''<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);" action="%(action)s">%(error)s <div id="progress">%(progress)s</div> <fieldset> <input type="hidden" name="__errorurl" value="%(url)s" /> <input type="hidden" name="__form_id" value="%(formid)s" /> <input type="hidden" name="__redirectvid" value="%(redirectvid)s" /> <input type="hidden" name="__redirectrql" value="%(redirectrql)s" /> <table class="listing"> <tr class="header"> <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>%(attrheaders)s </tr>%(lines)s </table> <table width="100%%"> <tr> <td align="left"> <input class="validateButton" type="submit" value="%(okvalue)s" title="%(oktitle)s" /> <input class="validateButton" type="reset" name="__action_cancel" value="%(cancelvalue)s" title="%(canceltitle)s" /> </td> </tr> </table> </fieldset></form>'''WIDGET_CELL=u'''\<td%(csscls)s>%(error)s <div>%(widget)s</div></td>'''defcall(self,**kwargs):"""a view to edit multiple entities of the same type the first column should be the eid """req=self.reqform=req.form_=req._sampleentity=self.complete_entity(0)attrheaders=[u'<th>%s</th>'%rdef[0].display_name(req,rdef[-1])forrdefinsampleentity.relations_by_category('primary','add')ifrdef[0].type!='eid']ctx={'action':self.build_url('edit'),'error':self.error_message(),'progress':_('validating...'),'url':xml_escape(req.url()),'formid':self.id,'redirectvid':xml_escape(form.get('__redirectvid','list')),'redirectrql':xml_escape(form.get('__redirectrql',self.rset.printable_rql())),'attrheaders':u'\n'.join(attrheaders),'lines':u'\n'.join(self.edit_form(ent)forentinself.rset.entities()),'okvalue':_('button_ok').capitalize(),'oktitle':_('validate modifications on selected items').capitalize(),'cancelvalue':_('button_reset').capitalize(),'canceltitle':_('revert changes').capitalize(),}self.w(self.EDITION_BODY%ctx)defreset_url(self,entity=None):self.build_url('view',rql=self.rset.printable_rql())defedit_form(self,entity):html=[]w=html.appendentity.complete()eid=entity.eidvalues=self.req.data.get('formvalues',())qeid=eid_param('eid',eid)checked=qeidinvaluesw(u'<tr class="%s">'%(entity.row%2andu'even'oru'odd'))w(u'<td>%s<input type="hidden" name="__type:%s" value="%s" /></td>'%(checkbox('eid',eid,checked=checked),eid,entity.e_schema))# attribute relations (skip eid which is handled by the checkboxwdg=entity.get_widgetwdgfactories=[wdg(rschema,x)forrschema,_,xinentity.relations_by_category('primary','add')ifrschema.type!='eid']# XXX both (add, delete)seid=xml_escape(dumps(eid))forwobjinwdgfactories:ifisinstance(wobj,ComboBoxWidget):wobj.attrs['onchange']="setCheckboxesState2('eid', %s, 'checked')"%seidelifisinstance(wobj,InputWidget):wobj.attrs['onkeypress']="setCheckboxesState2('eid', %s, 'checked')"%seiderror=wobj.render_error(entity)iferror:csscls=u' class="error"'else:csscls=u''w(self.WIDGET_CELL%{'csscls':csscls,'error':error,'widget':wobj.edit_render(entity)})w(u'</tr>')return'\n'.join(html)# XXX bw compatfromlogilab.common.deprecationimportclass_movedfromcubicweb.web.viewsimporteditviewsComboboxView=class_moved(editviews.ComboboxView)