13 min read

A quick demonstration of automatic forms

Let’s start by showing how this works, before getting into the details. To do that, we’ll add a project model to our application. A project can have any number of lists associated with it, so that related to-do lists can be grouped together. For now, let’s consider the project model by itself. Add the following lines to the app.py file, just after the Todo application class definition. We’ll worry later about how this fits into the application as a whole.

class IProject(interface.Interface):
name = schema.TextLine(title=u'Name',required=True)
kind = schema.Choice(title=u'Kind of project',
values=['personal','business'])
description = schema.Text(title=u'Description')
class AddProject(grok.Form):
grok.context(Todo)
form_fields = grok.AutoFields(IProject)

We’ll also need to add a couple of imports at the top of the file:

from zope import interface
from zope import schema

Save the file, restart the server, and go to the URL http://localhost:8080/todo/addproject. The result should be similar to the following screenshot:

Grok 1.0 Web Development

OK, where did the HTML for the form come from? We know that AddProject is some sort of a view, because we used the grok.context class annotation to set its context and name. Also, the name of the class, but in lowercase, was used in the URL, like in previous view examples.

The important new thing is how the form fields were created and used. First, a class named IProject was defined. The interface defines the fields on the form, and the grok.AutoFields method assigns them to the Form view class. That’s how the view knows which HTML form controls to generate when the form is rendered.

We have three fields: name, description, and kind. Later in the code, the grok.AutoFields line takes this IProject class and turns these fields into form fields.

That’s it. There’s no need for a template or a render method. The grok.Form view takes care of generating the HTML required to present the form, taking the information from the value of the form_fields attribute that the grok.AutoFields call generated.

Interfaces

The I in the class name stands for Interface. We imported the zope.interface package at the top of the file, and the Interface class that we have used as a base class for IProject comes from this package.

Example of an interface

An interface is an object that is used to specify and describe the external behavior of objects. In a sense, the interface is like a contract. A class is said to implement an interface when it includes all of the methods and attributes defined in an interface class. Let’s see a simple example:

from zope import interface
class ICaveman(interface.Interface):
weapon = interface.Attribute('weapon')

def hunt(animal):
"""Hunt an animal to get food"""
def eat(animal):
"""Eat hunted animal"""
def sleep()
"""Rest before getting up to hunt again"""

Here, we are describing how cavemen behave. A caveman will have a weapon, and he can hunt, eat, and sleep. Notice that the weapon is an attribute—something that belongs to the object, whereas hunt, eat, and sleep are methods.

Once the interface is defined, we can create classes that implement it. These classes are committed to include all of the attributes and methods of their interface class. Thus, if we say:

class Caveman(object):
interface.implements(ICaveman)

Then we are promising that the Caveman class will implement the methods and attributes described in the ICaveman interface:

weapon = 'ax'
def hunt(animal):
find(animal)
hit(animal,self.weapon)
def eat(animal):
cut(animal)
bite()
def sleep():
snore()
rest()

Note that though our example class implements all of the interface methods, there is no enforcement of any kind made by the Python interpreter. We could define a class that does not include any of the methods or attributes defined, and it would still work.

Interfaces in Grok

In Grok, a model can implement an interface by using the grok.implements method. For example, if we decided to add a project model, it could implement the IProject interface as follows:

class Project(grok.Container):
grok.implements(IProject)

Due to their descriptive nature, interfaces can be used for documentation. They can also be used for enabling component architectures, but we’ll see about that later on. What is of more interest to us right now is that they can be used for generating forms automatically.

Schemas

The way to define the form fields is to use the zope.schema package. This package includes many kinds of field definitions that can be used to populate a form.

Basically, a schema permits detailed descriptions of class attributes that are using fields. In terms of a form—which is what is of interest to us here—a schema represents the data that will be passed to the server when the user submits the form. Each field in the form corresponds to a field in the schema.

Let’s take a closer look at the schema we defined in the last section:

