Forms in Grok 1.0

Exclusive offer: get 50% off this eBook here
Grok 1.0 Web Development

Grok 1.0 Web Development — Save 50%

Create flexible, agile web applications using the power of Grok—a Python web framework

$23.99    $12.00
by Carlos de la Guardia | February 2010 | Open Source Web Development

Grok has a mechanism for automating the creation and processing of forms. We'll see how it works in this article by Carlos de la Guardia, author of Grok 1.0 Web Development, along with a few other form-related subjects:

  • What is an interface
  • What is a schema
  • How interfaces and schemas are used to generate forms automatically, using Grok's form components
  • How to create, add, and edit forms
  • How to filter fields and prevent them from appearing in a form
  • How to change form templates and presentation

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.

Grok 1.0 Web Development Create flexible, agile web applications using the power of Grok—a Python web framework
Published: February 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

Form actions

If you carefully look at the screenshot shown in the A quick demonstration of automatic forms section, you will see that the form has no submit buttons. In Grok, every form can have one or more actions, and for each action the form will have a submit button. The Grok action decorator is used to mark the methods of the form class that will be used as actions. In this case, the add method is decorated with it and the value of the text parameter, in this case Add project, will be used as the text on the button. To change the text on the button, simply modify the string passed to the decorator:

@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 add method receives all of the filled form fields in the data parameter, and then creates a Project instance. Next, it sets the attributes of project to the values in the form by calling the applyData method of the form. Finally, it adds the new project to the Todo instance and redirects the user to the project page.

Trying out the application

When you try out the application, there are two things to notice. First, when the add project form is displayed, the next_id field, which is used to name the projects, is shown. We could even edit it if we like. Obviously, we don't want this behavior.

Second, once the project has been created and we get redirected to the project page, everything works as before, even though we didn't touch the templates. Now that we have a structure based on models, the views and methods that were registered for them don't need to change, even though the containment hierarchy is different.

Another important thing to notice is that the add project form has no design or styling at all. This is because the form building mechanism uses a common template, which we haven't styled yet.

Grok 1.0 Web Development

Filtering fields

Remember that currently we have the next_id field of the project shown on the form. We don't want it there, so how do we remove it? Fortunately for us, the list of fields generated by the grok.AutoFields method can easily be filtered.

We can either select precisely the fields we need, using the select method:

form_fields = grok.AutoFields(Project).select('title','kind',
'description')

Or we can omit specific fields by using the omit method:


form_fields = grok.AutoFields(Project).omit('next_id')

In both cases, we pass the IDs of the fields as strings to the selected method. Now the next_id field is not there anymore, as you can see in the next screenshot.

Filtering fields can be useful not just for removing unwanted fields such as next_id. We can also have specialized forms for editing only a part of a schema, or for showing specific fields, depending on user information or input.

Using grok.EditForm

The form that we just made is intended to add a new project to the application, but what if we need to edit an existing project? In this case, we need a form that knows how to get the existing values of all of the fields on the form, and display them when editing. Another difference is that the add project form was assigned to the main Todo application, but an edit form would use as a context the actual project that it would be modifying.

That's why Grok has another kind of form component for editing. Using grok.EditForm is even easier than grok.AddForm. Here's all the code that we need to add to our application in order to be able to edit projects:

class EditProjectForm(grok.EditForm):
grok.context(Project)
grok.name('edit')
form_fields = grok.AutoFields(Project).omit('next_id')
label = "Edit the project"

As mentioned earlier, the context for this form is the Project class, which we set by using the grok.context class annotation. We give this form the name edit, so that it will be possible to just append that word to a project URL to get its edit view. As we discussed in the previous section, it is a good idea to eliminate the display of the next_id field from the form, so we use the omit method to do that. Finally, we set a label for the form and we are then ready to test it.

Start the application. If you haven't created a project already, please do so. Then, go to the URL: http://localhost:8080/todo/0/edit. Edit the project fields and then click on the Apply button. You should see a screen similar to the one shown in following screenshot:

Grok 1.0 Web Development

Notice how we didn't include a redirect after rendering the form, so that when we click on the Apply button we go back to the same form, but with a message telling us that the object was updated along with the date of modification. If we wanted, we could add an 'edit' action, by using the action decorator and a redirect, just like we did for the add form.

Modifying individual form fields

Having the add and edit forms created automatically by Grok is neat, but there are cases where we will need to make small modifications in how a form is rendered. Grok allows us to modify specific field attributes easily to make that happen.

Remember that each form field will be rendered by a widget, which could be thought of as views for specific fields. These views usually accept a number of parameters to allow the user to customize the appearance of the form in one way or another.

Just before being rendered, Grok's form components always call a method named setUpWidgets, which we can override in order to make modifications to the fields and their attributes.

In the add and edit project forms, the title of the project, which is of type TextLine, has a widget that displays the <input> tag used to capture its value with a length of 20 characters. Many project names could be longer than that, so we want to extend the length to 50 characters. Also, the text area for the description is too long for a project summary, so we'll cut it to five rows instead. Let's use the setUpWidgets method for this. Add the following lines to both the AddProjectForm and EditProjectForm classes:

def setUpWidgets(self, ignore_request=False):
super(EditProjectForm,self).setUpWidgets(ignore_request)
self.widgets['title'].displayWidth = 50
self.widgets['description'].height = 5

