zope.formlib

https://github.com/zopefoundation/zope.formlib/actions/workflows/tests.yml/badge.svg Documentation Status

Forms are web components that use widgets to display and input data. Typically a template displays the widgets by accessing an attribute or method on an underlying class.

Documentation is hosted at https://zopeformlib.readthedocs.io/en/latest/

Introduction and Basics

Forms

Forms are web components that use widgets to display and input data. Typically a template displays the widgets by accessing an attribute or method on an underlying class.

This document describes some tools to assist in form development. In the examples, we will show “forms” that are generated with simple print statements to keep the examples simpler. Most forms will use templates in practice.

This document starts with low-level APIs. We eventually build up to higher-level APIs that allow forms to be defined with just a little bit of meta data. Impatient readers may wish to skip to the later sections, especially the section on Helpful base classes. :)

A form class can define ordered collections of “form fields” using the Fields constructor. Form fields are distinct from and build on schema fields. A schema field specified attribute values. Form fields specify how a schema field should be used in a form. The simplest way to define a collection of form fields is by passing a schema to the Fields constructor:

>>> from zope import interface, schema
>>> class IOrder(interface.Interface):
...     identifier = schema.Int(title=u"Identifier", readonly=True)
...     name = schema.TextLine(title=u"Name")
...     min_size = schema.Float(title=u"Minimum size")
...     max_size = schema.Float(title=u"Maximum size")
...     color = schema.TextLine(title=u"Color", required=False)
...     now = schema.Datetime(title=u"Now", readonly=True)
>>> from zope.formlib import form
>>> class MyForm:
...     form_fields = form.Fields(IOrder)

This sets up a set of form fields from the interface, IOrder.

>>> len(MyForm.form_fields)
6
>>> [w.__name__ for w in MyForm.form_fields]
['identifier', 'name', 'min_size', 'max_size', 'color', 'now']

We can access individual form fields by name:

>>> MyForm.form_fields['name'].__name__
'name'

We can also select and order subsets using the select method of form fields:

>>> [w.__name__ for w in MyForm.form_fields.select('name', 'identifier')]
['name', 'identifier']

or by omitting fields:

>>> [w.__name__ for w in MyForm.form_fields.omit('now', 'identifier')]
['name', 'min_size', 'max_size', 'color']

We can omit read-only fields using the omit_readonly option when setting up the fields:

>>> class MyForm:
...     form_fields = form.Fields(IOrder, omit_readonly=True)
>>> [w.__name__ for w in MyForm.form_fields]
['name', 'min_size', 'max_size', 'color']

Getting HTML

Having defined form fields, we can use them to generate HTML forms. Typically, this is done at run time by form class instances. Let’s look at an example that displays some input widgets:

>>> class MyForm:
...     form_fields = form.Fields(IOrder, omit_readonly=True)
...
...     def __init__(self, context, request):
...         self.context, self.request = context, request
...
...     def __call__(self, ignore_request=False):
...         widgets = form.setUpWidgets(
...             self.form_fields, 'form', self.context, self.request,
...             ignore_request=ignore_request)
...         return '\n'.join([w() for w in widgets])

Here we used setUpWidgets to create widget instances from our form-field specifications. The second argument to setUpWidgets is a form prefix. All of the widgets on this form are given the same prefix. This allows multiple forms to be used within a single form tag, assuming that each form uses a different form prefix.

Now, we can display the form:

>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value=""  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value=""  />
<input class="textType" id="form.color" name="form.color" size="20"
       type="text" value=""  />

If the request contains any form data, that will be reflected in the output:

>>> request.form['form.name'] = u'bob'
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value=""  />
<input class="textType" id="form.color" name="form.color"
       size="20" type="text" value=""  />

Sometimes we don’t want this behavior: we want to ignore the request values, particularly after a form has been processed and before it is drawn again. This can be accomplished with the ignore_request argument in setUpWidgets.

>>> print(MyForm(None, request)(ignore_request=True))
... # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value=""  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value=""  />
<input class="textType" id="form.color" name="form.color" size="20"
       type="text" value=""  />

Reading data

Of course, we don’t just want to display inputs. We want to get the input data. We can use getWidgetsData for that:

>>> from pprint import pprint
>>> class MyForm:
...     form_fields = form.Fields(IOrder, omit_readonly=True)
...
...     def __init__(self, context, request):
...         self.context, self.request = context, request
...
...     def __call__(self):
...         widgets = form.setUpWidgets(
...             self.form_fields, 'form', self.context, self.request)
...
...         if 'submit' in self.request:
...             data = {}
...             errors = form.getWidgetsData(widgets, 'form', data)
...             if errors:
...                 print('There were errors:')
...                 for error in errors:
...                     print(error)
...         else:
...             data = None
...
...         for w in widgets:
...             print(w())
...             error = w.error()
...             if error:
...                 print(error)
...
...         return data

We check for a ‘submit’ variable in the form and, if we see it, we try to get the data, and errors. We call getWidgetsData, passing:

  • Our widgets
  • The form prefix, and
  • A data dictionary to contain input values found

The keys in the data dictionary have the form prefix stripped off.

If there are errors, we print them. When we display the widgets, we also check for errors and show them if present. Let’s add a submit variable:

>>> request.form['form.min_size'] = u''
>>> request.form['form.max_size'] = u''
>>> request.form['submit'] = u'Submit'
>>> MyForm(None, request)() # doctest: +NORMALIZE_WHITESPACE
There were errors:
('min_size', u'Minimum size', RequiredMissing('min_size'))
('max_size', u'Maximum size', RequiredMissing('max_size'))
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value=""  />
<span class="error">Required input is missing.</span>
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value=""  />
<span class="error">Required input is missing.</span>
<input class="textType" id="form.color" name="form.color" size="20"
       type="text" value=""  />
{'name': u'bob'}

Note that we got an error because we omitted the values for min_size and max size. If we provide an invalid value, we’ll get an error too:

>>> request.form['form.min_size'] = u'bob'
>>> MyForm(None, request)() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
There were errors:
(u'Invalid floating point data', ...ValueError...)
('max_size', u'Maximum size', RequiredMissing('max_size'))
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="bob"  />
<span class="error">Invalid floating point data</span>
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value=""  />
<span class="error">Required input is missing.</span>
<input class="textType" id="form.color" name="form.color" size="20"
       type="text" value=""  />
{'name': u'bob'}

If we provide valid data, we’ll get the data back:

>>> request.form['form.min_size'] = u'42'
>>> request.form['form.max_size'] = u'142'
>>> pprint(MyForm(None, request)(), width=1)
... # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="142.0"  />
<input class="textType" id="form.color" name="form.color" size="20"
       type="text" value=""  />
{'max_size': 142.0,
 'min_size': 42.0,
 'name': u'bob'}

It’s up to the form to decide what to do with the information.

Invariants

The getWidgetsData function checks individual field constraints. Interfaces can also provide invariants that we may also want to check. The checkInvariants function can be used to do that.

In our order example, it makes sense to require that the maximum is greater than or equal to the minimum:

>>> class IOrder(interface.Interface):
...     identifier = schema.Int(title=u"Identifier", readonly=True)
...     name = schema.TextLine(title=u"Name")
...     min_size = schema.Float(title=u"Minimum size")
...     max_size = schema.Float(title=u"Maximum size")
...     now = schema.Datetime(title=u"Now", readonly=True)
...
...     @interface.invariant
...     def maxGreaterThanMin(order):
...         if order.max_size < order.min_size:
...             raise interface.Invalid("Maximum is less than Minimum")

We can update our form to check the invariant using checkInvariants:

>>> class MyForm:
...     form_fields = form.Fields(IOrder, omit_readonly=True)
...
...     def __init__(self, context, request):
...         self.context, self.request = context, request
...
...     def __call__(self):
...         widgets = form.setUpWidgets(
...             self.form_fields, 'form', self.context, self.request)
...
...         if 'submit' in self.request:
...             data = {}
...             errors = form.getWidgetsData(widgets, 'form', data)
...             invariant_errors = form.checkInvariants(
...                 self.form_fields, data, self.context)
...             if errors:
...                 print('There were field errors:')
...                 for error in errors:
...                     print(error)
...
...             if invariant_errors:
...                 print('There were invariant errors:')
...                 for error in invariant_errors:
...                     print(error)
...         else:
...             data = None
...
...         for w in widgets:
...             print(w())
...             error = w.error()
...             if error:
...                 print(error)
...
...         return data

If we display the form again, we’ll get the same result:

>>> pprint(MyForm(None, request)(), width=1)
... # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="142.0"  />
{'max_size': 142.0,
 'min_size': 42.0,
 'name': u'bob'}

But if we reduce the maximum below the minimum, we’ll get an invariant error:

>>> request.form['form.min_size'] = u'42'
>>> request.form['form.max_size'] = u'14'
>>> pprint(MyForm(None, request)(), width=1)
... # doctest: +NORMALIZE_WHITESPACE
There were invariant errors:
Maximum is less than Minimum
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="14.0"  />
{'max_size': 14.0,
 'min_size': 42.0,
 'name': u'bob'}

We can have field errors and invariant errors:

>>> request.form['form.name'] = u''
>>> pprint(MyForm(None, request)(), width=1)
... # doctest: +NORMALIZE_WHITESPACE
There were field errors:
('name', u'Name', RequiredMissing('name'))
There were invariant errors:
Maximum is less than Minimum
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value=""  />
<span class="error">Required input is missing.</span>
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="14.0"  />
{'max_size': 14.0,
 'min_size': 42.0}

If the inputs for some fields tested by invariants are missing, the invariants are ignored:

>>> request.form['form.max_size'] = u''
>>> pprint(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
There were field errors:
('name', u'Name', RequiredMissing('name'))
('max_size', u'Maximum size', RequiredMissing('max_size'))
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value=""  />
<span class="error">Required input is missing.</span>
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value=""  />
<span class="error">Required input is missing.</span>
{'min_size': 42.0}

Edit Forms

A common application of forms is edit forms. Edit forms are special in 2 ways:

  • We want to get the initial data for widgets from the object being edited.
  • If there are no errors, we want to apply the changes back to the object being edited.

The form package provides some functions to assist with creating edit forms. When we set up our form_fields, we use the render_context option, which uses data from the context passed to setUpWidgets. Let’s create a content class that provides IOrder and a simple form that uses it:

>>> import datetime
>>> @interface.implementer(IOrder)
... class Order:
...
...     def __init__(self, identifier):
...         self.identifier = identifier
...         self.name = 'unknown'
...         self.min_size = 0.0
...         self.max_size = 0.0
...
...     now = property(lambda self: datetime.datetime.now())
>>> order = Order(1)
>>> class MyForm:
...     form_fields = form.Fields(
...                  IOrder, omit_readonly=True, render_context=True)
...
...     def __init__(self, context, request):
...         self.context, self.request = context, request
...
...     def __call__(self, ignore_request=False):
...         widgets = form.setUpWidgets(
...             self.form_fields, 'form', self.context, self.request,
...             ignore_request=ignore_request)
...
...         return '\n'.join([w() for w in widgets])
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="" />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0" />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="" />

Note that, in this case, we got the values from the request, because we used an old request. If we want to redraw the form after processing a request, it is safest to pass ignore_request = True to setUpWidgets so that the form is redrawn with the values as found in the object, not on the request.

>>> print(MyForm(order, request)(ignore_request=True))
... # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="unknown"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="0.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="0.0"  />

If we use a new request, we will of course get the same result:

>>> request = TestRequest()
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="unknown"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="0.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="0.0"  />

If we include read-only fields in an edit form, they will get display widgets:

>>> class MyForm:
...     form_fields = form.Fields(IOrder, render_context=True)
...     form_fields = form_fields.omit('now')
...
...     def __init__(self, context, request):
...         self.context, self.request = context, request
...
...     def __call__(self):
...         widgets = form.setUpWidgets(
...             self.form_fields, 'form', self.context, self.request)
...
...         return '\n'.join([w() for w in widgets])
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="unknown"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="0.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="0.0"  />

When the form is submitted, we need to apply the changes back to the object. We can use the applyChanges function for that:

>>> class MyForm:
...     form_fields = form.Fields(IOrder, render_context=True)
...     form_fields = form_fields.omit('now')
...
...     def __init__(self, context, request):
...         self.context, self.request = context, request
...
...     def __call__(self):
...         widgets = form.setUpWidgets(
...             self.form_fields, 'form', self.context, self.request)
...
...         if 'submit' in self.request:
...             data = {}
...             errors = form.getWidgetsData(widgets, 'form', data)
...             invariant_errors = form.checkInvariants(
...                 self.form_fields, data, self.context)
...             if errors:
...                 print('There were field errors:')
...                 for error in errors:
...                     print(error)
...
...             if invariant_errors:
...                 print('There were invariant errors:')
...                 for error in invariant_errors:
...                     print(error)
...
...             if not errors and not invariant_errors:
...                 changed = form.applyChanges(
...                     self.context, self.form_fields, data)
...
...         else:
...             data = changed = None
...
...         for w in widgets:
...             print(w())
...             error = w.error()
...             if error:
...                 print(error)
...
...         if changed:
...             print('Object updated')
...         else:
...             print('No changes')
...
...         return data

Now, if we submit the form with some data:

>>> request.form['form.name'] = u'bob'
>>> request.form['form.min_size'] = u'42'
>>> request.form['form.max_size'] = u'142'
>>> request.form['submit'] = u''
>>> pprint(MyForm(order, request)(), width=1)
... # doctest: +NORMALIZE_WHITESPACE
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="142.0"  />
Object updated
{'max_size': 142.0,
 'min_size': 42.0,
 'name': u'bob'}
>>> order.name
u'bob'
>>> order.max_size
142.0
>>> order.min_size
42.0

Note, however, that if we submit the same request, we’ll see that no changes were applied:

>>> pprint(MyForm(order, request)(), width=1)
... # doctest: +NORMALIZE_WHITESPACE
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="142.0"  />
No changes
{'max_size': 142.0,
 'min_size': 42.0,
 'name': u'bob'}

because the new and old values are the same.

The code we included in MyForm above is generic: it applies to any edit form.

Actions

Our commit logic is a little complicated. It would be far more complicated if there were multiple submit buttons.

We can use action objects to provide some distribution of application logic.

An action is an object that represents a handler for a submit button.

In the most common case, an action accepts a label and zero or more options provided as keyword parameters:

condition
A callable or name of a method to call to test whether the action is applicable. if the value is a method name, then the method will be passed the action when called, otherwise, the callable will be passed the form and the action.
validator
A callable or name of a method to call to validate and collect inputs. This is called only if the action was submitted and if the action either has no condition, or the condition evaluates to a true value. If the validator is provided as a method name, the method will be called with the action and a dictionary in which to save data. If the validator is provided as a callable, the callable will be called with the form, the action, and a dictionary in which to save data. The validator normally returns a (usually empty) list of widget input errors. It may also return None to behave as if the action wasn’t submitted.
success
A handler, called when the the action was submitted and there are no validation errors. The handler may be provided as either a callable or a method name. If the handler is provided as a method name, the method will be called with the action and a dictionary containing the form data. If the success handler is provided as a callable, the callable will be called with the form, the action, and a dictionary containing the data. The handler may return a form result (e.g. page), or may return None to indicate that the form should generate it’s own output.
failure
A handler, called when the the action was submitted and there are validation errors. The handler may be provided as either a callable or a method name. If the handler is provided as a method name, the method will be called with the action, a dictionary containing the form data, and a list of errors. If the failure handler is provided as a callable, the callable will be called with the form, the action, a dictionary containing the data, and a list of errors. The handler may return a form result (e.g. page), or may return None to indicate that the form should generate it’s own output.
prefix
A form prefix for the action. When generating submit actions, the prefix should be combined with the action name, separating the two with a dot. The default prefix is “actions”form.
name
The action name, without a prefix. If the label is a valid Python identifier, then the lower-case label will be used, otherwise, a hex encoding of the label will be used. If for some strange reason the labels in a set of actions with the same prefix is not unique, a name will have to be given for some actions to get unique names.
data
A bag of extra information that can be used by handlers, validators, or conditions.

Let’s update our edit form to use an action. We are also going to rearrange our form quite a bit to make things more modular:

  • We’ve created a separate validation method to validate inputs and compute errors.
  • We’ve created a handle_edit_action method for applying changes.
  • We’ve created a template method for displaying the form. Normally, this would be a ZPT template, but we just provide a Python version here.
  • We’ve created a call method that is described below
  • We’ve defined a number of instance attributes for passing information between the various methods:
    • status is a string that, if set, is displayed at the top of the form.
    • errors is the set of errors found when validating.
    • widgets is a list of set-up widgets

Here’s the new version:

>>> class MyForm:
...     form_fields = form.Fields(IOrder, render_context=True)
...     form_fields = form_fields.omit('now')
...
...     status = errors = None
...     prefix = 'form'
...
...     actions = form.Actions(
...         form.Action('Edit', success='handle_edit_action'),
...         )
...
...     def __init__(self, context, request):
...         self.context, self.request = context, request
...
...     def validate(self, action, data):
...         return (form.getWidgetsData(self.widgets, self.prefix, data) +
...                 form.checkInvariants(
...                     self.form_fields, data, self.context))
...
...     def handle_edit_action(self, action, data):
...         if form.applyChanges(self.context, self.form_fields, data):
...             self.status = 'Object updated'
...         else:
...             self.status = 'No changes'
...
...     def template(self):
...         if self.status:
...             print(self.status)
...
...         result = []
...
...         if self.errors:
...             result.append('There were errors:')
...             for error in self.errors:
...                 result.append(str(error))
...
...         for w in self.widgets:
...             result.append(w())
...             error = w.error()
...             if error:
...                 result.append(str(error))
...
...         for action in self.actions:
...             result.append(action.render())
...
...         return '\n'.join(result)
...
...     def __call__(self):
...         self.widgets = form.setUpWidgets(
...             self.form_fields, self.prefix, self.context, self.request)
...
...         data = {}
...         errors, action = form.handleSubmit(
...                              self.actions, data, self.validate)
...         self.errors = errors
...
...         if errors:
...             result = action.failure(data, errors)
...         elif errors is not None:
...             result = action.success(data)
...         else:
...             result = None
...
...         if result is None:
...             result = self.template()
...
...         return result

Lets walk through the __call__ method.

  • We set up our widgets as before.
  • We use handleSubmit to validate our data. We pass the form, actions, prefix, and validate method. For each action, handleSubmit checks to see if the action was submitted. If the action was submitted, it checks to see if it has a validator. If the action has a validator, the action’s validator is called, otherwise the validator passed is called. The validator result (a list of widget input errors) and the action are returned. If no action was submitted, then None is returned for the errors and the action.
  • If a action was submitted and there were no errors, we call the success method on the action. If the action has a handler defined, it will be called and the return value is returned, otherwise None is returned. A return value of None indicates that the form should generate it’s own result.
  • If a action was submitted but there were errors, we call the action’s failure method. If the action has a failure handler defined, it will be called and the return value is returned, otherwise None is returned. A return value of None indicates that the form should generate it’s own result.
  • No action was submitted, the result is set to None.
  • If we don’t have a result, we generate one with our template.

Let’s try the new version of our form:

>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="142.0"  />
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />

In this case, we didn’t get any output about changes because the request form data didn’t include a submit action that matched our action definition. Let’s add one and try again:

>>> request.form['form.actions.edit'] = u''
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
No changes
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="142.0"  />
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />

This time, we got a status message indicating that there weren’t any changes.

Let’s try changing some data:

>>> request.form['form.max_size'] = u'10/0'
>>> print(MyForm(order, request)())
... # doctest: +NORMALIZE_WHITESPACE
There were errors:
(u'Invalid floating point data',...ValueError...)
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="10/0"  />
<span class="error">Invalid floating point data</span>
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />

Oops, we had a typo, let’s fix it:

>>> request.form['form.max_size'] = u'10.0'
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
There were errors:
Maximum is less than Minimum
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="42.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="10.0"  />
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />

Oh yeah, we need to reduce the minimum too: :)

