Since, in the Plone Intranet project, we are following a design driven model, we cannot rely on a form generating library (such as z3c.form) to generate our forms for us, as the markup generated by this library does not conform to the markup in the design prototype.
Instead of using z3c.form to render our forms, we hand code our forms based on the markup found in the prototype.
We do however want to stay as close and true as possible to existing development patterns used in Plone. Therefore, we create custom Dexterity add/edit views for all Dexterity content types, and put the prototype markup in their templates.
Even though forms are hand-coded (by the designer), form submission, handling and content type creation/modification are all still handled by Dexterity/z3c.form.
However, in order to have z3c.form handle our submitted forms, we need to add
some extra markup (such as special
name attributes to our
elements) to our custom form markup.
Custom Add/Edit views for Dexterity content types¶
Each Add or Edit view consists of two parts. The form and a view which wraps it in a specific layout (also called a form wrapper).
Custom view (form wrapper) layout¶
Generally you wouldn’t care about the form wrapper view’s layout, but in the case of Plone Intranet, we need fully custom designer specified markup, so it was necessary to modify this layout.
Luckily, this only needed to be done once, and then all custom add/edit views will reuse it.
For posterity, here’s how it’s done.
You need to register a
layout_factory adapter. See:
This adapter has a layout template:
and a corresponding ZCML registration snippet:
The forms are subclasses of the default Dexterity add and edit forms, together
with a mixin class
DexterityFormMixin (the point of this mixin class is
explained in the templates section:
class AddForm(add.DefaultAddForm, views.DexterityFormMixin): """ Custom add form for the Document content type. """ template = ViewPageTemplateFile('templates/edit_document.pt')
Additionally, for each form we need to provide a custom template which contains the markup from the prototype.
In the example above, we specify the custom template as an attribute on the class.
Custom marshalling of data¶
In computer science the act of “marshalling” refers to transforming a certain representation of data into a different format suitable for transmission or computation.
z3c.form expects the form request data to conform to certain expectations, such
as that the keys of the request values need to have certain specific values,
form.widgets.IDublinCore.title for the title field.
We can fix this either by modifying the template markup (e.g. by adding a
name attribute on the
title widget with value
or we can handle it server side in the
def extractData(self, setErrors=True): exclude_from_nav = '' if not self.request.get(exclude_from_nav): # XXX: This is a required field, but not in the form. Not yet sure # what the right approach to deal with it is. # Either we deal with it here, or add a hidden field in the # template. self.request.form[exclude_from_nav] = 'selected' return super(AddForm, self).extractData()
The view (aka form wrapper)¶
For each form (i.e. add and edit), we need a view which acts as a form wrapper:
class EditView(edit.DefaultEditView): """ Custom edit view for the Document content type. """ form = EditForm
The view subclasses the default add and edit views from
Each view also requires a
form attribute to be set which points to the form
created in the section above.
Register add/edit views with zcml¶
The views need to be registered in zcml (in
The add view is a special case, since it’s an adapter on the
<adapter for="Products.CMFCore.interfaces.IFolderish ..interfaces.IThemeSpecific plone.dexterity.interfaces.IDexterityFTI" factory=".document.AddView" name="Document" provides="zope.publisher.interfaces.browser.IBrowserPage" />
Important parts to note is the
factory directive, which points to the view
class as well as the second interface passed to the
for directive, which is
the theme layer for
The edit view is registered like a normal browser page:
<browser:page for="plone.app.contenttypes.interfaces.IDocument" name="edit" class=".document.EditView" permission="cmf.ModifyPortalContent" />
The markup in the templates should come straight from the prototype (i.e. design driven).
However, in order to have z3c.form handler form submission, we need z3c.form to
render the form buttons and also add special
name attributes to our form
Additionally, for dynamic rendering, we need to sprinkle the markup with
which won’t be found in the prototype.
<fieldset class="horizontal"> <label>Title <sup class="required">*</sup> <a href="tooltip-help.html#subject-2" class="iconified icon-info-circle help pat-tooltip" data-pat-tooltip="trigger: click; source: ajax">More info</a> <input type="text" name="form.widgets.IDublinCore.title" tal:attributes="value view/title|nothing"/> </label> </fieldset>
name attribute which has a value of
The input needs this
name attribute for z3c.form to properly parse the
To get the correct
name attribute, I inspect a vanilla Plone form of the same content type.
Note also the
tal:attributes statement which ensures that the widget will
have the correct default value set.
The fact that
python:view['title']) can be called on the form,
is due to the
DexterityFormMixin class mentioned above. This class provides a custom
__getitem__ method which will look up the requested attribute, first in the
request, and then on the context (in the case of edit forms).
Unused but required fields¶
If your content type has required fields, which do not have widgets in the prototype, you’ll need to still add them to the form as hidden inputs, otherwise z3c.form will complain that they aren’t set:
<!-- XXX: This is a required field, but will not be used in Plone Intranet --> <input type="hidden" value="selected" name="form.widgets.IExcludeFromNavigation.exclude_from_nav"/>