Take care to substitute EditProjectForm for AddProjectForm on the super call when adding the method to its appropriate class. The setUpWidgets method is fairly simple. We first call the super class to make sure that we get the correct properties in the form before trying to modify them. Next, we modify any properties that we want for the field. In this case, we access the widgets property to get at the widgets for the fields that we defined, and change the values that we want.

Another thing that requires explanation is the ignore_request parameter that is passed to the setUpWidgets method. If this is set to False, as we have defined it, then this means that any field values present in the HTTP request will be applied to the corresponding fields. A value of True means that no values should be changed during this call.

Restart the application and you will see that the edit and add forms now present the widgets using the properties that we modified.

Form validation

Now we have working forms for adding and editing projects. In fact, the forms can do more than we have shown so far. For example, if we go to the add form and try to submit it without filling in the required title field, we'll get the form back, instead of being redirected. No project will be created, and an error message will be visible on the screen, warning us that the field can't be empty.

This validation happens automatically, but we can also add our own constraints by using the constraint parameter, when defining a field in the schema. For example, suppose that we absolutely need to have more than two words in the title. We can do that very easily. Just add the following lines before the interface definition:

def check_title(value):
return len(value.split())>2

Next, modify the title field definition to look like this:


title = schema.TextLine(title=u'Title', required=True,
constraint=check_title)

We first defined a function that will receive the field value as a parameter, and must return True if the value is valid, or False otherwise. We then assign this function to the constraint parameter, when defining the field in the interface. This is all very simple, but it allows us to add validations for whatever conditions we need to meet in our form data.

There are cases where a simple constraint is not enough to validate a field. For instance, imagine that what we need is that whenever the kind of the project is 'business', the description can't be empty. In this case, a constraint will not do, as whether or not the description field is valid depends on the value of another field.

A constraint that involves more than one field is known as an invariant in Grok. To define one, the @interface.invariant decorator is used. For the hypothetical case described earlier, we can use the following definition, which we'll add inside the Interface definition:

@interface.invariant
def businessNeedsDescription(project):
if project.kind=='business' and not project.description:
raise interface.Invalid(
"Business projects require a description")

Now, when we try to add a project of kind 'business', Grok will complain if the description is empty. Look at the next screenshot for reference:

Grok 1.0 Web Development

Customizing the form template

Earlier, we commented on the fact that our new project forms have no styling or design applied, and therefore they look markedly different from the rest of our application. It's time to change that.

The disadvantage of using automatic form generation is that the default template must be fairly generic to be useful in multiple applications. However, Grok allows us to set a custom template for editing the form. All we need to do is set the template attribute in the form:

template = grok.PageTemplateFile('custom_edit_form.pt')

Of course, for this to work we also have to provide the named template inside our application directory (not inside app_templates). For now, let's just add a stylesheet and a class to the generic edit template that comes with Grok. There is nothing special here, so we will just take the default edit form template and add the same stylesheet that we defined previously. Please look at the source code of this article, if you want to see the rest of the template.

<html>
<head>
<title tal:content="context/title">To-Do list manager</title>
<link rel="stylesheet" type="text/css"
tal:attributes="href static/styles.css" />
</head>

That's all that's needed in order to have our custom template for editing the form. Take a look at the next screenshot to see how it looks. Of course, we would have to further modify it to get it to look just like we want. We could even leave only the fields that we wanted, placed in an irregular arrangement, but the reason that we used the original template and modified it just a little is so that you can look at it and be careful with the sections where the validation messages are shown and the actions are generated.

Grok 1.0 Web Development

Summary

We have seen how to automatically generate forms by using schemas, and how it's possible to customize their rendering. In addition, we learned a little bit about some Zope Framework libraries, such as zope.schema and zope.interface.

 

If you have read this article you may be interested to view :

Grok 1.0 Web Development Create flexible, agile web applications using the power of Grok—a Python web framework
Published: February 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Carlos de la Guardia

Carlos has been doing web consulting and development since 1994, when selling any kind of project required two meetings just to explain what the Internet was in the first place. He was the co-founder of Aldea Systems, a web consulting company where he spent ten years working in all kinds of web projects using a diverse range of languages and tools. In 2005 he became an independent developer and consultant, specializing in Zope and Plone projects. He frequently blogs about Plone and other Zope-related subjects.

Books From Packt

Joomla! 1.5 Multimedia
Joomla! 1.5 Multimedia

TYPO3 4.3 Multimedia Cookbook
TYPO3 4.3 Multimedia Cookbook

Building Telephony Systems with OpenSIPS 1.6
Building Telephony Systems with OpenSIPS 1.6

Moodle 1.9 Teaching Techniques
Moodle 1.9 Teaching Techniques

Backbase 4 RIA Development
Backbase 4 RIA Development

AJAX and PHP: Building Modern Web Applications 2nd Edition
AJAX and PHP: Building Modern Web Applications 2nd Edition

jQuery UI 1.7: The User Interface Library for jQuery
    jQuery UI 1.7: The User Interface Library for jQuery

Apache MyFaces Trinidad 1.2: A Practical Guide
Apache MyFaces Trinidad 1.2: A Practical Guide

No votes yet
Get attribute error when I use the omit for the next_id by
Love the book and Grok, but... Attribute error next_id doesn't exist. Goes away when it's on the field.

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
m
g
c
X
s
r
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software