>>> request.form['form.min_size'] = u'1.0'
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
Object updated
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="1.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="10.0"  />
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />

Ah, much better. And our order has been updated:

>>> order.max_size
10.0
>>> order.min_size
1.0

Helpful base classes

Our form has a lot of repetitive code. A number of helpful base classes provide standard form implementation.

Form

The Form base class provides a number of common attribute definitions. It provides:

__init__
A constructor
validate
A default validation method
__call__
To render the form
template
A default template. Note that this is a NamedTemplate named “default”, so the template may also be overridden by registering an alternate default template.
prefix
A string added to all widget and action names.
setPrefix
method for changing the prefix
availableActions
method for getting available actions
adapters
Dictionary of objects implementing each given schema

Subclasses need to:

  • Provide a form_fields variable containing a list of form fields
  • a actions attribute containing a list of action definitions

Subclasses may:

  • Provide a label function or message id to produce a form label.

  • Override the setUpWidgets method to control how widgets are set up. This is fairly rarely needed.

  • Override the template. The form defines variables:

    status

    providing a short summary of the operation performed.

    widgets

    A collection of widgets, which can be accessed through iteration or by name

    errors

    A (possibly empty) list of errors

Let’s update our example to use the base class:

>>> class MyForm(form.Form):
...     form_fields = form.Fields(IOrder, render_context=True)
...     form_fields = form_fields.omit('now')
...
...     @form.action("Edit", failure='handle_edit_action_failure')
...     def handle_edit_action(self, action, data):
...         if form.applyChanges(self.context, self.form_fields, data):
...             self.status = 'Object updated'
...         else:
...             self.status = 'No changes'
...
...     def handle_edit_action_failure(self, action, data, errors):
...         self.status = 'There were %d errors.' % len(errors)

We inherited most of our behavior from the base class.

We also used the action decorator. The action decorator:

  • creates an actions variable if one isn’t already created,
  • defines an action with the given label and any other arguments, and
  • appends the action to the actions list.

The action decorator accepts the same arguments as the Action class with the exception of the success option.

The creation of the actions is a bit magic, but provides simplification in common cases.

Now we can try out our form:

>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
No changes
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="1.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="10.0"  />
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />
>>> request.form['form.min_size'] = u'20.0'
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
There were 1 errors.
Invalid: Maximum is less than Minimum
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="20.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="10.0"  />
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />
>>> request.form['form.max_size'] = u'30.0'
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
Object updated
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="20.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="30.0"  />
<input type="submit" id="form.actions.edit" name="form.actions.edit"
       value="Edit" class="button" />
>>> order.max_size
30.0
>>> order.min_size
20.0
EditForm

Our handle_edit_action action is common to edit forms. An EditForm base class captures this commonality. It also sets up widget widgets a bit differently. The EditForm base class sets up widgets as if the form fields had been set up with the render_context option.

>>> class MyForm(form.EditForm):
...     form_fields = form.Fields(IOrder)
...     form_fields = form_fields.omit('now')
>>> request.form['form.actions.apply'] = u''
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
No changes
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="20.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="30.0"  />
<input type="submit" id="form.actions.apply" name="form.actions.apply"
       value="Apply" class="button" />
>>> request.form['form.min_size'] = u'40.0'
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
There were errors
Invalid: Maximum is less than Minimum
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="40.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="30.0"  />
<input type="submit" id="form.actions.apply" name="form.actions.apply"
       value="Apply" class="button" />
>>> request.form['form.max_size'] = u'50.0'
>>> print(MyForm(order, request)())
... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Updated on ... ... ...  ...:...:...
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="40.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="50.0"  />
<input type="submit" id="form.actions.apply" name="form.actions.apply"
       value="Apply" class="button" />
>>> order.max_size
50.0
>>> order.min_size
40.0

Note that EditForm shows the date and time when content are modified.

Multiple Schemas and Adapters

Forms can use fields from multiple schemas. This can be done in a number of ways. For example, multiple schemas can be passed to Fields:

>>> class IDescriptive(interface.Interface):
...     title = schema.TextLine(title=u"Title")
...     description = schema.TextLine(title=u"Description")
>>> class MyForm(form.EditForm):
...     form_fields = form.Fields(IOrder, IDescriptive)
...     form_fields = form_fields.omit('now')

In addition, if the the object being edited doesn’t provide any of the schemas, it will be adapted to the schemas it doesn’t provide.

Suppose we have a generic adapter for storing descriptive information on objects:

>>> from zope import component
>>> @component.adapter(interface.Interface)
... @interface.implementer(IDescriptive)
... class Descriptive(object):
...     def __init__(self, context):
...         self.context = context
...
...     def title():
...         def get(self):
...             try:
...                 return self.context.__title
...             except AttributeError:
...                 return ''
...         def set(self, v):
...             self.context.__title = v
...         return property(get, set)
...     title = title()
...
...     def description():
...         def get(self):
...             try:
...                 return self.context.__description
...             except AttributeError:
...                 return ''
...         def set(self, v):
...             self.context.__description = v
...         return property(get, set)
...     description = description()
>>> component.provideAdapter(Descriptive)

Now, we can use a single form to edit both the regular order data and the descriptive data:

>>> request = TestRequest()
>>> print(MyForm(order, request)()) # doctest: +NORMALIZE_WHITESPACE
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="40.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="50.0"  />
<input class="textType" id="form.title" name="form.title" size="20"
       type="text" value=""  />
<input class="textType" id="form.description" name="form.description"
       size="20"
       type="text" value=""  />
<input type="submit" id="form.actions.apply" name="form.actions.apply"
       value="Apply" class="button" />
>>> request.form['form.name'] = u'bob'
>>> request.form['form.min_size'] = u'10.0'
>>> request.form['form.max_size'] = u'20.0'
>>> request.form['form.title'] = u'Widgets'
>>> request.form['form.description'] = u'Need more widgets'
>>> request.form['form.actions.apply'] = u''
>>> myform = MyForm(order, request)
>>> print(myform())
... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Updated on ... ... ...  ...:...:...
1
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size" size="10"
       type="text" value="10.0"  />
<input class="textType" id="form.max_size" name="form.max_size" size="10"
       type="text" value="20.0"  />
<input class="textType" id="form.title" name="form.title" size="20"
       type="text" value="Widgets"  />
<input class="textType" id="form.description" name="form.description"
       size="20"
       type="text" value="Need more widgets"  />
<input type="submit" id="form.actions.apply" name="form.actions.apply"
       value="Apply" class="button" />
>>> order.min_size
10.0
>>> order.title #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
AttributeError: Order instance has no attribute 'title'
>>> Descriptive(order).title
u'Widgets'

Often, we’d like to get at the adapters used. If EditForm is used, the adapters are available in the adapters attribute, which is a dictionary that allows adapters to be looked up by by schema or schema name:

>>> myform.adapters[IOrder].__class__.__name__
'Order'
>>> myform.adapters['IOrder'].__class__.__name__
'Order'
>>> myform.adapters[IDescriptive].__class__.__name__
'Descriptive'
>>> myform.adapters['IDescriptive'].__class__.__name__
'Descriptive'

If you aren’t using EditForm, you can get a dictionary populated in the same way by setUpWidgets by passing the dictionary as an adapters keyword argument.

Named Widget Access

The value returned from setUpWidgets supports named-based lookup as well as iteration:

>>> myform.widgets['name'].__class__.__name__
'TextWidget'
>>> myform.widgets['name'].name
'form.name'
>>> myform.widgets['title'].__class__.__name__
'TextWidget'
>>> myform.widgets['title'].name
'form.title'

Form-field manipulations

The form-field constructor is very flexible. We’ve already seen that we can supply multiple schemas. Here are some other things you can do.

Specifying individual fields

You can specify individual fields for a form. Here, we’ll create a form that collects just the name from IOrder and the title from IDescriptive:

