z3c.group
Group Forms
Group forms allow you to split up a form into several logical units without much overhead. To the parent form, groups should be only dealt with during coding and be transparent on the data extraction level.
For the examples to work, we have to bring up most of the form framework:
>>> from z3c.form import testing >>> testing.setupFormDefaults()
So let's first define a complex content component that warrants setting up multiple groups:
>>> import zope.interface >>> import zope.schema>>> class IVehicleRegistration(zope.interface.Interface): ... firstName = zope.schema.TextLine(title=u'First Name') ... lastName = zope.schema.TextLine(title=u'Last Name') ... ... license = zope.schema.TextLine(title=u'License') ... address = zope.schema.TextLine(title=u'Address') ... ... model = zope.schema.TextLine(title=u'Model') ... make = zope.schema.TextLine(title=u'Make') ... year = zope.schema.Int(title=u'Year')>>> class VehicleRegistration(object): ... zope.interface.implements(IVehicleRegistration) ... ... def __init__(self, **kw): ... for name, value in kw.items(): ... setattr(self, name, value)
The schema above can be separated into basic, license, and car information, where the latter two will be placed into groups. First we create the two groups:
>>> from z3c.form import field, group>>> class LicenseGroup(group.Group): ... label = u'License' ... fields = field.Fields(IVehicleRegistration).select( ... 'license', 'address')>>> class CarGroup(group.Group): ... label = u'Car' ... fields = field.Fields(IVehicleRegistration).select( ... 'model', 'make', 'year')
Most of the group is setup like any other (sub)form. Additionally, you can specify a label, which is a human-readable string that can be used for layout purposes.
Let's now create an add form for the entire vehicle registration. In comparison to a regular add form, you only need to add the GroupForm? as one of the base classes. The groups are specified in a simple tuple:
>>> import os >>> from zope.app.pagetemplate import viewpagetemplatefile >>> from z3c.form import form, tests>>> class RegistrationAddForm(group.GroupForm, form.AddForm): ... fields = field.Fields(IVehicleRegistration).select( ... 'firstName', 'lastName') ... groups = (LicenseGroup, CarGroup) ... ... template = viewpagetemplatefile.ViewPageTemplateFile( ... 'simple_groupedit.pt', os.path.dirname(tests.__file__)) ... ... def create(self, data): ... return VehicleRegistration(**data) ... ... def add(self, object): ... self.getContent()['obj1'] = object ... return object
Note: The order of the base classes is very important here. The GroupForm? class must be left of the AddForm? class, because the GroupForm? class overrides some methods of the AddForm? class.
Now we can instantiate the form:
>>> request = testing.TestRequest()>>> add = RegistrationAddForm(None, request) >>> add.update()
After the form is updated the tuple of group classes is converted to group instances:
>>> add.groups (<LicenseGroup object at ...>, <CarGroup object at ...>)
We can now render the form:
>>> print add.render()
<html>
<body>
<form action=".">
<div class="row">
<label for="form-widgets-firstName">First Name</label>
<input type="text" id="form-widgets-firstName"
name="form.widgets.firstName" class="textWidget textline-field"
value="" />
</div>
<div class="row">
<label for="form-widgets-lastName">Last Name</label>
<input type="text" id="form-widgets-lastName"
name="form.widgets.lastName" class="textWidget textline-field"
value="" />
</div>
<fieldgroup>
<legend>License</legend>
<div class="row">
<label for="form-widgets-license">License</label>
<input type="text" id="form-widgets-license"
name="form.widgets.license" class="textWidget textline-field"
value="" />
</div>
<div class="row">
<label for="form-widgets-address">Address</label>
<input type="text" id="form-widgets-address"
name="form.widgets.address" class="textWidget textline-field"
value="" />
</div>
</fieldgroup>
<fieldgroup>
<legend>Car</legend>
<div class="row">
<label for="form-widgets-model">Model</label>
<input type="text" id="form-widgets-model"
name="form.widgets.model" class="textWidget textline-field"
value="" />
</div>
<div class="row">
<label for="form-widgets-make">Make</label>
<input type="text" id="form-widgets-make"
name="form.widgets.make" class="textWidget textline-field"
value="" />
</div>
<div class="row">
<label for="form-widgets-year">Year</label>
<input type="text" id="form-widgets-year"
name="form.widgets.year" class="textWidget int-field"
value="" />
</div>
</fieldgroup>
<div class="action">
<input type="submit" id="form-buttons-add"
name="form.buttons.add" class="submitWidget button-field"
value="Add" />
</div>
</form>
</body>
</html>
Let's now submit the form, but forgetting to enter the address:
>>> request = testing.TestRequest(form={ ... 'form.widgets.firstName': u'Stephan', ... 'form.widgets.lastName': u'Richter', ... 'form.widgets.license': u'MA 40387', ... 'form.widgets.model': u'BMW', ... 'form.widgets.make': u'325', ... 'form.widgets.year': u'2005', ... 'form.buttons.add': u'Add' ... })>>> add = RegistrationAddForm(None, request) >>> add.update() >>> print add.render() <html> <body> <i>There were some errors.</i> <form action="."> ... <fieldgroup> <legend>License</legend> <ul> <li> Address: <div class="error">Required input is missing.</div> </li> </ul> ... </fieldgroup> ... </form> </body> </html>
As you can see, the template is clever enough to just report the errors at the top of the form, but still report the actual problem within the group. So what happens, if errors happen inside and outside a group?
>>> request = testing.TestRequest(form={ ... 'form.widgets.firstName': u'Stephan', ... 'form.widgets.license': u'MA 40387', ... 'form.widgets.model': u'BMW', ... 'form.widgets.make': u'325', ... 'form.widgets.year': u'2005', ... 'form.buttons.add': u'Add' ... })>>> add = RegistrationAddForm(None, request) >>> add.update() >>> print add.render() <html> <body> <i>There were some errors.</i> <ul> <li> Last Name: <div class="error">Required input is missing.</div> </li> </ul> <form action="."> ... <fieldgroup> <legend>License</legend> <ul> <li> Address: <div class="error">Required input is missing.</div> </li> </ul> ... </fieldgroup> ... </form> </body> </html>
Let's now successfully complete the add form.
>>> from zope.app.container import btree >>> context = btree.BTreeContainer()>>> request = testing.TestRequest(form={ ... 'form.widgets.firstName': u'Stephan', ... 'form.widgets.lastName': u'Richter', ... 'form.widgets.license': u'MA 40387', ... 'form.widgets.address': u'10 Main St, Maynard, MA', ... 'form.widgets.model': u'BMW', ... 'form.widgets.make': u'325', ... 'form.widgets.year': u'2005', ... 'form.buttons.add': u'Add' ... })>>> add = RegistrationAddForm(context, request) >>> add.update()
The object is now added to the container and all attributes should be set:
>>> reg = context['obj1'] >>> reg.firstName u'Stephan' >>> reg.lastName u'Richter' >>> reg.license u'MA 40387' >>> reg.address u'10 Main St, Maynard, MA' >>> reg.model u'BMW' >>> reg.make u'325' >>> reg.year 2005
Let's now have a look at an edit form for the vehicle registration:
>>> class RegistrationEditForm(group.GroupForm, form.EditForm): ... fields = field.Fields(IVehicleRegistration).select( ... 'firstName', 'lastName') ... groups = (LicenseGroup, CarGroup) ... ... template = viewpagetemplatefile.ViewPageTemplateFile( ... 'simple_groupedit.pt', os.path.dirname(tests.__file__))>>> request = testing.TestRequest()>>> edit = RegistrationEditForm(reg, request) >>> edit.update()
After updating the form, we can render the HTML:
>>> print edit.render()
<html>
<body>
<form action=".">
<div class="row">
<label for="form-widgets-firstName">First Name</label>
<input type="text" id="form-widgets-firstName"
name="form.widgets.firstName" class="textWidget textline-field"
value="Stephan" />
</div>
<div class="row">
<label for="form-widgets-lastName">Last Name</label>
<input type="text" id="form-widgets-lastName"
name="form.widgets.lastName" class="textWidget textline-field"
value="Richter" />
</div>
<fieldgroup>
<legend>License</legend>
<div class="row">
<label for="form-widgets-license">License</label>
<input type="text" id="form-widgets-license"
name="form.widgets.license" class="textWidget textline-field"
value="MA 40387" />
</div>
<div class="row">
<label for="form-widgets-address">Address</label>
<input type="text" id="form-widgets-address"
name="form.widgets.address" class="textWidget textline-field"
value="10 Main St, Maynard, MA" />
</div>
</fieldgroup>
<fieldgroup>
<legend>Car</legend>
<div class="row">
<label for="form-widgets-model">Model</label>
<input type="text" id="form-widgets-model"
name="form.widgets.model" class="textWidget textline-field"
value="BMW" />
</div>
<div class="row">
<label for="form-widgets-make">Make</label>
<input type="text" id="form-widgets-make"
name="form.widgets.make" class="textWidget textline-field"
value="325" />
</div>
<div class="row">
<label for="form-widgets-year">Year</label>
<input type="text" id="form-widgets-year"
name="form.widgets.year" class="textWidget int-field"
value="2005" />
</div>
</fieldgroup>
<div class="action">
<input type="submit" id="form-buttons-apply"
name="form.buttons.apply" class="submitWidget button-field"
value="Apply" />
</div>
</form>
</body>
</html>
The behavior when an error occurs is identical to that of the add form:
>>> request = testing.TestRequest(form={ ... 'form.widgets.firstName': u'Stephan', ... 'form.widgets.lastName': u'Richter', ... 'form.widgets.license': u'MA 40387', ... 'form.widgets.model': u'BMW', ... 'form.widgets.make': u'325', ... 'form.widgets.year': u'2005', ... 'form.buttons.apply': u'Apply' ... })>>> edit = RegistrationEditForm(reg, request) >>> edit.update() >>> print edit.render() <html> <body> <i>There were some errors.</i> <form action="."> ... <fieldgroup> <legend>License</legend> <ul> <li> Address: <div class="error">Required input is missing.</div> </li> </ul> ... </fieldgroup> ... </form> </body> </html>
Let's now complete the form successfully:
>>> request = testing.TestRequest(form={ ... 'form.widgets.firstName': u'Stephan', ... 'form.widgets.lastName': u'Richter', ... 'form.widgets.license': u'MA 4038765', ... 'form.widgets.address': u'11 Main St, Maynard, MA', ... 'form.widgets.model': u'Ford', ... 'form.widgets.make': u'F150', ... 'form.widgets.year': u'2006', ... 'form.buttons.apply': u'Apply' ... })>>> edit = RegistrationEditForm(reg, request) >>> edit.update()
The success message will be shown on the form, ...
>>> print edit.render()
<html>
<body>
<i>Data successfully updated.</i>
...
</body>
</html>
and the data is correctly updated:
>>> reg.firstName u'Stephan' >>> reg.lastName u'Richter' >>> reg.license u'MA 4038765' >>> reg.address u'11 Main St, Maynard, MA' >>> reg.model u'Ford' >>> reg.make u'F150' >>> reg.year 2006
And that's it!
