Creating a simple feedback form¶
Description
This section explains how to create a very basic feedback form.
Note
The code for this example is available to checkout from the collective as the *example.formlib* package.
For all practical sense formlib based components are really regular Zope view components with some convenient base classes for auto-generating output based on schemas and other configuration info. You will see that in a moment.
First, define an interface class with the schema of the form:
from zope.interface import Interface
from zope.schema import TextLine, Text
class IFeedbackForm(Interface):
"""
A typical feedback schema
"""
customer = TextLine(title=u'Customer',
description=u'Customer email',
required=True)
subject = TextLine(title=u'Subject',
required=True)
message = Text(title=u'Message',
description=u'The message body',
required=True)
The purpose of this interface is to define the fields of the form. The type of each schema field determines the type of widget that will be used by default for that field, so choose it carefully. To see all the schema fields available, read the zope.schema package's interfaces.
Next, create a form instance, which is a class that groups an ordered collection of fields and actions. To do that, simply subclass Five's PageForm class, a wrapper to the formlib Form class to keep Zope 2 happy. Type the following code into a Python file inside your product:
from five.formlib.formbase import PageForm
You will also need to make use of Five's strange hybrid between Zope 2 and Zope 3 page templates:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
The simplest way to define a collection of form fields is
using the
Fields
constructor with the previous schema:
from zope.formlib import form
class FeedbackForm(PageForm):
"""
A typical feedback form
"""
form_fields = form.Fields(IFeedbackForm)
By inheriting from the
PageForm
class, the
FeedbackForm
class inherit functionality from formlib itself. By default,
PageForm
knows how to generate all the HTML that will make up of a
finished form. But in order to do this, formlib needs to
know what fields are wanted. This is done by providing the
form_fields attribute. The
Fields
constructor is a formlib helper class that generates the
appropriate field items from any Zope 3 schema (in this
case, the schema interface defined above).
In order to provide a complete form, you need to specify the
action to perform when the "submit" button of the
form (or any other indicated) is activated. To define the
action, use the
form.action
decorator with a handler function for the submitted data.
More on actions later.
# use a dummy MailHost tool here to keep it simple
class MHost:
def __init__(self):
pass
def Send(self, sender, to, subject, body):
pass
class FeedbackForm(PageForm):
"""
A typical feedback form
"""
form_fields = form.Fields(IFeedbackForm)
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()
This is where the real work takes place. In this example,
the
feedback_result.pt
page template is rendered and returned. All the view's
attributes will be available inside this template, which
will be introduced later.
An example result form is:
<html metal:use-macro="context/@@standard_macros/view">
<head>
</head>
<body>
<div metal:fill-slot="body">
<h1 tal:content="view/label">Form label</h1>
<p>Thank you for your request about
<span tal:replace="view/mSubject">subject</span>,
<span tal:replace="view/mFrom">customer@mail</span>.</p>
<p>We will reply to it shortly.</p>
</div>
</body>
</html>
zope.formlib already includes a default general page form
template, with the fields labels, the widgets structures and
the submit buttons, so you only have to register your form
page with the appropiate ZCML snippet in order to make it
accesible from a browser. Assuming you've placed your code
into a file named
browser.py
:
<browser:page
name="feedback"
for="Products.CMFPlone.Portal.PloneSite"
class=".browser.FeedbackForm"
permission="zope.Public"
/>
Let's explain what this ZCML snippet means:
-
The
for
attribute indicates the class or interface this view will be available for; in this case, it will be shown only from the root of a Plone site. To see the interfaces provided by a certain object, fire up the ZMI, navigate up to your object and check the Interfaces tab. -
The
name
attribute sets the name of the view, so the form will be available from a URL with the formhttp://<plone-site>/feedback
. -
The
class
attribute indicates the view class responsible for displaying the page form, in this case, the FeedbackForm class inside thebrowser.py
file. -
The
permission
attribute specify the permission needed to access the page.
Among the most used permissions you can find:
-
zope.Public
- no restrictions, available to everyone. -
zope.View
- permission to view this component. -
zope.ManageContent
- add, edit and delete content objects.Note: Keen readers will notice the special name for configuring the new view component, browser:page. This XML tag actually employs an XML namespace prefix which needs to be defined. Normally this is added right onto the configure tag like this:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:five="http://namespaces.zope.org/five">
And that's all! Here's how the form and result pages will look like:
Contact form:
Result page: