Chapter 8
Zope Schemas and Widgets (Forms)

Difficulty

Newcomer

Skills

Problem/Task

In the early stages of development, the Zope 3 developers decided that it would be cumbersome to manually write HTML forms and to manually validate the input. We realized that if we would extend interfaces, we could auto-generate HTML forms and also automatically validate any input. This chapter gives some background information and formally introduces the zope.schema and zope.app.form packages.

Solution

8.1 History and Motivation

Originally, I simply wanted to port Formulator, a very successful Zope 2 product to auto-generate and validate forms, to Zope 3. In Formulator, one would create various input fields (like integers or text lines) in a form and provide some meta-data about the fields, like the maximum and minimum length of a string. You could then tell the form to simply render itself. For more details see http://zope.org/Members/infrae/Formulator.

Even though Formulator tried to split application logic and presentation, various parts were still not sufficiently separated, mainly due to the limitations Zope 2 provided. Therefore the original port remained a hack in Zope 3 until the idea of schemas was developed by Jim Fulton and Martijn Faassen (the original author of Formulator) during the Berlin BBQ Sprint (April 2002) when trying to combine Zope 3’s version of Formulator and class properties. After all presentation logic was removed, Formulator fields felt a lot like interface specification for attributes. So it was realized, that if we supply more meta-data to the attribute declarations in interfaces, then we could accomplish auto-generation and validation of HTML forms. These extended attributes are still known as “fields”. If an interface contains any fields, then this interface is conventionally called a schema.

The following three main goals of schemas developed:

  1. Full specification of properties on an API level
  2. Data input validation and conversion
  3. Automated GUI form generation (mainly for the Web browser)

8.2 Schema versus Interfaces

As mentioned before, schemas are just an extension to interfaces and therefore depend on the zope.interface package. Fields in schemas are equivalent to methods in interfaces. Both are complementary to each other, since they describe different aspects of an object. The methods of an interface describe the functionality of a component, while the schema’s fields represent the state.

It is thus not necessary to develop a new syntax for writing schemas and we simply reuse the interface declaration:


1  from zope.interface import Interface
2  from zope.schema import Text
3  
4  class IExample(Interface):
5  
6      text = Text(
7          title=u"Text",
8          description=u"The text of the example.",
9          required=True)

8.3 Core Schema Fields

After we have seen a simple example of a schema, let’s now look at all the basic fields and their properties.

For a formal listing of the Schema/Field API, see the API documentation tool at http://localhost:8080/++apidoc++ or see zope.schema.interfaces module.

8.4 Auto-generated Forms using the forms Package

Forms are much more Zope-specific than schemas and can be found in the zope.app.forms package. The views of schema fields are called widgets. Widgets responsible for data display and conversion in their specific presentation type. Currently widgets exist mainly for HTML (the Web browser).

Widgets are separated into two groups, display and input widgets. Display widgets are often very simply and only show a text representation of the Python object. The input widgets, however, are more complex and display a greater variety of choices. The following list shows all available browser-based input widgets (found in zope.app.form.browser):

Text Widgets

Text-based widgets always require some sort of keyboard input. A string representation of a field is then converted to the desired Python object, like and integer or a date.

Boolean Widgets

Boolean widgets’ only responsibility is to convert some binary input to the Python values True or False.

Single Selection Widgets

Widgets that allow a single item to be selected from a list of values are usually views of a field, a vocabulary and the request, instead of just the field and request pair. Therefore so called proxy-widgets are used to map from field-request to field-vocabulary-request pairs. For example the ChoiceInputWidget, which takes a Choice field and a request object, is simply a function that looks up another widget that is registered for the Choice field, its vocabulary and the request. Below is a list of all available widgets that require the latter three inputs.

Multiple Selections Widgets

This group of widgets is used to display input forms collection-based fields, such as List or Set. Similar to the single selection widgets, two proxy-widgets are used to look up the correct widget. The first step is to map from field- request to field- value_type- request using a widget called CollectionInputWidget. This allows us to use different widgets when the value type is an Int or Choice field for example. Optionally, a second proxy-widget is used to convert the field- value_type- request pair to a field- vocabulary- request pair, as it is the case when the value type is a choice field.

Miscellaneous Widgets

Here is a simple interactive example demonstrating the rendering and conversion functionality of a widget:


1  >>> from zope.publisher.browser import TestRequest
2  >>> from zope.schema import Int
3  >>> from zope.app.form.browser import IntWidget
4  >>> field = Int(__name__='number', title=u'Number', min=0, max=10)
5  >>> request = TestRequest(form={'field.number': u'9'})
6  >>> widget = IntWidget(field, request)
7  >>> widget.hasInput()
8  True
9  >>> widget.getInputValue()
10  9
11  >>> print widget().replace(' ', '\n  ')
12  <input
13    class="textType"
14    id="field.number"
15    name="field.number"
16    size="10"
17    type="text"
18    value="9"
19  
20    />

Note that you usually will not have to deal with these methods at all manually, since the form generator and data converter does all the work for you. The only method you will commonly overwrite is _validate(), which you will use to validate custom values. This brings us right into the next subject, customizing widgets.

There are two ways of customizing widgets. For small adjustments to some parameters (properties of the widget), one can use the browser:widget subdirective of the browser:addform and browser:editform directives. For example, to change the widget for a field called “name”, the following ZCML code can be used.


1  <browser:addform
2    ... >
3  
4    <browser:widget
5        field="name"
6        class="zope.app.form.browser.TextWidget"
7        displayWidth="45"
8        style="width: 100%"/>
9  
10  </browser:addform>

In this case we force the system to use the TextWidget for the name, set the display width to 45 characters and add a style attribute that should try to set the width of the input box to the available width.

The second possibility to change the widget of a field is to write a custom view class. In there, custom widgets are easily realized using the CustomWidget wrapper class. Here is a brief example:


1  from zope.app.form.widget import CustomWidget
2  from zope.app.form.browser import TextWidget
3  
4  class CustomTextWidget(TextWidget):
5      ...
6  
7  class SomeView:
8      name_widget = CustomWidget(CustomTextWidget)

More information about schemas can be found in the README.txt file of the zope.schema package. The Zope 3 development Web site also contains some additional material.

This concludes our introduction to schemas and forms. For examples of schemas and forms in practice, see the first chapters of the “Content Components - The Basics” part.