Customizing the template and the widgets¶
Description
Hack into the appearance of your form.
Customizing the template¶
plone.app.formprovides a handy default template named
pageform.pt
which integrates well with the default Plone skin, but
you might need to customize it or write your own one.
To do that, override the template
attribute of the form class
definition:
class FeedbackForm(PageForm):
"""
A typical feedback form
"""
label = u'Contact Us'
form_fields = form.Fields(IFeedbackForm)
template = ViewPageTemplateFile('feedback_form.pt')
result_template = ViewPageTemplateFile('feedback_result.pt')
@form.action("send")
def action_send(self, action, data):
mhost = MHost()
self.mFrom = data['customer']
self.mTo = "feedback@mycompany.com"
self.mSubject = data['subject']
self.mBody = data['message']
mhost.Send(self.mFrom, self.mTo, self.mSubject, self.mBody)
return self.result_template()
As already stated, all the view attributes will be available inside the page template, including:
- label - A label to display at the top of the form.
- prefix - A string added to all widget and action names.
- form_fields - The list of form's fields.
- widgets - A list of views for the former fields. The widgets are
looked up as multiadapters for each schema field and the request
providing
IDisplayWidget
orIInputWidget
. - errors - A list of errors encountered during validation.
- error_views - A list of views for the former errors. These views
are looked up as multiadapters for each error and the request
providing
zope.app.form.browser.interfaces.IWidgetInputErrorView
. - status - An update status message, normally generated by success or failure handlers.
- availableActions - The list of form's available actions.
- template - The template used to display the form.
It's reccommended to start with the default pageform.pt
and
customize it cutting, pasting, deleting and entering text and tags.
Using named templates
Another really zope3-ish method to choose the form template is using the zope.formlib named templates. Using named templates can be (and actually is) an overkill if you've designed your template to work with your form class as a single component. But if you write a form class and the template is just a visual customization of that form, you might want to be able to customize the template without having to reimplement the whole class, or let others do so. This is exactly how Plone overrides the default zope.formlib template with a more plone-ish one in the plone.app.form package.
Please note that this approach was not taken in the example product example.formlib.
Named templates are adapters for the form's view class to
INamedTemplate
, bound to the form class only by their names. This
way, a third party product (e.g. a theme) can register a different
template with the same name (usually in a different browser skin layer)
to override the default one. Moreover, they're very easy to use. Modify
and add the emphasized lines:
from zope.formlib.namedtemplate import NamedTemplate
# Five's ViewPageTemplateFile doesn't work correctly with formlib's NamedTemplateImplementation,
# so we use here the Plone implementation
from plone.app.form import named_template_adapter
class FeedbackForm(PageForm):
"""
A typical feedback form
"""
label = u'Contact Us'
form_fields = form.Fields(IFeedbackForm)
template = NamedTemplate('feedback.form')
result_template = ViewPageTemplateFile('feedback_result.pt')
# rest of the form class implementation...
feedback_template = named_template_adapter(
ViewPageTemplateFile('feedback_form.pt'))
In configure.zcml, add the following snippet to register the named template as an adapter for your form:
<adapter
factory=".browser.feedback_template"
for=".browser.FeedbackForm"
name="feedback.form"
/>
Name your page template feedback_form.pt
and you're done.
Customizing the widgets¶
As we've already stated earlier, form widgets are views for schema fields, i.e. multiadapters for each schema field and the request providing IDisplayWidget or IInputWidget, depending on if they display field data or offer editing funcionality to the user.
To do so, override the custom_widget
attribute of a field (which
defaults to None). Remember how we set up the form's fields:
class FeedbackForm(PageForm):
"""
A typical feedback form
"""
label = u'Contact Us'
form_fields = form.Fields(IFeedbackForm)
# rest of the form class...
The form_fields
fields are accessible throught a dict-like
interface, with the schema field names as keys, so we write:
from zope.app.form.browser import RadioWidget as _RadioWidget
def RadioWidget(field, request):
vocabulary = field.vocabulary
widget = _RadioWidget(field, vocabulary, request)
return widget
class FeedbackForm(PageForm):
"""
A typical feedback form
"""
label = u'Contact Us'
form_fields = form.Fields(IFeedbackForm)
form_fields['subject'].custom_widget = RadioWidget
# rest of the form class...
Here, we're specifying a custom widget for the subject field:
RadioWidget
, which displays a radio box for every item from the
field's vocabulary. The zope.app.form.browser and
plone.app.form.widgets packages provide a reasonable set of widgets to
use and customize, including dropdowns and Kupu/WYSIWYG. Unfortunately,
creating new widgets is out of the scope of this tutorial for now.
The RadioWidget
function deserves a little explanation. Believe it
or not, zope.formlib doesn't handle custom widgets with vocabularies
(called items widgets) properly, because it calls
form_field.custom_widget(field, request)
either the field has an
associated vocabulary or not, and item widgets have to be initialized
with a vocabulary argument too; so a wrapper function is needed to
workaround this issue.
Here's how the improved form looks like:

Form radio buttons