.. 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.