class IProject(interface.Interface):
name = schema.TextLine(title=u'Name',required=True)
kind = schema.Choice(title=u'Kind of project',
required=False,
values=['personal','business'])
description = schema.Text(title=u'Description',
required=False)

The schema that we are defining for IProject has three fields. There are several kinds of fields, which are listed in the following table. In our example, we have defined a name field, which will be a required field, and will have the label Name beside it. We also have a kind field, which is a list of options from which the user must pick one. Note that the default value for required is True, but it’s usually best to specify it explicitly, to avoid confusion. You can see how the list of possible values is passed statically by using the values parameter. Finally, description is a text field, which means it will have multiple lines of text.

Available schema attributes and field types

In addition to title, values, and required, each schema field can have a number of properties, as detailed in the following table:

Attribute

Description

title

A short summary or label.

description

A description of the field.

required

Indicates whether a field requires a value to exist.

readonly

If True, the field’s value cannot be changed.

default

The field’s default value may be None, or a valid field value.

missing_value

If input for this field is missing, and that’s OK, then this is the value to use.

order

The order attribute can be used to determine the order in which fields in a schema are defined. If one field is created after another (in the same thread), its order will be greater.

In addition to the field attributes described in the preceding table, some field types provide additional attributes. In the previous example, we saw that there are various field types, such as Text, TextLine, and Choice. There are several other field types available, as shown in the following table. We can create very sophisticated forms just by defining a schema in this way, and letting Grok generate them.

Field type

Description

Parameters

Bool

Boolean field.

 

Bytes

Field containing a byte string (such as

the python str). The value might be

constrained to be within length limits.

 

ASCII

Field containing a 7-bit ASCII string. No characters > DEL (chr(127)) are allowed. The value might be constrained to be within length limits.

 

BytesLine

Field containing a byte string without

new lines.

 

ASCIILine

Field containing a 7-bit ASCII string

without new lines.

 

Text

Field containing a Unicode string.

 

SourceText

Field for the source text of an object.

 

TextLine

Field containing a Unicode string

without new lines.

 

Password

Field containing a Unicode string

without new lines, which is set as

the password.

 

Int

Field containing an Integer value.

 

Float

Field containing a Float.

 

Decimal

Field containing a Decimal.

 

DateTime

Field containing a DateTime.

 

Date

Field containing a date.

 

Timedelta

Field containing a timedelta.

 

Time

Field containing time.

 

URI

A field containing an absolute URI.

 

Id

A field containing a unique identifier.

A unique identifier is either an absolute URI or a dotted name. If it’s a dotted name, it should have a module or package name as a prefix.

 

Choice

Field whose value is contained in a

predefined set.

values: A list of text choices for

the field.

vocabulary: A Vocabulary

object that will dynamically

produce the choices.

source: A different, newer way

to produce dynamic choices.

Note: only one of the three

should be provided. More

information about sources and

vocabularies is provided later in

this book.

Tuple

Field containing a value that

implements the API of a conventional

Python tuple.

value_type: Field value items

must conform to the given type, expressed via a field.

Unique. Specifies whether the

members of the collection must

be unique.

List

Field containing a value that

implements the API of a conventional

Python list.

value_type: Field value items

must conform to the given type, expressed via a field.

Unique. Specifies whether the

members of the collection must

be unique.

Set

Field containing a value that

implements the API of a conventional

Python standard library sets.Set or a Python 2.4+ set.

value_type: Field value items

must conform to the given type, expressed via a field.

FrozenSet

Field containing a value that

implements the API of a conventional Python2.4+ frozenset.

value_type: Field value items

must conform to the given type, expressed via a field.

Object

Field containing an object value.

Schema: The interface that

defines the fields comprising the

object.

Dict

Field containing a conventional

dictionary. The key_type and

value_type fields allow specification of restrictions for keys and values contained in the dictionary.

key_type: Field keys must

conform to the given type,

expressed via a field.

value_type: Field value items

must conform to the given type, expressed via a field.

Form fields and widgets

