sections to help building a window dev environment
"""form renderers, responsible to layout a form to html:organization: Logilab:copyright: 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"fromlogilab.commonimportdictattrfromlogilab.mtconverterimportxml_escapefromsimplejsonimportdumpsfromcubicweb.commonimporttagsfromcubicweb.appobjectimportAppObjectfromcubicweb.selectorsimportentity_implements,yesfromcubicweb.webimporteid_paramfromcubicweb.webimportformwidgetsasfwdgsfromcubicweb.web.widgetsimportcheckboxfromcubicweb.web.formfieldsimportHiddenInitialValueFieldclassFormRenderer(AppObject):"""basic renderer displaying fields in a two columns table label | value +--------------+--------------+ | field1 label | field1 input | +--------------+--------------+ | field1 label | field2 input | +--------------+--------------+ +---------+ | buttons | +---------+ """__registry__='formrenderers'id='default'_options=('display_fields','display_label','display_help','display_progress_div','table_class','button_bar_class',# add entity since it may be given to select the renderer'entity')display_fields=None# None -> all fieldsdisplay_label=Truedisplay_help=Truedisplay_progress_div=Truetable_class=u'attributeForm'button_bar_class=u'formButtonBar'def__init__(self,req=None,rset=None,row=None,col=None,**kwargs):super(FormRenderer,self).__init__(req,rset,row,col)ifself._set_options(kwargs):raiseValueError('unconsumed arguments %s'%kwargs)def_set_options(self,kwargs):forkeyinself._options:try:setattr(self,key,kwargs.pop(key))exceptKeyError:continuereturnkwargs# renderer interface ######################################################defrender(self,form,values):self._set_options(values)form.add_media()data=[]w=data.appendw(self.open_form(form,values))ifself.display_progress_div:w(u'<div id="progress">%s</div>'%self.req._('validating...'))w(u'<fieldset>')w(tags.input(type=u'hidden',name=u'__form_id',value=values.get('formvid',form.id)))ifform.redirect_path:w(tags.input(type='hidden',name='__redirectpath',value=form.redirect_path))self.render_fields(w,form,values)self.render_buttons(w,form)w(u'</fieldset>')w(u'</form>')errormsg=self.error_message(form)iferrormsg:data.insert(0,errormsg)return'\n'.join(data)defrender_label(self,form,field):iffield.labelisNone:returnu''label=self.req._(field.label)attrs={'for':form.context[field]['id']}iffield.required:attrs['class']='required'returntags.label(label,**attrs)defrender_help(self,form,field):help=[]descr=field.helpifcallable(descr):descr=descr(form)ifdescr:help.append('<div class="helper">%s</div>'%self.req._(descr))example=field.example_format(self.req)ifexample:help.append('<div class="helper">(%s: %s)</div>'%(self.req._('sample format'),example))returnu' '.join(help)# specific methods (mostly to ease overriding) #############################deferror_message(self,form):"""return formatted error message This method should be called once inlined field errors has been consumed """req=self.reqerrex=form.form_valerror# get extra errorsiferrexisnotNone:errormsg=req._('please correct the following errors:')displayed=form.form_displayed_errorserrors=sorted((field,err)forfield,errinerrex.errors.items()ifnotfieldindisplayed)iferrors:iflen(errors)>1:templstr='<li>%s</li>\n'else:templstr=' %s\n'forfield,errinerrors:iffieldisNone:errormsg+=templstr%errelse:errormsg+=templstr%'%s: %s'%(req._(field),err)iflen(errors)>1:errormsg='<ul>%s</ul>'%errormsgreturnu'<div class="errorMessage">%s</div>'%errormsgreturnu''defopen_form(self,form,values):ifform.form_needs_multipart:enctype='multipart/form-data'else:enctype='application/x-www-form-urlencoded'ifform.actionisNone:action=self.req.build_url('edit')else:action=form.actiontag=('<form action="%s" method="post" enctype="%s"'%(xml_escape(actionor'#'),enctype))ifform.domid:tag+=' id="%s"'%form.domidifform.onsubmit:tag+=' onsubmit="%s"'%xml_escape(form.onsubmit%dictattr(form))ifform.cssstyle:tag+=' style="%s"'%xml_escape(form.cssstyle)ifform.cssclass:tag+=' class="%s"'%xml_escape(form.cssclass)ifform.cwtarget:tag+=' cubicweb:target="%s"'%xml_escape(form.cwtarget)returntag+'>'defdisplay_field(self,form,field):ifisinstance(field,HiddenInitialValueField):field=field.visible_fieldreturn(self.display_fieldsisNoneorfield.nameinform.internal_fieldsor(field.name,field.role)inself.display_fieldsor(field.name,field.role)inform.internal_fields)defrender_fields(self,w,form,values):form.form_build_context(values)fields=self._render_hidden_fields(w,form)iffields:self._render_fields(fields,w,form)self.render_child_forms(w,form,values)defrender_child_forms(self,w,form,values):# renderforchildformingetattr(form,'forms',[]):self.render_fields(w,childform,values)def_render_hidden_fields(self,w,form):fields=form.fields[:]forfieldinform.fields:ifnotself.display_field(form,field):fields.remove(field)elifnotfield.is_visible():w(field.render(form,self))fields.remove(field)returnfieldsdef_render_fields(self,fields,w,form):byfieldset={}forfieldinfields:byfieldset.setdefault(field.fieldset,[]).append(field)ifform.fieldsets_in_order:fieldsets=form.fieldsets_in_orderelse:fieldsets=byfieldset.keys()forfieldsetinfieldsets:try:fields=byfieldset.pop(fieldset)exceptKeyError:self.warning('no such fieldset: %s (%s)',fieldset,form)continuew(u'<fieldset class="%s">'%(fieldsetoru'default'))iffieldset:w(u'<legend>%s</legend>'%self.req._(fieldset))w(u'<table class="%s">'%self.table_class)forfieldinfields:w(u'<tr class="%s_%s_row">'%(field.name,field.role))ifself.display_label:w(u'<th class="labelCol">%s</th>'%self.render_label(form,field))error=form.form_field_error(field)iferror:w(u'<td class="error">')w(error)else:w(u'<td>')w(field.render(form,self))ifself.display_help:w(self.render_help(form,field))w(u'</td></tr>')w(u'</table></fieldset>')ifbyfieldset:self.warning('unused fieldsets: %s',', '.join(byfieldset))defrender_buttons(self,w,form):ifnotform.form_buttons:returnw(u'<table class="%s">\n<tr>\n'%self.button_bar_class)forbuttoninform.form_buttons:w(u'<td>%s</td>\n'%button.render(form))w(u'</tr></table>')classBaseFormRenderer(FormRenderer):"""use form_renderer_id = 'base' if you want base FormRenderer layout even when selected for an entity """id='base'classEntityBaseFormRenderer(BaseFormRenderer):"""use form_renderer_id = 'base' if you want base FormRenderer layout even when selected for an entity """__select__=entity_implements('Any')defdisplay_field(self,form,field):ifnotsuper(EntityBaseFormRenderer,self).display_field(form,field):ifisinstance(field,HiddenInitialValueField):field=field.visible_fieldismeta=form.edited_entity.e_schema.is_metadata(field.name)returnismetaisnotNoneand(ismeta[0]inself.display_fieldsor(ismeta[0],'subject')inself.display_fields)returnTrueclassHTableFormRenderer(FormRenderer):"""display fields horizontally in a table +--------------+--------------+---------+ | field1 label | field2 label | | +--------------+--------------+---------+ | field1 input | field2 input | buttons +--------------+--------------+---------+ """id='htable'display_help=Falsedef_render_fields(self,fields,w,form):w(u'<table border="0">')w(u'<tr>')forfieldinfields:ifself.display_label:w(u'<th class="labelCol">%s</th>'%self.render_label(form,field))ifself.display_help:w(self.render_help(form,field))# empty slot for buttonsw(u'<th class="labelCol"> </th>')w(u'</tr>')w(u'<tr>')forfieldinfields:error=form.form_field_error(field)iferror:w(u'<td class="error">')w(error)else:w(u'<td>')w(field.render(form,self))w(u'</td>')w(u'<td>')forbuttoninform.form_buttons:w(button.render(form))w(u'</td>')w(u'</tr>')w(u'</table>')defrender_buttons(self,w,form):passclassEntityCompositeFormRenderer(FormRenderer):"""specific renderer for multiple entities edition form (muledit)"""id='composite'_main_display_fields=Nonedefrender_fields(self,w,form,values):ifnotform.is_subform:w(u'<table class="listing">')subfields=[fieldforfieldinform.forms[0].fieldsifself.display_field(form,field)andfield.is_visible()]ifsubfields:# main form, display table headersw(u'<tr class="header">')w(u'<th align="left">%s</th>'%tags.input(type='checkbox',title=self.req._('toggle check boxes'),onclick="setCheckboxesState('eid', this.checked)"))forfieldinsubfields:w(u'<th>%s</th>'%self.req._(field.label))w(u'</tr>')super(EntityCompositeFormRenderer,self).render_fields(w,form,values)ifnotform.is_subform:w(u'</table>')ifself._main_display_fields:super(EntityCompositeFormRenderer,self)._render_fields(self._main_display_fields,w,form)def_render_fields(self,fields,w,form):ifform.is_subform:entity=form.edited_entityvalues=form.form_previous_valuesqeid=eid_param('eid',entity.eid)cbsetstate="setCheckboxesState2('eid', %s, 'checked')"% \xml_escape(dumps(entity.eid))w(u'<tr class="%s">'%(entity.row%2andu'even'oru'odd'))# XXX turn this into a widget used on the eid fieldw(u'<td>%s</td>'%checkbox('eid',entity.eid,checked=qeidinvalues))forfieldinfields:error=form.form_field_error(field)iferror:w(u'<td class="error">')w(error)else:w(u'<td>')ifisinstance(field.widget,(fwdgs.Select,fwdgs.CheckBox,fwdgs.Radio)):field.widget.attrs['onchange']=cbsetstateelifisinstance(field.widget,fwdgs.Input):field.widget.attrs['onkeypress']=cbsetstate# XXX elsew(u'<div>%s</div>'%field.render(form,self))w(u'</td>\n')w(u'</tr>')else:self._main_display_fields=fieldsclassEntityFormRenderer(EntityBaseFormRenderer):"""specific renderer for entity edition form (edition)"""id='default'# needs some additional points in some case (XXX explain cases)__select__=EntityBaseFormRenderer.__select__&yes()_options=FormRenderer._options+('display_relations_form',)display_relations_form=Truedefrender(self,form,values):rendered=super(EntityFormRenderer,self).render(form,values)returnrendered+u'</div>'# close extra div introducted by open_formdefopen_form(self,form,values):attrs_fs_label=('<div class="iformTitle"><span>%s</span></div>'%self.req._('main informations'))attrs_fs_label+='<div class="formBody">'returnattrs_fs_label+super(EntityFormRenderer,self).open_form(form,values)defrender_fields(self,w,form,values):super(EntityFormRenderer,self).render_fields(w,form,values)self.inline_entities_form(w,form)ifform.edited_entity.has_eid()andself.display_relations_form:self.relations_form(w,form)def_render_fields(self,fields,w,form):ifnotform.edited_entity.has_eid()orform.edited_entity.has_perm('update'):super(EntityFormRenderer,self)._render_fields(fields,w,form)defrender_buttons(self,w,form):iflen(form.form_buttons)==3:w("""<table width="100%%"> <tbody> <tr><td align="center">%s </td><td style="align: right; width: 50%%;">%s%s </td></tr> </tbody> </table>"""%tuple(button.render(form)forbuttoninform.form_buttons))else:super(EntityFormRenderer,self).render_buttons(w,form)defrelations_form(self,w,form):srels_by_cat=form.srelations_by_category('generic','add')ifnotsrels_by_cat:returnu''req=self.req_=req._label=u'%s :'%_('This %s'%form.edited_entity.e_schema).capitalize()eid=form.edited_entity.eidw(u'<fieldset class="subentity">')w(u'<legend class="iformTitle">%s</legend>'%label)w(u'<table id="relatedEntities">')forrschema,target,relatedinform.relations_table():# already linked entitiesifrelated:w(u'<tr><th class="labelCol">%s</th>'%rschema.display_name(req,target))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>'%self.req._('view all'))w(u'<li class="invisible">%s</li>'%link)w(u'</ul>')w(u'</td>')w(u'</tr>')pendings=list(form.restore_pending_inserts())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>')definline_entities_form(self,w,form):"""create a form to edit entity's inlined relations"""ifnothasattr(form,'inlined_relations'):returnforrschema,targettypes,roleinform.inlined_relations():# 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].typeifform.should_inline_relation_form(rschema,targettype,role):self.inline_relation_form(w,form,rschema,targettype,role)definline_relation_form(self,w,form,rschema,targettype,role):entity=form.edited_entity__=self.req.__w(u'<div id="inline%sslot">'%rschema)existant=entity.has_eid()andentity.related(rschema)ifexistant:# display inline-edition view for all existing related entitiesw(form.view('inline-edition',existant,rtype=rschema,role=role,ptype=entity.e_schema,peid=entity.eid))ifrole=='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 viewifform.should_display_inline_creation_form(rschema,existant,card):w(form.view('inline-creation',None,etype=targettype,peid=entity.eid,ptype=entity.e_schema,rtype=rschema,role=role))# we can create more than one related entity, we thus display a link# to add new related entitiesifform.should_display_add_new_relation_link(rschema,existant,card):divid="addNew%s%s%s:%s"%(targettype,rschema,role,entity.eid)w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'%divid)js="addInlineCreationForm('%s', '%s', '%s', '%s')"%(entity.eid,targettype,rschema,role)ifcardin'1?':js="toggleVisibility('%s'); %s"%(divid,js)w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'%(rschema,entity.eid,js,__('add a %s'%targettype)))w(u'</div>')w(u'<div class="trame_grise"> </div>')w(u'</div>')classEntityInlinedFormRenderer(EntityFormRenderer):"""specific renderer for entity inlined edition form (inline-[creation|edition]) """id='inline'defrender(self,form,values):form.add_media()data=[]w=data.appendtry:w(u'<div id="div-%(divid)s" onclick="%(divonclick)s">'%values)exceptKeyError:w(u'<div id="div-%(divid)s">'%values)else:w(u'<div id="notice-%s" class="notice">%s</div>'%(values['divid'],self.req._('click on the box to cancel the deletion')))w(u'<div class="iformBody">')values['removemsg']=self.req.__('remove this %s'%form.edited_entity.e_schema)w(u'<div class="iformTitle"><span>%(title)s</span> ''#<span class="icounter">%(counter)s</span> ''[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'%values)# cleanup valuesforkeyin('title','removejs','removemsg'):values.pop(key)self.render_fields(w,form,values)w(u'</div></div>')return'\n'.join(data)defrender_fields(self,w,form,values):form.form_build_context(values)w(u'<fieldset id="fs-%(divid)s">'%values)fields=self._render_hidden_fields(w,form)w(u'</fieldset>')w(u'<fieldset class="subentity">')iffields:self._render_fields(fields,w,form)self.render_child_forms(w,form,values)self.inline_entities_form(w,form)w(u'</fieldset>')