[Checkins] SVN: z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt fixed inconsistend line endings which prevented test from running on Mac OS X
Michael Howitz
mh at gocept.com
Thu Nov 3 07:27:48 UTC 2011
Log message for revision 123275:
fixed inconsistend line endings which prevented test from running on Mac OS X
Changed:
U z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt
-=-
Modified: z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt 2011-11-03 07:26:59 UTC (rev 123274)
+++ z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt 2011-11-03 07:27:47 UTC (rev 123275)
@@ -1,1687 +1,1687 @@
-=====
-Forms
-=====
-
-The purpose of this package is to make development of forms as simple
-as possible, while still providing all the hooks to do customization
-at any level as required by our real-world use cases. Thus, once the
-system is set up with all its default registrations, it should be
-trivial to develop a new form.
-
-The strategy of this document is to provide the most common, and thus
-simplest, case first and then demonstrate the available customization
-options. In order to not overwhelm you with our set of well-chosen defaults,
-all the default component registrations have been made prior to doing those
-examples:
-
- >>> from z3c.form import testing
- >>> testing.setupFormDefaults()
-
-Note, since version 2.4.2 the IFomrLayer doesn't provide IBrowserRequest
-anymore. This is usefull if you like to use z3c.form components for other
-requets then the IBrowserRequest.
-
- >>> from zope.publisher.interfaces.browser import IBrowserRequest
- >>> import z3c.form.interfaces
- >>> z3c.form.interfaces.IFormLayer.isOrExtends(IBrowserRequest)
- False
-
-Before we can start writing forms, we must have the content to work with:
-
- >>> import zope.interface
- >>> import zope.schema
- >>> class IPerson(zope.interface.Interface):
- ...
- ... id = zope.schema.TextLine(
- ... title=u'ID',
- ... readonly=True,
- ... required=True)
- ...
- ... name = zope.schema.TextLine(
- ... title=u'Name',
- ... required=True)
- ...
- ... gender = zope.schema.Choice(
- ... title=u'Gender',
- ... values=('male', 'female'),
- ... required=False)
- ...
- ... age = zope.schema.Int(
- ... title=u'Age',
- ... description=u"The person's age.",
- ... min=0,
- ... default=20,
- ... required=False)
- ...
- ... @zope.interface.invariant
- ... def ensureIdAndNameNotEqual(person):
- ... if person.id == person.name:
- ... raise zope.interface.Invalid(
- ... "The id and name cannot be the same.")
-
- >>> from zope.schema.fieldproperty import FieldProperty
- >>> class Person(object):
- ... zope.interface.implements(IPerson)
- ... id = FieldProperty(IPerson['id'])
- ... name = FieldProperty(IPerson['name'])
- ... gender = FieldProperty(IPerson['gender'])
- ... age = FieldProperty(IPerson['age'])
- ...
- ... def __init__(self, id, name, gender=None, age=None):
- ... self.id = id
- ... self.name = name
- ... if gender:
- ... self.gender = gender
- ... if age:
- ... self.age = age
- ...
- ... def __repr__(self):
- ... return '<%s %r>' % (self.__class__.__name__, self.name)
-
-Okay, that should suffice for now.
-
-What's next? Well, first things first. Let's create an add form for the
-person. Since practice showed that the ``IAdding`` interface is overkill for
-most projects, the default add form of ``z3c.form`` requires you to define the
-creation and adding mechanism.
-
-**Note**:
-
- If it is not done, ``NotImplementedError[s]`` are raised:
-
- >>> from z3c.form.testing import TestRequest
- >>> from z3c.form import form, field
-
- >>> abstract = form.AddForm(None, TestRequest())
-
- >>> abstract.create({})
- Traceback (most recent call last):
- ...
- NotImplementedError
-
- >>> abstract.add(1)
- Traceback (most recent call last):
- ...
- NotImplementedError
-
- >>> abstract.nextURL()
- Traceback (most recent call last):
- ...
- NotImplementedError
-
-
-Thus let's now create a working add form:
-
- >>> class PersonAddForm(form.AddForm):
- ...
- ... fields = field.Fields(IPerson)
- ...
- ... def create(self, data):
- ... return Person(**data)
- ...
- ... def add(self, object):
- ... self.context[object.id] = object
- ...
- ... def nextURL(self):
- ... return 'index.html'
-
-This is as simple as it gets. We explicitly define the pieces that
-are custom to every situation and let the default setup of the
-framework do the rest. This is intentionally similar to
-``zope.formlib``, because we really like the simplicity of
-``zope.formlib``'s way of dealing with the common use cases.
-
-Let's try to add a new person object to the root folder (which
-was created during test setup). For this add form, of course, the
-context is now the root folder:
-
- >>> request = TestRequest()
- >>> addForm = PersonAddForm(root, request)
-
-Since forms are not necessarily pages -- in fact often they are not --
-they must not have a ``__call__`` method that does all the processing
-and rendering at once. Instead, we use the update/render
-pattern. Thus, we first call the ``update()`` method.
-
- >>> addForm.update()
-
-Actually a lot of things happen during this stage. Let us step through it one
-by one pointing out the effects.
-
-
-Find a widget manager and update it
------------------------------------
-
-The default widget manager knows to look for the ``fields`` attribute in the
-form, since it implements ``IFieldsForm``:
-
- >>> from z3c.form import interfaces
- >>> interfaces.IFieldsForm.providedBy(addForm)
- True
-
-The widget manager is then stored in the ``widgets`` attribute as promised by
-the ``IForm`` interface:
-
- >>> addForm.widgets
- <z3c.form.field.FieldWidgets object at ...>
-
-The widget manager will have four widgets, one for each field:
-
- >>> addForm.widgets.keys()
- ['id', 'name', 'gender', 'age']
-
-When the widget manager updates itself, several sub-tasks are processed. The
-manager goes through each field, trying to create a fully representative
-widget for the field.
-
-Field Availability
-~~~~~~~~~~~~~~~~~~
-
-Just because a field is requested in the field manager, does not mean that a
-widget has to be created for the field. There are cases when a field
-declaration might be ignored. The following reasons come to mind:
-
-* No widget is created if the data are not accessible in the content.
-* A custom widget manager has been registered to specifically ignore a field.
-
-In our simple example, all fields will be converted to widgets.
-
-Widget Creation
-~~~~~~~~~~~~~~~
-
-During the widget creation process, several pieces of information are
-transferred from the field to the widget:
-
- >>> age = addForm.widgets['age']
-
- # field.title -> age.label
-
- >>> age.label
- u'Age'
-
- # field.required -> age.required
-
- >>> age.required
- False
-
-All these values can be overridden at later stages of the updating
-process.
-
-Widget Value
-~~~~~~~~~~~~
-
-The next step is to determine the value that should be displayed by the
-widget. This value could come from three places (looked up in this order):
-
-1. The field's default value.
-2. The content object that the form is representing.
-3. The request in case a form has not been submitted or an error occurred.
-
-Since we are currently building an add form and not an edit form,
-there is no content object to represent, so the second step is not
-applicable. The third step is also not applicable as we do not have
-anything in the request. Therefore, the value should be the field's
-default value, or be empty. In this case the field provides a default
-value:
-
- >>> age.value
- u'20'
-
-While the default of the age field is actually the integer ``20``, the
-widget has converted the value to the output-ready string ``'20'``
-using a data converter.
-
-Widget Mode
-~~~~~~~~~~~
-
-Now the widget manager looks at the field to determine the widget mode -- in
-other words whether the widget is a display or edit widget. In this case all
-fields are input fields:
-
- >>> age.mode
- 'input'
-
-Deciding which mode to use, however, might not be a trivial operation. It
-might depend on several factors (items listed later override earlier ones):
-
-* The global ``mode`` flag of the widget manager
-* The permission to the content's data value
-* The ``readonly`` flag in the schema field
-* The ``mode`` flag in the field
-
-
-Widget Attribute Values
-~~~~~~~~~~~~~~~~~~~~~~~
-
-As mentioned before, several widget attributes are optionally overridden when
-the widget updates itself:
-
-* label
-* required
-* mode
-
-Since we have no customization components registered, all of those fields will
-remain as set before.
-
-
-Find an action manager, update and execute it
----------------------------------------------
-
-After all widgets have been instantiated and the ``update()`` method has been
-called successfully, the actions are set up. By default, the form machinery
-uses the button declaration on the form to create its actions. For the add
-form, an add button is defined by default, so that we did not need to create
-our own. Thus, there should be one action:
-
- >>> len(addForm.actions)
- 1
-
-The add button is an action and a widget at the same time:
-
- >>> addAction = addForm.actions['add']
- >>> addAction.title
- u'Add'
- >>> addAction.value
- u'Add'
-
-After everything is set up, all pressed buttons are executed. Once a submitted
-action is detected, a special action handler adapter is used to determine the
-actions to take. Since the add button has not been pressed yet, no action
-occurred.
-
-
-Rendering the form
-------------------
-
-Once the update is complete we can render the form. Since we have not
-specified a template yet, we have to do this now. We have prepared a small and
-very simple template as part of this example:
-
- >>> import os
- >>> from zope.browserpage.viewpagetemplatefile import BoundPageTemplate
- >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
- >>> from z3c.form import tests
- >>> def addTemplate(form):
- ... form.template = BoundPageTemplate(
- ... ViewPageTemplateFile(
- ... 'simple_edit.pt', os.path.dirname(tests.__file__)), form)
- >>> addTemplate(addForm)
-
-Let's now render the page:
-
- >>> print addForm.render()
- <html xmlns="http://www.w3.org/1999/xhtml">
- <body>
- <form action=".">
- <div class="row">
- <label for="form-widgets-id">ID</label>
- <input type="text" id="form-widgets-id"
- name="form.widgets.id"
- class="text-widget required textline-field"
- value="" />
- </div>
- <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="row">
- <label for="form-widgets-gender">Gender</label>
- <select id="form-widgets-gender" name="form.widgets.gender:list"
- class="select-widget choice-field" size="1">
- <option id="form-widgets-gender-novalue"
- value="--NOVALUE--">no value</option>
- <option id="form-widgets-gender-0" value="male">male</option>
- <option id="form-widgets-gender-1" value="female">female</option>
- </select>
- <input name="form.widgets.gender-empty-marker" type="hidden"
- value="1" />
- </div>
- <div class="row">
- <label for="form-widgets-age">Age</label>
- <input type="text" id="form-widgets-age" name="form.widgets.age"
- class="text-widget int-field" value="20" />
- </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>
-
-The update()/render() cycle is what happens when the form is called, i.e.
-when it is published:
-
- >>> print addForm()
- <html xmlns="http://www.w3.org/1999/xhtml">
- <body>
- <form action=".">
- <div class="row">
- <label for="form-widgets-id">ID</label>
- <input type="text" id="form-widgets-id"
- name="form.widgets.id"
- class="text-widget required textline-field"
- value="" />
- </div>
- <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="row">
- <label for="form-widgets-gender">Gender</label>
- <select id="form-widgets-gender" name="form.widgets.gender:list"
- class="select-widget choice-field" size="1">
- <option id="form-widgets-gender-novalue"
- value="--NOVALUE--">no value</option>
- <option id="form-widgets-gender-0" value="male">male</option>
- <option id="form-widgets-gender-1" value="female">female</option>
- </select>
- <input name="form.widgets.gender-empty-marker" type="hidden"
- value="1" />
- </div>
- <div class="row">
- <label for="form-widgets-age">Age</label>
- <input type="text" id="form-widgets-age" name="form.widgets.age"
- class="text-widget int-field" value="20" />
- </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>
-
-Note that we don't actually call render if the response has been set to a 3xx
-type status code (e.g. a redirect or not modified response), since the browser
-would not render it anyway:
-
- >>> request.response.setStatus(304)
- >>> print addForm()
-
-Let's go back to a normal status to continue the test.
-
- >>> request.response.setStatus(200)
-
-Submitting an add form successfully
------------------------------------
-
-Initially the root folder of the application is empty:
-
- >>> sorted(root)
- []
-
-Let's now fill the request with all the right values so that upon submitting
-the form with the "Add" button, the person should be added to the root folder:
-
- >>> request = TestRequest(form={
- ... 'form.widgets.id': u'srichter',
- ... 'form.widgets.name': u'Stephan Richter',
- ... 'form.widgets.gender': ['male'],
- ... 'form.widgets.age': u'20',
- ... 'form.buttons.add': u'Add'}
- ... )
-
- >>> addForm = PersonAddForm(root, request)
- >>> addForm.update()
-
- >>> sorted(root)
- [u'srichter']
- >>> stephan = root[u'srichter']
- >>> stephan.id
- u'srichter'
- >>> stephan.name
- u'Stephan Richter'
- >>> stephan.gender
- 'male'
- >>> stephan.age
- 20
-
-
-Submitting an add form with invalid data
-----------------------------------------
-
-Next we try to submit the add form with the required name missing. Thus, the
-add form should not complete with the addition, but return with the add form
-pointing out the error.
-
- >>> request = TestRequest(form={
- ... 'form.widgets.id': u'srichter',
- ... 'form.widgets.gender': ['male'],
- ... 'form.widgets.age': u'23',
- ... 'form.buttons.add': u'Add'}
- ... )
-
- >>> addForm = PersonAddForm(root, request)
- >>> addForm.update()
-
-The widget manager and the widget causing the error should have an error
-message:
-
- >>> [(error.widget.__name__, error) for error in addForm.widgets.errors]
- [('name', <ErrorViewSnippet for RequiredMissing>)]
-
- >>> addForm.widgets['name'].error
- <ErrorViewSnippet for RequiredMissing>
-
-Let's now render the form:
-
- >>> addTemplate(addForm)
- >>> print addForm.render()
- <html xmlns="http://www.w3.org/1999/xhtml">
- <body>
- <i>There were some errors.</i>
- <ul>
- <li>
- Name: <div class="error">Required input is missing.</div>
- </li>
- </ul>
- <form action=".">
- <div class="row">
- <label for="form-widgets-id">ID</label>
- <input type="text" id="form-widgets-id"
- name="form.widgets.id"
- class="text-widget required textline-field"
- value="srichter" />
- </div>
- <div class="row">
- <b><div class="error">Required input is missing.</div>
- </b><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="row">
- <label for="form-widgets-gender">Gender</label>
- <select id="form-widgets-gender" name="form.widgets.gender:list"
- class="select-widget choice-field" size="1">
- <option id="form-widgets-gender-novalue"
- value="--NOVALUE--">no value</option>
- <option id="form-widgets-gender-0" value="male"
- selected="selected">male</option>
- <option id="form-widgets-gender-1" value="female">female</option>
- </select>
- <input name="form.widgets.gender-empty-marker" type="hidden"
- value="1" />
- </div>
- <div class="row">
- <label for="form-widgets-age">Age</label>
- <input type="text" id="form-widgets-age" name="form.widgets.age"
- class="text-widget int-field" value="23" />
- </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>
-
-Note that the values of the field are now extracted from the request.
-
-Another way to receive an error is by not fulfilling the invariants of the
-schema. In our case, the id and name cannot be the same. So let's provoke the
-error now:
-
- >>> request = TestRequest(form={
- ... 'form.widgets.id': u'Stephan',
- ... 'form.widgets.name': u'Stephan',
- ... 'form.widgets.gender': ['male'],
- ... 'form.widgets.age': u'23',
- ... 'form.buttons.add': u'Add'}
- ... )
-
- >>> addForm = PersonAddForm(root, request)
- >>> addTemplate(addForm)
- >>> addForm.update()
-
-and see how the form looks like:
-
- >>> print addForm.render() # doctest: +NOPARSE_MARKUP
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <body>
- <i>There were some errors.</i>
- <ul>
- <li>
- <div class="error">The id and name cannot be the same.</div>
- </li>
- </ul>
- ...
- </body>
- </html>
-
-Let's try to provide a negative age, which is not possible either:
-
- >>> request = TestRequest(form={
- ... 'form.widgets.id': u'srichter',
- ... 'form.widgets.gender': ['male'],
- ... 'form.widgets.age': u'-5',
- ... 'form.buttons.add': u'Add'}
- ... )
-
- >>> addForm = PersonAddForm(root, request)
- >>> addForm.update()
-
- >>> [(view.widget.label, view) for view in addForm.widgets.errors]
- [(u'Name', <ErrorViewSnippet for RequiredMissing>),
- (u'Age', <ErrorViewSnippet for TooSmall>)]
-
-But the error message for a negative age is too generic:
-
- >>> print addForm.widgets['age'].error.render()
- <div class="error">Value is too small</div>
-
-It would be better to say that negative values are disallowed. So let's
-register a new error view snippet for the ``TooSmall`` error:
-
- >>> from z3c.form import error
-
- >>> class TooSmallView(error.ErrorViewSnippet):
- ... zope.component.adapts(
- ... zope.schema.interfaces.TooSmall, None, None, None, None, None)
- ...
- ... def update(self):
- ... super(TooSmallView, self).update()
- ... if self.field.min == 0:
- ... self.message = u'The value cannot be a negative number.'
-
- >>> zope.component.provideAdapter(TooSmallView)
-
- >>> addForm = PersonAddForm(root, request)
- >>> addForm.update()
- >>> print addForm.widgets['age'].error.render()
- <div class="error">The value cannot be a negative number.</div>
-
-Note: The ``adapts()`` declaration might look strange. An error view
-snippet is actually a multiadapter that adapts a combination of 6
-objects -- error, request, widget, field, form, content. By specifying
-only the error, we tell the system that we do not care about the other
-discriminators, which then can be anything. We could also have used
-``zope.interface.Interface`` instead, which would be equivalent.
-
-
-Additional Form Attributes and API
-----------------------------------
-
-Since we are talking about HTML forms here, add and edit forms support all
-relevant FORM element attributes as attributes on the class.
-
- >>> addForm.method
- 'post'
- >>> addForm.enctype
- 'multipart/form-data'
- >>> addForm.acceptCharset
- >>> addForm.accept
-
-The ``action`` attribute is computed. By default it is the current URL:
-
- >>> addForm.action
- 'http://127.0.0.1'
-
-The name is also computed. By default it takes the prefix and removes any
-trailing ".".
-
- >>> addForm.name
- 'form'
-
-The id is computed from the name, replacing dots with hyphens. Let's set
-the prefix to something containing more than one final dot and check how
-it works.
-
- >>> addForm.prefix = 'person.form.add.'
- >>> addForm.id
- 'person-form-add'
-
-The template can then use those attributes, if it likes to.
-
-In the examples previously we set the template manually. If no
-template is specified, the system tries to find an adapter. Without
-any special configuration, there is no adapter, so rendering the form
-fails:
-
- >>> addForm.template = None
- >>> addForm.render()
- Traceback (most recent call last):
- ...
- ComponentLookupError: ((...), <InterfaceClass ...IPageTemplate>, u'')
-
-The form module provides a simple component to create adapter
-factories from templates:
-
- >>> factory = form.FormTemplateFactory(
- ... testing.getPath('../tests/simple_edit.pt'), form=PersonAddForm)
-
-Let's register our new template-based adapter factory:
-
- >>> zope.component.provideAdapter(factory)
-
-Now the factory will be used to provide a template:
-
- >>> print addForm.render() # doctest: +NOPARSE_MARKUP
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- ...
- </html>
-
-Since a form can also be used as a page itself, it is callable. When
-you call it will invoke both the ``update()`` and ``render()``
-methods:
-
- >>> print addForm() # doctest: +NOPARSE_MARKUP
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- ...
- </html>
-
-The form also provides a label for rendering a required info. This required
-info depends by default on the given requiredInfo label and if at least one
-field is required:
-
- >>> addForm.requiredInfo
- u'<span class="required">*</span>– required'
-
-If we set the labelRequired to None, we do not get a requiredInfo label:
-
- >>> addForm.labelRequired = None
- >>> addForm.requiredInfo is None
- True
-
-
-Changing Widget Attribute Values
---------------------------------
-
-It frequently happens that a customer comes along and wants to
-slightly or totally change some of the text shown in forms or make
-optional fields required. It does not make sense to always have to
-adjust the schema or implement a custom schema for these use
-cases. With the z3c.form framework all attributes -- for which it is
-sensible to replace a value without touching the code -- are
-customizable via an attribute value adapter.
-
-To demonstrate this feature, let's change the label of the name widget
-from "Name" to "Full Name":
-
- >>> from z3c.form import widget
- >>> NameLabel = widget.StaticWidgetAttribute(
- ... u'Full Name', field=IPerson['name'])
- >>> zope.component.provideAdapter(NameLabel, name='label')
-
-When the form renders, the label has now changed:
-
- >>> addForm = PersonAddForm(root, TestRequest())
- >>> addTemplate(addForm)
- >>> addForm.update()
- >>> print testing.render(addForm, './/xmlns:div[2][@class="row"]')
- <div class="row">
- <label for="form-widgets-name">Full Name</label>
- <input class="text-widget required textline-field"
- id="form-widgets-name" name="form.widgets.name" type="text" value="">
- </div>
-
-
-Adding a "Cancel" button
-------------------------
-
-Let's say a client requests that all add forms should have a "Cancel"
-button. When the button is pressed, the user is forwarded to the next URL of
-the add form. As always, the goal is to not touch the core implementation of
-the code, but make those changes externally.
-
-Adding a button/action is a little bit more involved than changing a value,
-because you have to insert the additional action and customize the action
-handler. Based on your needs of flexibility, multiple approaches could be
-chosen. Here we demonstrate the simplest one.
-
-The first step is to create a custom action manager that always inserts a
-cancel action:
-
- >>> from z3c.form import button
- >>> class AddActions(button.ButtonActions):
- ... zope.component.adapts(
- ... interfaces.IAddForm,
- ... zope.interface.Interface,
- ... zope.interface.Interface)
- ...
- ... def update(self):
- ... self.form.buttons = button.Buttons(
- ... self.form.buttons,
- ... button.Button('cancel', u'Cancel'))
- ... super(AddActions, self).update()
-
-After registering the new action manager,
-
- >>> zope.component.provideAdapter(AddActions)
-
-the add form should display a cancel button:
-
- >>> addForm.update()
- >>> print testing.render(addForm, './/xmlns:div[@class="action"]')
- <div class="action">
- <input type="submit" id="form-buttons-add" name="form.buttons.add"
- class="submit-widget button-field" value="Add" />
- </div>
- <div class="action">
- <input type="submit" id="form-buttons-cancel" name="form.buttons.cancel"
- class="submit-widget button-field" value="Cancel" />
- </div>
-
-But showing the button does not mean it does anything. So we also need a
-custom action handler to handle the cancel action:
-
- >>> class AddActionHandler(button.ButtonActionHandler):
- ... zope.component.adapts(
- ... interfaces.IAddForm,
- ... zope.interface.Interface,
- ... zope.interface.Interface,
- ... button.ButtonAction)
- ...
- ... def __call__(self):
- ... if self.action.name == 'form.buttons.cancel':
- ... self.form._finishedAdd = True
- ... return
- ... super(AddActionHandler, self).__call__()
-
-After registering the action handler,
-
- >>> zope.component.provideAdapter(AddActionHandler)
-
-we can press the cancel button and we will be forwarded:
-
- >>> request = TestRequest(form={'form.buttons.cancel': u'Cancel'})
-
- >>> addForm = PersonAddForm(root, request)
- >>> addTemplate(addForm)
- >>> addForm.update()
- >>> addForm.render()
- ''
-
- >>> request.response.getStatus()
- 302
- >>> request.response.getHeader('Location')
- 'index.html'
-
-Eventually, we might have action managers and handlers that are much more
-powerful and some of the manual labor in this example would become
-unnecessary.
-
-
-Creating an Edit Form
----------------------
-
-Now that we have exhaustively covered the customization possibilities of add
-forms, let's create an edit form. Edit forms are even simpler than add forms,
-since all actions are completely automatic:
-
- >>> class PersonEditForm(form.EditForm):
- ...
- ... fields = field.Fields(IPerson)
-
-We can use the created person from the successful addition above.
-
- >>> editForm = PersonEditForm(root[u'srichter'], TestRequest())
-
-After adding a template, we can look at the form:
-
- >>> addTemplate(editForm)
- >>> editForm.update()
- >>> print editForm.render()
- <html xmlns="http://www.w3.org/1999/xhtml">
- <body>
- <form action=".">
- <div class="row">
- <label for="form-widgets-id">ID</label>
- <span id="form-widgets-id"
- class="text-widget required textline-field">srichter</span>
- </div>
- <div class="row">
- <label for="form-widgets-name">Full Name</label>
- <input type="text" id="form-widgets-name" name="form.widgets.name"
- class="text-widget required textline-field"
- value="Stephan Richter" />
- </div>
- <div class="row">
- <label for="form-widgets-gender">Gender</label>
- <select id="form-widgets-gender" name="form.widgets.gender:list"
- class="select-widget choice-field" size="1">
- <option id="form-widgets-gender-novalue"
- value="--NOVALUE--">no value</option>
- <option id="form-widgets-gender-0" value="male"
- selected="selected">male</option>
- <option id="form-widgets-gender-1" value="female">female</option>
- </select>
- <input name="form.widgets.gender-empty-marker" type="hidden"
- value="1" />
- </div>
- <div class="row">
- <label for="form-widgets-age">Age</label>
- <input type="text" id="form-widgets-age" name="form.widgets.age"
- class="text-widget int-field" value="20" />
- </div>
- <div class="action">
- <input type="submit" id="form-buttons-apply" name="form.buttons.apply"
- class="submit-widget button-field" value="Apply" />
- </div>
- </form>
- </body>
- </html>
-
-As you can see, the data are being pulled in from the context for the edit
-form. Next we will look at the behavior when submitting the form.
-
-
-Failure Upon Submission of Edit Form
-------------------------------------
-
-Let's now submit the form having some invalid data.
-
- >>> request = TestRequest(form={
- ... 'form.widgets.name': u'Claudia Richter',
- ... 'form.widgets.gender': ['female'],
- ... 'form.widgets.age': u'-1',
- ... 'form.buttons.apply': u'Apply'}
- ... )
-
- >>> editForm = PersonEditForm(root[u'srichter'], request)
- >>> addTemplate(editForm)
- >>> editForm.update()
- >>> print editForm.render()
- <html xmlns="http://www.w3.org/1999/xhtml">
- <body>
- <i>There were some errors.</i>
- <ul>
- <li>
- Age: <div class="error">The value cannot be a negative number.</div>
- </li>
- </ul>
- <form action=".">
- <div class="row">
- <label for="form-widgets-id">ID</label>
- <span id="form-widgets-id"
- class="text-widget required textline-field">srichter</span>
- </div>
- <div class="row">
- <label for="form-widgets-name">Full Name</label>
- <input type="text" id="form-widgets-name" name="form.widgets.name"
- class="text-widget required textline-field"
- value="Claudia Richter" />
- </div>
- <div class="row">
- <label for="form-widgets-gender">Gender</label>
- <select id="form-widgets-gender" name="form.widgets.gender:list"
- class="select-widget choice-field" size="1">
- <option id="form-widgets-gender-novalue"
- value="--NOVALUE--">no value</option>
- <option id="form-widgets-gender-0" value="male">male</option>
- <option id="form-widgets-gender-1" value="female"
- selected="selected">female</option>
- </select>
- <input name="form.widgets.gender-empty-marker" type="hidden"
- value="1" />
- </div>
- <div class="row">
- <b><div class="error">The value cannot be a negative number.</div>
- </b><label for="form-widgets-age">Age</label>
- <input type="text" id="form-widgets-age" name="form.widgets.age"
- class="text-widget int-field" value="-1" />
- </div>
- <div class="action">
- <input type="submit" id="form-buttons-apply" name="form.buttons.apply"
- class="submit-widget button-field" value="Apply" />
- </div>
- </form>
- </body>
- </html>
-
-
-Successfully Editing Content
-----------------------------
-
-Let's now resubmit the form with valid data, so the data should be updated.
-
- >>> request = TestRequest(form={
- ... 'form.widgets.name': u'Claudia Richter',
- ... 'form.widgets.gender': ['female'],
- ... 'form.widgets.age': u'27',
- ... 'form.buttons.apply': u'Apply'}
- ... )
-
- >>> editForm = PersonEditForm(root[u'srichter'], request)
- >>> addTemplate(editForm)
- >>> editForm.update()
- >>> print testing.render(editForm, './/xmlns:i')
- <i>Data successfully updated.</i>
-
- >>> stephan = root[u'srichter']
- >>> stephan.name
- u'Claudia Richter'
- >>> stephan.gender
- 'female'
- >>> stephan.age
- 27
-
-When an edit form is successfully committed, a detailed object-modified event
-is sent out telling the system about the changes. To see the error, let's
-create an event subscriber for object-modified events:
-
- >>> eventlog = []
- >>> import zope.lifecycleevent
- >>> @zope.component.adapter(zope.lifecycleevent.ObjectModifiedEvent)
- ... def logEvent(event):
- ... eventlog.append(event)
- >>> zope.component.provideHandler(logEvent)
-
-Let's now submit the form again, successfully changing the age:
-
- >>> request = TestRequest(form={
- ... 'form.widgets.name': u'Claudia Richter',
- ... 'form.widgets.gender': ['female'],
- ... 'form.widgets.age': u'29',
- ... 'form.buttons.apply': u'Apply'}
- ... )
-
- >>> editForm = PersonEditForm(root[u'srichter'], request)
- >>> addTemplate(editForm)
- >>> editForm.update()
-
-We can now look at the event:
-
- >>> event = eventlog[-1]
- >>> event
- <zope...ObjectModifiedEvent object at ...>
-
- >>> attrs = event.descriptions[0]
- >>> attrs.interface
- <InterfaceClass __builtin__.IPerson>
- >>> attrs.attributes
- ('age',)
-
-
-Successful Action with No Changes
----------------------------------
-
-When submitting the form without any changes, the form will tell you so.
-
- >>> request = TestRequest(form={
- ... 'form.widgets.name': u'Claudia Richter',
- ... 'form.widgets.gender': ['female'],
- ... 'form.widgets.age': u'29',
- ... 'form.buttons.apply': u'Apply'}
- ... )
-
- >>> editForm = PersonEditForm(root[u'srichter'], request)
- >>> addTemplate(editForm)
- >>> editForm.update()
- >>> print testing.render(editForm, './/xmlns:i')
- <i>No changes were applied.</i>
-
-
-Changing Status Messages
-------------------------
-
-Depending on the project, it is often desirable to change the status messages
-to fit the application. In ``zope.formlib`` this was hard to do, since the
-messages were buried within fairly complex methods that one did not want to
-touch. In this package all those messages are exposed as form attributes.
-
-There are three messages for the edit form:
-
-* ``formErrorsMessage`` -- Indicates that an error occurred while
- applying the changes. This message is also available for the add form.
-
-* ``successMessage`` -- The form data was successfully applied.
-
-* ``noChangesMessage`` -- No changes were found in the form data.
-
-Let's now change the ``noChangesMessage``:
-
- >>> editForm.noChangesMessage = u'No changes were detected in the form data.'
- >>> editForm.update()
- >>> print testing.render(editForm, './/xmlns:i')
- <i>No changes were detected in the form data.</i>
-
-When even more flexibility is required within a project, one could also
-implement these messages as properties looking up an attribute value. However,
-we have found this to be a rare case.
-
-
-Creating Edit Forms for Dictionaries
-------------------------------------
-
-Sometimes it is not desirable to edit a class instance that implements the
-fields, but other types of object. A good example is the need to modify a
-simple dictionary, where the field names are the keys. To do that, a special
-data manager for dictionaries is available:
-
- >>> from z3c.form import datamanager
- >>> zope.component.provideAdapter(datamanager.DictionaryField)
-
-The only step the developer has to complete is to re-implement the form's
-``getContent()`` method to return the dictionary:
-
- >>> personDict = {'id': u'rineichen', 'name': u'Roger Ineichen',
- ... 'gender': None, 'age': None}
- >>> class PersonDictEditForm(PersonEditForm):
- ... def getContent(self):
- ... return personDict
-
-We can now use the form as usual:
-
- >>> editForm = PersonDictEditForm(None, TestRequest())
- >>> addTemplate(editForm)
- >>> editForm.update()
- >>> print editForm.render()
- <html xmlns="http://www.w3.org/1999/xhtml">
- <body>
- <form action=".">
- <div class="row">
- <label for="form-widgets-id">ID</label>
- <span id="form-widgets-id"
- class="text-widget required textline-field">rineichen</span>
- </div>
- <div class="row">
- <label for="form-widgets-name">Full Name</label>
- <input type="text" id="form-widgets-name"
- name="form.widgets.name"
- class="text-widget required textline-field"
- value="Roger Ineichen" />
- </div>
- <div class="row">
- <label for="form-widgets-gender">Gender</label>
- <select id="form-widgets-gender" name="form.widgets.gender:list"
- class="select-widget choice-field" size="1">
- <option id="form-widgets-gender-novalue"
- value="--NOVALUE--" selected="selected">no value</option>
- <option id="form-widgets-gender-0" value="male">male</option>
- <option id="form-widgets-gender-1" value="female">female</option>
- </select>
- <input name="form.widgets.gender-empty-marker" type="hidden"
- value="1" />
- </div>
- <div class="row">
- <label for="form-widgets-age">Age</label>
- <input type="text" id="form-widgets-age"
- name="form.widgets.age" class="text-widget int-field"
- value="20" />
- </div>
- <div class="action">
- <input type="submit" id="form-buttons-apply"
- name="form.buttons.apply" class="submit-widget button-field"
- value="Apply" />
- </div>
- </form>
- </body>
- </html>
-
-Note that the name displayed in the form is identical to the one in the
-dictionary. Let's now submit a form to ensure that the data are also written to
-the dictionary:
-
- >>> request = TestRequest(form={
- ... 'form.widgets.name': u'Jesse Ineichen',
- ... 'form.widgets.gender': ['male'],
- ... 'form.widgets.age': u'5',
- ... 'form.buttons.apply': u'Apply'}
- ... )
- >>> editForm = PersonDictEditForm(None, request)
- >>> editForm.update()
-
- >>> len(personDict)
- 4
- >>> personDict['age']
- 5
- >>> personDict['gender']
- 'male'
- >>> personDict['id']
- u'rineichen'
- >>> personDict['name']
- u'Jesse Ineichen'
-
-
-Creating a Display Form
------------------------
-
-Creating a display form is simple; just instantiate, update and render it:
-
- >>> class PersonDisplayForm(form.DisplayForm):
- ... fields = field.Fields(IPerson)
- ... template = ViewPageTemplateFile(
- ... 'simple_display.pt', os.path.dirname(tests.__file__))
-
- >>> display = PersonDisplayForm(stephan, TestRequest())
- >>> display.update()
- >>> print display.render()
- Traceback (most recent call last):
- ...
- KeyError: '__conform__'
- <BLANKLINE>
- - Expression: "repeat/value/end"
- - Filename: ...select_display.pt
- - Location: (23:32)
- <BLANKLINE>
- - Source: ... tal:block condition="not:repeat/value/end">, </tal:block
- ...
-
-Simple Form Customization
--------------------------
-
-The form exposes several of the widget manager's attributes as attributes on
-the form. They are: ``mode``, ``ignoreContext``, ``ignoreRequest``, and
-``ignoreReadonly``.
-
-Here are the values for the display form we just created:
-
- >>> display.mode
- 'display'
- >>> display.ignoreContext
- False
- >>> display.ignoreRequest
- True
- >>> display.ignoreReadonly
- False
-
-These values should be equal to the ones of the widget manager:
-
- >>> display.widgets.mode
- 'display'
- >>> display.widgets.ignoreContext
- False
- >>> display.widgets.ignoreRequest
- True
- >>> display.widgets.ignoreReadonly
- False
-
-Now, if we change those values before updating the widgets, ...
-
- >>> display.mode = interfaces.INPUT_MODE
- >>> display.ignoreContext = True
- >>> display.ignoreRequest = False
- >>> display.ignoreReadonly = True
-
-... the widget manager will have the same values after updating the widgets:
-
- >>> display.updateWidgets()
-
- >>> display.widgets.mode
- 'input'
- >>> display.widgets.ignoreContext
- True
- >>> display.widgets.ignoreRequest
- False
- >>> display.widgets.ignoreReadonly
- True
-
-
-Extending Forms
----------------
-
-One very common use case is to extend forms. For example, you would like to
-use the edit form and its defined "Apply" button, but add another button
-yourself. Unfortunately, just inheriting the form is not enough, because the
-new button and handler declarations will override the inherited ones. Let me
-demonstrate the problem:
-
- >>> class BaseForm(form.Form):
- ... fields = field.Fields(IPerson).select('name')
- ...
- ... @button.buttonAndHandler(u'Apply')
- ... def handleApply(self, action):
- ... print 'success'
-
- >>> BaseForm.fields.keys()
- ['name']
- >>> BaseForm.buttons.keys()
- ['apply']
- >>> BaseForm.handlers
- <Handlers [<Handler for <Button 'apply' u'Apply'>>]>
-
-Let's now derive a form from the base form:
-
- >>> class DerivedForm(BaseForm):
- ... fields = field.Fields(IPerson).select('gender')
- ...
- ... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, action):
- ... print 'cancel'
-
- >>> DerivedForm.fields.keys()
- ['gender']
- >>> DerivedForm.buttons.keys()
- ['cancel']
- >>> DerivedForm.handlers
- <Handlers [<Handler for <Button 'cancel' u'Cancel'>>]>
-
-The obvious method to "inherit" the base form's information is to copy it
-over:
-
- >>> class DerivedForm(BaseForm):
- ... fields = BaseForm.fields.copy()
- ... buttons = BaseForm.buttons.copy()
- ... handlers = BaseForm.handlers.copy()
- ...
- ... fields += field.Fields(IPerson).select('gender')
- ...
- ... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, action):
- ... print 'cancel'
-
- >>> DerivedForm.fields.keys()
- ['name', 'gender']
- >>> DerivedForm.buttons.keys()
- ['apply', 'cancel']
- >>> DerivedForm.handlers
- <Handlers
- [<Handler for <Button 'apply' u'Apply'>>,
- <Handler for <Button 'cancel' u'Cancel'>>]>
-
-But this is pretty clumsy. Instead, the ``form`` module provides a helper
-method that will do the extending for you:
-
- >>> class DerivedForm(BaseForm):
- ... form.extends(BaseForm)
- ...
- ... fields += field.Fields(IPerson).select('gender')
- ...
- ... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, action):
- ... print 'cancel'
-
- >>> DerivedForm.fields.keys()
- ['name', 'gender']
- >>> DerivedForm.buttons.keys()
- ['apply', 'cancel']
- >>> DerivedForm.handlers
- <Handlers
- [<Handler for <Button 'apply' u'Apply'>>,
- <Handler for <Button 'cancel' u'Cancel'>>]>
-
-If you, for example do not want to extend the buttons, you can turn that off:
-
- >>> class DerivedForm(BaseForm):
- ... form.extends(BaseForm, ignoreButtons=True)
- ...
- ... fields += field.Fields(IPerson).select('gender')
- ...
- ... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, action):
- ... print 'cancel'
-
- >>> DerivedForm.fields.keys()
- ['name', 'gender']
- >>> DerivedForm.buttons.keys()
- ['cancel']
- >>> DerivedForm.handlers
- <Handlers
- [<Handler for <Button 'apply' u'Apply'>>,
- <Handler for <Button 'cancel' u'Cancel'>>]>
-
-If you, for example do not want to extend the handlers, you can turn that off:
-
- >>> class DerivedForm(BaseForm):
- ... form.extends(BaseForm, ignoreHandlers=True)
- ...
- ... fields += field.Fields(IPerson).select('gender')
- ...
- ... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, action):
- ... print 'cancel'
-
- >>> DerivedForm.fields.keys()
- ['name', 'gender']
- >>> DerivedForm.buttons.keys()
- ['apply', 'cancel']
- >>> DerivedForm.handlers
- <Handlers [<Handler for <Button 'cancel' u'Cancel'>>]>
-
-
-Custom widget factories
------------------------
-
-Another important part of a form is that we can use custom widgets. We can do
-this in a form by defining a widget factory for a field. We can get the field
-from the fields collection e.g. ``fields['foo']``. This means, we can define
-new widget factories by defining ``fields['foo'].widgetFactory = MyWidget``.
-Let's show a sample and define a custom widget:
-
- >>> from z3c.form.browser import text
- >>> class MyWidget(text.TextWidget):
- ... """My new widget."""
- ... klass = u'MyCSS'
-
-Now we can define a field widget factory:
-
- >>> def MyFieldWidget(field, request):
- ... """IFieldWidget factory for MyWidget."""
- ... return widget.FieldWidget(field, MyWidget(request))
-
-We register the ``MyWidget`` in a form like:
-
- >>> class MyEditForm(form.EditForm):
- ...
- ... fields = field.Fields(IPerson)
- ... fields['name'].widgetFactory = MyFieldWidget
-
-We can see that the custom widget gets used in the rendered form:
-
- >>> myEdit = MyEditForm(root[u'srichter'], TestRequest())
- >>> addTemplate(myEdit)
- >>> myEdit.update()
- >>> print testing.render(myEdit, './/xmlns:input[@id="form-widgets-name"]')
- <input type="text" id="form-widgets-name"
- name="form.widgets.name" class="MyCSS required textline-field"
- value="Claudia Richter" />
-
-
-Hidden fields
--------------
-
-Another important part of a form is that we can generate hidden widgets. We can
-do this in a form by defining a widget mode. We can do this by override the
-setUpWidgets method.
-
- >>> class HiddenFieldEditForm(form.EditForm):
- ...
- ... fields = field.Fields(IPerson)
- ... fields['name'].widgetFactory = MyFieldWidget
- ...
- ... def updateWidgets(self):
- ... super(HiddenFieldEditForm, self).updateWidgets()
- ... self.widgets['age'].mode = interfaces.HIDDEN_MODE
-
-We can see that the widget gets rendered as hidden:
-
- >>> hiddenEdit = HiddenFieldEditForm(root[u'srichter'], TestRequest())
- >>> addTemplate(hiddenEdit)
- >>> hiddenEdit.update()
- >>> print testing.render(hiddenEdit, './/xmlns:input[@id="form-widgets-age"]')
- <input type="hidden" id="form-widgets-age"
- name="form.widgets.age" class="hidden-widget"
- value="29" />
-
-
-Actions with Errors
--------------------
-
-Even though the data might be validated correctly, it sometimes happens that
-data turns out to be invalid while the action is executed. In those cases a
-special action execution error can be raised that wraps the original error.
-
- >>> class PersonAddForm(form.AddForm):
- ...
- ... fields = field.Fields(IPerson).select('id')
- ...
- ... @button.buttonAndHandler(u'Check')
- ... def handleCheck(self, action):
- ... data, errors = self.extractData()
- ... if data['id'] in self.getContent():
- ... raise interfaces.WidgetActionExecutionError(
- ... 'id', zope.interface.Invalid('Id already exists'))
-
-In this case the action execution error is specific to a widget. The framework
-will attach a proper error view to the widget and the widget manager:
-
- >>> request = TestRequest(form={
- ... 'form.widgets.id': u'srichter',
- ... 'form.buttons.check': u'Check'}
- ... )
-
- >>> addForm = PersonAddForm(root, request)
- >>> addForm.update()
-
- >>> addForm.widgets.errors
- (<InvalidErrorViewSnippet for Invalid>,)
- >>> addForm.widgets['id'].error
- <InvalidErrorViewSnippet for Invalid>
- >>> addForm.status
- u'There were some errors.'
-
-If the error is non-widget specific, then we can simply use the generic action
-execution error:
-
- >>> class PersonAddForm(form.AddForm):
- ...
- ... fields = field.Fields(IPerson).select('id')
- ...
- ... @button.buttonAndHandler(u'Check')
- ... def handleCheck(self, action):
- ... raise interfaces.ActionExecutionError(
- ... zope.interface.Invalid('Some problem occurred.'))
-
-Let's have a look at the result:
-
- >>> addForm = PersonAddForm(root, request)
- >>> addForm.update()
-
- >>> addForm.widgets.errors
- (<InvalidErrorViewSnippet for Invalid>,)
- >>> addForm.status
- u'There were some errors.'
-
-**Note**:
-
- The action execution errors are connected to the form via an event
- listener called ``handlerActionError``. This event listener listens for
- ``IActionErrorEvent`` events. If the event is called for an action associated
- with a form, the listener does its work as seen above. If the action is not
- coupled to a form, then event listener does nothing:
-
- >>> from z3c.form import action
-
- >>> cancel = action.Action(request, u'Cancel')
- >>> event = action.ActionErrorOccurred(cancel, ValueError(3))
-
- >>> form.handleActionError(event)
-
-
-Applying Changes
-----------------
-
-When applying the data of a form to a content component, the function
-``applyChanges()`` is called. It simply iterates through the fields of the
-form and uses the data managers to store the values. The output of the
-function is a list of changes:
-
- >>> roger = Person(u'roger', u'Roger')
- >>> roger
- <Person u'Roger'>
-
- >>> class BaseForm(form.Form):
- ... fields = field.Fields(IPerson).select('name')
- >>> myForm = BaseForm(roger, TestRequest())
-
- >>> form.applyChanges(myForm, roger, {'name': u'Roger Ineichen'})
- {<InterfaceClass __builtin__.IPerson>: ['name']}
-
- >>> roger
- <Person u'Roger Ineichen'>
-
-When a field is missing from the data, it is simply skipped:
-
- >>> form.applyChanges(myForm, roger, {})
- {}
-
-If the new and old value are identical, storing the data is skipped as well:
-
- >>> form.applyChanges(myForm, roger, {'name': u'Roger Ineichen'})
- {}
-
-In some cases the data converter for a field-widget pair returns the
-``NOT_CHANGED`` value. In this case, the field is skipped as well:
-
- >>> form.applyChanges(myForm, roger, {'name': interfaces.NOT_CHANGED})
- {}
-
- >>> roger
- <Person u'Roger Ineichen'>
-
-
-Refreshing actions
-------------------
-
-Sometimes, it's useful to update actions again after executing them,
-because some conditions could have changed. For example, imagine
-we have a sequence edit form that has a delete button. We don't
-want to show delete button when the sequence is empty. The button
-condition would handle this, but what if the sequence becomes empty
-as a result of execution of the delete action that was available?
-In that case we want to refresh our actions to new conditions to make
-our delete button not visible anymore. The ``refreshActions`` form
-variable is intended to handle this case.
-
-Let's create a simple form with an action that clears our context
-sequence.
-
- >>> class SequenceForm(form.Form):
- ...
- ... @button.buttonAndHandler(u'Empty', condition=lambda form:bool(form.context))
- ... def handleEmpty(self, action):
- ... self.context[:] = []
- ... self.refreshActions = True
-
-First, let's illustrate simple cases, when no button is pressed.
-The button will be available when context is not empty.
-
- >>> context = [1, 2, 3, 4]
- >>> request = TestRequest()
- >>> myForm = SequenceForm(context, request)
- >>> myForm.update()
- >>> addTemplate(myForm)
- >>> print testing.render(myForm, './/xmlns:div[@class="action"]')
- <div class="action">
- <input type="submit" id="form-buttons-empty" name="form.buttons.empty"
- class="submit-widget button-field" value="Empty" />
- </div>
-
-The button will not be available when the context is empty.
-
- >>> context = []
- >>> request = TestRequest()
- >>> myForm = SequenceForm(context, request)
- >>> myForm.update()
- >>> addTemplate(myForm)
- >>> print testing.render(myForm, './/xmlns:form')
- <form action=".">
- </form>
-
-Now, the most interesting case when context is not empty, but becomes
-empty as a result of pressing the "empty" button. We set the
-``refreshActions`` flag in the action handler, so our actions should
-be updated to new conditions.
-
- >>> context = [1, 2, 3, 4, 5]
- >>> request = TestRequest(form={
- ... 'form.buttons.empty': u'Empty'}
- ... )
- >>> myForm = SequenceForm(context, request)
- >>> myForm.update()
- >>> addTemplate(myForm)
- >>> print testing.render(myForm, './/xmlns:form')
- <form action=".">
- </form>
-
-Integration tests
------------------
-
-Identifying the different forms can be important if it comes to layout
-template lookup. Let's ensure that we support the right interfaces for the
-different forms.
-
-
-Form
-~~~~
-
- >>> from zope.interface.verify import verifyObject
- >>> from z3c.form import interfaces
- >>> obj = form.Form(None, None)
- >>> verifyObject(interfaces.IForm, obj)
- True
-
- >>> interfaces.IForm.providedBy(obj)
- True
-
- >>> from z3c.form import interfaces
- >>> interfaces.IDisplayForm.providedBy(obj)
- False
-
- >>> from z3c.form import interfaces
- >>> interfaces.IEditForm.providedBy(obj)
- False
-
- >>> from z3c.form import interfaces
- >>> interfaces.IAddForm.providedBy(obj)
- False
-
-
-DisplayForm
-~~~~~~~~~~~
-
- >>> from z3c.form import interfaces
- >>> obj = form.DisplayForm(None, None)
- >>> verifyObject(interfaces.IDisplayForm, obj)
- True
-
- >>> interfaces.IForm.providedBy(obj)
- True
-
- >>> from z3c.form import interfaces
- >>> interfaces.IDisplayForm.providedBy(obj)
- True
-
- >>> from z3c.form import interfaces
- >>> interfaces.IEditForm.providedBy(obj)
- False
-
- >>> from z3c.form import interfaces
- >>> interfaces.IAddForm.providedBy(obj)
- False
-
-
-EditForm
-~~~~~~~~
-
- >>> from z3c.form import interfaces
- >>> obj = form.EditForm(None, None)
- >>> verifyObject(interfaces.IEditForm, obj)
- True
-
- >>> interfaces.IForm.providedBy(obj)
- True
-
- >>> from z3c.form import interfaces
- >>> interfaces.IDisplayForm.providedBy(obj)
- False
-
- >>> from z3c.form import interfaces
- >>> interfaces.IEditForm.providedBy(obj)
- True
-
- >>> from z3c.form import interfaces
- >>> interfaces.IAddForm.providedBy(obj)
- False
-
-
-AddForm
-~~~~~~~
-
- >>> from z3c.form import interfaces
- >>> obj = form.AddForm(None, None)
- >>> verifyObject(interfaces.IAddForm, obj)
- True
-
- >>> interfaces.IForm.providedBy(obj)
- True
-
- >>> from z3c.form import interfaces
- >>> interfaces.IDisplayForm.providedBy(obj)
- False
-
- >>> from z3c.form import interfaces
- >>> interfaces.IEditForm.providedBy(obj)
- False
-
- >>> from z3c.form import interfaces
- >>> interfaces.IAddForm.providedBy(obj)
- True
+=====
+Forms
+=====
+
+The purpose of this package is to make development of forms as simple
+as possible, while still providing all the hooks to do customization
+at any level as required by our real-world use cases. Thus, once the
+system is set up with all its default registrations, it should be
+trivial to develop a new form.
+
+The strategy of this document is to provide the most common, and thus
+simplest, case first and then demonstrate the available customization
+options. In order to not overwhelm you with our set of well-chosen defaults,
+all the default component registrations have been made prior to doing those
+examples:
+
+ >>> from z3c.form import testing
+ >>> testing.setupFormDefaults()
+
+Note, since version 2.4.2 the IFormLayer doesn't provide IBrowserRequest
+anymore. This is usefull if you like to use z3c.form components for other
+requets then the IBrowserRequest.
+
+ >>> from zope.publisher.interfaces.browser import IBrowserRequest
+ >>> import z3c.form.interfaces
+ >>> z3c.form.interfaces.IFormLayer.isOrExtends(IBrowserRequest)
+ False
+
+Before we can start writing forms, we must have the content to work with:
+
+ >>> import zope.interface
+ >>> import zope.schema
+ >>> class IPerson(zope.interface.Interface):
+ ...
+ ... id = zope.schema.TextLine(
+ ... title=u'ID',
+ ... readonly=True,
+ ... required=True)
+ ...
+ ... name = zope.schema.TextLine(
+ ... title=u'Name',
+ ... required=True)
+ ...
+ ... gender = zope.schema.Choice(
+ ... title=u'Gender',
+ ... values=('male', 'female'),
+ ... required=False)
+ ...
+ ... age = zope.schema.Int(
+ ... title=u'Age',
+ ... description=u"The person's age.",
+ ... min=0,
+ ... default=20,
+ ... required=False)
+ ...
+ ... @zope.interface.invariant
+ ... def ensureIdAndNameNotEqual(person):
+ ... if person.id == person.name:
+ ... raise zope.interface.Invalid(
+ ... "The id and name cannot be the same.")
+
+ >>> from zope.schema.fieldproperty import FieldProperty
+ >>> class Person(object):
+ ... zope.interface.implements(IPerson)
+ ... id = FieldProperty(IPerson['id'])
+ ... name = FieldProperty(IPerson['name'])
+ ... gender = FieldProperty(IPerson['gender'])
+ ... age = FieldProperty(IPerson['age'])
+ ...
+ ... def __init__(self, id, name, gender=None, age=None):
+ ... self.id = id
+ ... self.name = name
+ ... if gender:
+ ... self.gender = gender
+ ... if age:
+ ... self.age = age
+ ...
+ ... def __repr__(self):
+ ... return '<%s %r>' % (self.__class__.__name__, self.name)
+
+Okay, that should suffice for now.
+
+What's next? Well, first things first. Let's create an add form for the
+person. Since practice showed that the ``IAdding`` interface is overkill for
+most projects, the default add form of ``z3c.form`` requires you to define the
+creation and adding mechanism.
+
+**Note**:
+
+ If it is not done, ``NotImplementedError[s]`` are raised:
+
+ >>> from z3c.form.testing import TestRequest
+ >>> from z3c.form import form, field
+
+ >>> abstract = form.AddForm(None, TestRequest())
+
+ >>> abstract.create({})
+ Traceback (most recent call last):
+ ...
+ NotImplementedError
+
+ >>> abstract.add(1)
+ Traceback (most recent call last):
+ ...
+ NotImplementedError
+
+ >>> abstract.nextURL()
+ Traceback (most recent call last):
+ ...
+ NotImplementedError
+
+
+Thus let's now create a working add form:
+
+ >>> class PersonAddForm(form.AddForm):
+ ...
+ ... fields = field.Fields(IPerson)
+ ...
+ ... def create(self, data):
+ ... return Person(**data)
+ ...
+ ... def add(self, object):
+ ... self.context[object.id] = object
+ ...
+ ... def nextURL(self):
+ ... return 'index.html'
+
+This is as simple as it gets. We explicitly define the pieces that
+are custom to every situation and let the default setup of the
+framework do the rest. This is intentionally similar to
+``zope.formlib``, because we really like the simplicity of
+``zope.formlib``'s way of dealing with the common use cases.
+
+Let's try to add a new person object to the root folder (which
+was created during test setup). For this add form, of course, the
+context is now the root folder:
+
+ >>> request = TestRequest()
+ >>> addForm = PersonAddForm(root, request)
+
+Since forms are not necessarily pages -- in fact often they are not --
+they must not have a ``__call__`` method that does all the processing
+and rendering at once. Instead, we use the update/render
+pattern. Thus, we first call the ``update()`` method.
+
+ >>> addForm.update()
+
+Actually a lot of things happen during this stage. Let us step through it one
+by one pointing out the effects.
+
+
+Find a widget manager and update it
+-----------------------------------
+
+The default widget manager knows to look for the ``fields`` attribute in the
+form, since it implements ``IFieldsForm``:
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IFieldsForm.providedBy(addForm)
+ True
+
+The widget manager is then stored in the ``widgets`` attribute as promised by
+the ``IForm`` interface:
+
+ >>> addForm.widgets
+ <z3c.form.field.FieldWidgets object at ...>
+
+The widget manager will have four widgets, one for each field:
+
+ >>> addForm.widgets.keys()
+ ['id', 'name', 'gender', 'age']
+
+When the widget manager updates itself, several sub-tasks are processed. The
+manager goes through each field, trying to create a fully representative
+widget for the field.
+
+Field Availability
+~~~~~~~~~~~~~~~~~~
+
+Just because a field is requested in the field manager, does not mean that a
+widget has to be created for the field. There are cases when a field
+declaration might be ignored. The following reasons come to mind:
+
+* No widget is created if the data are not accessible in the content.
+* A custom widget manager has been registered to specifically ignore a field.
+
+In our simple example, all fields will be converted to widgets.
+
+Widget Creation
+~~~~~~~~~~~~~~~
+
+During the widget creation process, several pieces of information are
+transferred from the field to the widget:
+
+ >>> age = addForm.widgets['age']
+
+ # field.title -> age.label
+
+ >>> age.label
+ u'Age'
+
+ # field.required -> age.required
+
+ >>> age.required
+ False
+
+All these values can be overridden at later stages of the updating
+process.
+
+Widget Value
+~~~~~~~~~~~~
+
+The next step is to determine the value that should be displayed by the
+widget. This value could come from three places (looked up in this order):
+
+1. The field's default value.
+2. The content object that the form is representing.
+3. The request in case a form has not been submitted or an error occurred.
+
+Since we are currently building an add form and not an edit form,
+there is no content object to represent, so the second step is not
+applicable. The third step is also not applicable as we do not have
+anything in the request. Therefore, the value should be the field's
+default value, or be empty. In this case the field provides a default
+value:
+
+ >>> age.value
+ u'20'
+
+While the default of the age field is actually the integer ``20``, the
+widget has converted the value to the output-ready string ``'20'``
+using a data converter.
+
+Widget Mode
+~~~~~~~~~~~
+
+Now the widget manager looks at the field to determine the widget mode -- in
+other words whether the widget is a display or edit widget. In this case all
+fields are input fields:
+
+ >>> age.mode
+ 'input'
+
+Deciding which mode to use, however, might not be a trivial operation. It
+might depend on several factors (items listed later override earlier ones):
+
+* The global ``mode`` flag of the widget manager
+* The permission to the content's data value
+* The ``readonly`` flag in the schema field
+* The ``mode`` flag in the field
+
+
+Widget Attribute Values
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As mentioned before, several widget attributes are optionally overridden when
+the widget updates itself:
+
+* label
+* required
+* mode
+
+Since we have no customization components registered, all of those fields will
+remain as set before.
+
+
+Find an action manager, update and execute it
+---------------------------------------------
+
+After all widgets have been instantiated and the ``update()`` method has been
+called successfully, the actions are set up. By default, the form machinery
+uses the button declaration on the form to create its actions. For the add
+form, an add button is defined by default, so that we did not need to create
+our own. Thus, there should be one action:
+
+ >>> len(addForm.actions)
+ 1
+
+The add button is an action and a widget at the same time:
+
+ >>> addAction = addForm.actions['add']
+ >>> addAction.title
+ u'Add'
+ >>> addAction.value
+ u'Add'
+
+After everything is set up, all pressed buttons are executed. Once a submitted
+action is detected, a special action handler adapter is used to determine the
+actions to take. Since the add button has not been pressed yet, no action
+occurred.
+
+
+Rendering the form
+------------------
+
+Once the update is complete we can render the form. Since we have not
+specified a template yet, we have to do this now. We have prepared a small and
+very simple template as part of this example:
+
+ >>> import os
+ >>> from zope.browserpage.viewpagetemplatefile import BoundPageTemplate
+ >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
+ >>> from z3c.form import tests
+ >>> def addTemplate(form):
+ ... form.template = BoundPageTemplate(
+ ... ViewPageTemplateFile(
+ ... 'simple_edit.pt', os.path.dirname(tests.__file__)), form)
+ >>> addTemplate(addForm)
+
+Let's now render the page:
+
+ >>> print addForm.render()
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <form action=".">
+ <div class="row">
+ <label for="form-widgets-id">ID</label>
+ <input type="text" id="form-widgets-id"
+ name="form.widgets.id"
+ class="text-widget required textline-field"
+ value="" />
+ </div>
+ <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="row">
+ <label for="form-widgets-gender">Gender</label>
+ <select id="form-widgets-gender" name="form.widgets.gender:list"
+ class="select-widget choice-field" size="1">
+ <option id="form-widgets-gender-novalue"
+ value="--NOVALUE--">no value</option>
+ <option id="form-widgets-gender-0" value="male">male</option>
+ <option id="form-widgets-gender-1" value="female">female</option>
+ </select>
+ <input name="form.widgets.gender-empty-marker" type="hidden"
+ value="1" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-age">Age</label>
+ <input type="text" id="form-widgets-age" name="form.widgets.age"
+ class="text-widget int-field" value="20" />
+ </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>
+
+The update()/render() cycle is what happens when the form is called, i.e.
+when it is published:
+
+ >>> print addForm()
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <form action=".">
+ <div class="row">
+ <label for="form-widgets-id">ID</label>
+ <input type="text" id="form-widgets-id"
+ name="form.widgets.id"
+ class="text-widget required textline-field"
+ value="" />
+ </div>
+ <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="row">
+ <label for="form-widgets-gender">Gender</label>
+ <select id="form-widgets-gender" name="form.widgets.gender:list"
+ class="select-widget choice-field" size="1">
+ <option id="form-widgets-gender-novalue"
+ value="--NOVALUE--">no value</option>
+ <option id="form-widgets-gender-0" value="male">male</option>
+ <option id="form-widgets-gender-1" value="female">female</option>
+ </select>
+ <input name="form.widgets.gender-empty-marker" type="hidden"
+ value="1" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-age">Age</label>
+ <input type="text" id="form-widgets-age" name="form.widgets.age"
+ class="text-widget int-field" value="20" />
+ </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>
+
+Note that we don't actually call render if the response has been set to a 3xx
+type status code (e.g. a redirect or not modified response), since the browser
+would not render it anyway:
+
+ >>> request.response.setStatus(304)
+ >>> print addForm()
+
+Let's go back to a normal status to continue the test.
+
+ >>> request.response.setStatus(200)
+
+Submitting an add form successfully
+-----------------------------------
+
+Initially the root folder of the application is empty:
+
+ >>> sorted(root)
+ []
+
+Let's now fill the request with all the right values so that upon submitting
+the form with the "Add" button, the person should be added to the root folder:
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.id': u'srichter',
+ ... 'form.widgets.name': u'Stephan Richter',
+ ... 'form.widgets.gender': ['male'],
+ ... 'form.widgets.age': u'20',
+ ... 'form.buttons.add': u'Add'}
+ ... )
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+
+ >>> sorted(root)
+ [u'srichter']
+ >>> stephan = root[u'srichter']
+ >>> stephan.id
+ u'srichter'
+ >>> stephan.name
+ u'Stephan Richter'
+ >>> stephan.gender
+ 'male'
+ >>> stephan.age
+ 20
+
+
+Submitting an add form with invalid data
+----------------------------------------
+
+Next we try to submit the add form with the required name missing. Thus, the
+add form should not complete with the addition, but return with the add form
+pointing out the error.
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.id': u'srichter',
+ ... 'form.widgets.gender': ['male'],
+ ... 'form.widgets.age': u'23',
+ ... 'form.buttons.add': u'Add'}
+ ... )
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+
+The widget manager and the widget causing the error should have an error
+message:
+
+ >>> [(error.widget.__name__, error) for error in addForm.widgets.errors]
+ [('name', <ErrorViewSnippet for RequiredMissing>)]
+
+ >>> addForm.widgets['name'].error
+ <ErrorViewSnippet for RequiredMissing>
+
+Let's now render the form:
+
+ >>> addTemplate(addForm)
+ >>> print addForm.render()
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <i>There were some errors.</i>
+ <ul>
+ <li>
+ Name: <div class="error">Required input is missing.</div>
+ </li>
+ </ul>
+ <form action=".">
+ <div class="row">
+ <label for="form-widgets-id">ID</label>
+ <input type="text" id="form-widgets-id"
+ name="form.widgets.id"
+ class="text-widget required textline-field"
+ value="srichter" />
+ </div>
+ <div class="row">
+ <b><div class="error">Required input is missing.</div>
+ </b><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="row">
+ <label for="form-widgets-gender">Gender</label>
+ <select id="form-widgets-gender" name="form.widgets.gender:list"
+ class="select-widget choice-field" size="1">
+ <option id="form-widgets-gender-novalue"
+ value="--NOVALUE--">no value</option>
+ <option id="form-widgets-gender-0" value="male"
+ selected="selected">male</option>
+ <option id="form-widgets-gender-1" value="female">female</option>
+ </select>
+ <input name="form.widgets.gender-empty-marker" type="hidden"
+ value="1" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-age">Age</label>
+ <input type="text" id="form-widgets-age" name="form.widgets.age"
+ class="text-widget int-field" value="23" />
+ </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>
+
+Note that the values of the field are now extracted from the request.
+
+Another way to receive an error is by not fulfilling the invariants of the
+schema. In our case, the id and name cannot be the same. So let's provoke the
+error now:
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.id': u'Stephan',
+ ... 'form.widgets.name': u'Stephan',
+ ... 'form.widgets.gender': ['male'],
+ ... 'form.widgets.age': u'23',
+ ... 'form.buttons.add': u'Add'}
+ ... )
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addTemplate(addForm)
+ >>> addForm.update()
+
+and see how the form looks like:
+
+ >>> print addForm.render() # doctest: +NOPARSE_MARKUP
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <i>There were some errors.</i>
+ <ul>
+ <li>
+ <div class="error">The id and name cannot be the same.</div>
+ </li>
+ </ul>
+ ...
+ </body>
+ </html>
+
+Let's try to provide a negative age, which is not possible either:
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.id': u'srichter',
+ ... 'form.widgets.gender': ['male'],
+ ... 'form.widgets.age': u'-5',
+ ... 'form.buttons.add': u'Add'}
+ ... )
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+
+ >>> [(view.widget.label, view) for view in addForm.widgets.errors]
+ [(u'Name', <ErrorViewSnippet for RequiredMissing>),
+ (u'Age', <ErrorViewSnippet for TooSmall>)]
+
+But the error message for a negative age is too generic:
+
+ >>> print addForm.widgets['age'].error.render()
+ <div class="error">Value is too small</div>
+
+It would be better to say that negative values are disallowed. So let's
+register a new error view snippet for the ``TooSmall`` error:
+
+ >>> from z3c.form import error
+
+ >>> class TooSmallView(error.ErrorViewSnippet):
+ ... zope.component.adapts(
+ ... zope.schema.interfaces.TooSmall, None, None, None, None, None)
+ ...
+ ... def update(self):
+ ... super(TooSmallView, self).update()
+ ... if self.field.min == 0:
+ ... self.message = u'The value cannot be a negative number.'
+
+ >>> zope.component.provideAdapter(TooSmallView)
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+ >>> print addForm.widgets['age'].error.render()
+ <div class="error">The value cannot be a negative number.</div>
+
+Note: The ``adapts()`` declaration might look strange. An error view
+snippet is actually a multiadapter that adapts a combination of 6
+objects -- error, request, widget, field, form, content. By specifying
+only the error, we tell the system that we do not care about the other
+discriminators, which then can be anything. We could also have used
+``zope.interface.Interface`` instead, which would be equivalent.
+
+
+Additional Form Attributes and API
+----------------------------------
+
+Since we are talking about HTML forms here, add and edit forms support all
+relevant FORM element attributes as attributes on the class.
+
+ >>> addForm.method
+ 'post'
+ >>> addForm.enctype
+ 'multipart/form-data'
+ >>> addForm.acceptCharset
+ >>> addForm.accept
+
+The ``action`` attribute is computed. By default it is the current URL:
+
+ >>> addForm.action
+ 'http://127.0.0.1'
+
+The name is also computed. By default it takes the prefix and removes any
+trailing ".".
+
+ >>> addForm.name
+ 'form'
+
+The id is computed from the name, replacing dots with hyphens. Let's set
+the prefix to something containing more than one final dot and check how
+it works.
+
+ >>> addForm.prefix = 'person.form.add.'
+ >>> addForm.id
+ 'person-form-add'
+
+The template can then use those attributes, if it likes to.
+
+In the examples previously we set the template manually. If no
+template is specified, the system tries to find an adapter. Without
+any special configuration, there is no adapter, so rendering the form
+fails:
+
+ >>> addForm.template = None
+ >>> addForm.render()
+ Traceback (most recent call last):
+ ...
+ ComponentLookupError: ((...), <InterfaceClass ...IPageTemplate>, u'')
+
+The form module provides a simple component to create adapter
+factories from templates:
+
+ >>> factory = form.FormTemplateFactory(
+ ... testing.getPath('../tests/simple_edit.pt'), form=PersonAddForm)
+
+Let's register our new template-based adapter factory:
+
+ >>> zope.component.provideAdapter(factory)
+
+Now the factory will be used to provide a template:
+
+ >>> print addForm.render() # doctest: +NOPARSE_MARKUP
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ ...
+ </html>
+
+Since a form can also be used as a page itself, it is callable. When
+you call it will invoke both the ``update()`` and ``render()``
+methods:
+
+ >>> print addForm() # doctest: +NOPARSE_MARKUP
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ ...
+ </html>
+
+The form also provides a label for rendering a required info. This required
+info depends by default on the given requiredInfo label and if at least one
+field is required:
+
+ >>> addForm.requiredInfo
+ u'<span class="required">*</span>– required'
+
+If we set the labelRequired to None, we do not get a requiredInfo label:
+
+ >>> addForm.labelRequired = None
+ >>> addForm.requiredInfo is None
+ True
+
+
+Changing Widget Attribute Values
+--------------------------------
+
+It frequently happens that a customer comes along and wants to
+slightly or totally change some of the text shown in forms or make
+optional fields required. It does not make sense to always have to
+adjust the schema or implement a custom schema for these use
+cases. With the z3c.form framework all attributes -- for which it is
+sensible to replace a value without touching the code -- are
+customizable via an attribute value adapter.
+
+To demonstrate this feature, let's change the label of the name widget
+from "Name" to "Full Name":
+
+ >>> from z3c.form import widget
+ >>> NameLabel = widget.StaticWidgetAttribute(
+ ... u'Full Name', field=IPerson['name'])
+ >>> zope.component.provideAdapter(NameLabel, name='label')
+
+When the form renders, the label has now changed:
+
+ >>> addForm = PersonAddForm(root, TestRequest())
+ >>> addTemplate(addForm)
+ >>> addForm.update()
+ >>> print testing.render(addForm, './/xmlns:div[2][@class="row"]')
+ <div class="row">
+ <label for="form-widgets-name">Full Name</label>
+ <input class="text-widget required textline-field"
+ id="form-widgets-name" name="form.widgets.name" type="text" value="">
+ </div>
+
+
+Adding a "Cancel" button
+------------------------
+
+Let's say a client requests that all add forms should have a "Cancel"
+button. When the button is pressed, the user is forwarded to the next URL of
+the add form. As always, the goal is to not touch the core implementation of
+the code, but make those changes externally.
+
+Adding a button/action is a little bit more involved than changing a value,
+because you have to insert the additional action and customize the action
+handler. Based on your needs of flexibility, multiple approaches could be
+chosen. Here we demonstrate the simplest one.
+
+The first step is to create a custom action manager that always inserts a
+cancel action:
+
+ >>> from z3c.form import button
+ >>> class AddActions(button.ButtonActions):
+ ... zope.component.adapts(
+ ... interfaces.IAddForm,
+ ... zope.interface.Interface,
+ ... zope.interface.Interface)
+ ...
+ ... def update(self):
+ ... self.form.buttons = button.Buttons(
+ ... self.form.buttons,
+ ... button.Button('cancel', u'Cancel'))
+ ... super(AddActions, self).update()
+
+After registering the new action manager,
+
+ >>> zope.component.provideAdapter(AddActions)
+
+the add form should display a cancel button:
+
+ >>> addForm.update()
+ >>> print testing.render(addForm, './/xmlns:div[@class="action"]')
+ <div class="action">
+ <input type="submit" id="form-buttons-add" name="form.buttons.add"
+ class="submit-widget button-field" value="Add" />
+ </div>
+ <div class="action">
+ <input type="submit" id="form-buttons-cancel" name="form.buttons.cancel"
+ class="submit-widget button-field" value="Cancel" />
+ </div>
+
+But showing the button does not mean it does anything. So we also need a
+custom action handler to handle the cancel action:
+
+ >>> class AddActionHandler(button.ButtonActionHandler):
+ ... zope.component.adapts(
+ ... interfaces.IAddForm,
+ ... zope.interface.Interface,
+ ... zope.interface.Interface,
+ ... button.ButtonAction)
+ ...
+ ... def __call__(self):
+ ... if self.action.name == 'form.buttons.cancel':
+ ... self.form._finishedAdd = True
+ ... return
+ ... super(AddActionHandler, self).__call__()
+
+After registering the action handler,
+
+ >>> zope.component.provideAdapter(AddActionHandler)
+
+we can press the cancel button and we will be forwarded:
+
+ >>> request = TestRequest(form={'form.buttons.cancel': u'Cancel'})
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addTemplate(addForm)
+ >>> addForm.update()
+ >>> addForm.render()
+ ''
+
+ >>> request.response.getStatus()
+ 302
+ >>> request.response.getHeader('Location')
+ 'index.html'
+
+Eventually, we might have action managers and handlers that are much more
+powerful and some of the manual labor in this example would become
+unnecessary.
+
+
+Creating an Edit Form
+---------------------
+
+Now that we have exhaustively covered the customization possibilities of add
+forms, let's create an edit form. Edit forms are even simpler than add forms,
+since all actions are completely automatic:
+
+ >>> class PersonEditForm(form.EditForm):
+ ...
+ ... fields = field.Fields(IPerson)
+
+We can use the created person from the successful addition above.
+
+ >>> editForm = PersonEditForm(root[u'srichter'], TestRequest())
+
+After adding a template, we can look at the form:
+
+ >>> addTemplate(editForm)
+ >>> editForm.update()
+ >>> print editForm.render()
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <form action=".">
+ <div class="row">
+ <label for="form-widgets-id">ID</label>
+ <span id="form-widgets-id"
+ class="text-widget required textline-field">srichter</span>
+ </div>
+ <div class="row">
+ <label for="form-widgets-name">Full Name</label>
+ <input type="text" id="form-widgets-name" name="form.widgets.name"
+ class="text-widget required textline-field"
+ value="Stephan Richter" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-gender">Gender</label>
+ <select id="form-widgets-gender" name="form.widgets.gender:list"
+ class="select-widget choice-field" size="1">
+ <option id="form-widgets-gender-novalue"
+ value="--NOVALUE--">no value</option>
+ <option id="form-widgets-gender-0" value="male"
+ selected="selected">male</option>
+ <option id="form-widgets-gender-1" value="female">female</option>
+ </select>
+ <input name="form.widgets.gender-empty-marker" type="hidden"
+ value="1" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-age">Age</label>
+ <input type="text" id="form-widgets-age" name="form.widgets.age"
+ class="text-widget int-field" value="20" />
+ </div>
+ <div class="action">
+ <input type="submit" id="form-buttons-apply" name="form.buttons.apply"
+ class="submit-widget button-field" value="Apply" />
+ </div>
+ </form>
+ </body>
+ </html>
+
+As you can see, the data are being pulled in from the context for the edit
+form. Next we will look at the behavior when submitting the form.
+
+
+Failure Upon Submission of Edit Form
+------------------------------------
+
+Let's now submit the form having some invalid data.
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.name': u'Claudia Richter',
+ ... 'form.widgets.gender': ['female'],
+ ... 'form.widgets.age': u'-1',
+ ... 'form.buttons.apply': u'Apply'}
+ ... )
+
+ >>> editForm = PersonEditForm(root[u'srichter'], request)
+ >>> addTemplate(editForm)
+ >>> editForm.update()
+ >>> print editForm.render()
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <i>There were some errors.</i>
+ <ul>
+ <li>
+ Age: <div class="error">The value cannot be a negative number.</div>
+ </li>
+ </ul>
+ <form action=".">
+ <div class="row">
+ <label for="form-widgets-id">ID</label>
+ <span id="form-widgets-id"
+ class="text-widget required textline-field">srichter</span>
+ </div>
+ <div class="row">
+ <label for="form-widgets-name">Full Name</label>
+ <input type="text" id="form-widgets-name" name="form.widgets.name"
+ class="text-widget required textline-field"
+ value="Claudia Richter" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-gender">Gender</label>
+ <select id="form-widgets-gender" name="form.widgets.gender:list"
+ class="select-widget choice-field" size="1">
+ <option id="form-widgets-gender-novalue"
+ value="--NOVALUE--">no value</option>
+ <option id="form-widgets-gender-0" value="male">male</option>
+ <option id="form-widgets-gender-1" value="female"
+ selected="selected">female</option>
+ </select>
+ <input name="form.widgets.gender-empty-marker" type="hidden"
+ value="1" />
+ </div>
+ <div class="row">
+ <b><div class="error">The value cannot be a negative number.</div>
+ </b><label for="form-widgets-age">Age</label>
+ <input type="text" id="form-widgets-age" name="form.widgets.age"
+ class="text-widget int-field" value="-1" />
+ </div>
+ <div class="action">
+ <input type="submit" id="form-buttons-apply" name="form.buttons.apply"
+ class="submit-widget button-field" value="Apply" />
+ </div>
+ </form>
+ </body>
+ </html>
+
+
+Successfully Editing Content
+----------------------------
+
+Let's now resubmit the form with valid data, so the data should be updated.
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.name': u'Claudia Richter',
+ ... 'form.widgets.gender': ['female'],
+ ... 'form.widgets.age': u'27',
+ ... 'form.buttons.apply': u'Apply'}
+ ... )
+
+ >>> editForm = PersonEditForm(root[u'srichter'], request)
+ >>> addTemplate(editForm)
+ >>> editForm.update()
+ >>> print testing.render(editForm, './/xmlns:i')
+ <i>Data successfully updated.</i>
+
+ >>> stephan = root[u'srichter']
+ >>> stephan.name
+ u'Claudia Richter'
+ >>> stephan.gender
+ 'female'
+ >>> stephan.age
+ 27
+
+When an edit form is successfully committed, a detailed object-modified event
+is sent out telling the system about the changes. To see the error, let's
+create an event subscriber for object-modified events:
+
+ >>> eventlog = []
+ >>> import zope.lifecycleevent
+ >>> @zope.component.adapter(zope.lifecycleevent.ObjectModifiedEvent)
+ ... def logEvent(event):
+ ... eventlog.append(event)
+ >>> zope.component.provideHandler(logEvent)
+
+Let's now submit the form again, successfully changing the age:
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.name': u'Claudia Richter',
+ ... 'form.widgets.gender': ['female'],
+ ... 'form.widgets.age': u'29',
+ ... 'form.buttons.apply': u'Apply'}
+ ... )
+
+ >>> editForm = PersonEditForm(root[u'srichter'], request)
+ >>> addTemplate(editForm)
+ >>> editForm.update()
+
+We can now look at the event:
+
+ >>> event = eventlog[-1]
+ >>> event
+ <zope...ObjectModifiedEvent object at ...>
+
+ >>> attrs = event.descriptions[0]
+ >>> attrs.interface
+ <InterfaceClass __builtin__.IPerson>
+ >>> attrs.attributes
+ ('age',)
+
+
+Successful Action with No Changes
+---------------------------------
+
+When submitting the form without any changes, the form will tell you so.
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.name': u'Claudia Richter',
+ ... 'form.widgets.gender': ['female'],
+ ... 'form.widgets.age': u'29',
+ ... 'form.buttons.apply': u'Apply'}
+ ... )
+
+ >>> editForm = PersonEditForm(root[u'srichter'], request)
+ >>> addTemplate(editForm)
+ >>> editForm.update()
+ >>> print testing.render(editForm, './/xmlns:i')
+ <i>No changes were applied.</i>
+
+
+Changing Status Messages
+------------------------
+
+Depending on the project, it is often desirable to change the status messages
+to fit the application. In ``zope.formlib`` this was hard to do, since the
+messages were buried within fairly complex methods that one did not want to
+touch. In this package all those messages are exposed as form attributes.
+
+There are three messages for the edit form:
+
+* ``formErrorsMessage`` -- Indicates that an error occurred while
+ applying the changes. This message is also available for the add form.
+
+* ``successMessage`` -- The form data was successfully applied.
+
+* ``noChangesMessage`` -- No changes were found in the form data.
+
+Let's now change the ``noChangesMessage``:
+
+ >>> editForm.noChangesMessage = u'No changes were detected in the form data.'
+ >>> editForm.update()
+ >>> print testing.render(editForm, './/xmlns:i')
+ <i>No changes were detected in the form data.</i>
+
+When even more flexibility is required within a project, one could also
+implement these messages as properties looking up an attribute value. However,
+we have found this to be a rare case.
+
+
+Creating Edit Forms for Dictionaries
+------------------------------------
+
+Sometimes it is not desirable to edit a class instance that implements the
+fields, but other types of object. A good example is the need to modify a
+simple dictionary, where the field names are the keys. To do that, a special
+data manager for dictionaries is available:
+
+ >>> from z3c.form import datamanager
+ >>> zope.component.provideAdapter(datamanager.DictionaryField)
+
+The only step the developer has to complete is to re-implement the form's
+``getContent()`` method to return the dictionary:
+
+ >>> personDict = {'id': u'rineichen', 'name': u'Roger Ineichen',
+ ... 'gender': None, 'age': None}
+ >>> class PersonDictEditForm(PersonEditForm):
+ ... def getContent(self):
+ ... return personDict
+
+We can now use the form as usual:
+
+ >>> editForm = PersonDictEditForm(None, TestRequest())
+ >>> addTemplate(editForm)
+ >>> editForm.update()
+ >>> print editForm.render()
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <form action=".">
+ <div class="row">
+ <label for="form-widgets-id">ID</label>
+ <span id="form-widgets-id"
+ class="text-widget required textline-field">rineichen</span>
+ </div>
+ <div class="row">
+ <label for="form-widgets-name">Full Name</label>
+ <input type="text" id="form-widgets-name"
+ name="form.widgets.name"
+ class="text-widget required textline-field"
+ value="Roger Ineichen" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-gender">Gender</label>
+ <select id="form-widgets-gender" name="form.widgets.gender:list"
+ class="select-widget choice-field" size="1">
+ <option id="form-widgets-gender-novalue"
+ value="--NOVALUE--" selected="selected">no value</option>
+ <option id="form-widgets-gender-0" value="male">male</option>
+ <option id="form-widgets-gender-1" value="female">female</option>
+ </select>
+ <input name="form.widgets.gender-empty-marker" type="hidden"
+ value="1" />
+ </div>
+ <div class="row">
+ <label for="form-widgets-age">Age</label>
+ <input type="text" id="form-widgets-age"
+ name="form.widgets.age" class="text-widget int-field"
+ value="20" />
+ </div>
+ <div class="action">
+ <input type="submit" id="form-buttons-apply"
+ name="form.buttons.apply" class="submit-widget button-field"
+ value="Apply" />
+ </div>
+ </form>
+ </body>
+ </html>
+
+Note that the name displayed in the form is identical to the one in the
+dictionary. Let's now submit a form to ensure that the data are also written to
+the dictionary:
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.name': u'Jesse Ineichen',
+ ... 'form.widgets.gender': ['male'],
+ ... 'form.widgets.age': u'5',
+ ... 'form.buttons.apply': u'Apply'}
+ ... )
+ >>> editForm = PersonDictEditForm(None, request)
+ >>> editForm.update()
+
+ >>> len(personDict)
+ 4
+ >>> personDict['age']
+ 5
+ >>> personDict['gender']
+ 'male'
+ >>> personDict['id']
+ u'rineichen'
+ >>> personDict['name']
+ u'Jesse Ineichen'
+
+
+Creating a Display Form
+-----------------------
+
+Creating a display form is simple; just instantiate, update and render it:
+
+ >>> class PersonDisplayForm(form.DisplayForm):
+ ... fields = field.Fields(IPerson)
+ ... template = ViewPageTemplateFile(
+ ... 'simple_display.pt', os.path.dirname(tests.__file__))
+
+ >>> display = PersonDisplayForm(stephan, TestRequest())
+ >>> display.update()
+ >>> print display.render()
+ Traceback (most recent call last):
+ ...
+ KeyError: '__conform__'
+ <BLANKLINE>
+ - Expression: "repeat/value/end"
+ - Filename: ...select_display.pt
+ - Location: (23:32)
+ <BLANKLINE>
+ - Source: ... tal:block condition="not:repeat/value/end">, </tal:block
+ ...
+
+Simple Form Customization
+-------------------------
+
+The form exposes several of the widget manager's attributes as attributes on
+the form. They are: ``mode``, ``ignoreContext``, ``ignoreRequest``, and
+``ignoreReadonly``.
+
+Here are the values for the display form we just created:
+
+ >>> display.mode
+ 'display'
+ >>> display.ignoreContext
+ False
+ >>> display.ignoreRequest
+ True
+ >>> display.ignoreReadonly
+ False
+
+These values should be equal to the ones of the widget manager:
+
+ >>> display.widgets.mode
+ 'display'
+ >>> display.widgets.ignoreContext
+ False
+ >>> display.widgets.ignoreRequest
+ True
+ >>> display.widgets.ignoreReadonly
+ False
+
+Now, if we change those values before updating the widgets, ...
+
+ >>> display.mode = interfaces.INPUT_MODE
+ >>> display.ignoreContext = True
+ >>> display.ignoreRequest = False
+ >>> display.ignoreReadonly = True
+
+... the widget manager will have the same values after updating the widgets:
+
+ >>> display.updateWidgets()
+
+ >>> display.widgets.mode
+ 'input'
+ >>> display.widgets.ignoreContext
+ True
+ >>> display.widgets.ignoreRequest
+ False
+ >>> display.widgets.ignoreReadonly
+ True
+
+
+Extending Forms
+---------------
+
+One very common use case is to extend forms. For example, you would like to
+use the edit form and its defined "Apply" button, but add another button
+yourself. Unfortunately, just inheriting the form is not enough, because the
+new button and handler declarations will override the inherited ones. Let me
+demonstrate the problem:
+
+ >>> class BaseForm(form.Form):
+ ... fields = field.Fields(IPerson).select('name')
+ ...
+ ... @button.buttonAndHandler(u'Apply')
+ ... def handleApply(self, action):
+ ... print 'success'
+
+ >>> BaseForm.fields.keys()
+ ['name']
+ >>> BaseForm.buttons.keys()
+ ['apply']
+ >>> BaseForm.handlers
+ <Handlers [<Handler for <Button 'apply' u'Apply'>>]>
+
+Let's now derive a form from the base form:
+
+ >>> class DerivedForm(BaseForm):
+ ... fields = field.Fields(IPerson).select('gender')
+ ...
+ ... @button.buttonAndHandler(u'Cancel')
+ ... def handleCancel(self, action):
+ ... print 'cancel'
+
+ >>> DerivedForm.fields.keys()
+ ['gender']
+ >>> DerivedForm.buttons.keys()
+ ['cancel']
+ >>> DerivedForm.handlers
+ <Handlers [<Handler for <Button 'cancel' u'Cancel'>>]>
+
+The obvious method to "inherit" the base form's information is to copy it
+over:
+
+ >>> class DerivedForm(BaseForm):
+ ... fields = BaseForm.fields.copy()
+ ... buttons = BaseForm.buttons.copy()
+ ... handlers = BaseForm.handlers.copy()
+ ...
+ ... fields += field.Fields(IPerson).select('gender')
+ ...
+ ... @button.buttonAndHandler(u'Cancel')
+ ... def handleCancel(self, action):
+ ... print 'cancel'
+
+ >>> DerivedForm.fields.keys()
+ ['name', 'gender']
+ >>> DerivedForm.buttons.keys()
+ ['apply', 'cancel']
+ >>> DerivedForm.handlers
+ <Handlers
+ [<Handler for <Button 'apply' u'Apply'>>,
+ <Handler for <Button 'cancel' u'Cancel'>>]>
+
+But this is pretty clumsy. Instead, the ``form`` module provides a helper
+method that will do the extending for you:
+
+ >>> class DerivedForm(BaseForm):
+ ... form.extends(BaseForm)
+ ...
+ ... fields += field.Fields(IPerson).select('gender')
+ ...
+ ... @button.buttonAndHandler(u'Cancel')
+ ... def handleCancel(self, action):
+ ... print 'cancel'
+
+ >>> DerivedForm.fields.keys()
+ ['name', 'gender']
+ >>> DerivedForm.buttons.keys()
+ ['apply', 'cancel']
+ >>> DerivedForm.handlers
+ <Handlers
+ [<Handler for <Button 'apply' u'Apply'>>,
+ <Handler for <Button 'cancel' u'Cancel'>>]>
+
+If you, for example do not want to extend the buttons, you can turn that off:
+
+ >>> class DerivedForm(BaseForm):
+ ... form.extends(BaseForm, ignoreButtons=True)
+ ...
+ ... fields += field.Fields(IPerson).select('gender')
+ ...
+ ... @button.buttonAndHandler(u'Cancel')
+ ... def handleCancel(self, action):
+ ... print 'cancel'
+
+ >>> DerivedForm.fields.keys()
+ ['name', 'gender']
+ >>> DerivedForm.buttons.keys()
+ ['cancel']
+ >>> DerivedForm.handlers
+ <Handlers
+ [<Handler for <Button 'apply' u'Apply'>>,
+ <Handler for <Button 'cancel' u'Cancel'>>]>
+
+If you, for example do not want to extend the handlers, you can turn that off:
+
+ >>> class DerivedForm(BaseForm):
+ ... form.extends(BaseForm, ignoreHandlers=True)
+ ...
+ ... fields += field.Fields(IPerson).select('gender')
+ ...
+ ... @button.buttonAndHandler(u'Cancel')
+ ... def handleCancel(self, action):
+ ... print 'cancel'
+
+ >>> DerivedForm.fields.keys()
+ ['name', 'gender']
+ >>> DerivedForm.buttons.keys()
+ ['apply', 'cancel']
+ >>> DerivedForm.handlers
+ <Handlers [<Handler for <Button 'cancel' u'Cancel'>>]>
+
+
+Custom widget factories
+-----------------------
+
+Another important part of a form is that we can use custom widgets. We can do
+this in a form by defining a widget factory for a field. We can get the field
+from the fields collection e.g. ``fields['foo']``. This means, we can define
+new widget factories by defining ``fields['foo'].widgetFactory = MyWidget``.
+Let's show a sample and define a custom widget:
+
+ >>> from z3c.form.browser import text
+ >>> class MyWidget(text.TextWidget):
+ ... """My new widget."""
+ ... klass = u'MyCSS'
+
+Now we can define a field widget factory:
+
+ >>> def MyFieldWidget(field, request):
+ ... """IFieldWidget factory for MyWidget."""
+ ... return widget.FieldWidget(field, MyWidget(request))
+
+We register the ``MyWidget`` in a form like:
+
+ >>> class MyEditForm(form.EditForm):
+ ...
+ ... fields = field.Fields(IPerson)
+ ... fields['name'].widgetFactory = MyFieldWidget
+
+We can see that the custom widget gets used in the rendered form:
+
+ >>> myEdit = MyEditForm(root[u'srichter'], TestRequest())
+ >>> addTemplate(myEdit)
+ >>> myEdit.update()
+ >>> print testing.render(myEdit, './/xmlns:input[@id="form-widgets-name"]')
+ <input type="text" id="form-widgets-name"
+ name="form.widgets.name" class="MyCSS required textline-field"
+ value="Claudia Richter" />
+
+
+Hidden fields
+-------------
+
+Another important part of a form is that we can generate hidden widgets. We can
+do this in a form by defining a widget mode. We can do this by override the
+setUpWidgets method.
+
+ >>> class HiddenFieldEditForm(form.EditForm):
+ ...
+ ... fields = field.Fields(IPerson)
+ ... fields['name'].widgetFactory = MyFieldWidget
+ ...
+ ... def updateWidgets(self):
+ ... super(HiddenFieldEditForm, self).updateWidgets()
+ ... self.widgets['age'].mode = interfaces.HIDDEN_MODE
+
+We can see that the widget gets rendered as hidden:
+
+ >>> hiddenEdit = HiddenFieldEditForm(root[u'srichter'], TestRequest())
+ >>> addTemplate(hiddenEdit)
+ >>> hiddenEdit.update()
+ >>> print testing.render(hiddenEdit, './/xmlns:input[@id="form-widgets-age"]')
+ <input type="hidden" id="form-widgets-age"
+ name="form.widgets.age" class="hidden-widget"
+ value="29" />
+
+
+Actions with Errors
+-------------------
+
+Even though the data might be validated correctly, it sometimes happens that
+data turns out to be invalid while the action is executed. In those cases a
+special action execution error can be raised that wraps the original error.
+
+ >>> class PersonAddForm(form.AddForm):
+ ...
+ ... fields = field.Fields(IPerson).select('id')
+ ...
+ ... @button.buttonAndHandler(u'Check')
+ ... def handleCheck(self, action):
+ ... data, errors = self.extractData()
+ ... if data['id'] in self.getContent():
+ ... raise interfaces.WidgetActionExecutionError(
+ ... 'id', zope.interface.Invalid('Id already exists'))
+
+In this case the action execution error is specific to a widget. The framework
+will attach a proper error view to the widget and the widget manager:
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.id': u'srichter',
+ ... 'form.buttons.check': u'Check'}
+ ... )
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+
+ >>> addForm.widgets.errors
+ (<InvalidErrorViewSnippet for Invalid>,)
+ >>> addForm.widgets['id'].error
+ <InvalidErrorViewSnippet for Invalid>
+ >>> addForm.status
+ u'There were some errors.'
+
+If the error is non-widget specific, then we can simply use the generic action
+execution error:
+
+ >>> class PersonAddForm(form.AddForm):
+ ...
+ ... fields = field.Fields(IPerson).select('id')
+ ...
+ ... @button.buttonAndHandler(u'Check')
+ ... def handleCheck(self, action):
+ ... raise interfaces.ActionExecutionError(
+ ... zope.interface.Invalid('Some problem occurred.'))
+
+Let's have a look at the result:
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+
+ >>> addForm.widgets.errors
+ (<InvalidErrorViewSnippet for Invalid>,)
+ >>> addForm.status
+ u'There were some errors.'
+
+**Note**:
+
+ The action execution errors are connected to the form via an event
+ listener called ``handlerActionError``. This event listener listens for
+ ``IActionErrorEvent`` events. If the event is called for an action associated
+ with a form, the listener does its work as seen above. If the action is not
+ coupled to a form, then event listener does nothing:
+
+ >>> from z3c.form import action
+
+ >>> cancel = action.Action(request, u'Cancel')
+ >>> event = action.ActionErrorOccurred(cancel, ValueError(3))
+
+ >>> form.handleActionError(event)
+
+
+Applying Changes
+----------------
+
+When applying the data of a form to a content component, the function
+``applyChanges()`` is called. It simply iterates through the fields of the
+form and uses the data managers to store the values. The output of the
+function is a list of changes:
+
+ >>> roger = Person(u'roger', u'Roger')
+ >>> roger
+ <Person u'Roger'>
+
+ >>> class BaseForm(form.Form):
+ ... fields = field.Fields(IPerson).select('name')
+ >>> myForm = BaseForm(roger, TestRequest())
+
+ >>> form.applyChanges(myForm, roger, {'name': u'Roger Ineichen'})
+ {<InterfaceClass __builtin__.IPerson>: ['name']}
+
+ >>> roger
+ <Person u'Roger Ineichen'>
+
+When a field is missing from the data, it is simply skipped:
+
+ >>> form.applyChanges(myForm, roger, {})
+ {}
+
+If the new and old value are identical, storing the data is skipped as well:
+
+ >>> form.applyChanges(myForm, roger, {'name': u'Roger Ineichen'})
+ {}
+
+In some cases the data converter for a field-widget pair returns the
+``NOT_CHANGED`` value. In this case, the field is skipped as well:
+
+ >>> form.applyChanges(myForm, roger, {'name': interfaces.NOT_CHANGED})
+ {}
+
+ >>> roger
+ <Person u'Roger Ineichen'>
+
+
+Refreshing actions
+------------------
+
+Sometimes, it's useful to update actions again after executing them,
+because some conditions could have changed. For example, imagine
+we have a sequence edit form that has a delete button. We don't
+want to show delete button when the sequence is empty. The button
+condition would handle this, but what if the sequence becomes empty
+as a result of execution of the delete action that was available?
+In that case we want to refresh our actions to new conditions to make
+our delete button not visible anymore. The ``refreshActions`` form
+variable is intended to handle this case.
+
+Let's create a simple form with an action that clears our context
+sequence.
+
+ >>> class SequenceForm(form.Form):
+ ...
+ ... @button.buttonAndHandler(u'Empty', condition=lambda form:bool(form.context))
+ ... def handleEmpty(self, action):
+ ... self.context[:] = []
+ ... self.refreshActions = True
+
+First, let's illustrate simple cases, when no button is pressed.
+The button will be available when context is not empty.
+
+ >>> context = [1, 2, 3, 4]
+ >>> request = TestRequest()
+ >>> myForm = SequenceForm(context, request)
+ >>> myForm.update()
+ >>> addTemplate(myForm)
+ >>> print testing.render(myForm, './/xmlns:div[@class="action"]')
+ <div class="action">
+ <input type="submit" id="form-buttons-empty" name="form.buttons.empty"
+ class="submit-widget button-field" value="Empty" />
+ </div>
+
+The button will not be available when the context is empty.
+
+ >>> context = []
+ >>> request = TestRequest()
+ >>> myForm = SequenceForm(context, request)
+ >>> myForm.update()
+ >>> addTemplate(myForm)
+ >>> print testing.render(myForm, './/xmlns:form')
+ <form action=".">
+ </form>
+
+Now, the most interesting case when context is not empty, but becomes
+empty as a result of pressing the "empty" button. We set the
+``refreshActions`` flag in the action handler, so our actions should
+be updated to new conditions.
+
+ >>> context = [1, 2, 3, 4, 5]
+ >>> request = TestRequest(form={
+ ... 'form.buttons.empty': u'Empty'}
+ ... )
+ >>> myForm = SequenceForm(context, request)
+ >>> myForm.update()
+ >>> addTemplate(myForm)
+ >>> print testing.render(myForm, './/xmlns:form')
+ <form action=".">
+ </form>
+
+Integration tests
+-----------------
+
+Identifying the different forms can be important if it comes to layout
+template lookup. Let's ensure that we support the right interfaces for the
+different forms.
+
+
+Form
+~~~~
+
+ >>> from zope.interface.verify import verifyObject
+ >>> from z3c.form import interfaces
+ >>> obj = form.Form(None, None)
+ >>> verifyObject(interfaces.IForm, obj)
+ True
+
+ >>> interfaces.IForm.providedBy(obj)
+ True
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IDisplayForm.providedBy(obj)
+ False
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IEditForm.providedBy(obj)
+ False
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IAddForm.providedBy(obj)
+ False
+
+
+DisplayForm
+~~~~~~~~~~~
+
+ >>> from z3c.form import interfaces
+ >>> obj = form.DisplayForm(None, None)
+ >>> verifyObject(interfaces.IDisplayForm, obj)
+ True
+
+ >>> interfaces.IForm.providedBy(obj)
+ True
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IDisplayForm.providedBy(obj)
+ True
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IEditForm.providedBy(obj)
+ False
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IAddForm.providedBy(obj)
+ False
+
+
+EditForm
+~~~~~~~~
+
+ >>> from z3c.form import interfaces
+ >>> obj = form.EditForm(None, None)
+ >>> verifyObject(interfaces.IEditForm, obj)
+ True
+
+ >>> interfaces.IForm.providedBy(obj)
+ True
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IDisplayForm.providedBy(obj)
+ False
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IEditForm.providedBy(obj)
+ True
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IAddForm.providedBy(obj)
+ False
+
+
+AddForm
+~~~~~~~
+
+ >>> from z3c.form import interfaces
+ >>> obj = form.AddForm(None, None)
+ >>> verifyObject(interfaces.IAddForm, obj)
+ True
+
+ >>> interfaces.IForm.providedBy(obj)
+ True
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IDisplayForm.providedBy(obj)
+ False
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IEditForm.providedBy(obj)
+ False
+
+ >>> from z3c.form import interfaces
+ >>> interfaces.IAddForm.providedBy(obj)
+ True
More information about the checkins
mailing list