Schema fields are perfect for defining data structures, but when dealing with forms sometimes they are not enough. In fact, once you generate a form using a schema as a base, Grok turns the schema fields into form fields. A form field is like a schema field but has an extended set of methods and attributes. It also has a default associated widget that is responsible for the appearance of the field inside the form.

Rendering forms requires more than the fields and their types. A form field needs to have a user interface, and that is what a widget provides. A Choice field, for example, could be rendered as a <select> box on the form, but it could also use a collection of checkboxes, or perhaps radio buttons. Sometimes, a field may not need to be displayed on a form, or a writable field may need to be displayed as text instead of allowing users to set the field’s value.

Form components

Grok offers four different components that automatically generate forms. We have already worked with the first one of these, grok.Form. The other three are specializations of this one:

  • grok.AddForm is used to add new model instances.
  • grok.EditForm is used for editing an already existing instance.
  • grok.DisplayForm simply displays the values of the fields.

A Grok form is itself a specialization of a grok.View, which means that it gets the same methods as those that are available to a view. It also means that a model does not actually need a view assignment if it already has a form. In fact, simple applications can get away by using a form as a view for their objects. Of course, there are times when a more complex view template is needed, or even when fields from multiple forms need to be shown in the same view. Grok can handle these cases as well, which we will see later on.

Adding a project container at the root of the site

To get to know Grok’s form components, let’s properly integrate our project model into our to-do list application. We’ll have to restructure the code a little bit, as currently the to-do list container is the root object of the application. We need to have a project container as the root object, and then add a to-do list container to it.

To begin, let’s modify the top of app.py, immediately before the TodoList class definition, to look like this:

import grok
from zope import interface, schema

class Todo(grok.Application, grok.Container):
def __init__(self):
super(Todo, self).__init__()
self.title = 'To-Do list manager'
self.next_id = 0
def deleteProject(self,project):
del self[project]

First, we import zope.interface and zope.schema. Notice how we keep the Todo class as the root application class, but now it can contain projects instead of lists. We also omitted the addProject method, because the grok.AddForm instance is going to take care of that. Other than that, the Todo class is almost the same.

class IProject(interface.Interface):
title = schema.TextLine(title=u'Title',required=True)
kind = schema.Choice(title=u'Kind of project',values=['personal',
'business'])
description = schema.Text(title=u'Description',required=False)
next_id = schema.Int(title=u'Next id',default=0)

We then have the interface definition for IProject, where we add the title, kind, description, and next_id fields. These were the fields that we previously added during the call to the __init__ method at the time of product initialization.

class Project(grok.Container):
grok.implements(IProject)
def addList(self,title,description):
id = str(self.next_id)
self.next_id = self.next_id+1
self[id] = TodoList(title,description)
def deleteList(self,list):
del self[list]

The key thing to notice in the Project class definition is that we use the grok.implements class declaration to see that this class will implement the schema that we have just defined.

class AddProjectForm(grok.AddForm):
grok.context(Todo)
grok.name('index')
form_fields = grok.AutoFields(Project)
label = "To begin, add a new project"

@grok.action('Add project')
def add(self,**data):
project = Project()
self.applyData(project,**data)
id = str(self.context.next_id)
self.context.next_id = self.context.next_id+1
self.context[id] = project
return self.redirect(self.url(self.context[id]))

The actual form view is defined after that, by using grok.AddForm as a base class. We assign this view to the main Todo container by using the grok.context annotation. The name index is used for now, so that the default page for the application will be the ‘add form’ itself.

Next, we create the form fields by calling the grok.AutoFields method. Notice that this time the argument to this method call is the Project class directly, rather than the interface. This is possible because the Project class was associated with the correct interface when we previously used grok.implements.

After we have assigned the fields, we set the label attribute of the form to the text: To begin, add a new project. This is the title that will be shown on the form.

In addition to this new code, all occurrences of grok.context(Todo) in the rest of the file need to be changed to grok.context(Project), as the to-do lists and their views will now belong to a project and not to the main Todo application. For details, take a look at the source code of this article for Grok 1.0 Web Development>>Chapter 5.

LEAVE A REPLY

Please enter your comment!
Please enter your name here