zope.formlib is the form framework in Zope 3 that makes it easy to generate browser forms using Zope 3 schemas and perform validation on user input. This is of course something what we’ve come to expect using using existing tools like Archetypes schemas and CMFFormController. The good news about formlib is that you can already use it in Plone (and we do so extensively in the Plone 3 version of PrimaGIS).
One of the advantages of formlib is that you can easily take multiple schemas, throw them into a single form, select the fields that you want to include in the form and have formlib automatically handle the different schemas by adapting your content object accordingly when saving the data. Although formlib matches up very nicely to Archetypes generated forms (if you ignore the small amount of fields/widgets available for formlib compared to AT) there is one feature in Archetypes that does not exist in formlib: field reordering.
In Archetypes, you can take an
Archetypes.Schema.Schema instance and reorder fields programmatically using the
Schema.moveField() method, e.g.
>>> from Products.Archetypes.atapi import Schema, StringField >>> schema = Schema((StringField('a'), ... StringField('b'), ... StringField('c'))) >>> schema.keys() ['a','b','c'] >>> schema.moveField('c', before='a') >>> schema.keys() ['c','a','b'] >>> schema.moveField('a', pos='bottom') >>> schema.keys() ['c','b','a']
Moving fields around is usually necessary when you’re (re)using an existing Schema defined somewhere else and wish to modify it for your own use. Having to define a new schema (by copying code) simply to get the form to display fields in a different order would feel like a waste of resources so having the ability (and a nice API) to modify existing schemas is useful.
For this reason I implemented an enhanced version of the
zope.formlib.form.Fields class that supports reordering formlib fields using an API almost identical to the one in Archetypes. The package is called
hexagonit.form and is available from the Cheeseshop.
To use the enhanced version, you simply use
hexagonit.form.orderable.OrderableFields in place of
zope.formlib.form.Fields in your code. Below is a dummy example demonstrating its use.
We first need to declare a simple schema for which the form will be generated.
>>> from zope.interface import Interface >>> from zope.schema import TextLine, Bool, Int >>> class ISomeSchema(Interface): ... text = TextLine(title=u"text field") ... boolean = Bool(title=u"boolean field") ... integer = Int(title=u"integer field")
Now that we have a schema, we can generate the form fields using
>>> from hexagonit.form.orderable import OrderableFields >>> form_fields = OrderableFields(ISomeSchema)
form_fields variable now contains your normal formlib fields with the additional
moveField method that allows reordering the fields on the fly.
>>> [field.__name__ for field in form_fields] ['text', 'boolean', 'integer'] >>> form_fields.moveField("boolean", direction="up") >>> [field.__name__ for field in form_fields] ['boolean', 'text', 'integer'] >>> form_fields.moveField("boolean", position=2) >>> [field.__name__ for field in form_fields] ['integer', 'text', 'boolean'] >>> form_fields.moveField('boolean', before='integer') >>> [field.__name__ for field in form_fields] ['boolean', 'integer', 'text']
moveField method allows reordering the form fields in a variety of ways using the different keyword parameters:
directionparameter with values “up” and “down” for changing the position of the field relative to its current position
positionparameter with values “first” and “last” (or alternatively “top” and “bottom” ) or using absolute positions with integer values (first field at position 0) to place the field in a specific position
beforeparameters to place the field in a position relative to another field.
The doctests in the package describe the functionality of the
moveField in full detail. An actual form implementation would look something like this in Plone:
from Products.Five.formlib import formbase from hexagonit.form.orderable import OrderableFields from somewhere import IMySchema, MyCustomWidget class MyAddForm(formbase.AddFormBase): # Instantiate the form fields form_fields = OrderableFields(IMySchema) # All normal functionality of zope.formlib.form.Fields is # available, such as [field].custom_widget, .omit(), .select() etc. form_fields['somefield'].custom_widget = MyCustomWidget # After setting up the fields you can reorder them according # to your needs form_fields.moveField('somefield', position='last') form_fields.moveField('otherfield', direction='up') # Rest of form implementation follows..
The easiest way to install and try
hexagonit.form is to use easy_install:
$ easy_install hexagonit.form
You can also manually download the egg or the source tarball from the Cheeseshop page.