# HG changeset patch # User Sylvain Thénault # Date 1264533956 -3600 # Node ID 785c56bdacc66f647c7df01578731c1661f2c8b3 # Parent 0e9cf6593382ee0306cdfe8f098b7d99f0d6807c [forms] the last touch: handle inlined relation forms as fields by introducing a simple InlinedRelationField. This makese things more flexible while removing a lost of overriding necessary. diff -r 0e9cf6593382 -r 785c56bdacc6 web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Tue Jan 26 20:22:13 2010 +0100 +++ b/web/data/cubicweb.edition.js Tue Jan 26 20:25:56 2010 +0100 @@ -230,8 +230,8 @@ } -function updateInlinedEntitiesCounters(rtype) { - jQuery('#inline' + rtype + 'slot span.icounter').each(function (i) { +function updateInlinedEntitiesCounters(rtype, role) { + jQuery('div.inline-' + rtype + '-' + role + '-slot span.icounter').each(function (i) { this.innerHTML = i+1; }); } @@ -252,7 +252,7 @@ var form = jQuery(dom); form.css('display', 'none'); form.insertBefore(insertBefore).slideDown('fast'); - updateInlinedEntitiesCounters(rtype); + updateInlinedEntitiesCounters(rtype, role); reorderTabindex(); jQuery(CubicWeb).trigger('inlinedform-added', form); // if the inlined form contains a file input, we must force @@ -273,10 +273,10 @@ /* * removes the part of the form used to edit an inlined entity */ -function removeInlineForm(peid, rtype, eid, showaddnewlink) { +function removeInlineForm(peid, rtype, role, eid, showaddnewlink) { jqNode(['div', peid, rtype, eid].join('-')).slideUp('fast', function() { $(this).remove(); - updateInlinedEntitiesCounters(rtype); + updateInlinedEntitiesCounters(rtype, role); }); if (showaddnewlink) { toggleVisibility(showaddnewlink); diff -r 0e9cf6593382 -r 785c56bdacc6 web/views/autoform.py --- a/web/views/autoform.py Tue Jan 26 20:22:13 2010 +0100 +++ b/web/views/autoform.py Tue Jan 26 20:25:56 2010 +0100 @@ -9,14 +9,16 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.common.decorators import cached, iclassmethod +from logilab.common.decorators import iclassmethod from cubicweb import typed_eid -from cubicweb.web import stdmsgs, uicfg -from cubicweb.web import form, formwidgets as fwdgs +from cubicweb.web import stdmsgs, uicfg, form, \ + formwidgets as fw, formfields as ff from cubicweb.web.views import forms, editforms, editviews -_afs = uicfg.autoform_section +_AFS = uicfg.autoform_section +_AFFK = uicfg.autoform_field_kwargs + class AutomaticEntityForm(forms.EntityFieldsForm): """base automatic form to edit any entity. @@ -35,9 +37,9 @@ cwtarget = 'eformframe' cssclass = 'entityForm' copy_nav_params = True - form_buttons = [fwdgs.SubmitButton(), - fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'), - fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] + form_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_section formtype = 'main' # set this to a list of [(relation, role)] if you want to explictily tell @@ -89,14 +91,31 @@ except form.FieldNotFound: # meta attribute such as _format continue - if self.formtype == 'main' and entity.has_eid() and ( - self.display_fields is None or - '_cw_generic_field' in self.display_fields): - try: - self.fields.append(self.field_by_name('_cw_generic_field')) - except form.FieldNotFound: - # no editable relation - pass + if self.formtype == 'main': + if self.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 form + for formview in self.inlined_form_views(): + field = self._inlined_form_view_field(formview) + self.fields.append(field) + if not field.fieldset in fsio: + fsio.append(field.fieldset) + # add the generic relation field if necessary + if entity.has_eid() and ( + self.display_fields is None or + '_cw_generic_field' in self.display_fields): + try: + field = self.field_by_name('_cw_generic_field') + except form.FieldNotFound: + # no editable relation + pass + else: + self.fields.append(field) + if not field.fieldset in fsio: + 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) @@ -108,42 +127,6 @@ return None return self.maxrelitems + 1 - @property - def needs_multipart(self): - """true if the form needs enctype=multipart/form-data""" - return self._subform_needs_multipart() - - def build_context(self, rendervalues=None): - super(AutomaticEntityForm, self).build_context(rendervalues) - for form in self.inlined_forms(): - form.build_context(rendervalues) - - def _subform_needs_multipart(self, _tested=None): - if _tested is None: - _tested = set() - if super(AutomaticEntityForm, self).needs_multipart: - return True - # take a look at inlined forms to check (recursively) if they - # need multipart handling. - # XXX: this is very suboptimal because inlined forms will be - # selected / instantiated twice : here and during form rendering. - # Potential solutions: - # -> use subforms for inlined forms to get easiser access - # -> use a simple onload js function to check if there is - # a input type=file in the form - # -> generate the
node when the content is rendered - # and we know the correct enctype (formrenderer's w attribute - # is not a StringIO) - for formview in self.inlined_form_views(): - if formview.form: - if hasattr(formview.form, '_subform_needs_multipart'): - needs_multipart = formview.form._subform_needs_multipart(_tested) - else: - needs_multipart = formview.form.needs_multipart - if needs_multipart: - return True - return False - def action(self): """return the form's action attribute. Default to validateform if not explicitly overriden. @@ -159,13 +142,38 @@ action = property(action, set_action) + # autoform specific fields ################################################# + + def _generic_relations_field(self): + try: + srels_by_cat = self.srelations_by_category('generic', 'add', strict=True) + warn('[3.6] %s: srelations_by_category is deprecated, use uicfg or ' + 'override editable_relations instead' % classid(form), + DeprecationWarning) + except AttributeError: + srels_by_cat = self.editable_relations() + if not srels_by_cat: + raise form.FieldNotFound('_cw_generic_field') + fieldset = u'%s :' % self._cw.__('This %s' % self.edited_entity.e_schema) + fieldset = fieldset.capitalize() + return editviews.GenericRelationsField(self.editable_relations(), + fieldset=fieldset, label=None) + + def _inlined_form_view_field(self, view): + # XXX allow more customization + kwargs = _AFFK.etype_get(self.edited_entity.e_schema, view.rtype, + view.role, view.etype) + if kwargs is None: + kwargs = {} + return editforms.InlinedFormField(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 """ - return _afs.relations_by_section( + return _AFS.relations_by_section( self.edited_entity, self.formtype, section, permission, strict) def editable_attributes(self, strict=False): @@ -194,13 +202,11 @@ """ return self._relations_by_section('inlined') - # generic relations modifier ############################################### - - # inlined forms support #################################################### + # inlined forms control #################################################### - @cached def inlined_form_views(self): - """compute and return list of inlined form views (hosting the inlined form object) + """compute and return list of inlined form views (hosting the inlined + form object) """ allformviews = [] entity = self.edited_entity @@ -231,11 +237,6 @@ allformviews += formviews return allformviews - def inlined_forms(self): - for formview in self.inlined_form_views(): - if formview.form: # may be None for the addnew_link artefact form - yield formview.form - def should_inline_relation_form(self, rschema, targettype, role): """return true if the given relation with entity has role and a targettype target should be inlined @@ -280,8 +281,9 @@ # display inline-edition view for all existing related entities for i, relentity in enumerate(related.entities()): if relentity.has_perm('update'): - yield vvreg.select('inline-edition', self._cw, rset=related, - row=i, col=0, rtype=rschema, role=role, + yield vvreg.select('inline-edition', self._cw, + rset=related, row=i, col=0, + etype=ttype, rtype=rschema, role=role, peid=entity.eid, pform=self) def inline_creation_form_view(self, rschema, ttype, role): @@ -295,46 +297,45 @@ ## 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(('*', 'creation_date'), 'main', 'metadata') -_afs.tag_attribute(('*', 'modification_date'), 'main', 'metadata') -_afs.tag_attribute(('*', 'cwuri'), 'main', 'metadata') -_afs.tag_attribute(('*', 'has_text'), 'main', 'hidden') -_afs.tag_subject_of(('*', 'in_state', '*'), 'main', 'hidden') -_afs.tag_subject_of(('*', 'owned_by', '*'), 'main', 'metadata') -_afs.tag_subject_of(('*', 'created_by', '*'), 'main', 'metadata') -_afs.tag_subject_of(('*', 'require_permission', '*'), 'main', 'hidden') -_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_subject_of(('CWPermission', 'require_group', '*'), 'main', 'attributes') -_afs.tag_subject_of(('CWPermission', 'require_group', '*'), 'muledit', 'attributes') -_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') +_AFS.tag_attribute(('*', 'eid'), 'main', 'attributes') +_AFS.tag_attribute(('*', 'eid'), 'muledit', 'attributes') +_AFS.tag_attribute(('*', 'description'), 'main', 'attributes') +_AFS.tag_attribute(('*', 'creation_date'), 'main', 'metadata') +_AFS.tag_attribute(('*', 'modification_date'), 'main', 'metadata') +_AFS.tag_attribute(('*', 'cwuri'), 'main', 'metadata') +_AFS.tag_attribute(('*', 'has_text'), 'main', 'hidden') +_AFS.tag_subject_of(('*', 'in_state', '*'), 'main', 'hidden') +_AFS.tag_subject_of(('*', 'owned_by', '*'), 'main', 'metadata') +_AFS.tag_subject_of(('*', 'created_by', '*'), 'main', 'metadata') +_AFS.tag_subject_of(('*', 'require_permission', '*'), 'main', 'hidden') +_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_subject_of(('CWPermission', 'require_group', '*'), 'main', 'attributes') +_AFS.tag_subject_of(('CWPermission', 'require_group', '*'), 'muledit', 'attributes') +_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') -uicfg.autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'), - {'widget': fwdgs.TextInput}) -uicfg.autoform_field_kwargs.tag_subject_of(('TrInfo', 'wf_info_for', '*'), - {'widget': fwdgs.HiddenInput}) +_AFFK.tag_attribute(('RQLExpression', 'expression'), + {'widget': fw.TextInput}) +_AFFK.tag_subject_of(('TrInfo', 'wf_info_for', '*'), + {'widget': fw.HiddenInput}) def registration_callback(vreg): global etype_relation_field diff -r 0e9cf6593382 -r 785c56bdacc6 web/views/editforms.py --- a/web/views/editforms.py Tue Jan 26 20:22:13 2010 +0100 +++ b/web/views/editforms.py Tue Jan 26 20:25:56 2010 +0100 @@ -21,10 +21,9 @@ specified_etype_implements, yes) from cubicweb.view import EntityView from cubicweb import tags -from cubicweb.web import uicfg, stdmsgs, eid_param +from cubicweb.web import uicfg, stdmsgs, eid_param, \ + formfields as ff, formwidgets as fw from cubicweb.web.form import FormViewMixIn, FieldNotFound -from cubicweb.web.formfields import guess_field -from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton from cubicweb.web.views import forms _pvdc = uicfg.primaryview_display_ctrl @@ -36,8 +35,8 @@ domid = 'deleteconf' copy_nav_params = True - form_buttons = [Button(stdmsgs.BUTTON_DELETE, cwaction='delete'), - Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] + form_buttons = [fw.Button(stdmsgs.BUTTON_DELETE, cwaction='delete'), + fw.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] @property def action(self): return self._cw.build_url('edit') @@ -230,8 +229,8 @@ form = self._cw.vreg['forms'].select( formid, self._cw, entity=entity, domid='%s-form' % divid, cssstyle='display: none', onsubmit=onsubmit, action='#', - form_buttons=[SubmitButton(), Button(stdmsgs.BUTTON_CANCEL, - onclick=cancelclick)], + form_buttons=[fw.SubmitButton(), + fw.Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)], **formargs) form.event_args = event_args return form @@ -410,8 +409,8 @@ __regid__ = 'muledit' domid = 'entityForm' onsubmit = "return validateForm('%s', null);" % domid - form_buttons = [SubmitButton(_('validate modifications on selected items')), - ResetButton(_('revert changes'))] + form_buttons = [fw.SubmitButton(_('validate modifications on selected items')), + fw.ResetButton(_('revert changes'))] def __init__(self, req, rset, **kwargs): kwargs.setdefault('__redirectrql', rset.printable_rql()) @@ -450,6 +449,53 @@ self.w(form.render(formvid='edition')) +# inlined form handling ######################################################## + +class InlinedFormField(ff.Field): + def __init__(self, view=None, **kwargs): + if view.role == 'object': + fieldset = u'%s_object%s' % view.rtype + else: + fieldset = view.rtype + #kwargs.setdefault('fieldset', fieldset) + kwargs.setdefault('label', None) + super(InlinedFormField, self).__init__(name=view.rtype, role=view.role, + eidparam=True, **kwargs) + self.view = view + + def render(self, form, renderer): + """render this field, which is part of form, using the given form + renderer + """ + view = self.view + i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema, + view.rtype, view.role) + return u'
%s
' % ( + view.rtype, view.role, + view.render(i18nctx=i18nctx, row=view.cw_row, col=view.cw_col)) + + def form_init(self, form): + """method called before by build_context to trigger potential field + initialization requiring the form instance + """ + if self.view.form: + self.view.form.build_context(form.formvalues) + + @property + def needs_multipart(self): + if self.view.form: + # take a look at inlined forms to check (recursively) if they need + # multipart handling. + return self.view.form.needs_multipart + return False + + def has_been_modified(self, form): + return False + + def process_posted(self, form): + pass # handled by the subform + + class InlineEntityEditionFormView(FormViewMixIn, EntityView): """ :attr peid: the parent entity's eid hosting the inline form @@ -460,7 +506,7 @@ __regid__ = 'inline-edition' __select__ = non_final_entity() & match_kwargs('peid', 'rtype') - _select_attrs = ('peid', 'rtype', 'role', 'pform') + _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype') removejs = "removeInlinedEntity('%s', '%s', '%s')" def __init__(self, *args, **kwargs): @@ -548,7 +594,6 @@ __regid__ = 'inline-creation' __select__ = (match_kwargs('peid', 'rtype') & specified_etype_implements('Any')) - _select_attrs = InlineEntityEditionFormView._select_attrs + ('etype',) @property def removejs(self): @@ -559,9 +604,11 @@ # we have to make this link appears back. This is done by giving add new link # id to removeInlineForm. if card not in '?1': - return "removeInlineForm('%s', '%s', '%s')" - divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid) - return "removeInlineForm('%%s', '%%s', '%%s', '%s')" % divid + return "removeInlineForm('%%s', '%%s', '%s', '%%s')" % self.role + divid = "addNew%s%s%s:%s" % ( + self.etype, self.rtype, self.role, self.peid) + return "removeInlineForm('%%s', '%%s', '%s', '%%s', '%s')" % ( + self.role, divid) @cached def _entity(self): diff -r 0e9cf6593382 -r 785c56bdacc6 web/views/formrenderers.py --- a/web/views/formrenderers.py Tue Jan 26 20:22:13 2010 +0100 +++ b/web/views/formrenderers.py Tue Jan 26 20:25:56 2010 +0100 @@ -373,10 +373,6 @@ attrs_fs_label += '
' return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values) - def render_fields(self, w, form, values): - super(EntityFormRenderer, self).render_fields(w, form, values) - self.inline_entities_form(w, form) - def _render_fields(self, fields, w, form): if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'): super(EntityFormRenderer, self)._render_fields(fields, w, form) @@ -395,29 +391,6 @@ """ % tuple(button.render(form) for button in form.form_buttons)) else: super(EntityFormRenderer, self).render_buttons(w, form) - # NOTE: should_* and display_* method extracted and moved to the form to - # ease overriding - - def inline_entities_form(self, w, form): - """create a form to edit entity's inlined relations""" - if not hasattr(form, 'inlined_form_views'): - return - keysinorder = [] - formviews = form.inlined_form_views() - for formview in formviews: - if not (formview.rtype, formview.role) in keysinorder: - keysinorder.append( (formview.rtype, formview.role) ) - for key in keysinorder: - self.inline_relation_form(w, form, [fv for fv in formviews - if (fv.rtype, fv.role) == key]) - - def inline_relation_form(self, w, form, formviews): - i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema, - formviews[0].rtype, formviews[0].role) - w(u'
' % formviews[0].rtype) - for formview in formviews: - w(formview.render(i18nctx=i18nctx, row=formview.cw_row, col=formview.cw_col)) - w(u'
') class EntityInlinedFormRenderer(EntityFormRenderer): @@ -460,6 +433,5 @@ if fields: self._render_fields(fields, w, form) self.render_child_forms(w, form, values) - self.inline_entities_form(w, form) w(u'')