The z3ext.layoutform package provides advanced form classes based on the well-known z3c.form package. In addition to z3c.form features, z3ext.layoutform provides:
Form classes provided by this package are extended versions of z3c.form classes. If you are not familiar with z3c.form, please, read its documentation before.
The z3ext.layoutform module provides 4 generic form classes for use to create forms for z3ext:
This is a base class for all other forms in this package. It provides a generic pagelet-based form without any default buttons. It also implements support for sub-forms and field groups, but this feature will be described in a separate section. In addition, this class supports form “label” and “description” that will be rendered in UI. Let’s provide an example of a form:
from z3ext.layoutform import PageletForm, Fields, button
class ExampleForm(PageletForm):
fields = Fields(ISomeSchema)
label = u'Example Form'
description = u'This form will allow you to enter fields from ISomeSchema.'
@button.buttonAndHandler(u'Done', name='done')
def handleDone(self, action):
data, errors = self.extractData()
if errors:
IStatusMessage(self.request).add(
(self.formErrorsMessage,) + errors, 'formError')
else:
pass # do something
Notice, that we imported “Fields” and “button” from the z3ext.layouform module. While “button” is just a short-cut to the z3c.form.button module, the “Fields” class is a modified version of z3c.form‘s Fields. It removes all proxies from given fields to make sure they won’t cause any problems. This is useful, when you want to use, for example, persistent schema fields that are protected by security proxy.
Also, we used z3ext.statusmessage to indicate errors in this example. z3ext.layoutform provides own message type for form errors, called “formError”. It has special message adding logic: you should pass a sequence instead of single message in the IStatusMessage.add method. The first element should be a string, containing main message (we used predefined string in PageletForm.formErrorsMessage), and the rest of the sequence should be z3c.form.interfaces.IErrorViewSnippet objects, as returned by form’s extractData method. Also note, that widget-bound error view snippets won’t be rendered in resulting status message, as they are rendered later next to their widget.
The PageletForm class provides several generic messages that are recommended to be used in standard situations. They are available as PageletForm‘s class variables:
Form are registered as usual pagelets using the z3ext:pagelet ZCML directive:
<z3ext:pagelet
for=".ISomeSchema"
name="someform.html"
class=".ExampleForm"
/>
See z3ext.layout documentation for more detailed description of this directive.
This form class is pretty the same as PageletForm, except that it renders widgets in display mode, so it’s used for displaying field values instead of editing them. Example is very simple:
from z3ext.layoutform import PageletDisplayForm, Fields
class ExampleDisplayForm(PageletDisplayForm):
fields = Fields(ISomeSchema)
The PageletAddForm is handy base class for any types of content adding forms. It contains the “add” button that handles field validation, calling add methods and request redirection and the “cancel” button to redirect the user back. All you need is to provide the create and add methods. Example:
from z3ext.layoutform import PageletAddForm, Fields
class ExampleAddForm(PageletAddForm):
fields = Fields(ISomeSchema)
def create(self, data):
# data argument is a dictionary containing validated and converted
# values submitted using the form. all we need is to create an object
# using this data and return it.
return SomeObject(data)
def add(self, obj):
# obj argument is an object just created by the "create" method
someStorage.add(obj)
By default, the “add” button redirects an user to the added object and the “cancel” button redirects to the current context object that the form is for. You can override this behaviour by providing own “nextURL” and “cancelURL” methods, for example:
class ExampleAddForm2(ExampleAddForm):
def nextURL(self):
obj = self._addedObject # added object is available in "_addedObject" attribute
return absoluteURL(obj, self.request) + '/context'
def cancelURL(self):
return self.request.getApplicationURL()
The PageletEditForm class is used for creating content edit forms. It has the “save” button that does form validation and applies changes. For basic usage you don’t need to provide any additional methods, just define fields. Example:
from z3ext.layoutform import PageletEditForm, Fields
class ExampleEditForm(PageletEditForm):
fields = Fields(ISomeSchema)
This form will apply changes to the context object it is registered for. You can override the changes applying logic by overriding applyChanges method:
class ExampleEditForm2(PageletEditForm):
fields = Fields(ISomeSchema)
def applyChanges(self, data):
content = self.getContent() # override this method if you want object
# other thant context to be edited.
# ...apply changes using own logic...
# data argument given to this method contains a dictionary with
# validated and converted form values
return True # the returned value converted to bool indicates whether
# there was any change applied or not. you can simply return
# a dictionary of changes returned by z3c.form.form.applyChanges
By default, the form doesn’t redirect user anywhere on a successful submit, but you can override this behaviour by providing the nextURL method:
class ExampleEditForm3(PageletEditForm):
fields = Fields(ISomeSchema)
def nextURL(self):
# returning empty string means no redirect and is default behaviour,
# but we'll override it by returning the current URL, so the form will
# redirect to itself.
return self.request.getURL()
To make UI more consistent, there are custom styles provided for some of generic actions: save, cancel and add. To make a button recieve its styles, you need to mark it with one of these interfaces, contained in z3ext.layouform.interfaces:
The easiest way to do that is pass the “provides” argument to the z3c.form.button.buttonAndhandler function. For example:
class SomeForm(PageletEditForm):
@button.buttonAndHandler(u'Save', provides=ISaveAction)
def handleSave(self, action):
pass # do something
z3ext forms support field groups provided by z3c.form.group module in a pluggable way. They are looked up dynamically using named adapter from context, form and request object triple to the z3ext.layoutform.interfaces.IPageletSubform interface. To define a field group, first, you need to make it’s class implement this interface. Example:
from zope.interface import implements
from z3c.form.group import Group
from z3ext.layoutform.interfaces import IPageletSubForm
class ExampleGroup(Group):
implements(IPageletSubForm)
weight = 0 # weight hint for ordering groups in a parent form
fields = Fields(ISomeSchema)
def __init__(self, context, form, request):
# the order of positional arguments in z3c.form's Group initializer
# is different from one required by IPageletSubForm adapter lookup,
# so change the order to fix this problem.
super(ExampleGroup, self).__init__(context, request, form)
def isAvailable(self):
return True # group can be disabled depending on some conditions
def postUpdate(self):
pass # this method is called after parent form update
After that, you need to register it for your parent form using the “adapter” ZCML directive:
<adapter
for="* .ExampleForm *"
provides="z3ext.layoutform.interfaces.IPageletSubForm"
name="example.group"
factory=".ExampleGroup"
/>
We register our field group for the ExampleForm, for any context and request type.
The field group values are extracted in the parent form’s extractData as if these fields was outside the field group, so in the example above, ISomeSchema fields we used for our field group will be available in parent form’s extracted data dictionary without any difference from other form fields defined in parent form.
Sub-forms are also supported by z3ext forms. They differ from field groups in that they doesn’t have any impact on parent form, as they have separate fields which values are not used in parent form.
As field groups, sub-forms are looked up as named adapters from (context, form, request) object triple to the IPageletSubform interface and to define a sub-form, you need it to implement that interface. Sub-forms should also provide the z3c.form.interfaces.ISubForm interface.
The PageletForm is NOT a good base class for a sub-forms as it itself does much handling subform handling. So, for simple sub-forms there’s another base class provided - PageletBaseForm, which handles the pagelet-based rendering and nothing more. Let’s provide an example of sub-form:
from z3c.form.interfaces import ISubForm
from z3ext.layoutform.form import PageletBaseForm
class ExampleSubForm(PageletBaseForm):
implements(ISubForm, IPageletSubForm)
weight = 10 # weight hint for ordering groups in a parent form
fields = Fields(ISomeSchema)
def __init__(self, context, form, request):
super(ExampleSubForm, self).__init__(context, request)
self.parentForm = self.__parent__ = parentForm
def isAvailable(self):
return True # group can be disabled depending on some conditions
def postUpdate(self):
# this method is called after parent form update
# ... do some processing ...
pass
This form is registered just like the field group, using the “adapter” ZCML directive:
<adapter
for="* .ExampleForm *"
provides="z3ext.layoutform.interfaces.IPageletSubForm"
name="example.subform"
factory=".ExampleSubForm"
/>
Note
There’s a more complex sub-form base class provided for editing content, see z3ext.layoutform.PageletEditSubForm. It supports own actions, managed by the parent form and provides the “save” action, just like PageletEditForm.
Sometimes there’s a need to plug in some additional separate form or even non-form pagelet to the existing form. This is supported by z3ext.layoutform. Additional forms differ from sub-forms in that they are rendered in a separate “form” tag and don’t influence on each other.
To append a form or other pagelet to the form, you need to provide the IPageletSubForm adapter, just like with groups and sub-forms, but you need to make sure that your form/view provides neither ISubForm nor IGroup.
For example, let’s add an additional non-form pagelet to our form. First, we need to create the IPageletSubForm adapter. That also provides “update” and “render” methods:
class AdditionalView(object):
weight = 10
def __init__(self, context, form, request):
self.context = context
self.request = request
self.parentForm = form
def update(self):
pass
def render(self):
return u'Rendered content'
def isAvailable(self):
return True
def postUpdate(self):
pass
After that, we register it with adapter directive just like a sub-form or field group:
<adapter
for="* .ExampleForm *"
provides="z3ext.layoutform.interfaces.IPageletSubForm"
name="example.view"
factory=".AdditionalView"
/>
TODO: describe all parts pagelets and how they work together