6. Add Forms for IAdding

While using IAdding-based add forms is strongly discouraged by this package due to performance and code complexity concerns, there is still the need for add forms based on IAdding, especially when one wants to extend the default ZMI and use the add menu.

Before we get started, we need to register a bunch of form-related components:

>>> from z3c.form import testing
>>> testing.setupFormDefaults()

Let’s first create a content component:

>>> import zope.interface
>>> import zope.schema
>>> class IPerson(zope.interface.Interface):
...
...     name = zope.schema.TextLine(
...         title=u'Name',
...         required=True)
>>> from zope.schema.fieldproperty import FieldProperty
>>> @zope.interface.implementer(IPerson)
... class Person(object):
...     name = FieldProperty(IPerson['name'])
...
...     def __init__(self, name):
...         self.name = name
...
...     def __repr__(self):
...         return '<%s %r>' %(self.__class__.__name__, self.name)

Next we need a container to which we wish to add the person:

>>> from zope.container.btree import BTreeContainer
>>> people = BTreeContainer()

When creating and adding a new object using the IAdding API, the container is adapted to IAdding view:

>>> request = testing.TestRequest()
>>> from zope.app.container.browser.adding import Adding
>>> adding = Adding(people, request)
>>> adding
<zope.app.container.browser.adding.Adding object at ...>

To be able to create a person using IAdding, we need to create an add form for it now:

>>> import os
>>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
>>> from z3c.form import tests, field
>>> from z3c.form.adding import AddForm
>>> class AddPersonForm(AddForm):
...     template = ViewPageTemplateFile(
...         'simple_edit.pt', os.path.dirname(tests.__file__))
...
...     fields = field.Fields(IPerson)
...
...     def create(self, data):
...         return Person(**data)

Besides the usual template and field declarations, one must also implement the create() method. Note that the add() and nextURL() methods are implemented for you already in comparison to the default add form. After instantiating the form, …

>>> add = AddPersonForm(adding, request)

… we can now view the form:

>>> print(add())
<html xmlns="http://www.w3.org/1999/xhtml">
  <body>
    <form action=".">
      <div class="row">
        <label for="form-widgets-name">Name</label>
        <input type="text" id="form-widgets-name" name="form.widgets.name"
               class="text-widget required textline-field" value="" />
      </div>
      <div class="action">
        <input type="submit" id="form-buttons-add"
               name="form.buttons.add" class="submit-widget button-field"
               value="Add" />
      </div>
    </form>
  </body>
</html>

Once the form is filled out and the add button is clicked, …

>>> request = testing.TestRequest(
...     form={'form.widgets.name': u'Stephan', 'form.buttons.add': 1})
>>> adding = Adding(people, request)
>>> add = AddPersonForm(adding, request)
>>> add.update()

… the person is added to the container:

>>> sorted(people.keys())
[u'Person']
>>> people['Person']
<Person u'Stephan'>

When the add form is rendered, nothing is returned and only the redirect header is set to the next URL. For this to work, we need to setup the location root correctly:

>>> from zope.traversing.interfaces import IContainmentRoot
>>> zope.interface.alsoProvides(people, IContainmentRoot)
>>> add.render()
''
>>> request.response.getHeader('Location')
'http://127.0.0.1/@@contents.html'