>>> class MyForm(form.EditForm):
...     form_fields = form.Fields(IOrder['name'],
...                               IDescriptive['title'])
...     actions = ()
>>> print(MyForm(order, TestRequest())()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.title" name="form.title" size="20"
       type="text" value="Widgets"  />

You can also use stand-alone fields:

>>> class MyForm(form.EditForm):
...     form_fields = form.Fields(
...         schema.TextLine(__name__='name', title=u"Who?"),
...         IDescriptive['title'],
...         )
...     actions = ()
>>> print(MyForm(order, TestRequest())()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value="bob"  />
<input class="textType" id="form.title" name="form.title" size="20"
       type="text" value="Widgets"  />

But make sure the fields have a ‘__name__’, as was done above.

Concatenating field collections

It is sometimes convenient to combine multiple field collections. Field collections support concatenation. For example, we may want to combine field definitions:

>>> class MyExpandedForm(form.Form):
...     form_fields = (
...         MyForm.form_fields
...         +
...         form.Fields(IDescriptive['description'])
...         )
...     actions = ()
>>> print(MyExpandedForm(order, TestRequest())())
... # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.name" name="form.name"
       size="20" type="text" value=""  />
<input class="textType" id="form.title" name="form.title"
       size="20" type="text" value=""  />
<input class="textType" id="form.description" name="form.description"
       size="20" type="text" value=""  />
Using fields for display

Normally, any writable fields get input widgets. We may want to indicate that some fields should be used for display only. We can do this using the for_display option when setting up form_fields:

>>> class MyForm(form.EditForm):
...     form_fields = (
...         form.Fields(IOrder, for_display=True).select('name')
...         +
...         form.Fields(IOrder).select('min_size', 'max_size')
...         )
>>> print(MyForm(order, TestRequest())()) # doctest: +NORMALIZE_WHITESPACE
bob
<input class="textType" id="form.min_size" name="form.min_size"
       size="10" type="text" value="10.0"  />
<input class="textType" id="form.max_size" name="form.max_size"
       size="10" type="text" value="20.0"  />
<input type="submit" id="form.actions.apply" name="form.actions.apply"
       value="Apply" class="button" />

Note that if all of the fields in an edit form are for display:

>>> class MyForm(form.EditForm):
...     form_fields = form.Fields(IOrder, for_display=True
...                               ).select('name', 'min_size', 'max_size')
>>> print(MyForm(order, TestRequest())()) # doctest: +NORMALIZE_WHITESPACE
bob
10.0
20.0

we don’t get an edit action. This is because the edit action defined by EditForm has a condition to prevent it’s use when there are no input widgets. Check it out for an example of using action conditions.

Using fields for input

We may want to indicate that some fields should be used for input even if the underlying schema field is read-only. We can do this using the for_input option when setting up form_fields:

>>> class MyForm(form.Form):
...     form_fields = form.Fields(IOrder, for_input=True,
...                                   render_context=True)
...     form_fields = form_fields.omit('now')
...
...     actions = ()
>>> print(MyForm(order, TestRequest())()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.identifier" name="form.identifier"
       size="10" type="text" value="1"  />
<input class="textType" id="form.name" name="form.name"
       size="20" type="text" value="bob"  />
<input class="textType" id="form.min_size" name="form.min_size"
       size="10" type="text" value="10.0"  />
<input class="textType" id="form.max_size" name="form.max_size"
       size="10" type="text" value="20.0"  />

Displaying or editing raw data

Sometimes, you want to display or edit data that doesn’t come from an object. One way to do this is to pass the data to setUpWidgets.

Lets look at an example:

>>> class MyForm(form.Form):
...
...     form_fields = form.Fields(IOrder)
...     form_fields = form_fields.omit('now')
...
...     actions = ()
...
...     def setUpWidgets(self, ignore_request=False):
...         self.widgets = form.setUpWidgets(
...             self.form_fields, self.prefix, self.context, self.request,
...             data=dict(identifier=42, name=u'sally'),
...             ignore_request=ignore_request
...             )

In this case, we supplied initial data for the identifier and the name. Now if we display the form, we’ll see our data and defaults for the fields we didn’t supply data for:

>>> print(MyForm(None, TestRequest())()) # doctest: +NORMALIZE_WHITESPACE
42
<input class="textType" id="form.name" name="form.name"
       size="20" type="text" value="sally"  />
<input class="textType" id="form.min_size" name="form.min_size"
       size="10" type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size"
       size="10" type="text" value=""  />

If data are passed in the request, they override initial data for input fields:

>>> request = TestRequest()
>>> request.form['form.name'] = u'fred'
>>> request.form['form.identifier'] = u'0'
>>> request.form['form.max_size'] = u'100'
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
42
<input class="textType" id="form.name" name="form.name"
       size="20" type="text" value="fred"  />
<input class="textType" id="form.min_size" name="form.min_size"
       size="10" type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size"
       size="10" type="text" value="100.0"  />

We’ll get display fields if we ask for display fields when setting up our form fields:

>>> class MyForm(form.Form):
...
...     form_fields = form.Fields(IOrder, for_display=True)
...     form_fields = form_fields.omit('now')
...
...     actions = ()
...
...     def setUpWidgets(self, ignore_request=False):
...         self.widgets = form.setUpWidgets(
...             self.form_fields, self.prefix, self.context, self.request,
...             data=dict(identifier=42, name=u'sally'),
...             ignore_request=ignore_request
...             )
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
42
sally
<BLANKLINE>
<BLANKLINE>

Note that we didn’t get data from the request because we are using all display widgets.

Passing ignore_request=True to the setUpWidgets function ignores the request for all values passed in the data dictionary, in order to help with redrawing a form after a successful action handler. We’ll fake that quickly by forcing ignore_request to be True.

>>> class MyForm(form.Form):
...
...     form_fields = form.Fields(IOrder)
...     form_fields = form_fields.omit('now')
...
...     actions = ()
...
...     def setUpWidgets(self, ignore_request=False):
...         self.widgets = form.setUpWidgets(
...             self.form_fields, self.prefix, self.context, self.request,
...             data=dict(identifier=42, name=u'sally'),
...             ignore_request=True # =ignore_request
...             )
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
42
<input class="textType" id="form.name" name="form.name"
       size="20" type="text" value="sally"  />
<input class="textType" id="form.min_size" name="form.min_size"
       size="10" type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size"
       size="10" type="text" value=""  />

Specifying Custom Widgets

It is possible to use custom widgets for specific fields. This can be done for a variety of reasons, but the provided mechanism should work for any of them.

Custom widgets are specified by providing a widget factory that should be used instead of the registered field view. The factory will be called in the same way as any other field view factory, with the bound field and the request as arguments.

Let’s create a simple custom widget to use in our demonstration:

>>> import zope.formlib.widget

>>> class ISODisplayWidget(zope.formlib.widget.DisplayWidget):
...
...     def __call__(self):
...         return '<span class="iso-datetime">2005-05-04</span>'

To set the custom widget factory for a field, assign to the custom_widget attribute of the form field object:

>>> class MyForm(form.Form):
...     actions = ()
...
...     form_fields = form.Fields(IOrder).select("now")
...
...     # Here we set the custom widget:
...
...     form_fields["now"].custom_widget = ISODisplayWidget

>>> print(MyForm(None, request)())
<span class="iso-datetime">2005-05-04</span>
Specifying Fields individually

All of the previous examples set up fields as collections. We can also set up forms individually and pass them to the Fields constructor. This is especially useful for passing options that really only apply to a single field. The previous example can be written more simply as:

>>> class MyForm(form.Form):
...     actions = ()
...
...     form_fields = form.Fields(
...         form.Field(IOrder['now'], custom_widget=ISODisplayWidget),
...         )
>>> print(MyForm(None, request)())
<span class="iso-datetime">2005-05-04</span>
Computing default values

We saw earlier that we could provide initial widget data by passing a dictionary to setUpWidgets. We can also supply a function or method name when we set up form fields.

We might like to include the now field in our forms. We can provide a function for getting the needed initial value:

>>> import datetime
>>> class MyForm(form.Form):
...     actions = ()
...
...     def now(self):
...         return datetime.datetime(2002, 12, 2, 12, 30)
...
...     form_fields = form.Fields(
...         form.Fields(IOrder).omit('now'),
...         form.Field(IOrder['now'], get_rendered=now),
...         )
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
<BLANKLINE>
<input class="textType" id="form.name" name="form.name"
       size="20" type="text" value="fred"  />
<input class="textType" id="form.min_size" name="form.min_size"
       size="10" type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size"
       size="10" type="text" value="100.0"  />
<span class="dateTime">2002 12 2  12:30:00 </span>

Now try the same with the AddFormBase which uses a setUpInputWidget:

>>> class MyAddForm(form.AddFormBase):
...     actions = ()
...
...     def now(self):
...         return datetime.datetime(2002, 12, 2, 12, 30)
...
...     form_fields = form.Fields(
...         form.Fields(IOrder).omit('now'),
...         form.Field(IOrder['now'], get_rendered=now),
...         )
...
...     def setUpWidgets(self, ignore_request=True):
...         super(MyAddForm, self).setUpWidgets(ignore_request)
>>> print(MyAddForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.identifier" name="form.identifier"
       size="10" type="text" value=""  />
<input class="textType" id="form.name" name="form.name" size="20"
       type="text" value=""  />
<input class="textType" id="form.min_size" name="form.min_size"
       size="10" type="text" value=""  />
<input class="textType" id="form.max_size" name="form.max_size"
       size="10" type="text" value=""  />
<input class="textType" id="form.now" name="form.now" size="20"
       type="text" value="2002-12-02 12:30:00"  />

Note that a EditForm can’t make use of a get_rendered method. The get_rendered method does only set initial values.

Note that the function passed must take a form as an argument. The setUpWidgets function takes an optional ‘form’ argument, which must be passed if any fields use the get_rendered option. The form base classes always pass the form to setUpWidgets.

Advanced Usage Hints

This section documents patterns for advanced usage of the formlib package.

Multiple button groups

Multiple button groups can be accomplished many ways, but the way we’ve found that reuses the most code is the following:

>>> class MyForm(form.Form):
...     form_fields = form.Fields(IOrder)
...     primary_actions = form.Actions()
...     secondary_actions = form.Actions()
...     # can use @zope.cachedescriptors.property.Lazy for performance
...     def actions(self):
...         return list(self.primary_actions) + list(self.secondary_actions)
...     @form.action(u'Edit', primary_actions)
...     def handle_edit_action(self, action, data):
...         if form.applyChanges(self.context, self.form_fields, data):
...             self.status = 'Object updated'
...         else:
...             self.status = 'No changes'
...     @form.action(u'Submit for review...', secondary_actions)
...     def handle_review_action(self, action, data):
...         print("do something here")
...

The template then can render the button groups separately–something like the following, for instance:

<input tal:repeat="action view/primary_actions"
   tal:replace="structure action/render"
   />

and:

<input tal:repeat="action view/secondary_actions"
   tal:replace="structure action/render"
   />

But the form machinery can still find the correct button.

Dividing display of widget errors and invariant errors

Even though the form machinery only has a single errors attribute, if designers wish to render widget errors differently than invariant errors, they can be separated reasonably easily. The separation takes advantage of the fact that all widget errors should implement zope.formlib.interfaces.IWidgetInputError, and invariant errors shouldn’t, because they don’t come from a widget. Therefore, a simple division such as the following should suffice.

Omitting the form prefix

For certain use cases (e.g. forms that post data to a different server whose software you do not control) it is important to be able to generate forms without a prefix. Using an empty string for the prefix omits it entirely.

>>> form_fields = form.Fields(IOrder).select('name')
>>> request = TestRequest()
>>> widgets = form.setUpWidgets(form_fields, '', None, request)
>>> print(widgets['name']()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="name" name="name" size="20"
       type="text" value=""  />

Of course, getting the widget data still works.

>>> request.form['name'] = 'foo'
>>> widgets = form.setUpWidgets(form_fields, '', None, request)
>>> data = {}
>>> form.getWidgetsData(widgets, '', data)
[]
>>> data
{'name': u'foo'}

And the value from the request is also visible in the rendered form.

>>> print(widgets['name']()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="name" name="name" size="20"
       type="text" value="foo"  />

The same is true when using the other setup*Widgets helpers.

>>> widgets = form.setUpInputWidgets(form_fields, '', None, request)
>>> print(widgets['name']()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="name" name="name" size="20"
       type="text" value="foo"  />
>>> order = Order(42)
>>> widgets = form.setUpEditWidgets(form_fields, '', order, request)
>>> print(widgets['name']()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="name" name="name" size="20"
       type="text" value="foo"  />
>>> widgets = form.setUpDataWidgets(form_fields, '', None, request)
>>> print(widgets['name']()) # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="name" name="name" size="20"
       type="text" value="foo"  />

Form actions have their own prefix in addition to the form prefix. This can be suppressed for each action by passing the empty string as the ‘prefix’ argument.

>>> class MyForm(form.Form):
...
...     prefix = ''
...     form_fields = form.Fields()
...
...     @form.action('Button 1', name='button1')
...     def handle_button1(self, action, data):
...         self.status = 'Button 1 detected'
...
...     @form.action('Button 2', prefix='', name='button2')
...     def handle_button2(self, action, data):
...         self.status = 'Button 2 detected'
...
>>> request = TestRequest()
>>> request.form['actions.button1'] = ''
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
Button 1 detected
<input type="submit" id="actions.button1" name="actions.button1"
       value="Button 1" class="button" />
<input type="submit" id="button2" name="button2"
       value="Button 2" class="button" />
>>> request = TestRequest()
>>> request.form['button2'] = ''
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
Button 2 detected
<input type="submit" id="actions.button1" name="actions.button1"
       value="Button 1" class="button" />
<input type="submit" id="button2" name="button2"
       value="Button 2" class="button" />

It is also possible to keep the form prefix and just suppress the ‘actions’ prefix.

>>> class MyForm(form.Form):
...
...     form_fields = form.Fields()
...
...     @form.action('Button', prefix='', name='button')
...     def handle_button(self, action, data):
...         self.status = 'Button detected'
...
>>> request = TestRequest()
>>> request.form['form.button'] = ''
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
Button detected
<input type="submit" id="form.button" name="form.button"
       value="Button" class="button" />

Additional Cases

Automatic Context Adaptation

As you may know already, the formlib will automatically adapt the context to find a widget and data for a particular field. In an early version of zope.formlib, it simply used field.interface to get the interface to adapt to. Unfortunately, this call returns the interface the field has been defined in and not the interface you got the field from. The following lines demonstrate the correct behavior:

>>> import zope.interface
>>> import zope.schema
>>> class IFoo(zope.interface.Interface):
...     title = zope.schema.TextLine()
>>> class IFooBar(IFoo):
...     pass

Here is the unexpected behavior that caused formlib to do the wrong thing:

>>> IFooBar['title'].interface
<InterfaceClass __builtin__.IFoo>

Note: If this behavior ever changes, the formlib can be simplified again.

>>> @zope.interface.implementer(IFooBar)
... class FooBar(object):
...     title = u'initial'
>>> foobar = FooBar()
>>> class Blah(object):
...     def __conform__(self, iface):
...         if iface is IFooBar:
...             return foobar
>>> blah = Blah()

Let’s now generate the form fields and instantiate the widgets:

>>> from zope.formlib import form
>>> form_fields = form.FormFields(IFooBar)
>>> request = TestRequest()
>>> widgets = form.setUpEditWidgets(form_fields, 'form', blah, request)
>>> print(widgets.get('title')())
<input class="textType" id="form.title" name="form.title"
       size="20" type="text" value="initial" />

Here are some more places where the behavior was incorrect:

>>> widgets = form.setUpWidgets(form_fields, 'form', blah, request)
>>> print(widgets.get('title')())
<input class="textType" id="form.title" name="form.title"
       size="20" type="text" value="" />
>>> form.checkInvariants(form_fields, {'title': 'new'}, blah)
[]
>>> form.applyChanges(blah, form_fields, {'title': 'new'})
True
Event descriptions

The ObjectModifiedEvent can be annotated with descriptions about the involved schemas and fields. The formlib provides these annotations with the help of the applyData function, which returns a list of modification descriptions:

>>> form.applyData(blah, form_fields, {'title': 'modified'})
{<InterfaceClass __builtin__.IFooBar>: ['title']}

The events are annotated with these descriptions. We need a subscriber to log these infos:

>>> def eventLog(event):
...     desc = event.descriptions[0]
...     print('Modified:', desc.interface.__identifier__, desc.attributes)
>>> zope.event.subscribers.append(eventLog)
>>> class MyForm(form.EditForm):
...     form_fields = form.FormFields(IFooBar)
>>> request = TestRequest()
>>> request.form['form.title'] = u'again modified'
>>> request.form['form.actions.apply'] = u''
>>> MyForm(FooBar(), request)()
Modified: __builtin__.IFooBar ('title',)
...

Cleanup:

>>> zope.event.subscribers.remove(eventLog)
Actions that cause a redirect

When an action causes a redirect, the following render phase is omitted as the result will not be displayed anyway. This is both a performance improvement and for avoiding application bugs with one-time session information.

>>> class MyForm(form.Form):
...     form_fields = form.FormFields(IFooBar)
...     @form.action("Redirect")
...     def redirect(self, action, data):
...         print('Action: redirect')
...         self.request.response.redirect('foo')
...     @form.action("Stay")
...     def redirect(self, action, data):
...         print('Action: stay')
...         pass
...     def render(self):
...         print('render was called')
...         return ''
>>> request = TestRequest()
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
render was called
>>> request.form['form.actions.redirect'] = u''
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
Action: redirect
>>> request = TestRequest()
>>> request.form['form.actions.stay'] = u''
>>> print(MyForm(None, request)()) # doctest: +NORMALIZE_WHITESPACE
Action: stay
render was called
Prevent form submit for GET requests

It can be useful to only accept form submits over POST requests. This, for example, prevents replaying data-modifying actions when reloading a page in a web browser (most web browsers warn users for re-submitting the form when reloading a page that was the result of a POST request). This also helps (but is not enough by itself!) in preventing CSRF attacks.

Whenever a form component has set the method attribute on the class, it is used when validating the form data.

>>> class MyPOSTForm(form.Form):
...     method = 'POST'
...
...     form_fields = form.FormFields(IFooBar)
...
...     @form.action("Handle")
...     def handle(self, action, data):
...         print('Action: handle %s' % data)
...
...     def render(self):
...         return ''

This is a GET request for a form that specifies it can only validate POST requests:

>>> request = TestRequest()
>>> request.form['form.title'] = u'Submitted Title'
>>> request.form['form.actions.handle'] = u''
>>> MyPOSTForm(None, request)() 
Traceback (most recent call last):
...
MethodNotAllowed: None, <zope.publisher.browser.TestRequest instance URL=http://127.0.0.1>

By setting the correct request method we validate input:

>>> request = TestRequest()
>>> request.method = 'POST'
>>> request.form['form.title'] = u'Submitted Title'
>>> request.form['form.actions.handle'] = u''
>>> print(MyPOSTForm(None, request)()) 
Action: handle {'title': 'Submitted Title'}

Although slightly convoluted, we could require the submit to go over a GET request:

>>> class MyGETForm(form.Form):
...     method = 'GET'
...
...     form_fields = form.FormFields(IFooBar)
...
...     @form.action("Handle")
...     def handle(self, action, data):
...         print('Action: handle %s' % data)
...
...     def render(self):
...         return ''

>>> request = TestRequest()
>>> request.method = 'POST'
>>> request.form['form.actions.handle'] = u''
>>> MyGETForm(None, request)() 
Traceback (most recent call last):
...
MethodNotAllowed: None, <zope.publisher.browser.TestRequest instance URL=http://127.0.0.1>

>>> request = TestRequest()
>>> request.form['form.title'] = u'Submitted Title'
>>> request.form['form.actions.handle'] = u''
>>> print(MyGETForm(None, request)()) 
Action: handle {'title': 'Submitted Title'}

Note how the default value for method is None, meaning all request methods are accepted:

>>> class MyForm(form.Form):
...     form_fields = form.FormFields(IFooBar)
...
...     @form.action("Handle")
...     def handle(self, action, data):
...         print('Action: handle %s' % data)
...
...     def render(self):
...         return ''

>>> request = TestRequest()
>>> request.method = 'POST'
>>> request.form['form.title'] = u'Submitted Title'
>>> request.form['form.actions.handle'] = u''
>>> print(MyForm(None, request)()) 
Action: handle {'title': 'Submitted Title'}

>>> request = TestRequest()
>>> request.form['form.title'] = u'Submitted Title'
>>> request.form['form.actions.handle'] = u''
>>> print(MyForm(None, request)()) 
Action: handle {'title': 'Submitted Title'}
Prevent Cross-site Request Forgery (CSRF) attacks

The cross-site request forgery protection in zope.formlib assumes the attacker cannot get hold of information stored in a cookie that is send to the domain handling the form submit. zope.formlib verifies that the token as sent with the cookie is identical to the value as sent with the form (as a hidden input field).

zope.formlib will set a random token in the cookie when first accessing the form. Any subsequent form rendering and submit handling will use the token stored in this cookie.

Thus this token is reused for all forms for as long the cookie is available.

The cookie is set to expiry when the web browser quits.

This protection works best when used in combination with the afformentioned acceptable request method restriction.

Issues to research:

  • Is the name “__csrftoken__ acceptable?
  • I do not see a scheme for having a token per form without keep server- side, which I try to avoid.
  • One cannot submit a form as the very first request to that form, as the token will not have been set just yet. I think this acceptable.
  • Tests for applications that use form components with CSRF protection enabled, is cumbersome. Can we help that somehow?
  • Is using os.urandom for generating a token sufficient and available cross-platform? Could uuid.uuid4 be an alternative?

When first visting a form, a CSRF token will be set in the cookie:

>>> class MyForm(form.Form):
...     protected = True
...
...     form_fields = form.FormFields(IFooBar)
...
...     @form.action("Handle")
...     def handle(self, action, data):
...         print('Action: handle %s' % data)

>>> request = TestRequest()
>>> myform = MyForm(None, request)
>>> _ = myform()  # "render" the form.
>>> csrfcookie = request.response.getCookie('__csrftoken__')
>>> csrfcookie['httponly']
True
>>> csrftoken = csrfcookie['value']
>>> csrftoken == myform.csrftoken
True

When submitting the form, the token in the cookie (that will be sent as part of the request) needs to be identical to the value of the hidden form field “__csrftoken__”:

>>> request = TestRequest(
...     **{'HTTP_COOKIE': '__csrftoken__=%s;' % csrftoken})
>>> request.form['form.title'] = 'Submitted title'
>>> request.form['form.actions.handle'] = 'true'
>>> request.form['__csrftoken__'] = csrftoken
>>> myform = MyForm(None, request)
>>> _ = myform()
Action: handle {'title': u'Submitted title'}

If for some reason the cookie is not set, the form will raise an error:

>>> request = TestRequest(**{'HTTP_COOKIE': ''})
>>> request.form['form.title'] = 'Submitted title'
>>> request.form['form.actions.handle'] = 'true'
>>> request.form['__csrftoken__'] = csrftoken
>>> myform = MyForm(None, request)
>>> _ = myform()   
Traceback (most recent call last):
...
InvalidCSRFTokenError: Invalid CSRF token

As an attacker cannot read the cookie value, he can only guess the corresponding form value, that is hard get right, so most proably wrong:

>>> request = TestRequest(
...     **{'HTTP_COOKIE': '__csrftoken__=%s;' % csrftoken})
>>> request.form['form.title'] = 'Submitted title'
>>> request.form['form.actions.handle'] = 'true'
>>> request.form['__csrftoken__'] = 'a guessed value'
>>> myform = MyForm(None, request)
>>> _ = myform()  
Traceback (most recent call last):
...
InvalidCSRFTokenError: Invalid CSRF token

When the form value is missing altogether, the form obviously raises an error too:

>>> request = TestRequest(
...     **{'HTTP_COOKIE': '__csrftoken__=%s;' % csrftoken})
>>> request.form['form.title'] = 'Submitted title'
>>> request.form['form.actions.handle'] = 'true'
>>> myform = MyForm(None, request)
>>> _ = myform()   
Traceback (most recent call last):
...
InvalidCSRFTokenError: Invalid CSRF token

To repeat: this protection works as long as the cookie value is identical to the submitted form value. No state is kept on the server. We can demonstrate this by inventing a token value here in the test ourselves:

>>> csrftoken = 'MYNICETOKENVALUE'
>>> request = TestRequest(
...     **{'HTTP_COOKIE': '__csrftoken__=%s;' % csrftoken})
>>> request.form['form.title'] = 'Submitted title'
>>> request.form['form.actions.handle'] = 'true'
>>> request.form['__csrftoken__'] = csrftoken
>>> myform = MyForm(None, request)
>>> _ = myform()
Action: handle {'title': u'Submitted title'}

It is possible to have multiple forms in one page. Of course only one of these forms can be submitted at one point in time, but the CSRF token should not confuse things:

>>> class FormOne(form.Form):
...     prefix = 'form_one'
...
...     protected = True
...
...     form_fields = form.FormFields(IFooBar)
...
...     @form.action("Handle")
...     def handle(self, action, data):
...         print('Action: handle in Form One')

>>> class FormTwo(form.Form):
...     prefix = 'form_two'
...
...     protected = True
...
...     form_fields = form.FormFields(IFooBar)
...
...     @form.action("Handle")
...     def handle(self, action, data):
...         print('Action: handle in Form Two')

>>> from zope.publisher.browser import BrowserPage
>>> class MultiForm(BrowserPage):
...     def __init__(self, context, request):
...         self.formone = FormOne(context, request)
...         self.formtwo = FormTwo(context, request)
...
...     def __call__(self):
...         return '\n'.join((self.formone(), self.formtwo()))
...

Render the initial multi form view:

>>> request = TestRequest()
>>> multi = MultiForm(None, request)
>>> result = multi()
>>> print(result)
<input class="textType" id="form_one.title"
  name="form_one.title" size="20" type="text" value=""  />
<inut type="hidden" name="__csrftoken__" value="..."
<input type="submit" id="form_one.actions.handle"
  name="form_one.actions.handle" value="Handle" class="button" />
<input class="textType" id="form_two.title"
  name="form_two.title" size="20" type="text" value=""  />
<inut type="hidden" name="__csrftoken__" value="..."
<input type="submit" id="form_two.actions.handle"
  name="form_two.actions.handle" value="Handle" class="button" />

The CSRF tokens in both the hidden form fields should be identical to the one set in the cookie:

>>> csrftoken = request.response.getCookie('__csrftoken__')['value']
>>> len(result.split(str(csrftoken)))
3
>>> multi.formone.csrftoken == multi.formtwo.csrftoken == csrftoken
True

We can indeed submit data to the forms:

>>> request = TestRequest(
...     **{'HTTP_COOKIE': '__csrftoken__=%s;' % csrftoken})
>>> request.form['form_one.title'] = 'Submitted title'
>>> request.form['form_one.actions.handle'] = 'true'
>>> request.form['__csrftoken__'] = csrftoken
>>> multi = MultiForm(None, request)
>>> _ = multi()
Action: handle in Form One

>>> request = TestRequest(
...     **{'HTTP_COOKIE': '__csrftoken__=%s;' % csrftoken})
>>> request.form['form_two.title'] = 'Submitted title'
>>> request.form['form_two.actions.handle'] = 'true'
>>> request.form['__csrftoken__'] = csrftoken
>>> multi = MultiForm(None, request)
>>> _ = multi()
Action: handle in Form Two

There is a view for the InvalidCSRFTokenError:

>>> from zope.component import getMultiAdapter
>>> from zope.formlib.interfaces import InvalidCSRFTokenError
>>> from zope.formlib.errors import InvalidCSRFTokenErrorView
>>> error = InvalidCSRFTokenError('Invalid CSRF token')
>>> request = TestRequest()
>>> print(InvalidCSRFTokenErrorView(error, request)())
Invalid CSRF token

Browser Widgets

Formlib defines widgets: views on bound schema fields. Many of these are straightforward. For instance, see the TextWidget in textwidgets.py, which is a subclass of BrowserWidget in widget.py. It is registered as an zope.publisher.interfaces.browser.IBrowserRequest view of an zope.schema.interfaces.ITextLine schema field, providing the IInputWidget interface:

<view
    type="zope.publisher.interfaces.browser.IBrowserRequest"
    for="zope.schema.interfaces.ITextLine"
    provides="zope.formlib.interfaces.IInputWidget"
    factory=".TextWidget"
    permission="zope.Public"
    />

The widget then receives the field and the request as arguments to the factory (i.e., the TextWidget class).

Some widgets in formlib extend this pattern. The widget registration is extended for zope.schema.Choice fields and for the collection fields.

Default Choice Field Widget Registration and Lookup

All field widgets are obtained by looking up a browser IInputWidget or IDisplayWidget view for the field object. For zope.schema.Choice fields, the default registered widget defers all of its behavior to the result of another lookup: a browser widget view for the field and the Choice field’s vocabulary.

This allows registration of Choice widgets that differ on the basis of the vocabulary type. For example, a widget for a vocabulary of images might have a significantly different user interface than a widget for a vocabulary of words. A dynamic vocabulary might implement zope.schema.interfaces.IIterableVocabulary if its contents are below a certain length, but not implement the marker “iterable” interface if the number of possible values is above the threshhold.

This also means that choice widget factories are called with with an additional argument. Rather than being called with the field and the request as arguments, choice widgets receive the field, vocabulary, and request as arguments.

Some zope.schema.Choice widgets may also need to provide a source interface, particularly if the vocabulary is too big to iterate over.

Default Collection Field Widget Registration and Lookup

The default configured lookup for collection fields – List, Tuple, and Set, for instance – begins with the usual lookup for a browser widget view for the field object. This widget defers its display to the result of another lookup: a browser widget view registered for the field and the field’s value_type (the type of the contained values). This allows registrations for collection widgets that differ on the basis of the members – a widget for entering a list of text strings might differ significantly from a widget for entering a list of dates…or even a list of choices, as discussed below.

This registration pattern has three implications that should be highlighted.

  • First, collection fields that do not specify a value_type probably cannot have a reasonable widget.
  • Second, collection widgets that wish to be the default widget for a collection with any value_type should be registered for the collection field and a generic value_type: the zope.schema.interfaces.IField interface. Do not register the generic widget for the collection field only or you will break the lookup behavior as described here.
  • Third, like choice widget factories, sequence widget factories (classes or functions) take three arguments. Typical sequence widgets receive the field, the value_type, and the request as arguments.
Collections of Choices

If a collection field’s value_type is a zope.schema.Choice field, the second widget again defers its behavior, this time to a third lookup based on the collection field and the choice’s vocabulary. This means that a widget for a list of large image choices can be different than a widget for a list of small image choices (with a different vocabulary interface), different from a widget for a list of keyword choices, and different from a set of keyword choices.

Some advanced applications may wish to do a further lookup on the basis of the unique attribute of the collection field–perhaps looking up a named view with a “unique” or “lenient” token depending on the field’s value, but this is not enabled in the default Zope 3 configuration.

Registering Widgets for a New Collection Field Type

Because of this lookup pattern, basic widget registrations for new field types must follow a recipe. For example, a developer may introduce a new Bag field type for simple shopping cart functionality and wishes to add widgets for it within the default Zope 3 collection widget registration. The bag widgets should be registered something like this.

The only hard requirement is that the developer must register the bag + choice widget: the widget is just the factory for the third dispatch as described above, so the developer can use the already implemented widgets listed below:

<view
    type="zope.publisher.interfaces.browser.IBrowserRequest"
    for="zope.schema.interfaces.IBag
         zope.schema.interfaces.IChoice"
    provides="zope.formlib.interfaces.IDisplayWidget"
    factory=".ChoiceCollectionDisplayWidget"
    permission="zope.Public"
    />

<view
    type="zope.publisher.interfaces.browser.IBrowserRequest"
    for="zope.schema.interfaces.IBag
         zope.schema.interfaces.IChoice"
    provides="zope.formlib.interfaces.IInputWidget"
    factory=".ChoiceCollectionInputWidget"
    permission="zope.Public"
    />

Beyond this, the developer may also have a generic bag widget she wishes to register. This might look something like this, assuming there’s a BagSequenceWidget available in this package:

<view
    type="zope.publisher.interfaces.browser.IBrowserRequest"
    for="zope.schema.interfaces.IBag
         zope.schema.interfaces.IField"
    provides="zope.formlib.interfaces.IInputWidget"
    factory=".BagSequenceWidget"
    permission="zope.Public"
    />

Then any widgets for the bag and a vocabulary would be registered according to this general pattern, in which zope.schema.interfaces.IIterableVocabulary would be the interface of any appropriate vocabulary and BagWidget is some appropriate widget:

<view
    type="zope.publisher.interfaces.browser.IBrowserRequest"
    for="zope.schema.interfaces.IBag
         zope.schema.interfaces.IIterableVocabulary"
    provides="zope.formlib.interfaces.IInputWidget"
    factory=".BagWidget"
    permission="zope.Public"
    />

Choice widgets and the missing value

Choice widgets for a non-required field include a “no value” item to allow for not selecting any value at all. This value used to be omitted for required fields on the assumption that the widget should avoid invalid input from the start.

However, if the context object doesn’t yet have a field value set and there’s no default value, a dropdown widget would have to select an arbitrary value due to the way it is displayed in the browser. This way, the field would always validate, but possibly with a value the user never chose consciously.

Starting with version zope.app.form 3.6.0, dropdown widgets for required fields display a “no value” item even for required fields if an arbitrary value would have to be selected by the widget otherwise.

To switch the old behaviour back on for backwards compatibility, do:

zope.formlib.itemswidgets.EXPLICIT_EMPTY_SELECTION = False

during application start-up.

Error handling

These are a couple of functional tests that were written on-the-go … In the future this might become more extensive …

Displaying invalidation errors

Validation errors, e.g. cause by invariants, are converted into readable text by adapting them to IWidgetInputErrorView:

>>> from zope.publisher.browser import TestRequest
>>> from zope.interface.exceptions import Invalid
>>> from zope.component import getMultiAdapter
>>> from zope.formlib.interfaces import IWidgetInputErrorView
>>> error = Invalid("You are wrong!")
>>> message = getMultiAdapter((error, TestRequest()),
...         IWidgetInputErrorView).snippet()
>>> message
u'<span class="error">You are wrong!</span>'

Interface invariant methods raise zope.interface.Invalid exception. Test if this exception gets handled by the error_views.

>>> myError = Invalid('My error message')
>>> import zope.formlib.form
>>> mybase = zope.formlib.form.FormBase(None, TestRequest())
>>> mybase.errors = (myError,)
>>> save = mybase.error_views()
>>> next(save)
u'<span class="error">My error message</span>'

Now we need to set up the translation framework:

>>> from zope import component, interface
>>> from zope.i18n.interfaces import INegotiator
>>> @interface.implementer(INegotiator)
... class Negotiator:
...     def getLanguage(*ignored): return 'test'
>>> component.provideUtility(Negotiator())
>>> from zope.i18n.testmessagecatalog import TestMessageFallbackDomain
>>> component.provideUtility(TestMessageFallbackDomain)

And yes, we can even handle an i18n message in an Invalid exception:

>>> from zope.i18nmessageid import MessageFactory
>>> _ = MessageFactory('my.domain')
>>> myError = Invalid(_('My i18n error message'))
>>> mybase = zope.formlib.form.FormBase(None, TestRequest())
>>> mybase.errors = (myError,)
>>> save = mybase.error_views()
>>> next(save)
u'<span class="error">[[my.domain][My i18n error message]]</span>'

Displaying widget input errors

WidgetInputError exceptions also work with i18n messages:

>>> from zope.formlib.interfaces import WidgetInputError
>>> myError = WidgetInputError(
...     field_name='summary',
...     widget_title=_(u'Summary'),
...     errors=_(u'Foo'))
>>> mybase = zope.formlib.form.FormBase(None, TestRequest())
>>> mybase.errors = (myError,)
>>> save = mybase.error_views()
>>> next(save)
u'[[my.domain][Summary]]: <span class="error">[[my.domain][Foo]]</span>'

Object Widget

The following example shows a Family with Mother and Father. First define the interface for a person:

>>> from zope.interface import Interface, implementer
>>> from zope.schema import TextLine
>>> class IPerson(Interface):
...     """Interface for Persons."""
...
...     name = TextLine(title=u'Name', description=u'The first name')

Let’s define the class:

>>> @implementer(IPerson)
... class Person(object):
...     def __init__(self, name=''):
...         self.name = name

Let’s define the interface family:

>>> from zope.schema import Object
>>> class IFamily(Interface):
...     """The familiy interface."""
...
...     mother = Object(title=u'Mother',
...                     required=False,
...                     schema=IPerson)
...
...     father = Object(title=u'Father',
...                     required=False,
...                     schema=IPerson)

Let’s define the class Family using zope.schema.fieldproperty.FieldProperty for mother and father. FieldProperty instances validate the values if they get added:

>>> from zope.schema.fieldproperty import FieldProperty
>>> @implementer(IFamily)
... class Family(object):
...     """The familiy interface."""
...     mother = FieldProperty(IFamily['mother'])
...     father = FieldProperty(IFamily['father'])
...
...     def __init__(self, mother=None, father=None):
...         self.mother = mother
...         self.father = father

Let’s make an instance of Family with None attributes:

>>> family = Family()
>>> bool(family.mother == None)
True
>>> bool(family.father == None)
True

Let’s make an instance of Family with None attributes:

>>> mother = Person(u'Margrith')
>>> father = Person(u'Joe')
>>> family = Family(mother, father)
>>> IPerson.providedBy(family.mother)
True
>>> IPerson.providedBy(family.father)
True

Let’s define a dummy class which doesn’t implements IPerson:

>>> class Dummy(object):
...     """Dummy class."""
...     def __init__(self, name=''):
...         self.name = name

Raise a zope.schema.interfaces.SchemaNotProvided exception if we add a Dummy instance to a Family object:

>>> foo = Dummy('foo')
>>> bar = Dummy('bar')
>>> family = Family(foo, bar)
... # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
SchemaNotProvided

Now let’s setup a enviroment for use the widget like in a real application:

>>> from zope.publisher.browser import TestRequest
>>> from zope.schema.interfaces import ITextLine
>>> from zope.schema import TextLine
>>> from zope.formlib.widgets import TextWidget
>>> from zope.formlib.widgets import ObjectWidget
>>> from zope.formlib.interfaces import IInputWidget

Register the zope.schema.TextLine widget used in the IPerson interface for the field ‘name’.

>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> from zope.component import provideAdapter
>>> provideAdapter(TextWidget, (ITextLine, IDefaultBrowserLayer),
...                IInputWidget)

Let’s define a request and provide input value for the mothers name used in the family object:

>>> request = TestRequest(HTTP_ACCEPT_LANGUAGE='pl')
>>> request.form['field.mother.name'] = u'Margrith Ineichen'

Before we update the object let’s check the value name of the mother instance on the family object:

>>> family.mother.name
u'Margrith'

Now let’s initialize a ObjectWidget with the right attributes:

>>> mother_field = IFamily['mother']
>>> factory = Person
>>> widget = ObjectWidget(mother_field, request, factory)

Now comes the magic. Apply changes means we force the ObjectWidget to read the request, extract the value and save it on the content. The ObjectWidget instance uses a real Person class (factory) for add the value. The value is temporary stored in this factory class. The ObjectWidget reads the value from this factory and set it to the attribute ‘name’ of the instance mother (The object mother is already there). If we don’t have an instance mother already stored in the family object, the factory instance will be stored directly to the family attribute mother. For more information see the method zope.formlib.objectwidget.ObjectWidget.applyChanges.

>>> widget.applyChanges(family)
True

Test the updated mother’s name value on the object family:

>>> family.mother.name
u'Margrith Ineichen'
>>> IPerson.providedBy(family.mother)
True

So, now you know my mothers and fathers name. I hope it’s also clear how to use the zope.schema.Object field and the ObjectWidget.

Source Widgets

Sources are objects that represent sets of values from which one might choose and are used with zope.schema.Choice schema fields. Source widgets currently fall into two categories:

  • widgets for iterable sources
  • widgets for queryable sources

Sources (combined with the available adapters) may support both approaches, but no widgets currently support both.

In both cases, the widgets need views that can be used to get tokens to represent source values in forms, as well as textual representations of values. We use the zope.browser.interfaces.ITerms views for that.

All of our examples will be using the component architecture:

>>> import zope.interface
>>> import zope.component
>>> import zope.schema

This ITerms implementation can be used for the sources involved in our tests:

>>> import base64
>>> import binascii
>>> from zope.browser.interfaces import ITerms
>>> import zope.publisher.interfaces.browser
>>> from zope.schema.vocabulary import SimpleTerm
>>> from zope.formlib._compat import toUnicode
>>> @zope.interface.implementer(ITerms)
... class ListTerms:
...
...     def __init__(self, source, request):
...         pass # We don't actually need the source or the request :)
...
...     def getTerm(self, value):
...         title = toUnicode(value)
...         try:
...             # This convoluted mess makes it Py2 and Py3 friendly.
...             token = str(base64.b64encode(title.encode()).strip().decode())
...         except binascii.Error:
...             raise LookupError(token)
...         return SimpleTerm(value, token=token, title=title)
...
...     def getValue(self, token):
...         return base64.b64decode(token).decode()

This view just uses the unicode representations of values as titles and the base-64 encoding of the titles as tokens. This is a very simple strategy that’s only approriate when the values have short and unique unicode representations.

All of the source widgets are in a single module:

>>> import zope.formlib.source

We’ll also need request objects:

>>> from zope.publisher.browser import TestRequest

Iterable Source Widgets

Iterable sources are expected to be simpler than queriable sources, so they represent a good place to start. The most important aspect of iterable sources for widgets is that it’s actually possible to enumerate all the values from the source. This allows each possible value to be listed in a <select> form field.

Let’s start with a simple example. We have a very trivial source, which is basically a list:

>>> @zope.interface.implementer(zope.schema.interfaces.IIterableSource)
... class SourceList(list):
...     pass

We need to register our ITerms view:

>>> zope.component.provideAdapter(
...     ListTerms,
...     (SourceList, zope.publisher.interfaces.browser.IBrowserRequest))

Let’s define a choice field using our iterable source:

>>> dog = zope.schema.Choice(
...    __name__ = 'dog',
...    title=u"Dogs",
...    source=SourceList(['spot', 'bowser', 'prince', 'duchess', 'lassie']),
...    )

>>> dog = dog.bind(object())

When we get a choice input widget for a choice field, the default widget factory gets a view on the field and the field’s source. We’ll just create the view directly:

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceSelectWidget(
...     dog, dog.source, request)

>>> print(widget())
<div>
<div class="value">
<select id="field.dog" name="field.dog" size="5" >
<option value="c3BvdA==">spot</option>
<option value="Ym93c2Vy">bowser</option>
<option value="cHJpbmNl">prince</option>
<option value="ZHVjaGVzcw==">duchess</option>
<option value="bGFzc2ll">lassie</option>
</select>
</div>
<input name="field.dog-empty-marker" type="hidden" value="1" />
</div>

Since the field is required, an empty selection is not valid:

>>> widget.getInputValue() #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
MissingInputError: ('field.dog', u'Dogs', None)

Also, the widget is required in this case:

>>> widget.required
True

If the request contains a value, it is marked as selected:

>>> request.form["field.dog-empty-marker"] = "1"
>>> request.form["field.dog"] = "Ym93c2Vy"

>>> print(widget())
<div>
<div class="value">
<select id="field.dog" name="field.dog" size="5" >
<option value="c3BvdA==">spot</option>
<option selected="selected" value="Ym93c2Vy">bowser</option>
<option value="cHJpbmNl">prince</option>
<option value="ZHVjaGVzcw==">duchess</option>
<option value="bGFzc2ll">lassie</option>
</select>
</div>
<input name="field.dog-empty-marker" type="hidden" value="1" />
</div>

If we set the displayed value for the widget, that value is marked as selected:

>>> widget.setRenderedValue("duchess")
>>> print(widget())
<div>
<div class="value">
<select id="field.dog" name="field.dog" size="5" >
<option value="c3BvdA==">spot</option>
<option value="Ym93c2Vy">bowser</option>
<option value="cHJpbmNl">prince</option>
<option selected="selected" value="ZHVjaGVzcw==">duchess</option>
<option value="bGFzc2ll">lassie</option>
</select>
</div>
<input name="field.dog-empty-marker" type="hidden" value="1" />
</div>

Dropdown widgets are achieved with SourceDropdownWidget, which simply generates a selection list of size 1:

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceDropdownWidget(
...     dog, dog.source, request)
>>> print(widget()) 
<div>
<div class="value">
<select id="field.dog" name="field.dog" size="1" >
<option selected="selected" value="">(nothing selected)</option>...

An alternative to SourceSelectWidget for small numbers of items is SourceRadioWidget that provides a radio button group for the items:

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceRadioWidget(
...     dog, dog.source, request)
>>> print(widget()) 
<div>
<div class="value">
<label for="field.dog.0"><input class="radioType" id="field.dog.0"
    name="field.dog" type="radio" value="c3BvdA==" />&nbsp;spot</label><br
/><label for="field.dog.1"><input class="radioType" id="field.dog.1"
    name="field.dog" type="radio" value="Ym93c2Vy" />&nbsp;bowser</label><br
/><label for="field.dog.2"><input class="radioType" id="field.dog.2"
    name="field.dog" type="radio" value="cHJpbmNl" />&nbsp;prince</label><br
/><label for="field.dog.3"><input class="radioType" id="field.dog.3"
    name="field.dog" type="radio" value="ZHVjaGVzcw==" />&nbsp;duchess</label><br
/><label for="field.dog.4"><input class="radioType" id="field.dog.4"
    name="field.dog" type="radio" value="bGFzc2ll" />&nbsp;lassie</label>
</div>
<input name="field.dog-empty-marker" type="hidden" value="1" />
</div>

We’ll select an item by setting the appropriate fields in the request:

>>> request.form['field.dog-empty-marker'] = '1'
>>> request.form['field.dog'] = 'bGFzc2ll'
>>>
>>> widget = zope.formlib.source.SourceRadioWidget(
...     dog, dog.source, request)
>>> print(widget()) 
<div>
<div class="value">
<label for="field.dog.0"><input class="radioType" id="field.dog.0"
    name="field.dog" type="radio" value="c3BvdA==" />&nbsp;spot</label><br
/><label for="field.dog.1"><input class="radioType" id="field.dog.1"
    name="field.dog" type="radio" value="Ym93c2Vy" />&nbsp;bowser</label><br
/><label for="field.dog.2"><input class="radioType" id="field.dog.2"
    name="field.dog" type="radio" value="cHJpbmNl" />&nbsp;prince</label><br
/><label for="field.dog.3"><input class="radioType" id="field.dog.3"
    name="field.dog" type="radio" value="ZHVjaGVzcw==" />&nbsp;duchess</label><br
/><label for="field.dog.4"><input class="radioType" checked="checked"
    id="field.dog.4" name="field.dog" type="radio" value="bGFzc2ll"
    />&nbsp;lassie</label>
</div>
<input name="field.dog-empty-marker" type="hidden" value="1" />
</div>

For list-valued fields with items chosen from iterable sources, there are the SourceMultiSelectWidget and SourceOrderedMultiSelectWidget widgets. The latter widget includes support for re-ording the list items. SourceOrderedMultiSelectWidget is configured as the default widget for lists of choices.

If you don’t need ordering support through the web UI, then you can use the simpler SourceMultiSelectWidget:

>>> dogSource = SourceList([
...     u'spot', u'bowser', u'prince', u'duchess', u'lassie'])
>>> dogs = zope.schema.List(
...     __name__ = 'dogs',
...     title=u"Dogs",
...     value_type=zope.schema.Choice(
...         source=dogSource,
...     )
... )
>>> dogs = dogs.bind(object()) # give the field a context

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceMultiSelectWidget(
...     dogs, dogSource, request)

Let’s look at the rendered widget:

>>> print(widget()) 
<div>
<div class="value">
<select id="field.dogs" multiple="multiple" name="field.dogs:list"
  size="5" ><option value="c3BvdA==">spot</option>
<option value="Ym93c2Vy">bowser</option>
<option value="cHJpbmNl">prince</option>
<option value="ZHVjaGVzcw==">duchess</option>
<option value="bGFzc2ll">lassie</option></select>
</div>
<input name="field.dogs-empty-marker" type="hidden" value="1" />
</div>

We have no input yet:

>>> try:
...     widget.getInputValue()
... except zope.formlib.interfaces.MissingInputError:
...     print('no input')
no input

Select an item:

>>> request.form['field.dogs-empty-marker'] = '1'
>>> request.form['field.dogs'] = ['bGFzc2ll']
>>> widget.getInputValue()
['lassie']

and another:

>>> request.form['field.dogs'] = ['cHJpbmNl', 'bGFzc2ll']
>>> widget.getInputValue()
['prince', 'lassie']

Finally, what does the widget look like now:

>>> print(widget()) 
<div>
<div class="value">
<select id="field.dogs" multiple="multiple" name="field.dogs:list"
  size="5" ><option value="c3BvdA==">spot</option>
<option value="Ym93c2Vy">bowser</option>
<option selected="selected" value="cHJpbmNl">prince</option>
<option value="ZHVjaGVzcw==">duchess</option>
<option selected="selected" value="bGFzc2ll">lassie</option></select>
</div>
<input name="field.dogs-empty-marker" type="hidden" value="1" />
</div>

An alternative for small numbers of items is to use SourceMultiCheckBoxWidget:

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceMultiCheckBoxWidget(
...     dogs, dogSource, request)

The rendered widget:

>>> print(widget()) 
<div>
<div class="value">
<label for="field.dogs.0"><input class="checkboxType" id="field.dogs.0"
  name="field.dogs" type="checkbox" value="c3BvdA==" />&nbsp;spot</label><br
  /><label for="field.dogs.1"><input class="checkboxType" id="field.dogs.1"
      name="field.dogs" type="checkbox" value="Ym93c2Vy"
      />&nbsp;bowser</label><br
  /><label for="field.dogs.2"><input class="checkboxType" id="field.dogs.2"
      name="field.dogs" type="checkbox" value="cHJpbmNl"
      />&nbsp;prince</label><br
  /><label for="field.dogs.3"><input class="checkboxType" id="field.dogs.3"
      name="field.dogs" type="checkbox"
      value="ZHVjaGVzcw==" />&nbsp;duchess</label><br
  /><label for="field.dogs.4"><input class="checkboxType" id="field.dogs.4"
      name="field.dogs" type="checkbox" value="bGFzc2ll"
      />&nbsp;lassie</label>
</div>
<input name="field.dogs-empty-marker" type="hidden" value="1" />
</div>

We have no input yet:

>>> try:
...     widget.getInputValue()
... except zope.formlib.interfaces.MissingInputError:
...     print('no input')
no input

Select an item:

>>> request.form['field.dogs-empty-marker'] = '1'
>>> request.form['field.dogs'] = ['bGFzc2ll']
>>> widget.getInputValue()
['lassie']

and another:

>>> request.form['field.dogs'] = ['c3BvdA==', 'bGFzc2ll']
>>> widget.getInputValue()
['spot', 'lassie']

Finally, what does the widget look like now:

>>> print(widget()) 
<div>
<div class="value">
<label for="field.dogs.0"><input class="checkboxType" checked="checked"
  id="field.dogs.0" name="field.dogs" type="checkbox" value="c3BvdA=="
  />&nbsp;spot</label><br
  /><label for="field.dogs.1"><input class="checkboxType" id="field.dogs.1"
      name="field.dogs" type="checkbox" value="Ym93c2Vy"
      />&nbsp;bowser</label><br
  /><label for="field.dogs.2"><input class="checkboxType" id="field.dogs.2"
      name="field.dogs" type="checkbox" value="cHJpbmNl"
      />&nbsp;prince</label><br
  /><label for="field.dogs.3"><input class="checkboxType" id="field.dogs.3"
      name="field.dogs" type="checkbox"
      value="ZHVjaGVzcw==" />&nbsp;duchess</label><br
  /><label for="field.dogs.4"><input class="checkboxType" checked="checked"
      id="field.dogs.4" name="field.dogs" type="checkbox" value="bGFzc2ll"
      />&nbsp;lassie</label>
</div>
<input name="field.dogs-empty-marker" type="hidden" value="1" />
</div>

For list ordering support, use SourceOrderedMultiSelectWidget:

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceOrderedMultiSelectWidget(
...     dogs, dogSource, request)

The widget is too complicated to show in complete rendered form here. Insted, we’ll inspect the properties of the widget:

>>> from zope.formlib.interfaces import MissingInputError
>>> try:
...     widget.getInputValue()
... except MissingInputError:
...     print('no input')
no input

>>> widget.choices() == [
...     {'text': u'spot',    'value': 'c3BvdA=='},
...     {'text': u'bowser',  'value': 'Ym93c2Vy'},
...     {'text': u'prince',  'value': 'cHJpbmNl'},
...     {'text': u'duchess', 'value': 'ZHVjaGVzcw=='},
...     {'text': u'lassie',  'value': 'bGFzc2ll'}
... ]
True

>>> widget.selected()
[]

Let’s try out selecting items. Select one item:

>>> request.form['field.dogs-empty-marker'] = '1'
>>> request.form['field.dogs'] = ['bGFzc2ll']
>>> from pprint import pprint
>>> pprint(widget.selected()) 
[{'text': u'lassie',  'value': 'bGFzc2ll'}]

>>> widget.getInputValue()
['lassie']

Select two items:

>>> request.form['field.dogs'] = ['c3BvdA==', 'bGFzc2ll']
>>> pprint(widget.selected())  
[{'text': u'spot',    'value': 'c3BvdA=='},
 {'text': u'lassie',  'value': 'bGFzc2ll'}]

>>> widget.getInputValue()
['spot', 'lassie']

For set-valued fields, use SourceMultiSelectSetWidget:

>>> dogSet = zope.schema.Set(
...     __name__ = 'dogSet',
...     title=u"Dogs",
...     value_type=zope.schema.Choice(
...         source=dogSource,
...     )
... )
>>> dogSet = dogSet.bind(object()) # give the field a context
>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceMultiSelectSetWidget(
...     dogSet, dogSource, request)

>>> try:
...     widget.getInputValue()
... except zope.formlib.interfaces.MissingInputError:
...     print('no input')
no input

>>> print(widget())  
<div>
<div class="value">
<select id="field.dogSet" multiple="multiple"
    name="field.dogSet:list" size="5" ><option value="c3BvdA==">spot</option>
<option value="Ym93c2Vy">bowser</option>
<option value="cHJpbmNl">prince</option>
<option value="ZHVjaGVzcw==">duchess</option>
<option value="bGFzc2ll">lassie</option></select>
</div>
<input name="field.dogSet-empty-marker" type="hidden" value="1" />
</div>

Let’s try out selecting items. Select one item:

>>> request.form['field.dogSet-empty-marker'] = '1'
>>> request.form['field.dogSet'] = ['bGFzc2ll']
>>> widget.getInputValue()
set(['lassie'])

Select two items:

>>> request.form['field.dogSet'] = ['c3BvdA==', 'bGFzc2ll']
>>> sorted(widget.getInputValue())
['lassie', 'spot']

The rendered widget (still with the two items selected) looks like this:

>>> print(widget())  
<div>
<div class="value">
<select id="field.dogSet" multiple="multiple"
    name="field.dogSet:list" size="5" ><option selected="selected"
    value="c3BvdA==">spot</option>
<option value="Ym93c2Vy">bowser</option>
<option value="cHJpbmNl">prince</option>
<option value="ZHVjaGVzcw==">duchess</option>
<option selected="selected" value="bGFzc2ll">lassie</option></select>
</div>
<input name="field.dogSet-empty-marker" type="hidden" value="1" />
</div>

Source Widget Query Framework

An important aspect of sources is that they may have too many values to enumerate. Rather than listing all of the values, we, instead, provide interfaces for querying values and selecting values from query results. Matters are further complicated by the fact that different sources may have very different interfaces for querying them.

To make matters more interesting, a source may be an aggregation of several collections, each with their own querying facilities. An example of such a source is a principal source, where principals might come from a number of places, such as an LDAP database and ZCML-based principal definitions.

The default widgets for selecting values from sources use the following approach:

  • One or more query objects are obtained from the source by adapting the source to zope.schema.interfaces.ISourceQueriables. If no adapter is obtained, then the source itself is assumed to be queriable.
  • For each queriable found, a zope.formlib.interfaces.ISourceQueryView view is looked up. This view is used to obtain the HTML for displaying a query form. The view is also used to obtain search results.

Let’s start with a simple example. We have a very trivial source, which is basically a list:

>>> @zope.interface.implementer(zope.schema.interfaces.ISource)
... class SourceList(list):
...     pass

We need to register our ITerms view:

>>> zope.component.provideAdapter(
...     ListTerms,
...     (SourceList, zope.publisher.interfaces.browser.IBrowserRequest))

We aren’t going to provide an adapter to ISourceQueriables, so the source itself will be used as it’s own queriable. We need to provide a query view for the source:

>>> @zope.interface.implementer(
...         zope.formlib.interfaces.ISourceQueryView)
... @zope.component.adapter(
...         SourceList,
...         zope.publisher.interfaces.browser.IBrowserRequest,
...         )
... class ListQueryView:
...
...     def __init__(self, source, request):
...         self.source = source
...         self.request = request
...
...     def render(self, name):
...         return (
...             '<input name="%s.string">\n'
...             '<input type="submit" name="%s" value="Search">'
...             % (name, name)
...             )
...
...     def results(self, name):
...         if name in self.request:
...             search_string = self.request.get(name+'.string')
...             if search_string is not None:
...                 return [value
...                         for value in self.source
...                         if search_string in value
...                         ]
...         return None

>>> zope.component.provideAdapter(ListQueryView)

Now, we can define a choice field:

>>> dog = zope.schema.Choice(
...    __name__ = 'dog',
...    title=u"Dogs",
...    source=SourceList(['spot', 'bowser', 'prince', 'duchess', 'lassie']),
...    )

As before, we’ll just create the view directly:

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceInputWidget(
...     dog, dog.source, request)

Now if we render the widget, we’ll see the input value (initially nothing) and a form elements for seaching for values:

>>> print(widget())
<div class="value">
  <div class="row">
    <div class="label">
     Selected
    </div>
    <div class="field">
     Nothing
    </div>
  </div>
  <input type="hidden" name="field.dog.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.dog.query.string">
<input type="submit" name="field.dog.query" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

This shows that we haven’t selected a dog. We get a search box that we can type seach strings into. Let’s supply a search string. We do this by providing data in the form and by “selecting” the submit button:

>>> request.form['field.dog.displayed'] = u'y'
>>> request.form['field.dog.query.string'] = u'o'
>>> request.form['field.dog.query'] = u'Search'

Because the field is required, a non-selection is not valid. Thus, while the widget still hasInput, it will raise an error when you getInputValue:

>>> widget.hasInput()
True
>>> widget.getInputValue() 
Traceback (most recent call last):
...
MissingInputError: ('dog', u'Dogs', None)

If the field is not required:

>>> dog.required = False

then as long as the field is displayed, the widget still has input but returns the field’s missing value:

>>> widget.hasInput()
True
>>> widget.getInputValue() # None

Now if we render the widget, we’ll see the search results:

>>> dog.required = True
>>> print(widget())
<div class="value">
  <div class="row">
    <div class="label">
     Selected
    </div>
    <div class="field">
     Nothing
    </div>
  </div>
  <input type="hidden" name="field.dog.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.dog.query.string">
<input type="submit" name="field.dog.query" value="Search">
      </div> <!-- queryinput -->
      <div class="queryresults">
<select name="field.dog.query.selection">
<option value="Ym93c2Vy">bowser</option>
<option value="c3BvdA==">spot</option>
</select>
<input type="submit" name="field.dog.query.apply" value="Apply" />
      </div> <!-- queryresults -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

If we select an item:

>>> request.form['field.dog.displayed'] = u'y'
>>> del request.form['field.dog.query.string']
>>> del request.form['field.dog.query']
>>> request.form['field.dog.query.selection'] = u'c3BvdA=='
>>> request.form['field.dog.query.apply'] = u'Apply'

Then we’ll show the newly selected value:

>>> print(widget())
<div class="value">
  <div class="row">
    <div class="label">
     Selected
    </div>
    <div class="field">
     spot
    </div>
  </div>
  <input type="hidden" name="field.dog" value="c3BvdA==" />
  <input type="hidden" name="field.dog.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.dog.query.string">
<input type="submit" name="field.dog.query" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

Note that we should have an input value now, since pressing the ‘Apply’ button provides us with input:

>>> widget.hasInput()
True

We should also be able to get the input value:

>>> widget.getInputValue()
'spot'

Now, let’s look at a more complicated example. We’ll define a source that combines multiple sources:

>>> @zope.interface.implementer(
...        zope.schema.interfaces.ISource,
...        zope.schema.interfaces.ISourceQueriables,
...        )
... class MultiSource:
...
...     def __init__(self, *sources):
...         self.sources = [(toUnicode(i), s) for (i, s) in enumerate(sources)]
...
...     def __contains__(self, value):
...         for i, s in self.sources:
...             if value in s:
...                 return True
...         return False
...
...     def getQueriables(self):
...         return self.sources

This multi-source implements ISourceQueriables. It assumes that the sources it’s given are queriable and just returns the sources as the queryable objects.

We can reuse our terms view:

>>> zope.component.provideAdapter(
...     ListTerms,
...     (MultiSource, zope.publisher.interfaces.browser.IBrowserRequest))

Now, we’ll create a pet choice that combines dogs and cats:

>>> pet = zope.schema.Choice(
...    __name__ = 'pet',
...    title=u"Dogs and Cats",
...    source=MultiSource(
...      dog.source,
...      SourceList(['boots', 'puss', 'tabby', 'tom', 'tiger']),
...      ),
...    )

and a widget:

>>> widget = zope.formlib.source.SourceInputWidget(
...     pet, pet.source, request)

Now if we display the widget, we’ll see search inputs for both dogs and cats:

>>> print(widget())
<div class="value">
  <div class="row">
    <div class="label">
     Selected
    </div>
    <div class="field">
     Nothing
    </div>
  </div>
  <input type="hidden" name="field.pet.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.pet.MA__.string">
<input type="submit" name="field.pet.MA__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
    <div class="query">
      <div class="queryinput">
<input name="field.pet.MQ__.string">
<input type="submit" name="field.pet.MQ__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

As before, we can perform a search:

>>> request.form['field.pet.displayed'] = u'y'
>>> request.form['field.pet.MQ__.string'] = u't'
>>> request.form['field.pet.MQ__'] = u'Search'

In which case, we’ll get some results:

>>> print(widget())
<div class="value">
  <div class="row">
    <div class="label">
     Selected
    </div>
    <div class="field">
     Nothing
    </div>
  </div>
  <input type="hidden" name="field.pet.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.pet.MA__.string">
<input type="submit" name="field.pet.MA__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
    <div class="query">
      <div class="queryinput">
<input name="field.pet.MQ__.string">
<input type="submit" name="field.pet.MQ__" value="Search">
      </div> <!-- queryinput -->
      <div class="queryresults">
<select name="field.pet.MQ__.selection">
<option value="Ym9vdHM=">boots</option>
<option value="dGFiYnk=">tabby</option>
<option value="dGlnZXI=">tiger</option>
<option value="dG9t">tom</option>
</select>
<input type="submit" name="field.pet.MQ__.apply" value="Apply" />
      </div> <!-- queryresults -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

from which we can choose:

>>> request.form['field.pet.displayed'] = u'y'
>>> del request.form['field.pet.MQ__.string']
>>> del request.form['field.pet.MQ__']
>>> request.form['field.pet.MQ__.selection'] = u'dGFiYnk='
>>> request.form['field.pet.MQ__.apply'] = u'Apply'

and get a selection:

>>> print(widget())
<div class="value">
  <div class="row">
    <div class="label">
     Selected
    </div>
    <div class="field">
     tabby
    </div>
  </div>
  <input type="hidden" name="field.pet" value="dGFiYnk=" />
  <input type="hidden" name="field.pet.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.pet.MA__.string">
<input type="submit" name="field.pet.MA__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
    <div class="query">
      <div class="queryinput">
<input name="field.pet.MQ__.string">
<input type="submit" name="field.pet.MQ__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

Note that we should have an input value now, since pressing the ‘Apply’ button provides us with input:

>>> widget.hasInput()
True

and we can get the input value:

>>> widget.getInputValue()
'tabby'

There’s a display widget, which doesn’t use queriables, since it doesn’t assign values:

>>> request = TestRequest()
>>> widget = zope.formlib.source.SourceDisplayWidget(
...     pet, pet.source, request)
>>> print(widget())
Nothing
>>> from zope.formlib.interfaces import IBrowserWidget
>>> IBrowserWidget.providedBy(widget)
True

>>> widget.setRenderedValue('tabby')
>>> print(widget())
tabby

Like any good display widget, input is not required:

>>> widget.required
False

If we specify a list of choices:

>>> pets = zope.schema.List(__name__ = 'pets', title=u"Pets",
...                         value_type=pet)

when a widget is computed for the field, a view will be looked up for the field and the source, where, in this case, the field is a list field. We’ll just call the widget factory directly:

>>> widget = zope.formlib.source.SourceListInputWidget(
...     pets, pets.value_type.source, request)

If we render the widget:

>>> print(widget())
<div class="value">
  <input type="hidden" name="field.pets.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MA__.string">
<input type="submit" name="field.pets.MA__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MQ__.string">
<input type="submit" name="field.pets.MQ__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

Here the output looks very similar to the simple choice case. We get a search input for each source. In this case, we don’t show any inputs (TODO we probably should make it clearer that there are no selected values.)

As before, we can search one of the sources:

>>> request.form['field.pets.displayed'] = u'y'
>>> request.form['field.pets.MQ__.string'] = u't'
>>> request.form['field.pets.MQ__'] = u'Search'

In which case, we’ll get some results:

>>> print(widget())
<div class="value">
  <input type="hidden" name="field.pets.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MA__.string">
<input type="submit" name="field.pets.MA__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MQ__.string">
<input type="submit" name="field.pets.MQ__" value="Search">
      </div> <!-- queryinput -->
      <div class="queryresults">
<select name="field.pets.MQ__.selection:list" multiple>
<option value="Ym9vdHM=">boots</option>
<option value="dGFiYnk=">tabby</option>
<option value="dGlnZXI=">tiger</option>
<option value="dG9t">tom</option>
</select>
<input type="submit" name="field.pets.MQ__.apply" value="Apply" />
      </div> <!-- queryresults -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

from which we can select some values:

>>> request.form['field.pets.displayed'] = u'y'
>>> del request.form['field.pets.MQ__.string']
>>> del request.form['field.pets.MQ__']
>>> request.form['field.pets.MQ__.selection'] = [
...     u'dGFiYnk=', u'dGlnZXI=', u'dG9t']
>>> request.form['field.pets.MQ__.apply'] = u'Apply'

Which then leads to the selections appearing as widget selections:

>>> print(widget())
<div class="value">
  <input type="checkbox" name="field.pets.checked:list" value="dGFiYnk=" />
  tabby
  <input type="hidden" name="field.pets:list" value="dGFiYnk=" />
  <br />
  <input type="checkbox" name="field.pets.checked:list" value="dGlnZXI=" />
  tiger
  <input type="hidden" name="field.pets:list" value="dGlnZXI=" />
  <br />
  <input type="checkbox" name="field.pets.checked:list" value="dG9t" />
  tom
  <input type="hidden" name="field.pets:list" value="dG9t" />
  <br />
  <input type="submit" name="field.pets.remove" value="Remove" />
  <br />
  <input type="hidden" name="field.pets.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MA__.string">
<input type="submit" name="field.pets.MA__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MQ__.string">
<input type="submit" name="field.pets.MQ__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

We can get the selected values:

>>> widget.getInputValue()
['tabby', 'tiger', 'tom']

We now see the values we selected. We also have checkboxes and buttons that allow us to remove selections:

>>> request.form['field.pets.displayed'] = u'y'
>>> request.form['field.pets'] = [u'dGFiYnk=', u'dGlnZXI=', u'dG9t']
>>> del request.form['field.pets.MQ__.selection']
>>> del request.form['field.pets.MQ__.apply']
>>> request.form['field.pets.checked'] = [u'dGFiYnk=', u'dG9t']
>>> request.form['field.pets.remove'] = u'Remove'

>>> print(widget())
<div class="value">
  <input type="checkbox" name="field.pets.checked:list" value="dGlnZXI=" />
  tiger
  <input type="hidden" name="field.pets:list" value="dGlnZXI=" />
  <br />
  <input type="submit" name="field.pets.remove" value="Remove" />
  <br />
  <input type="hidden" name="field.pets.displayed" value="y" />
  <div class="queries">
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MA__.string">
<input type="submit" name="field.pets.MA__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
    <div class="query">
      <div class="queryinput">
<input name="field.pets.MQ__.string">
<input type="submit" name="field.pets.MQ__" value="Search">
      </div> <!-- queryinput -->
    </div> <!-- query -->
  </div> <!-- queries -->
</div> <!-- value -->

Using vocabulary-dependent widgets with sources

if you have a widget that uses old-style vocabularies but don’t have the time to rewrite it for sources, all is not lost! The wrapper IterableSourceVocabulary can be used to make sources and ITerms look like a vocabulary. This allows us to use vocabulary-based widgets with sources instead of vocabularies.

Usage:

>>> from zope.schema.vocabulary import SimpleTerm

>>> values  = [u'a', u'b', u'c']
>>> tokens  = [ '0',  '1',  '2']
>>> titles  = [u'A', u'B', u'C']

>>> terms = [SimpleTerm(values[i], token=tokens[i], title=titles[i]) \
...     for i in range(0,len(values))]

>>> @zope.interface.implementer(zope.schema.interfaces.IIterableSource)
... class TestSource(list):
...     pass
>>> source = TestSource(values)

>>> @zope.interface.implementer(ITerms)
... class TestTerms(object):
...     def __init__(self, source, request):
...         pass
...     def getTerm(self, value):
...         index = values.index(value)
...         return terms[index]
...     def getValue(self, token):
...         index = tokens.index(token)
...         return values[index]

>>> zope.component.provideAdapter(
...     TestTerms,
...     (TestSource, zope.publisher.interfaces.browser.IBrowserRequest))

>>> from zope.formlib.source import IterableSourceVocabulary
>>> request = TestRequest()
>>> vocab = IterableSourceVocabulary(source, request)
>>> from zope.interface.verify import verifyClass, verifyObject
>>> verifyClass(zope.schema.interfaces.IVocabularyTokenized, \
...     IterableSourceVocabulary)
True
>>> verifyObject(zope.schema.interfaces.IVocabularyTokenized, vocab)
True

>>> len(vocab)
3
>>> (u'a' in vocab) and (u'b' in vocab) and (u'c' in vocab)
True
>>> [value for value in vocab] == terms
True
>>> term = vocab.getTerm(u'b')
>>> (term.value, term.token, term.title)
(u'b', '1', u'B')
>>> term = vocab.getTermByToken('2')
>>> (term.value, term.token, term.title)
(u'c', '2', u'C')

Changes

5.0.1 (2021-10-25)

  • Add support for Python 3.10.

5.0.0 (2021-10-25)

Possibly breaking changes
  • Fix checking of constraints on field contents. The prefix of an IFormField can still be empty and now officially allows dots. See pull request 35.
Features
  • Add support for Python 3.9 and 3.10.
Other changes
  • Remove unused non-BBB imports.
  • Adjust checkbox widget test to new default for required on boolean fields.

4.7.1 (2020-03-31)

  • Ensure all objects have consistent interface resolution orders. See issue 30.
  • Remove support for deprecated python setup.py test command.

4.7.0 (2020-02-27)

  • Move inline javascript function definitions containing “<”, “>” or “&” into external files to follow the XHTML recommendation concerning XML/HTML compatibility (#25)
  • Add support for Python 3.8.
  • Drop support for Python 3.4.

4.6.0 (2019-02-12)

  • Add support for Python 3.7.
  • Make the tests compatible with zope.i18n >= 4.5.

4.5.0 (2018-09-27)

4.4.0 (2017-08-15)

  • Add support for Python 3.5, and 3.6.
  • Drop support for Python 2.6 and 3.3.
  • Use UTF-8 as default encoding when casting bytes to unicode for Python 2 and 3.

4.3.0 (2014-12-24)

  • Add support for PyPy. (PyPy3 is pending release of a fix for: https://bitbucket.org/pypy/pypy/issue/1946)
  • Add support for Python 3.4.
  • Add support for testing on Travis.
  • Explicitly hide span in orderedSelectionList.pt. This only contains hidden inputs, but Internet Explorer 10 was showing them anyway.
  • Support for CSRF protection.
  • Added support for restricting the acceptable request method for the form submit.

4.3.0a1 (2013-02-27)

  • Added support for Python 3.3.

4.2.1 (2013-02-22)

  • Moved default values for the BooleanDisplayWidget from module to class definition to make them changeable in instance.

4.2.0 (2012-11-27)

  • LP #1017884: Add redirect status codes (303, 307) to the set which prevent form rendering.
  • Replaced deprecated zope.component.adapts usage with equivalent zope.component.adapter decorator.
  • Replaced deprecated zope.interface.implements usage with equivalent zope.interface.implementer decorator.
  • Dropped support for Python 2.5.
  • Make separator of SourceSequenceDisplayWidget configurable.

4.1.1 (2012-03-16)

  • Added ignoreContext attribute to form classes to control whether checkInvariants takes the context of the form into account when checking interface invariants.

    By default ignoreContext is set to False. On the AddForm it is True by default because the context of this form is naturally not suitable as context for the interface invariant.

4.1.0 (2012-03-15)

  • checkInvariants now takes the context of the form into account when checking interface invariants.
  • Tests are no longer compatible with Python 2.4.

4.0.6 (2011-08-20)

  • Fixed bug in orderedSelectionList.pt template.

4.0.5 (2010-09-16)

  • Fixed Action name parameter handling, since 4.0.3 all passed names were lowercased.

4.0.4 (2010-07-06)

  • Fixed tests to pass under Python 2.7.
  • Fix validation of “multiple” attributes in orderedSelectionList.pt.

4.0.3 (2010-05-06)

  • Keep Actions from raising exceptions when passed Unicode lables [LP:528468].
  • Improve display of the “nothing selected” case for optional Choice fields [LP:269782].
  • Improve truth testing for ItemDisplayWidget [LP:159232].
  • Don’t blow up if TypeError raised during token conversion [LP:98491].

4.0.2 (2010-03-07)

  • Adapted tests for Python 2.4 (enforce sorting for short pprint output)

4.0.1 (2010-02-21)

  • Documentation uploaded to PyPI now contains widget documentation.
  • Escape MultiCheckBoxWidget content [LP:302427].

4.0 (2010-01-08)

  • Widget implementation and all widgets from zope.app.form have been moved into zope.formlib, breaking zope.formlib’s dependency on zope.app.form (instead zope.app.form now depends on zope.formlib).

    Widgets can all be imported from zope.formlib.widgets.

    Widget base classes and render functionality is in zope.formlib.widget.

    All relevant widget interfaces are now in zope.formlib.interfaces.

3.10.0 (2009-12-22)

  • Use named template from zope.browserpage in favor of zope.app.pagetemplate.

3.9.0 (2009-12-22)

  • Use ViewPageTemplateFile from zope.browserpage.

3.8.0 (2009-12-22)

  • Adjusted test output to new zope.schema release.

3.7.0 (2009-12-18)

  • Rid ourselves from zope.app test dependencies.
  • Fix: Button label needs escaping

3.6.0 (2009-05-18)

  • Remove deprecated imports.
  • Remove dependency on zope.app.container (use IAdding from zope.browser.interfaces) instead. Depend on zope.browser>=1.1 (the version with IAdding).
  • Moved namedtemplate to zope.app.pagetemplate, to cut some dependencies on zope.formlib when using this feature. Left BBB imports here.

3.5.2 (2009-02-21)

  • Adapt tests for Python 2.5 output.

3.5.1 (2009-01-31)

  • Adapt tests to upcoming zope.schema release 3.5.1.

3.5.0 (2009-01-26)

New Features
  • Test dependencies are declared in a test extra now.

  • Introduced zope.formlib.form.applyData which works like applyChanges but returns a dictionary with information about which attribute of which schema changed. This information is then sent along with the IObjectModifiedEvent.

    This fixes https://bugs.launchpad.net/zope3/+bug/98483.

Bugs Fixed
  • Actions that cause a redirect (301, 302) do not cause the render method to be called anymore.
  • The zope.formlib.form.Action class didn’t fully implement zope.formlib.interfaces.IAction.
  • zope.formlib.form.setupWidgets and zope.formlib.form.setupEditWidgets did not check for write access on the adapter but on context. This fixes https://bugs.launchpad.net/zope3/+bug/219948

3.4.0 (2007-09-28)

No further changes since 3.4.0a1.

3.4.0a1 (2007-04-22)

Initial release as a separate project, corresponds to zope.formlib from Zope 3.4.0a1

API Details

API Reference

zope.formlib.interfaces

Form interfaces

exception ConversionError(error_name, original_exception=None)[source]

Bases: exceptions.Exception

A conversion error occurred.

exception ErrorContainer[source]

Bases: exceptions.Exception

A base error class for collecting multiple errors.

exception FormError[source]

Bases: exceptions.Exception

There was an error in managing the form

interface IAction[source]

Extends: zope.formlib.interfaces.ISubPage

Form submit actions

available()

Return a boolean indicating whether the action is available

name

Action name

Implementation:zope.schema.TextLine
Read Only:False
Required:True
Default Value:None
Allowed Type:unicode
success(data)

Handle sucessful submition

This method is called when the action was submitted and the submitted data was valid.

submitted()

Return a boolean indicating whether the action was submitted

label

Action label

Implementation:zope.schema.TextLine
Read Only:False
Required:True
Default Value:None
Allowed Type:unicode
failure(data, errors)

Handle unsucessful submition

This method is called when the action was submitted and the submitted data was not valid.

validator

Action validator

This is a callable object that will be passed a form and an action and that returns a (possibly empty) list of widget input errors.

__name__

Action name with its prefix

Implementation:zope.schema.TextLine
Read Only:False
Required:True
Default Value:None
Allowed Type:unicode
validate(data)

Validate inputs

If an action was submitted and has a custom validator, then the validator result is returned. Otherwise, None is returned.

Validated inputs, if any, are placed into the mapping object passed as an argument,

data

Application data

Implementation:zope.schema.Dict
Read Only:False
Required:True
Default Value:None
Allowed Type:dict
condition

Action condition

This is a callable object that will be passed a form and an action and that returns a boolean to indicate whether the action is available.

__get__(form, form_class=None)

Bind an action to a form

Note that the other methods defined in this interface are valid only after the action has been bound to a form.

interface IActions[source]

An action collection

IActions provide ordered collections of actions that also support name-based lookup.

__iter__()

Return an interator in the actions, in order

__getitem__(name)

Get the action with the given name

Actions are computed from form fields (IFormField). If the form field used to create an action has a prefix, then that should be reflected in the name passed.

__add__(actions)

Add two actions collections

The result has the actions in the first collection followed by the actions in the second collection.

Actions should have different names in the two collections. The bahavior is undefined if the names overlap.

interface IAddFormCustomization[source]

Extends: zope.formlib.interfaces.IFormBaseCustomization

Form responsible for adding an object.

createAndAdd(data)

Create and return an object that has been added to the context.

The data argument is a dictionary with values supplied by the form.

If any user errors occur, they should be collected into a list and raised as a WidgetsError.

This is normally expected to simply call the create() and add() methods.

nextURL()

Return the URL to be displayed after the add operation.

This can be relative to the view’s context.

The default implementation returns self.context.nextURL(), i.e. it delegates to the IAdding view.

create(data)

Create and return an object to be added to the context.

The data argument is a dictionary with values supplied by the form.

If any user errors occur, they should be collected into a list and raised as a WidgetsError.

add(object)

Add an object to the context. Returns the added object.

interface IBoundAction[source]

Extends: zope.formlib.interfaces.IAction

An action that has been bound to a form

form

The form to which the action is bound

interface IBrowserWidget[source]

Extends: zope.formlib.interfaces.IWidget

A widget for use in a web browser UI.

__call__()

Render the widget.

hidden()

Render the widget as a hidden field.

error()

Render the validation error for the widget, or return an empty string if no error

interface IDisplayWidget[source]

Extends: zope.formlib.interfaces.IWidget

A widget for displaying a field value.

required

Required

If True, widget should be displayed as requiring input.

Display widgets should never be required.

Implementation:zope.schema.Bool
Read Only:False
Required:True
Default Value:None
Allowed Type:bool
interface IForm[source]

Base type for forms

This exists primarily to provide something for which to register form-related conponents.

interface IFormAPI[source]

API to facilitate creating forms, provided by zope.formlib.form

Action(label, **options)

Define a submit action

options:

condition
A callable or name of a method to call to test whether the action is applicable. if the value is a method name, then the method will be passed the action when called, otherwise, the callables will be passed the form and the action.
validator
A callable or name of a method to call to validate and collect inputs. This is called only if the action was submitted and if the action either has no condition, or the condition evaluates to a true value. If the validator is provided as a method name, the method will be called the action and a dictionary in which to save data. If the validator is provided as a callable, the callable will be called the form, the action, and a dictionary in which to save data. The validator normally returns a (usually empty) list of widget input errors. It may also return None to behave as if the action wasn’t submitted.
success
A handler, called when the the action was submitted and there are no validation errors. The handler may be provided as either a callable or a method name. If the handler is provided as a method name, the method will be called the action and a dictionary containing the form data. If the success handler is provided as a callable, the callable will be called the form, the action, and a dictionary containing the data. The handler may return a form result (e.g. page), or may return None to indicate that the form should generate it’s own output.
failure
A handler, called when the the action was submitted and there are validation errors. The handler may be provided as either a callable or a method name. If the handler is provided as a method name, the method will be called the action, a dictionary containing the form data, and a list of errors. If the failure handler is provided as a callable, the callable will be called the form, the action, a dictionary containing the data, and a list of errors. The handler may return a form result (e.g. page), or may return None to indicate that the form should generate it’s own output.
prefix
A form prefix for the action. When generating submit actions, the prefix should be combined with the action name, separating the two with a dot. The default prefix is “actions”form.
name
The action name, without a prefix. If the label is a valid Python identifier, then the lowe-case label will be used, otherwise, a hex encoding of the label will be used. If for some strange reason the labels in a set of actions with the same prefix is not unique, a name will have to be given for some actions to get unique names.
css_class
The CSS class for the action. The class defaults to “action”form.
data
A bag of extra information that can be used by handlers, validators, or conditions.
setUpEditWidgets(form_fields, form_prefix, context, request, adapters=None, for_display=False, ignore_request=False)

Set up widgets for editing or displaying content

An IWidgets is returned based on the give form fields.

The resulting widgets will be input widgets unless:

  • the corresponding form field was defined with the for_display option,
  • the underlying field is read only, or
  • the for_display opetion to setUpEditWidgets was passed a true value.

The widgets fields are bound to the context after it is adapted to the field schema. A mapping object can be passed to setUpEditWidgets to capture the adapters created. The adapters are placed in the mapping using both interfaces and interface names as keys.

If the ignore_request option is passed a true value, then widget’s rendered data will be set from the context, and user inputs will be ignored.

setUpInputWidgets(form_fields, form_prefix, context, request, ignore_request=False)

Set up widgets for input

An IWidgets is returned based on the give form fields.

All of the resulting widgets will be input widgets, regardless of whether the form fields are for display or whether the underlying schema fields are read only. This is so that one can easily build an input form, such as an add form from an existing schema.

The widgets will have prefixes that combine the given form prefix and any form-field prefixes.

A context argument is provided to allow field binding.

If ignore_request passed a true value, then the widgets will not initialize their values from the request.

Fields(*arguments, **options)

Create form-fields collection (IFormFields)

Creates a form-field collection from a collection of:

  • Schemas
  • Schema fields
  • form fields (IFormField)
  • form-field collections (IFormFields)

An IFormFields is returned.

The following options are supported:

name
Provide a name to use for the field.
prefix
The form-field prefix for new form-fields created. When form-field collections are passed, their contents keep their existing prefixes are retained.
for_display
A flag indicating whether the form-fiellds are to be used for display. This value is used for for_display attributes of all created form fields. This option does not effect input from form-field collections.
for_input
A flag indicating whether the form-fiellds are to be used for input. This value is used for for_input attributes of all created form fields. This option does not effect input from form-field collections.
render_context
A flag indicating whether the default values to render should come from the form context. See IFormField.
applyChanges(context, form_fields, data, adapters=None)

Apply form data to an object

For each form field that has data, the data are applied to the context argument. The context is adapter to the schema for each field. If an adapters mapping is passed, it will be used as a cache. Typically, it would be a mapping object populated when setUpEditWidgets was called.

Field(schema_field, **options)

Define a form field from a schema field and usage options

The following options are supported:

name
Provide a name to use for the field.
prefix
The form-field prefix.
for_display
A flag indicating whether the form-field is to be used for display. See IFormField.
for_input
A flag indicating whether the form-field is to be used for input. See IFormField.
custom_widget
Factory to use for widget construction. See IFormField.
render_context
A flag indicating whether the default value to render should come from the form context. See IFormField.
get_rendered
A callable or form method name to be used to get a default rendered value. See IFormField.
checkInvariants(form_fields, form_data)

Check schema invariants for input data

For each schema that was used to define the form fields and that had invariants relevent to the fields, the invariants are checked. Invariants that refer to fields not included in the form fields are ignored.

A list of errors is returned.

setUpDataWidgets(form_fields, form_prefix, context, request, data=(), for_display=False, ignore_request=False)

Set up widgets for input or display

An IWidgets is returned based on the give form fields.

The resulting widgets will be input widgets unless:

  • the corresponding form field was defined with the for_display option,
  • the underlying field is read only, or
  • the for_display opetion to setUpEditWidgets was passed a true value.

A data mapping argument can be passed to provide initial data.

If the ignore_request option is passed a true value, then widget’s rendered data will be set from the passed data or from field defaults, and user inputs will be ignored.

action(label, **options)

Create an action factory

This function creates a factory for creating an action from a function, using the function as the action success handler. The options are the same as for the Action constructor except that the options don’t include the success option.

The function is designed to be used as a decorator (Python 2.4 and later), as in:

@action("Edit")
def handle_edit(self, action, data):
    ...
validate(form, actions, form_prefix, data, default_validate=None)

Process a submitted action, if any

Check each of the given actions to see if any were submitted.

If an action was submitted, then validate the input. The input is called by calling the action’s validator, ir it has one, or by calling the default_validate passed in.

If the input is validated successfully, and the action has one success handler, then the success handler is called.

If the input was validated and there were errors, then the action’s failure handler will be called, if it has one.

If an action was submitted, then the function returns the result of validation and the action. The result of validation is normally a boolean, but may be None if no validator was provided.

If no action was submitted, then None is returned for both the result of validation and the action.

getWidgetsData(widgets, form_prefix, data)

Get input data and input errors

A sequence of input errors are returned. Any data available are added to the data argument, which must be a mapping argument. The keys in the output mapping are widget/form-field names without the form prefix.

FormBase

Base class for creating forms

The FormBase class provides reuasable implementation for creating forms. It implements ISubPage, IBrowserPage, and IFormBaseCustomization. Subclasses will override or use attributes defined by IFormBaseCustomization.

interface IFormBaseCustomization[source]

Extends: zope.formlib.interfaces.ISubPage, zope.publisher.interfaces.browser.IBrowserPage

Attributes provided by the Form base class

These attributes may be used or overridden.

Note that the update and render methods are designed to to work together. If you override one, you probably need to override the other, unless you use original versions in your override.

status

An update status message

This is normally generated by success or failure handlers.

setUpWidgets(ignore_request=False)

Set up the form’s widgets.

The default implementation uses the form definitions in the form_fields attribute and setUpInputWidgets.

The function should set the widgets attribute.

errors

Sequence of errors encountered during validation

template

Template used to display the form

This can be overridden in 2 ways:

  1. You can override the attribute in a subclass
  2. You can register an alternate named template, named “default” for your form.
form_fields

The form’s form field definitions

This attribute is used by many of the default methods.

resetForm()

Reset any cached data because underlying content may have changed

label

A label to display at the top of a form

form_reset

Boolean indicating whether the form needs to be reset

error_views()

Return views of any errors.

The errors are returned as an iterable.

widgets

The form’s widgets

  • set by setUpWidgets
  • used by validate
validate(action, data)

The default form validator

If an action is submitted and the action doesn’t have it’s own validator then this function will be called.

form_result

Return from action result method

interface IFormField[source]

Definition of a field to be included in a form

This should not be confused with a schema field.

for_display

Is the form field for display only?

If this attribute has a true value, then a display widget will be used for the field even if it is writable.

Implementation:zope.schema.Bool
Read Only:False
Required:True
Default Value:None
Allowed Type:bool
render_context

Should the rendered value come from the form context?

If this attribute has a true value, and there is no other source of rendered data, then use data from the form context to set the rendered value for the widget. This attribute is ignored if:

  • There is user input and user input is not being ignored, or
  • Data for the value is passed to setUpWidgets.

If the value is true, then it is evaluated as a collection of bit flags with the flags:

DISPLAY_UNWRITEABLE

If the field isn’t writable, then use a display widget

TODO untested

SKIP_UNAUTHORIZED

If the user is not priviledges to perform the requested operation, then omit a widget.

TODO unimplemented

Implementation:zope.schema.Choice
Read Only:False
Required:True
Default Value:False
for_input

Is the form field for input?

If this attribute has a true value, then an input widget will be used for the field even if it is readonly.

Implementation:zope.schema.Bool
Read Only:False
Required:True
Default Value:None
Allowed Type:bool
field

Schema field that defines the data of the form field

prefix

Prefix

Form-field prefix. The form-field prefix is used to disambiguate fields with the same name (e.g. from different schema) within a collection of form fields.

Implementation:zope.schema.ASCII
Read Only:False
Required:False
Default Value:‘’
Allowed Type:str
get_rendered

Object to call to get a rendered value

This attribute may be set to a callable object or to a form method name to call to get a value to be rendered in a widget.

This attribute is ignored if:

  • There is user input and user input is not being ignored, or
  • Data for the value is passed to setUpWidgets.
__name__

Field name

This is the name, without any proefix, used for the field. It is usually the same as the name of the for field’s schem field.

Implementation:zope.schema.ASCII
Read Only:False
Required:True
Default Value:None
Allowed Type:str
custom_widget

Factory to use for widget construction.

If not set, normal view lookup will be used.

interface IFormFields[source]

A colection of form fields (IFormField objects)

__getitem__(name)

Return the form field with the given name

If the desired firld has a prefix, then the given name should be the prefix, a dot, and the unprefixed name. Otherwise, the given name is just the field name.

Raise a KeyError if a field can’t be found for the given name.

get(name, default=None)

Return the form field with the given name

If the desired firld has a prefix, then the given name should be the prefix, a dot, and the unprefixed name. Otherwise, the given name is just the field name.

Return the default if a field can’t be found for the given name.

omit(*names)

Omit fields with given names

__iter__()

Iterate over the form fields

__len__()

Get the number of fields

__add__(form_fields)

Add two form fields collections (IFormFields)

Return a new IFormFields that is the concatination of the two IFormFields.

select(*names)

Select fields with given names in order

Return a new IFormFields that is a selection from the original IFormFields that has the named fields in the specified order.

interface IInputWidget[source]

Extends: zope.formlib.interfaces.IWidget

A widget for editing a field value.

applyChanges(content)

Validate the user input data and apply it to the content.

Return a boolean indicating whether a change was actually applied.

This raises an error if there is no user input.

required

Required

If True, widget should be displayed as requiring input.

By default, this value is the field’s ‘required’ attribute. This field can be set to False for widgets that always provide input (e.g. a checkbox) to avoid unnecessary ‘required’ UI notations.

Implementation:zope.schema.Bool
Read Only:False
Required:True
Default Value:None
Allowed Type:bool
hasInput()

Returns True if the widget has input.

Input is used by the widget to calculate an ‘input value’, which is a value that can be legally assigned to a field.

Note that the widget may return True, indicating it has input, but still be unable to return a value from getInputValue. Use hasValidInput to determine whether or not getInputValue will return a valid value.

A widget that does not have input should generally not be used to update its bound field. Values set using setRenderedValue() do not count as user input.

A widget that has been rendered into a form which has been submitted must report that it has input. If the form containing the widget has not been submitted, the widget shall report that it has no input.

getInputValue()

Return value suitable for the widget’s field.

The widget must return a value that can be legally assigned to its bound field or otherwise raise WidgetInputError.

The return value is not affected by setRenderedValue().

hasValidInput()

Returns True is the widget has valid input.

This method is similar to hasInput but it also confirms that the input provided by the user can be converted to a valid field value based on the field constraints.

interface IPageForm[source]

Extends: zope.formlib.interfaces.IForm, zope.publisher.interfaces.browser.IBrowserPage

A component that displays a form as a page.

interface ISimpleInputWidget[source]

Extends: zope.formlib.interfaces.IBrowserWidget, zope.formlib.interfaces.IInputWidget

A widget that uses a single HTML element to collect user input.

cssClass

CSS Class

The element class attribute.

Implementation:zope.schema.TextLine
Read Only:False
Required:False
Default Value:None
Allowed Type:unicode
tag

Tag

The widget HTML element.

Implementation:zope.schema.TextLine
Read Only:False
Required:True
Default Value:None
Allowed Type:unicode
type

Type

The element type attribute

Implementation:zope.schema.TextLine
Read Only:False
Required:False
Default Value:None
Allowed Type:unicode
extra

Extra

The element extra attribute.

Implementation:zope.schema.TextLine
Read Only:False
Required:False
Default Value:None
Allowed Type:unicode
interface ISourceQueryView[source]

View support for querying non-iterable sources

results(name)

Return the results of the query

The query view should use name as the prefix for its widgets.

The value returned is an iterable.

None may be returned to indicate that there are no results.

render(name)

Return a rendering of the search form elements

The query view should use name as the prefix for its widgets.

interface ISubPage[source]

A component that computes part of a page

prefix

Page-element prefix

All named or identified page elements in a subpage should have names and identifiers that begin with a subpage prefix followed by a dot.

Implementation:zope.schema.ASCII
Read Only:True
Required:True
Default Value:None
Allowed Type:str
update()

Update content ot view information based on user input

render()

Render the sub page, returning a unicode string

setPrefix(prefix)

Update the subpage prefix

interface ISubPageForm[source]

Extends: zope.formlib.interfaces.IForm, zope.formlib.interfaces.ISubPage

A component that displays a part of a page.

The rendered output must not have a form tag. It is the responsibility of the surrounding page to supply a form tag.

interface IWidget[source]

Extends: zope.browser.interfaces.IView

Generically describes the behavior of a widget.

Note that this level must be still presentation independent.

name

The unique widget name

This must be unique within a set of widgets.

hint

A hint regarding the use of the widget.

Hints are traditionally rendered using tooltips in GUIs, but may be rendered differently depending on the UI implementation.

Hint may be translated for the request.

The attribute may be implemented as either a read-write or read-only property, depending on the requirements for a specific implementation.

setRenderedValue(value)

Set the value to be rendered by the widget.

Calling this method will override any values provided by the user.

For input widgets (IInputWidget implementations), calling this sets the value that will be rendered even if there is already user input.

label

The widget label.

Label may be translated for the request.

The attribute may be implemented as either a read-write or read-only property, depending on the requirements for a specific implementation.

visible

A flag indicating whether or not the widget is visible.

setPrefix(prefix)

Set the name prefix used for the widget

The widget name is used to identify the widget’s data within input data. For example, for HTTP forms, the widget name is used for the form key.

It is acceptable to reset the prefix: set it once to read values from the request, and again to redraw with a different prefix but maintained state.

interface IWidgetFactory[source]

A factory that creates the widget

__call__(context, request)

Return a widget

interface IWidgetInputError[source]

Placeholder for a snippet View

doc()

Returns a string that represents the error message.

interface IWidgetInputErrorView[source]

Display an input error as a snippet of text.

snippet()

Convert a widget input error to an html snippet.

interface IWidgets[source]

A widget collection

IWidgets provide ordered collections of widgets that also support:

  • Name-based lookup
  • Keeping track of whether a widget is being used for input or display
__iter_input_and_widget__()

Return an iterator of flag/widget pairs

The flags indicate whether the corresponding widgets are used for input. This is necessary because there is currently no way to introspect a widget to determine whether it is being used for input.

__iter__()

Return an interator in the widgets, in order

__add__(widgets)

Add two widgets collections

The result has the widgets in the first collection followed by the widgets in the second collection.

Widgets should have different names in the two collections. The bahavior is undefined if the names overlap.

__getitem__(name)

Get the widget with the given name

Widgets are computed from form fields (IFormField). If the form field used to create a widget has a prefix, then that should be reflected in the name passed.

exception InvalidCSRFTokenError[source]

Bases: zope.formlib.interfaces.InvalidFormError

The form submit could not be handled as the CSRF token is missing or incorrect.

exception InvalidFormError[source]

Bases: exceptions.Exception

The form submit could not be validated.

exception MissingInputError(field_name, widget_title, errors=None)[source]

Bases: zope.formlib.interfaces.WidgetInputError

Required data was not supplied.

Initialize Error

errors is a ValidationError or a list of ValidationError objects

exception WidgetInputError(field_name, widget_title, errors=None)[source]

Bases: zope.exceptions.interfaces.UserError

One or more user input errors occurred.

Initialize Error

errors is a ValidationError or a list of ValidationError objects

exception WidgetsError(errors, widgetsData={})[source]

Bases: zope.formlib.interfaces.ErrorContainer

A collection of errors from widget processing.

widgetValues is a map containing the list of values that were obtained from the widgets, keyed by field name.

zope.formlib.boolwidgets

Browser widgets for items

class BooleanDisplayWidget(context, request)[source]

Bases: zope.formlib.widget.DisplayWidget

class CheckBoxWidget(context, request)[source]

Bases: zope.formlib.widget.SimpleInputWidget

A checkbox widget used to display Bool fields.

For more detailed documentation, including sample code, see tests/test_checkboxwidget.py.

hasInput()[source]

Check whether the field is represented in the form.

zope.formlib.errors

Error related things.

class InvalidCSRFTokenErrorView(context, request)[source]

Bases: zope.publisher.browser.BrowserPage

class InvalidErrorView(context, request)[source]

Bases: object

Display a validation error as a snippet of text.

snippet()[source]

Convert a widget input error to an html snippet

>>> from zope.interface.exceptions import Invalid
>>> error = Invalid("You made an error!")
>>> InvalidErrorView(error, None).snippet()
u'<span class="error">You made an error!</span>'

zope.formlib.exception

Form-related exception views

class WidgetInputErrorView(context, request)[source]

Bases: object

Display an input error as a snippet of text.

snippet()[source]

Convert a widget input error to an html snippet

>>> from zope.formlib.interfaces import WidgetInputError
>>> class TooSmallError(object):
...     def doc(self):
...         return "Foo input < 1"
>>> err = WidgetInputError("foo", "Foo", TooSmallError())
>>> view = WidgetInputErrorView(err, None)
>>> view.snippet()
u'<span class="error">Foo input &lt; 1</span>'

The only method that IWidgetInputError promises to implement is doc(). Therefore, other implementations of the interface should also work.

>>> from zope.formlib.interfaces import ConversionError
>>> err = ConversionError('Could not convert to float.')
>>> view = WidgetInputErrorView(err, None)
>>> view.snippet()
u'<span class="error">Could not convert to float.</span>'

zope.formlib.form

Forms.

This module provides the zope.formlib.interfaces.IFormAPI interface.

class Action(label, success=None, failure=None, condition=None, validator=None, prefix='actions', name=None, data=None)[source]

Bases: object

See zope.formlib.interfaces.IAction

bindMethods(**methods)[source]

Bind methods to the action

setName(name)[source]

Make sure name is ASCIIfiable. Use action label if name is None

setPrefix(prefix)[source]

Set prefix

AddForm

alias of zope.formlib.form.PageAddForm

class AddFormBase(context, request)[source]

Bases: zope.formlib.form.FormBase

DisplayForm

alias of zope.formlib.form.PageDisplayForm

class DisplayFormBase(context, request)[source]

Bases: zope.formlib.form.FormBase

EditForm

alias of zope.formlib.form.PageEditForm

class EditFormBase(context, request)[source]

Bases: zope.formlib.form.FormBase

Field

alias of zope.formlib.form.FormField

Fields

alias of zope.formlib.form.FormFields

Form

alias of zope.formlib.form.PageForm

class FormBase(context, request)[source]

Bases: zope.publisher.browser.BrowserPage

class FormField(field, name=None, prefix='', for_display=None, for_input=None, custom_widget=None, render_context=False, get_rendered=None, interface=None)[source]

Bases: object

Implementation of zope.formlib.interfaces.IFormField.

class FormFields(*args, **kw)[source]

Bases: object

Implementation of zope.formlib.interfaces.IFormFields.

omit(*names)[source]

Return a modified instance omitting given fields.

select(*names)[source]

Return a modified instance with an ordered subset of fields.

exception NoInputData[source]

Bases: zope.interface.exceptions.Invalid

There was no input data because:

  • It wasn’t asked for
  • It wasn’t entered by the user
  • It was entered by the user, but the value entered was invalid

This exception is part of the internal implementation of checkInvariants.

class PageAddForm(context, request)[source]

Bases: zope.formlib.form.AddFormBase

class PageDisplayForm(context, request)[source]

Bases: zope.formlib.form.DisplayFormBase

class PageEditForm(context, request)[source]

Bases: zope.formlib.form.EditFormBase

class PageForm(context, request)[source]

Bases: zope.formlib.form.FormBase

class SubPageDisplayForm(context, request)[source]

Bases: zope.formlib.form.DisplayFormBase

class SubPageEditForm(context, request)[source]

Bases: zope.formlib.form.EditFormBase

class SubPageForm(context, request)[source]

Bases: zope.formlib.form.FormBase

class Widgets(widgets, prefix_length=None, prefix=None)[source]

Bases: object

Implementation of zope.formlib.interfaces.IWidgets.

class action(label, actions=None, **options)[source]

Bases: object

See zope.formlib.interfaces.IFormAPI.action

applyChanges(context, form_fields, data, adapters=None)[source]

See zope.formlib.interfaces.IFormAPI.applyChanges

checkInvariants(form_fields, form_data, context)[source]

See zope.formlib.interfaces.IFormAPI.checkInvariants

expandPrefix(prefix)[source]

Expand prefix string by adding a trailing period if needed.

expandPrefix(p) should be used instead of p+’.’ in most contexts.

getWidgetsData(widgets, form_prefix, data)[source]

See zope.formlib.interfaces.IFormAPI.getWidgetsData

handleSubmit(actions, data, default_validate=None)[source]

Handle a submit.

setUpWidgets(form_fields, form_prefix=None, context=None, request=None, form=None, data=(), adapters=None, ignore_request=False)[source]

Sets up widgets.

zope.formlib.i18n

I18N support for zope.formlib

zope.formlib.itemswidgets

Browser widgets for items

class DropdownWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.SelectWidget

Variation of the SelectWidget that uses a drop-down list.

Initialize the widget.

class ItemDisplayWidget(*args, **kw)[source]

Bases: zope.formlib.itemswidgets.SingleDataHelper, zope.formlib.itemswidgets.ItemsWidgetBase

Simple single-selection display that can be used in many cases.

class ItemsEditWidgetBase(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.SingleDataHelper, zope.formlib.itemswidgets.ItemsWidgetBase

Widget Base for rendering item-related fields.

These widgets work with Choice fields and Sequence fields that have Choice as value_type.

Initialize the widget.

renderItem(index, text, value, name, cssClass)[source]

Render an item for a particular value.

renderItemsWithValues(values)[source]

Render the list of possible values, with those found in values being marked as selected.

renderSelectedItem(index, text, value, name, cssClass)[source]

Render an item for a particular value that is selected.

setPrefix(prefix)[source]

Set the prefix of the input name.

Once we set the prefix of input field, we use the name of the input field and the postfix ‘-query’ for the associated query view.

class ItemsMultiDisplayWidget(*args, **kw)[source]

Bases: zope.formlib.itemswidgets.MultiDataHelper, zope.formlib.itemswidgets.ItemsWidgetBase

Displays a sequence of items.

renderItems(value)[source]

Render items of sequence.

class ItemsMultiEditWidgetBase(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.MultiDataHelper, zope.formlib.itemswidgets.ItemsEditWidgetBase

Items widget supporting multiple selections.

Initialize the widget.

class ItemsWidgetBase(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.TranslationHook, zope.formlib.widget.SimpleInputWidget

Convenience base class for widgets displaying items/choices.

Initialize the widget.

convertTokensToValues(tokens)[source]

Convert term tokens to the terms themselves.

Tokens are used in the HTML form to represent terms. This method takes the form tokens and converts them back to terms.

hasInput()[source]

Check whether we have any input.

setPrefix(prefix)[source]

Set the prefixes for the field names of the form.

textForValue(term)[source]

Extract a string from the term.

The term must be a vocabulary tokenized term.

This can be overridden to support more complex term objects. The token is returned here since it’s the only thing known to be a string, or str()able.

class ListDisplayWidget(*args, **kw)[source]

Bases: zope.formlib.itemswidgets.ItemsMultiDisplayWidget

Display widget for ordered multi-selection fields.

This can be used for both Sequence, List, and Tuple fields.

class MultiCheckBoxWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.ItemsMultiEditWidgetBase

Provide a list of checkboxes that provide the choice for the list.

Initialize the widget.

renderItem(index, text, value, name, cssClass)[source]

Render an item of the list.

renderSelectedItem(index, text, value, name, cssClass)[source]

Render a selected item of the list.

class MultiDataHelper[source]

Bases: object

Mix-in helper class for getting the term from the HTML form.

This is used when we expect a multiple inputs, i.e. Sequence fields with a Choice field as value_type.

class MultiSelectFrozenSetWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.MultiSelectWidget

Provide a selection list for the set to be selected.

Initialize the widget.

class MultiSelectSetWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.MultiSelectWidget

Provide a selection list for the set to be selected.

Initialize the widget.

class MultiSelectWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.ItemsMultiEditWidgetBase

Provide a selection list for the list to be selected.

Initialize the widget.

class OrderedMultiSelectWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.ItemsMultiEditWidgetBase

A multi-selection widget with ordering support.

Initialize the widget.

choices()[source]

Return a set of tuples (text, value) that are available.

selected()[source]

Return a list of tuples (text, value) that are selected.

class RadioWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.SelectWidget

Radio widget for single item choices.

This widget can be used when the number of selections is going to be small.

Initialize the widget.

renderItem(index, text, value, name, cssClass)[source]

Render an item of the list.

renderSelectedItem(index, text, value, name, cssClass)[source]

Render a selected item of the list.

class SelectWidget(field, vocabulary, request)[source]

Bases: zope.formlib.itemswidgets.ItemsEditWidgetBase

Provide a selection list for the item.

Initialize the widget.

class SetDisplayWidget(*args, **kw)[source]

Bases: zope.formlib.itemswidgets.ItemsMultiDisplayWidget

Display widget for unordered multi-selection fields.

This can be used for both Set field.

class SingleDataHelper[source]

Bases: object

Mix-in helper class for getting the term from the HTML form.

This is used when we expect a single input, i.e. the Choice field.

class TranslationHook[source]

Bases: object

A mixin class that provides the translation capabilities.

zope.formlib.namedtemplate

BBB

zope.formlib.objectwidget

Browser widgets for text-like data

class ObjectWidget(context, request, factory, **kw)[source]

Bases: zope.formlib.widget.BrowserWidget, zope.formlib.widget.InputWidget

A widget over an Interface that contains Fields.

factory

factory used to create content that this widget (field) represents

*_widget

Optional CustomWidgets used to generate widgets for the fields in this widget
applyChanges(content)[source]

See zope.formlib.interfaces.IInputWidget.applyChanges

getInputValue()[source]

Return converted and validated widget data.

The value for this field will be represented as an ObjectStorage instance which holds the subfield values as attributes. It will need to be converted by higher-level code into some more useful object (note that the default EditView calls applyChanges, which does this).

hasInput()[source]

Is there input data for the field

Return True if there is data and False otherwise.

hidden()[source]

Render the object as hidden fields.

setRenderedValue(value)[source]

Set the default data for the widget.

The given value should be used even if the user has entered data.

zope.formlib.sequencewidget

Browser widgets for sequences

class ListSequenceWidget(context, field, request, subwidget=None)[source]

Bases: zope.formlib.sequencewidget.SequenceWidget

class SequenceDisplayWidget(context, field, request, subwidget=None)[source]

Bases: zope.formlib.widget.DisplayWidget

class SequenceWidget(context, field, request, subwidget=None)[source]

Bases: zope.formlib.widget.BrowserWidget, zope.formlib.widget.InputWidget

A widget baseclass for a sequence of fields.

subwidget - Optional CustomWidget used to generate widgets for the
items in the sequence
getInputValue()[source]

Return converted and validated widget data.

If there is no user input and the field is required, then a MissingInputError will be raised.

If there is no user input and the field is not required, then the field default value will be returned.

A WidgetInputError is raised in the case of one or more errors encountered, inputting, converting, or validating the data.

hasInput()[source]

Is there input data for the field

Return True if there is data and False otherwise.

hidden()[source]

Render the list as hidden fields.

widgets()[source]

Return a list of widgets to display

class TupleSequenceWidget(context, field, request, subwidget=None)[source]

Bases: zope.formlib.sequencewidget.SequenceWidget

zope.formlib.source

Source widgets support

class IterableSourceVocabulary(source, request)[source]

Bases: object

Adapts an iterable source into a legacy vocabulary.

This can be used to wrap sources to make them usable with widgets that expect vocabularies. Note that there must be an ITerms implementation registered to obtain the terms.

class SourceDisplayWidget(field, source, request)[source]

Bases: zope.formlib.widget.DisplayWidget

class SourceDropdownWidget(field, source, request)[source]

Bases: zope.formlib.source.SourceSelectWidget

Variation of the SourceSelectWidget that uses a drop-down list.

class SourceInputWidget(field, source, request)[source]

Bases: zope.formlib.widget.InputWidget

class SourceListInputWidget(field, source, request)[source]

Bases: zope.formlib.source.SourceInputWidget

class SourceMultiCheckBoxWidget(field, source, request)[source]

Bases: zope.formlib.itemswidgets.MultiCheckBoxWidget

Provide a list of checkboxes that provide the choice for the list.

class SourceMultiSelectFrozenSetWidget(field, source, request)[source]

Bases: zope.formlib.itemswidgets.MultiSelectFrozenSetWidget

Provide a selection list for the frozenset to be selected.

class SourceMultiSelectSetWidget(field, source, request)[source]

Bases: zope.formlib.itemswidgets.MultiSelectSetWidget

Provide a selection list for the set to be selected.

class SourceMultiSelectWidget(field, source, request)[source]

Bases: zope.formlib.itemswidgets.MultiSelectWidget

A multi-selection widget with ordering support.

class SourceOrderedMultiSelectWidget(field, source, request)[source]

Bases: zope.formlib.itemswidgets.OrderedMultiSelectWidget

A multi-selection widget with ordering support.

class SourceRadioWidget(field, source, request)[source]

Bases: zope.formlib.itemswidgets.RadioWidget

Radio widget for single item choices.

class SourceSelectWidget(field, source, request)[source]

Bases: zope.formlib.itemswidgets.SelectWidget

Provide a selection list for the item.

class SourceSequenceDisplayWidget(field, source, request)[source]

Bases: zope.formlib.source.SourceDisplayWidget

zope.formlib.textwidgets

Browser widgets with text-based input

class ASCII(context, request)[source]

Bases: zope.formlib.textwidgets.Bytes

class ASCIIAreaWidget(context, request)[source]

Bases: zope.formlib.textwidgets.Bytes, zope.formlib.textwidgets.TextAreaWidget

ASCIIArea widget.

Multi-line string input.

>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import ASCII
>>> field = ASCII(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Hello\r\nworld!'})
>>> widget = ASCIIAreaWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
'Hello\nworld!'
class ASCIIDisplayWidget(context, request)[source]

Bases: zope.formlib.textwidgets.BytesDisplayWidget

ASCII display widget

class ASCIIWidget(*args)[source]

Bases: zope.formlib.textwidgets.BytesWidget

ASCII widget.

Single-line data (string) input

class Bytes(context, request)[source]

Bases: zope.formlib.widget.SimpleInputWidget

class BytesAreaWidget(context, request)[source]

Bases: zope.formlib.textwidgets.Bytes, zope.formlib.textwidgets.TextAreaWidget

BytesArea widget.

Multi-line string input.

>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import Bytes
>>> field = Bytes(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Hello\r\nworld!'})
>>> widget = BytesAreaWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
'Hello\nworld!'
class BytesDisplayWidget(context, request)[source]

Bases: zope.formlib.widget.DisplayWidget

Bytes display widget

class BytesWidget(*args)[source]

Bases: zope.formlib.textwidgets.Bytes, zope.formlib.textwidgets.TextWidget

Bytes widget.

Single-line data (string) input

>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import BytesLine
>>> field = BytesLine(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Bob'})
>>> widget = BytesWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
'Bob'
class DateDisplayWidget(context, request)[source]

Bases: zope.formlib.widget.DisplayWidget

Date display widget.

The cssClass and displayStyle attributes may be set to control the formatting of the value.

displayStyle must be one of ‘full’, ‘long’, ‘medium’, ‘short’, or None (’’ is accepted an an alternative to None to support provision of a value from ZCML).

class DateI18nWidget(*args)[source]

Bases: zope.formlib.textwidgets.TextWidget

I18n date entry widget.

The displayStyle attribute may be set to control the formatting of the value.

displayStyle must be one of ‘full’, ‘long’, ‘medium’, ‘short’, or None (’’ is accepted an an alternative to None to support provision of a value from ZCML).

class DateWidget(*args)[source]

Bases: zope.formlib.textwidgets.DatetimeWidget

Date entry widget.

class DatetimeDisplayWidget(context, request)[source]

Bases: zope.formlib.textwidgets.DateDisplayWidget

Datetime display widget.

The cssClass and displayStyle attributes may be set to control the formatting of the value.

displayStyle must be one of ‘full’, ‘long’, ‘medium’, ‘short’, or None (’’ is accepted an an alternative to None to support provision of a value from ZCML).

class DatetimeI18nWidget(*args)[source]

Bases: zope.formlib.textwidgets.DateI18nWidget

I18n datetime entry widget.

The displayStyle attribute may be set to control the formatting of the value.

displayStyle must be one of ‘full’, ‘long’, ‘medium’, ‘short’, or None (’’ is accepted an an alternative to None to support provision of a value from ZCML).

NOTE: If you need timezone information you need to set displayStyle to either ‘long’ or ‘full’ since other display styles just ignore it.

class DatetimeWidget(*args)[source]

Bases: zope.formlib.textwidgets.TextWidget

Datetime entry widget.

class DecimalWidget(*args)[source]

Bases: zope.formlib.textwidgets.TextWidget

class FileWidget(*args)[source]

Bases: zope.formlib.textwidgets.TextWidget

File Widget

hasInput()[source]

See IWidget.hasInput.

Returns True if the submitted request form contains a value for the widget, otherwise returns False.

Some browser widgets may need to implement a more sophisticated test for input. E.g. checkbox values are not supplied in submitted forms when their value is ‘off’ – in this case the widget will need to add a hidden element to signal its presence in the form.

class FloatWidget(*args)[source]

Bases: zope.formlib.textwidgets.TextWidget

class IntWidget(*args)[source]

Bases: zope.formlib.textwidgets.TextWidget

Integer number widget.

Let’s make sure that zeroes are rendered properly:

>>> from zope.schema import Int
>>> field = Int(__name__='foo', title=u'on')
>>> widget = IntWidget(field, None)
>>> widget.setRenderedValue(0)
>>> 'value="0"' in widget()
True
NativeString

alias of zope.formlib.textwidgets.Bytes

NativeStringDisplayWidget

alias of zope.formlib.textwidgets.BytesDisplayWidget

NativeStringWidget

alias of zope.formlib.textwidgets.BytesWidget

class PasswordWidget(*args)[source]

Bases: zope.formlib.textwidgets.TextWidget

Password Widget

class Text(context, request)[source]

Bases: zope.formlib.widget.SimpleInputWidget

class TextAreaWidget(context, request)[source]

Bases: zope.formlib.widget.SimpleInputWidget

TextArea widget.

Multi-line text (unicode) input.

>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import Text
>>> field = Text(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Hello\r\nworld!'})
>>> widget = TextAreaWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
u'Hello\nworld!'
>>> def normalize(s):
...   return '\n  '.join(filter(None, s.split(' ')))
>>> print(normalize( widget() ))
<textarea
  cols="60"
  id="field.foo"
  name="field.foo"
  rows="15"
  >Hello
world!</textarea>
>>> print(normalize( widget.hidden() ))
<input
  class="hiddenType"
  id="field.foo"
  name="field.foo"
  type="hidden"
  value="Hello&#13;&#10;world!"
  />

Calling setRenderedValue will change what gets output:

>>> widget.setRenderedValue("Hey\ndude!")
>>> print(normalize( widget() ))
<textarea
  cols="60"
  id="field.foo"
  name="field.foo"
  rows="15"
  >Hey
dude!</textarea>

Check that HTML is correctly encoded and decoded:

>>> request = TestRequest(
...     form={'field.foo': u'<h1>&copy;</h1>'})
>>> widget = TextAreaWidget(field, request)
>>> widget.getInputValue()
u'<h1>&copy;</h1>'
>>> print(normalize( widget() ))
<textarea
  cols="60"
  id="field.foo"
  name="field.foo"
  rows="15"
  >&lt;h1&gt;&amp;copy;&lt;/h1&gt;</textarea>

There was a but which caused the content of <textarea> tags not to be rendered correctly when there was a conversion error. Make sure the quoting works correctly:

>>> from zope.schema import Text
>>> field = Text(__name__='description', title=u'Description')
>>> from zope.formlib.interfaces import ConversionError
>>> class TestTextAreaWidget(TextAreaWidget):
...     def _toFieldValue(self, input):
...         if 'foo' in input:
...             raise ConversionError("I don't like foo.")
...         return input
...
>>> request = TestRequest(form={'field.description': u'<p>bar</p>'})
>>> widget = TestTextAreaWidget(field, request)
>>> widget.getInputValue()
u'<p>bar</p>'
>>> print(normalize( widget() ))
<textarea
  cols="60"
  id="field.description"
  name="field.description"
  rows="15"
  >&lt;p&gt;bar&lt;/p&gt;</textarea>
>>> request = TestRequest(form={'field.description': u'<p>foo</p>'})
>>> widget = TestTextAreaWidget(field, request)
>>> try:
...     widget.getInputValue()
... except ConversionError as error:
...     print(error.doc())
I don't like foo.
>>> print(normalize( widget() ))
<textarea
  cols="60"
  id="field.description"
  name="field.description"
  rows="15"
  >&lt;p&gt;foo&lt;/p&gt;</textarea>
class TextWidget(*args)[source]

Bases: zope.formlib.widget.SimpleInputWidget

Text widget.

Single-line text (unicode) input

>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import TextLine
>>> field = TextLine(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Bob'})
>>> widget = TextWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
u'Bob'
>>> def normalize(s):
...   return '\n  '.join(filter(None, s.split(' ')))
>>> print(normalize( widget() ))
<input
  class="textType"
  id="field.foo"
  name="field.foo"
  size="20"
  type="text"
  value="Bob"
  />
>>> print(normalize( widget.hidden() ))
<input
  class="hiddenType"
  id="field.foo"
  name="field.foo"
  type="hidden"
  value="Bob"
  />

Calling setRenderedValue will change what gets output:

>>> widget.setRenderedValue("Barry")
>>> print(normalize( widget() ))
<input
  class="textType"
  id="field.foo"
  name="field.foo"
  size="20"
  type="text"
  value="Barry"
  />

Check that HTML is correctly encoded and decoded:

>>> request = TestRequest(
...     form={'field.foo': u'<h1>&copy;</h1>'})
>>> widget = TextWidget(field, request)
>>> widget.getInputValue()
u'<h1>&copy;</h1>'
>>> print(normalize( widget() ))
<input
  class="textType"
  id="field.foo"
  name="field.foo"
  size="20"
  type="text"
  value="&lt;h1&gt;&amp;copy;&lt;/h1&gt;"
  />
class URIDisplayWidget(context, request)[source]

Bases: zope.formlib.widget.DisplayWidget

URI display widget.

Variables:linkTarget – The value of the target attribute for the generated hyperlink. If this is not set, no target attribute is generated.

zope.formlib.utility

Form utility functions

This is an implementation only used by zope.formlib.objectwidget, not by the rest of the widgets in zope.formlib. We would like to keep it this way.

This module is not directly tested: zope.app.form does have tests to test this, and the objectwidget implementation tests this indirectly.

At some point we would like to rewrite zope.formlib.objectwidget so it uses the infrastructure provided by zope.formlib itself.

applyWidgetsChanges(view, schema, target=None, names=None)[source]

Updates an object with values from a view’s widgets.

view contained the widgets that perform the update. By default, the widgets will update the view’s context.

target can be specified as an alternative object to update.

schema contrains the values provided by the widgets.

names can be specified to update a subset of the schema constrained values.

setUpWidget(view, name, field, viewType, value=<object object>, prefix=None, ignoreStickyValues=False, context=None)[source]

Sets up a single view widget.

The widget will be an attribute of the view. If there is already an attribute of the given name, it must be a widget and it will be initialized with the given value if not no_value.

If there isn’t already a view attribute of the given name, then a widget will be created and assigned to the attribute.

setUpWidgets(view, schema, viewType, prefix=None, ignoreStickyValues=False, initial={}, names=None, context=None)[source]

Sets up widgets for the fields defined by a schema.

Appropriate for collecting input without a current object implementing the schema (such as an add form).

view is the view that will be configured with widgets.

viewType is the type of widgets to create (e.g. IInputWidget or IDisplayWidget).

schema is an interface containing the fields that widgets will be created for.

prefix is a string that is prepended to the widget names in the generated HTML. This can be used to differentiate widgets for different schemas.

ignoreStickyValues is a flag that, when True, will cause widget sticky values to be replaced with the context field value or a value specified in initial.

initial is a mapping of field names to initial values.

names is an optional iterable that provides an ordered list of field names to use. If names is None, the list of fields will be defined by the schema.

context provides an alternative context for acquisition.

zope.formlib.widget

Browser Widget Definitions

class BrowserWidget(context, request)[source]

Bases: zope.formlib.widget.Widget, zope.publisher.browser.BrowserView

Base class for browser widgets.

>>> setUp()

The class provides some basic functionality common to all browser widgets.

Browser widgets have a required attribute, which indicates whether or not the underlying field requires input. By default, the widget’s required attribute is equal to the field’s required attribute:

>>> from zope.schema import Field
>>> from zope.publisher.browser import TestRequest
>>> field = Field(required=True)
>>> widget = BrowserWidget(field, TestRequest())
>>> widget.required
True
>>> field.required = False
>>> widget = BrowserWidget(field, TestRequest())
>>> widget.required
False

However, the two required values are independent of one another:

>>> field.required = True
>>> widget.required
False

Browser widgets have an error state, which can be rendered in a form using the error() method. The error method delegates the error rendering to a view that is registered as providing IWidgetInputErrorView. To illustrate, we can create and register a simple error display view:

>>> from zope.component import provideAdapter
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> from zope.formlib.interfaces import IWidgetInputError
>>> @implementer(IWidgetInputErrorView)
... class SnippetErrorView:
...     def __init__(self, context, request):
...         self.context = context
...     def snippet(self):
...         return "The error: " + str(self.context.errors)
>>> provideAdapter(SnippetErrorView,
...                (IWidgetInputError, IDefaultBrowserLayer),
...                IWidgetInputErrorView, '')

Whever an error occurs, widgets should set _error:

>>> widget._error = WidgetInputError('foo', 'Foo', ('Err1', 'Err2'))

so that it can be displayed using the error() method:

>>> widget.error()
"The error: ('Err1', 'Err2')"
>>> tearDown()
class CustomWidgetFactory(widget_factory, *args, **kw)[source]

Bases: object

Custom Widget Factory.

class DisplayWidget(context, request)[source]

Bases: zope.formlib.widget.BrowserWidget

class InputWidget(context, request)[source]

Bases: zope.formlib.widget.Widget

Mixin class providing some default input widget methods.

class SimpleInputWidget(context, request)[source]

Bases: zope.formlib.widget.BrowserWidget, zope.formlib.widget.InputWidget

A baseclass for simple HTML form widgets.

>>> setUp()

Simple input widgets read input from a browser form. To illustrate, we will use a test request with two form values:

>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest(form={
...     'field.foo': u'hello\r\nworld',
...     'baz.foo': u'bye world'})

Like all widgets, simple input widgets are a view to a field context:

>>> from zope.schema import Field
>>> field = Field(__name__='foo', title=u'Foo')
>>> widget = SimpleInputWidget(field, request)

Widgets are named using their field’s name:

>>> widget.name
'field.foo'

The default implementation for the widget label is to use the field title:

>>> widget.label
u'Foo'

According the request, the widget has input because ‘field.foo’ is present:

>>> widget.hasInput()
True
>>> widget.getInputValue()
u'hello\r\nworld'

Widgets maintain an error state, which is used to communicate invalid input or other errors:

>>> widget._error is None
True
>>> widget.error()
''

setRenderedValue is used to specify the value displayed by the widget to the user. This value, however, is not the same as the input value, which is read from the request:

>>> widget.setRenderedValue('Hey\nfolks')
>>> widget.getInputValue()
u'hello\r\nworld'
>>> widget._error is None
True
>>> widget.error()
''

You can use ‘setPrefix’ to remove or modify the prefix used to create the widget name as follows:

>>> widget.setPrefix('')
>>> widget.name
'foo'
>>> widget.setPrefix('baz')
>>> widget.name
'baz.foo'

getInputValue always returns a value that can legally be assigned to the widget field. To illustrate widget validation, we can add a constraint to its field:

>>> import re
>>> field.constraint = re.compile('.*hello.*').match

Because we modified the widget’s name, the widget will now read different form input:

>>> request.form[widget.name]
u'bye world'

This input violates the new field constraint and therefore causes an error when getInputValue is called:

>>> widget.getInputValue() #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
WidgetInputError: ('foo', u'Foo', ConstraintNotSatisfied(u'bye world'))

Simple input widgets require that input be available in the form request. If input is not present, a MissingInputError is raised:

>>> del request.form[widget.name]
>>> widget.getInputValue() #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
MissingInputError: ('baz.foo', u'Foo', None)

A MissingInputError indicates that input is missing from the form altogether. It does not indicate that the user failed to provide a value for a required field. The MissingInputError above was caused by the fact that the form does have any input for the widget:

>>> request.form[widget.name] #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
KeyError: 'baz.foo'

If a user fails to provide input for a field, the form will contain the input provided by the user, namely an empty string:

>>> request.form[widget.name] = ''

In such a case, if the field is required, a WidgetInputError will be raised on a call to getInputValue:

>>> field.required = True
>>> widget.getInputValue() #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
WidgetInputError: ('foo', u'Foo', RequiredMissing('foo'))

However, if the field is not required, the empty string will be converted by the widget into the field’s missing_value and read as a legal field value:

>>> field.required = False
>>> widget.getInputValue() is field.missing_value
True

Another type of exception is a conversion error. It is raised when a value cannot be converted to the desired Python object. Here is an example of a floating point.

>>> from zope.schema import Float
>>> field = Float(__name__='price', title=u'Price')
>>> from zope.formlib.interfaces import ConversionError
>>> class FloatWidget(SimpleInputWidget):
...     def _toFieldValue(self, input):
...         try:
...             return float(input)
...         except ValueError as v:
...             raise ConversionError('Invalid floating point data', v)
...
...     def _toFormValue(self, value):
...         value = super(FloatWidget, self)._toFormValue(value)
...         return '%.2f' % value
>>> request = TestRequest(form={'field.price': u'32.0'})
>>> widget = FloatWidget(field, request)
>>> widget.getInputValue()
32.0
>>> widget()
u'<input class="textType" id="field.price" name="field.price" required="True" type="text" value="32.00"  />'
>>> request = TestRequest(form={'field.price': u'<p>foo</p>'})
>>> widget = FloatWidget(field, request)
>>> try:
...     widget.getInputValue()
... except ConversionError as error:
...     print(error.doc())
Invalid floating point data
>>> widget()
u'<input class="textType" id="field.price" name="field.price" required="True" type="text" value="&lt;p&gt;foo&lt;/p&gt;"  />'
>>> tearDown()
hasInput()[source]

See IWidget.hasInput.

Returns True if the submitted request form contains a value for the widget, otherwise returns False.

Some browser widgets may need to implement a more sophisticated test for input. E.g. checkbox values are not supplied in submitted forms when their value is ‘off’ – in this case the widget will need to add a hidden element to signal its presence in the form.

class UnicodeDisplayWidget(context, request)[source]

Bases: zope.formlib.widget.DisplayWidget

Display widget that converts the value to unicode before display.

class Widget(context, request)[source]

Bases: object

Mixin class providing functionality common across widget types.

renderTag(tag, **kw)[source]

Render the tag. Well, not all of it, as we may want to / it.

zope.formlib.widgets

Browser widgets

Indices and tables