zope.formlib¶
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, andvalidate
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, thenNone
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? Coulduuid.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: thezope.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==" /> spot</label><br
/><label for="field.dog.1"><input class="radioType" id="field.dog.1"
name="field.dog" type="radio" value="Ym93c2Vy" /> bowser</label><br
/><label for="field.dog.2"><input class="radioType" id="field.dog.2"
name="field.dog" type="radio" value="cHJpbmNl" /> prince</label><br
/><label for="field.dog.3"><input class="radioType" id="field.dog.3"
name="field.dog" type="radio" value="ZHVjaGVzcw==" /> duchess</label><br
/><label for="field.dog.4"><input class="radioType" id="field.dog.4"
name="field.dog" type="radio" value="bGFzc2ll" /> 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==" /> spot</label><br
/><label for="field.dog.1"><input class="radioType" id="field.dog.1"
name="field.dog" type="radio" value="Ym93c2Vy" /> bowser</label><br
/><label for="field.dog.2"><input class="radioType" id="field.dog.2"
name="field.dog" type="radio" value="cHJpbmNl" /> prince</label><br
/><label for="field.dog.3"><input class="radioType" id="field.dog.3"
name="field.dog" type="radio" value="ZHVjaGVzcw==" /> duchess</label><br
/><label for="field.dog.4"><input class="radioType" checked="checked"
id="field.dog.4" name="field.dog" type="radio" value="bGFzc2ll"
/> 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==" /> spot</label><br
/><label for="field.dogs.1"><input class="checkboxType" id="field.dogs.1"
name="field.dogs" type="checkbox" value="Ym93c2Vy"
/> bowser</label><br
/><label for="field.dogs.2"><input class="checkboxType" id="field.dogs.2"
name="field.dogs" type="checkbox" value="cHJpbmNl"
/> prince</label><br
/><label for="field.dogs.3"><input class="checkboxType" id="field.dogs.3"
name="field.dogs" type="checkbox"
value="ZHVjaGVzcw==" /> duchess</label><br
/><label for="field.dogs.4"><input class="checkboxType" id="field.dogs.4"
name="field.dogs" type="checkbox" value="bGFzc2ll"
/> 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=="
/> spot</label><br
/><label for="field.dogs.1"><input class="checkboxType" id="field.dogs.1"
name="field.dogs" type="checkbox" value="Ym93c2Vy"
/> bowser</label><br
/><label for="field.dogs.2"><input class="checkboxType" id="field.dogs.2"
name="field.dogs" type="checkbox" value="cHJpbmNl"
/> prince</label><br
/><label for="field.dogs.3"><input class="checkboxType" id="field.dogs.3"
name="field.dogs" type="checkbox"
value="ZHVjaGVzcw==" /> duchess</label><br
/><label for="field.dogs.4"><input class="checkboxType" checked="checked"
id="field.dogs.4" name="field.dogs" type="checkbox" value="bGFzc2ll"
/> 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 anIFormField
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)¶
- Fix IE issue in /@@user-information?user_id=TestUser for orderedSelectionList (GH#17)
- Move documentation to https://zopeformlib.readthedocs.io
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 equivalentzope.component.adapter
decorator. - Replaced deprecated
zope.interface.implements
usage with equivalentzope.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 whethercheckInvariants
takes the context of the form into account when checking interface invariants.By default
ignoreContext
is set toFalse
. On theAddForm
it isTrue
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
fromzope.browser.interfaces
) instead. Depend onzope.browser>=1.1
(the version withIAdding
). - Moved
namedtemplate
tozope.app.pagetemplate
, to cut some dependencies onzope.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 likeapplyChanges
but returns a dictionary with information about which attribute of which schema changed. This information is then sent along with theIObjectModifiedEvent
.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.
-
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 theIAdding
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.
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:
- You can override the attribute in a subclass
- 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 originalIFormFields
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 fromgetInputValue
. UsehasValidInput
to determine whether or notgetInputValue
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()
.
-
-
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 aValidationError
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 aValidationError
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
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
.
zope.formlib.errors¶
Error related things.
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 < 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
-
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
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
.
-
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
PageForm
(context, request)[source]¶ Bases:
zope.formlib.form.FormBase
-
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
.
-
expandPrefix
(prefix)[source]¶ Expand prefix string by adding a trailing period if needed.
expandPrefix(p) should be used instead of p+’.’ in most contexts.
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.
-
renderItemsWithValues
(values)[source]¶ Render the list of possible values, with those found in
values
being marked as selected.
-
-
class
ItemsMultiDisplayWidget
(*args, **kw)[source]¶ Bases:
zope.formlib.itemswidgets.MultiDataHelper
,zope.formlib.itemswidgets.ItemsWidgetBase
Displays a sequence of items.
-
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.
-
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.
-
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.
-
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.
-
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.
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-
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 callsapplyChanges
, which does this).
-
hasInput
()[source]¶ Is there input data for the field
Return
True
if there is data andFalse
otherwise.
Render the object as hidden fields.
-
zope.formlib.sequencewidget¶
Browser widgets for sequences
-
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 andFalse
otherwise.
Render the list as hidden fields.
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
SourceDropdownWidget
(field, source, request)[source]¶ Bases:
zope.formlib.source.SourceSelectWidget
Variation of the SourceSelectWidget that uses a drop-down list.
-
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.
zope.formlib.textwidgets¶
Browser widgets with text-based input
-
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
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
anddisplayStyle
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
anddisplayStyle
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
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
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
¶
-
NativeStringWidget
¶ alias of
zope.formlib.textwidgets.BytesWidget
-
class
PasswordWidget
(*args)[source]¶ Bases:
zope.formlib.textwidgets.TextWidget
Password Widget
-
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 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>©</h1>'}) >>> widget = TextAreaWidget(field, request) >>> widget.getInputValue() u'<h1>©</h1>'
>>> print(normalize( widget() )) <textarea cols="60" id="field.foo" name="field.foo" rows="15" ><h1>&copy;</h1></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" ><p>bar</p></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" ><p>foo</p></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>©</h1>'}) >>> widget = TextWidget(field, request) >>> widget.getInputValue() u'<h1>©</h1>'
>>> print(normalize( widget() )) <input class="textType" id="field.foo" name="field.foo" size="20" type="text" value="<h1>&copy;</h1>" />
-
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, notarget
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 givenvalue
if notno_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, whenTrue
, 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 isNone
, 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 providingIWidgetInputErrorView
. 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
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. TheMissingInputError
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 togetInputValue
:>>> 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="<p>foo</p>" />'
>>> 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.
zope.formlib.widgets¶
Browser widgets