[Product-Developers] Re: z3c.form - subforms

Daniel Nouri daniel.nouri at gmail.com
Fri May 23 13:39:07 UTC 2008


Hi Martin!

Martin Aspeli writes:

> I'm getting a bit lost in the plone.z3cform/z3c.form jungle. :)
>
> I want to have a free-standing form (that is, not an edit form for an
> object) to define a CV. It should have:
>
>  - a name field
>  - a date-of-birth field
>  - an arbitrary number of "education" entries, each of which should have:
> 	- an institution name
> 	- a graduation date
>
> Visually, I'd like this to look like:
>
> Name:            [___________]
> Date of birth:   [___]
> Education history:
>  +--------------------------------------+
>  | [ ]Institution: ACME University      |
>  |    Graduated:   2003                 |
>  |                                      |
>  | Institution: [_____________]         |
>  | Graduated:   [____]                  |
>  | [Add another] [Remove]               |
>  +--------------------------------------+
>
> [Submit]
>
> The idea here is that the user can enter an institution and a date,
> and then add another entry by clicking "Add another".
>
> Perhaps this is a bit too clever, but it'd be nice. ;)

Subforms are quite nice for this.  Let me try and give you some starting
points:

You'll have a master form (CVForm) and a subform class (EducationForm).
The first one will represent IPersonal (name, date of birth), the other
will have IEducation for its schema.

You'll collect subforms for IEducation in your CVForm in the 'subforms'
attribute.  This way, your subforms will be rendered by the template.

Your CVForm will need to use something like S&D's form-with-subforms.pt
template [1].  (If you feel this should be moved in plone.z3cform,
please feel free to add it there, and make it a NamedTemplate.)

Your CVForm's update method will look something like this:

  >>>     def update(self):
  ...         self.subforms = []
  ...         subform = EducationForm(self.context, self.request, self)
  ...         subform.prefix = 'education%s.' % len(self.subforms)
  ...         self.subforms.append(subform)
  ...         subform.update()
  ...         super(CVForm, self).update()

Note that the EducationForm receives the parent form in its
constructor.  Just subclass EducationForm from form.Form:

  >>> class EducationForm(form.Form):
  ...     template = z3viewpagetemplatefile.ViewPageTemplateFile('subform.pt')
  ...     fields = field.Fields(IEducation)
  ...     ignoreContext = True
  ... 
  ...     def __init__(self, context, request, parent):
  ...         super(EducationForm, self).__init__(context, request)
  ...         self.parentForm = parent

The idea is to override update() in your EducationForm to add subforms
on demand to the parent form: If in self.extractData() you don't get any
errors, you can probably assume that you need to add another subform.
This is a bit of a hack.  You should really use a hidden widget to find
out exactly if the subform was present in the request:

  ...     def update(self):
  ...         super(EducationForm, self).update()
  ...         self.data, self.errors = self.extractData()
  ...         if not self.errors:
  ...             self._add_subform()
  ...         # XXX: The elif won't work, use a hidden boolean widget instead!
  ...         elif not self.data:
  ...             self.data, self.errors = None, None
  ...             self.updateWidgets() # Unset errors on widgets for display

  ...     def _add_subform(self):
  ...         next = EducationForm(self.context, self.request, self.parentForm)
  ...         next.prefix = 'education%s.' % len(self.parentForm.subforms)
  ...         self.parentForm.subforms.append(next)
  ...         next.update()
  ...         self.mode = 'display'
  ...         self.updateWidgets()

Also note how I make self a 'display' form and update the widgets when I
add another subform to the list.  Forms have 'input' mode by default.

The subform will have either a remove or an add button, based on if it's
an input or display form.  In this example, I won't let the add handler
do anything, since I add subforms already in update (which is executed
before the handlers).  Maybe it'd be more practical to have the add
handler add a form to the end:

  ...     @z3c.form.button.buttonAndHandler('Add',
  ...         condition=lambda form:form.mode == 'input')
  ...     def handle_add(self):
  ...         pass
  ... 
  ...     @z3c.form.button.buttonAndHandler('Remove',
  ...         condition=lambda form:form.mode == 'display')
  ...     def handle_remove(self):
  ...         self.parentForm.subforms.remove(self)

The CVForm's submit handler can then gather data and errors from all its
subforms in its submit handler:

  ...     @z3c.form.button.buttonAndHandler('Submit')
  ...     def handle_submit(self, action):
  ...         data, errors = self.extractData() # my own data
  ...         if errors:
  ...             self.status = u"Please correct the indicated errors."
  ...             return
  ...         for subform in self.subforms:
  ...             if subform.errors:
  ...                 self.status = u"Please correct the indicated errors."
  ...                 return
  ... 
  ...         # Do stuff here with submitted data...
  ...         subforms_data = [f.data for f in self.subforms]


I hope that this gives you at least an idea on how to do this.  It's a
quick example from the top of my head and I haven't tested it, but I
think it should give you a start.  Hardly anything is Zope 2/Plone
specific here.

Subforms are discussed in depth here:

  http://docs.carduner.net/z3c.form/subform.html

I'd suggest that for more advice on best practices, you write to the
Zope users list.

And then, for the record, there's this example in S&D [2], which I
already pointed you to on IRC.  This form allows you to edit items of a
container recursively and add items.  These items may be of the
container type itself (thus the recursion), rich text fields, reference
fields etc.


[1] http://dev.plone.org/collective/browser/collective.dancing/trunk/collective/dancing/browser/form-with-subforms.pt
[2] http://dev.plone.org/collective/browser/collective.dancing/trunk/collective/dancing/browser/collector.py

-- 
Daniel Nouri
http://danielnouri.org





More information about the Product-Developers mailing list