.. index:: z3c.form, z3c, formui, form, formui
Forms With z3c.form
*******************
The next logical step for ZContact is to be able to add contacts using
a form that asks us for the first and last name of the new contact.
We can generate such a form from the schema (``IContact``) we defined
in ``interfaces.py`` using the ``z3c.form`` package.
.. index:: buildout
Adding z3c.form and z3c.formui as Dependencies
==============================================
Before we can use z3c.form though, we have to add it as a dependency
to our application.
To add z3c.form as a dependency, open up ``setup.py`` in the root
directory of your application and add ``'z3c.form',`` to the
``install_requires`` parameter (should be near line 25). At this time
you should also add ``z3c.formui`` and ``z3c.layer`` as dependencies, which is needed to
render the form in a nice layout.
Next you will want to include the zcml configuration for ``z3c.form``
and ``z3c.formui`` into the ``configure.zcml`` file located in
``zcontact/src/configure.zcml``. ``z3c.form`` makes use of numerous
other z3c.* components which define new zcml directives. To use these
directives, you must include meta files at the top of the
``configure.zcml`` *before any other includes*:
.. code-block:: xml
Next you want to include the actual ``z3c.form`` and ``z3c.formui``
packages at the end of the ``configure.zcml`` file:
.. code-block:: xml
Now all we have to do is rerun the buildout process so that the new
eggs for ``z3c.form`` and ``z3c.formui`` are downloaded and made
available. Just run::
$ ./bin/buildout -N
The ``-N`` option skips downloading eggs you already downloaded
(*highly recommended*).
Setting up your Application to Work with z3c.form
=================================================
Unfortunately, we aren't quite ready to use z3c.form. Since the z3c
packages have created a somewhat different pattern for building
applications, there are some extra steps to get them working. One of
these steps is setting up your own layer and skin. Unfortunately,
many zcml directives don't force you to explicitly say which layer you
want the page or view registered for. When you don't specify the
layer, it gets registered with a default layer. It turns out that
many packages (including Rotterdam) register all their views on the
default layer, making this layer highly polluted with stuff we don't
want. To avoid this pollution, the z3c.* packages register all their
views on layer specific to the package in question. To use the z3c.*
package properly, you must extend these layers with your own layer.
Now we will create a layer that allows us to use views from the
z3c.form package.
.. index:: z3c.layer
Creating a Layer
----------------
Create a new file, ``src/zcontact/layer.py`` and add the following
code:
.. code-block:: python
from z3c.form.interfaces import IFormLayer
from z3c.layer.pagelet import IPageletBrowserLayer
class IZContactBrowserLayer(IFormLayer, IPageletBrowserLayer):
"""ZContact browser layer with form support."""
``IFormLayer`` has views for all the widgets used in the generated
forms and ``IPageletBrowserLayer`` provides some error and other
useful utility views.
.. index:: skin
Creating a Skin
---------------
To access this layer from a browser, we have to create a skin. So
create another file, ``src/zcontact/skin.py`` and add the following
code:
.. code-block:: python
import z3c.formui.interfaces
from zcontact import layer
class IZContactBrowserSkin(z3c.formui.interfaces.IDivFormLayer,
layer.IZContactBrowserLayer):
"""The ZContact browser skin using the div-based layout."""
Note that our skin is going to inherit from the ``IDivFormLayer``
layer, defined in the ``z3c.formui`` package. When the forms get
rendered, fields will appear in ``
`` tags rather than in a
table. There exists another layer for a table based layout. Thanks
to the component architecture, it is equally possible for you to write
your own custom form layout layer, but we won't do that here.
Now we must register the skin in zcml with a new file,
``src/zcontact/skin.zcml``. We will make our skin accessible at
http://localhost:8080/++skin++ZContact/ which should look like this:
.. code-block:: xml
Don't forget to include this new zcml file in
``zcontact/configure.zcml`` with the line
``
``.
.. index:: add form
Creating an Add Form
====================
We'll start with creating a new module, ``zcontact.browser``, by
adding a browser directory to the ``src/zcontact/`` directory along
with an empty ``__init__.py`` file. Now we can create and open a new
file, ``zcontact/browser/contact.py`` where we will define all the
forms.
Start by adding the following code to the ``browser/contact.py``
file:
.. code-block:: python
from z3c.form import form, field
from zcontact import interfaces
class ContactAddForm(form.AddForm):
"""A simple add form for contacts."""
fields = field.Fields(interfaces.IContact)
The ``form.AddForm`` class that we inherit from just defines some
methods for creating and adding our new object, which we will override
later, and some buttons typically found on an add form.
Next we will add a page that uses this class to display the form.
Open ``zcontact/browser/configure.zcml`` and add the following:
.. code-block:: xml
We register the page with the name addContact.html and for the IFolder
interface. Since the root folder of every new zope instance
implements IFolder, we should be able to access this page at
http://localhost:8080/++skin++ZContact/@@addContact.html. Don't forget to include the
browser package in the ``zcontact/configure.zcml`` file by adding a
line, ``
`` to the bottom.
Now we should be ready for action, so restart the server by running
``./bin/paster serve deploy.ini`` (or ``debug.ini`` if you want) and
browse to http://localhost:8080/++skin++ZContact/@@addContact.html.
You should see a very simple form like this:
.. image:: images/addFormSimple.png
Finishing the Add Form
======================
If you actually tried to use the add form, and cliked the add button,
you should have gotten a ``NotImplemented`` error. If you chose to
run the debug.ini configuration instead of deploy.ini with paster, then
you probably got a nice screen like this:
.. image:: images/serverError.png
from this screen you can expand any line in the traceback and enter in
python code to debug the problem. I was very impressed the first time
I saw this.
To fix the problem, we must implement three methods for the
``ContactAddForm`` class: ``create``, ``add`` and ``nextURL``. I
decided to implement it as below to keep it short, but feel free to
get creative:
.. code-block:: python
from z3c.form import form, field
from zcontact import interfaces
from zcontact.contact import Contact
class ContactAddForm(form.AddForm):
"""A simple add form for contacts."""
fields = field.Fields(interfaces.IContact)
def create(self, data):
contact = Contact()
form.applyChanges(self, contact, data)
return contact
def add(self, contact):
self._name = "%s-%s" % (contact.lastName.lower(), contact.firstName.lower())
self.context[self._name] = contact
def nextURL(self):
return '/'
In the create method, we used the ``form.applyChanges`` function to set
the values for the ``firstName`` and ``lastName`` attributes of the
new contact. The data passed to the create method is a mapping
between field names and the data entered, already converted to the
right python data types. For example, we could have done
``contact.firstName = data['firstName']``.
I've set the ``nextURL`` method to return a hard coded path which
should take you back to the default Rotterdam skin where you can see the newly
created contact in the contents view. We do this because we have not
yet written our own contents views for our own layer/skin and must go
back to Rotterdam.
.. index:: display form, edit form
Display and Edit Forms
======================
Display and Edit Forms are even easier than add forms because you
don't have to implement extra methods. Let's start by creating such
a Display form.
Creating a Display Form
-----------------------
To create the display form, we will want a new class that inherits
from ``form.Form``. To make the widgets display as plane text rather
than input widgets, we must set the mode of the form to
``DISPLAY_MODE``, which is a constant that can be imported from
``z3c.form.interfaces``. Open up
``zcontact/browser/contact.py`` and add the following code:
.. code-block:: python
class ContactDisplayForm(form.Form):
"""A simple display form for contacts."""
fields = field.Fields(interfaces.IContact)
mode = DISPLAY_MODE
and don't forget to register the new form in ``configure.zcml`` with:
.. code-block:: xml
Now that we have a display form, we can change the ``nextURL`` method
of the ``ContactAddForm`` to point to the newly created contact. It
should look like this:
.. code-block:: python
def nextURL(self):
return absoluteURL(self.context[self._name], self.request)
Don't forget to include the line
``from zope.traversing.browser.absoluteurl import absoluteURL`` at the
top of the file. If all goes well, you should be able to restart the
server and add a new contact at
http://localhost:8080/++skin++ZContact/@@addContact.html to then be
taken to the display form. It will look like this:
.. image:: images/DisplayFormScreenShot.png
.. index:: buttons
Adding buttons to a form
------------------------
Now let's add two buttons, one for editing the contact and one to
delete the contact. Start by adding ``from z3c.form import button``
at the top of ``contact.py``. When a user clicks on a button, they
submit the form to the url specified by the action attribute of the
form. By default, the action is set to the url of the form itself so
when we click on a form button, the form gets reloaded. As the form
is being processed, it checks which button was pressed and calls
appropriate *handlers* which are defined as methods of the
``ContactDisplayForm`` class.
With z3c.form, we can define a button and a handler at the same time
using a decorator. For the delete button, we'll want to delete the
contact and send the user back to the add form. Add the following
code to the ``ContactDisplayForm`` class:
.. code-block:: python
@button.buttonAndHandler(u'Delete', name='delete')
def handleDelete(self, action):
name = getName(self.context)
parent = getParent(self.context)
del parent[name]
nextURL = absoluteURL(parent, self.request)+'/@@addContact.html'
self.request.response.redirect(nextURL)
Be sure to add the following imports to the top of the file:
.. code-block:: python
from z3c.form import form, field, button
from z3c.form.interfaces import DISPLAY_MODE
from zope.traversing.browser.absoluteurl import absoluteURL
from zope.traversing.api import getParent, getName
Now we'll add an Edit button. We don't yet have an edit form yet, so
the edit button will take us to a page that doesn't exist. For now we
will just put it in as a place holder. Add the following to the
``ContactDisplayForm`` class:
.. code-block:: python
@button.buttonAndHandler(u'Edit', name="edit")
def handleEdit(self, action):
nextURL = absoluteURL(self.context, self.request) + '/@@editContact.html'
self.request.response.redirect(nextURL)
Now you can restart the server and try out the buttons. Your form
should now look like this:
.. image:: images/DisplayFormButtonScreenShot.png
Creating an Edit Form
---------------------
By now you should nearly be a pro at this auto generated form stuff.
Our final step is creating an edit form, which is the simplest form of
them all. Here is the code to add:
.. code-block:: python
class ContactEditForm(form.EditForm):
"""A simple edit form for contacts."""
fields = field.Fields(interfaces.IContact)
along with the necessary zcml configuration:
.. code-block:: xml
Go ahead and try out the edit form by clicking on the Edit button from
the display form. You'll notice that we already have an apply button
to submit the changes. After editing, we are presented with a status
message explaining either success or failure. At this point, we want
a way to get back to the add form, so add a 'Done' button to the
``ContactEditForm`` class with the code:
.. code-block:: python
@button.buttonAndHandler(u'Done', name='done')
def handleDone(self, action):
self.request.response.redirect(absoluteURL(self.context, self.request))
But wait! As soon as we create our own button, we override the
buttons declared in the ``form.EditForm`` class. To avoid this, we
must *extend* the ``form.EditForm`` class by putting
``form.extends(form.EditForm)`` just after the ``ContactEditForm``
class declaration. In otherwords, ``ContactEditForm`` should now look
like this:
.. code-block:: python
class ContactEditForm(form.EditForm):
"""A simple edit form for contacts."""
form.extends(form.EditForm)
fields = field.Fields(interfaces.IContact)
@button.buttonAndHandler(u'Done', name='done')
def handleDone(self, action):
self.request.response.redirect(absoluteURL(self.context, self.request))
You should now have an edit form that looks like
this:
.. image:: images/EditFormScreenShot.png
Rounding out the Application with a Front Page
==============================================
Before we jump right into skinning, let's make one more page to kind
of round out our application. This next page will be the main front
page for our application and will provide a link to the add contact
form, and links to each of the existing contacts. This can all be
done in a simple page template in ``zcontact/browser/frontpage.pt``:
.. code-block:: xml
Welcome to ZContact
Please tell me what you would like to do:
To make this appear for the front page of our application, we are
going to register the page in zcml for the ``IRootFolder`` interface.
Add the following to ``zcontact/browser/configure.zcml``:
.. code-block:: xml
Restart your server and check it out at
http://localhost:8080/++skin++ZContact/ and you should have something
that looks like this:
.. image:: images/FrontPageScreenShot.png
With a few forms in place and a decently working app, we can start
looking at other z3c.* packages.