3.3 Easy form feedback

This third and final example will show you how to use Draco's Form and FormRewriter classes to simplify the verification and feedback of html forms. We will adapt the example from the previous section to use these classes.

Let's start with the process of form verification. The verification process can be split up in three logical phases:

  1. Verify that all the required parameters are present. Optional parameters that are absent will have a default value assigned to them.
  2. Verify that each parameter contains an appropriate value.
  3. Optionally, verify constraints between different parameters and/or external data.

Draco provides the utility class Form that helps you to implement the three phase verification process described above. How to use it is best shown by an example. The following code fragment implements the checkmessage() handler from the previous section using a Form.

1.  from draco.handler import Handler
2.  from draco.session import session
3.  from draco.response import response
4.  from draco.database import database
5.  from draco.util import attributes
6.  from draco.form import *
7.
8.  u_add = '/addmessage.dsp'
9.
10. class AddMessageForm(Form):
11.
12.     def __init__(self):
13.         Form.__init__(self)
14.         self.addField(StringField('subject', absent='No subject!'))
15.         self.addField(TextField('body', absent='No body!'))
16.
17. class MessageHandler(Handler):
18.
19.     def checkmessage(self, path, args):
20.     """
21.     Check the form from "addmessage.dsp".
22.     """
23.     form = AddMessageForm()
24.     try:
25.         form.parse(args)
26.     except FormError, err:
27.         ns_add = session.namespace(u_add)
28.         ns_add.update(attributes(err))
29.         ns_add.update(args)
30.         response.redirect(u_add)
31.     cursor = database.cursor()
32.     cursor.execute("""
33.             insert into messages
34.             (subject, body) values (%(subject)s, %(body)s)
35.             """, form)
36.     self['status'] = 'The message has been added successfully.'

There are a lot of new features here so let's discuss them one at a time. An important part is at lines 10-15, where a new form is defined. This is done by subclassing Form that was imported from draco.form at line 6. Then, in the constructor at lines 14,15, two objects are added to the form: a StringField and a TextField. This is how you build up forms: you add one or more Field subclasses. Each field corresponds to one input element and the type of the field specifies which values are acceptable. Draco provides an extensive set of Field subclasses in the draco.form module. Two field types are used here: StringField and TextField. They verify that their input is a single line string and an arbitrary text respectively.

The form is instantiated at line 23. At line 25, its parse() method is called to parse the args namespace. If no error occurs, we can access the processed arguments by using form as a namespace. This is shown at lines 32-35 where the message is stored in the database.

If the form cannot be verified, a FormError exception is raised. Normally, you'd want to catch this exception and provide form feedback. The exception object contains two attributes, status and err_fields. The first attribute is a string describing the error. By default this is a terse english error message but it can be customized using illegal= and absent= keyword parameters in the Field constructor. The second attribute, err_fields, contains a list of fields names that are in error3.2. At line 28, we use the attributes() utility function to extract these attributes and put them in a dictionary so that we can store them all at once in the session. At line 29, the arguments that were provided are stored in the session too and at line 30 the remote client is redirected back to the form.

By adding Field to the form, you can implement phases 1 and 2 of the form verification process described above. To check dependencies between form fields and/or exernal data, you must provide your own parse() method. Typically, you'd first call Form's parse() method and then check your dependencies.

Let's close the subject of form verification and continue with form feedback. In the example from the previous section we have substituted back form variables by using <%= and %> markers to insert values at strategic locations in the template. This process is rather tedious and error prone. Fortunately Draco provides an automated way of doing this.

First we must introduce Draco's tag rewriting system. This system is explained in detail in section 5.5 of this manual but for now it is sufficient to know that you can register a special function to rewrite html tags in a template. Each time a html tag is encountered, its registered functions are run. Such functions are called rewriter functions and can change the attributes of an html tag and can append some literal text. This mechanism is used by Draco to implement url tagging, image size detection, and also form feedback. Below is a new version of the addmessage handler that makes use of this. Only the handler is shown, its enclosing handler class is left out.

1. from draco.form import *
2.
3.    def addmessage(self, path, args):
4.         """
5.         Show a form to add a message.
6.         """
7.         ns_add = session.namespace(u_add)
8.         self.update(ns_add)
9.         ns_add.clear()
10.        rewriter = FormRewriter(self)
11.        rewriter.install()

At line 10 we instantiate a FormRewriter, passing the interface (self) as a parameter. This rewriter is registered with the rewrite manager at line 11. The rewriter will look for form elements in the html and will put back values with the same name from the namespace that we passed as an argument to the constructor. Now, the /addmessage.dsp template doesn't need to contain anymore code blocks, except the expression that formats status.



Footnotes

... error3.2
The default parse() method quits on the first error it encouters so err_fields will contain only one element.