James Gardner: Home > Work > Code > FormBuild > 2.2.0 > FormBuild 2 Manual

FormBuild v2.2.0 documentation

FormBuild 2 Manual

FormBuild 2.0 is a complete re-write of FormBuild 0.1 with a vastly simplified API focusing on the core use cases and with automatic variable escaping built in to help prevent XSS attacks. The new API is much easier to understand.

FormBuild is not designed to be the solution to every possible form problem. Building complex forms is a complex business and abstracting away that complexity only serves to furhter complicate the problem. Instead FormBuild is designed to be handy tool for handling the simple cases and it will stay out of your way for the more complex cases making form building as simple as it can be.

Core Concepts

When you are creating a form you want to use an API which conceptually can be setup like this:

form = Form(
    values={'email': 'someone@example.com'},
    errors={'email':'example.com is not a valid domain name'}
)

and used in a template like this:

${form.start(action="/some/action")}
${form.errors.get('email')}
Email: ${form.text(name='email', value=form.values.get('email') or 'mail@example.com')}
${form.end()}

This API will allow you to build forms with default values, as well as display error messages and submitted values when they are set when constructing the form object.

This sort of API can be very easily implemented using the existing HTML helpers from the WebHelpers package. You could write a class that looks like this:

from webhelpers.html.tags import text

class Form:
    def __init__(self, values=None, errors=None):
        self.values = values
        self.errors = errors
        if values is None:
            self.values = {}
        if errors is None:
            self.errors = {}

    def text(self, name, **attrs):
        # Call the webhelper function
        return text(name, **attrs)

    ... etc for other methods

This API works nicely but in practice has five problems:

  • This API doesn’t help you to layout fields in a sensible HTML structure with labels, help text etc
  • It is inconvenient to have to specify the value in the template via a call to form.values.get(), couldn’t the value be populated automatically and use a default if no value is specified?
  • It is inconvenient to handle the error in this way because for a real example you would need to have some if and else statements to test if the error existed and to format it appropriately in HTML.
  • You might want to extend the class with extra methods, for example to fetch values from a database to populate options in a dropdown.
  • This API doesn’t support all the features of the old FormBuild such as capturing or modifying field values.

Each of these problems can be solved. Let’s look at how FormBuild solves them.

Layout API

To support common requirements for layouts the FormBuild Form class has a field() method which is used like this:

${form.start_with_layout(action="/some/action")}
${form.field(
    label='Email',
    field=dict(type='text', name='email', default=''),
    required=True
)}
${form.end_with_layout()}

The start_with_layout() and end_with_layout() methods add the <form> tags as usual but also add the code needed to start and end the HTML structures generated by the calls to field().

There are also start_layout() and end_layout() methods which you can use if you want to separate the generation of the form tags from the generation of the HTML required for the layout.

The field() method takes a number of extra arguments to customise variables such as whether the field is marked as required, the help text to display after it and any words to appear under the label or the field itself.

Notice that when working with a field() the arguments which you would normally pass to the induvidual form method as passed as a dictionary via the field argument. The field() method will handle calling the appropriate method to generate the HTML for the field. As a result of this alternative calling method you also need to pass the field type as one of the dictionary arguments.

Another consequence of calling field() rather than the form’s individual field methods is that you don’t need to specify an error message. Because the name of the field is passed as one of the values of the dictionary passed via the field arguments, the field() hepler can lookup any error message and display it accordingly. The same also applies to the field’s value. The field() method can look it up from the arguments used to create the form object and pass the correct value when it constructs the field from the field arguments. It is still useful to be able to pass a default value for a field so the field() method can also correctly handle a default key in the field argument, using the default if no value is set, otherwise using the value instead.

Note

The old FormBuild used a series of functions for starting and stopping various components but in practice the flexibility was not needed. It was easier to simply implement an alternative field() function supporting the different layout required. This is the approach you should follow in FormBuild 2.

Custom Field Types

It is sometimes useful to be able to create a custom field type. A common requirement is that a dropdown be populated with possible values from a database. We might want to implement a custom type of dropdown field which is capable of performing a database lookup.

An implementation might look like this:

class CustomForm(Form):

    def database_dropdown(table, cursor, **attrs):
        cursor.execute('SELECT value, label FROM %s', table)
        attrs['options'] = cursor.fecthall()
        return self.dropdown(**attrs)

The field could then be used like this in the template:

form.database_dropdown(table='Color', cursor=cursor, name='color')

In order for this implementation to work the database cursor object would have to be availble in the template. It would be much easier if the cursor could be set when the form was constructed via some sort of state variable. This is exactly what happens. Form classes take a state argument in addition to their values and errors arguments. The state argument can contain any state information your induvidual form generation methods might require. As such it is very similar to the state argument you might use with a FormEncode validator.

Here’s how you might construct the CustomForm:

form = CustomForm(
    values={'email': 'someone@example.com'},
    errors={'email':'example.com is not a valid domain name'}.
    state={'cursor': cursor}
)

The database_dropdown() method can then look like this:

def database_dropdown(table, **attrs):
    self.state['cursor'].execute('SELECT value, label FROM %s', table)
    attrs['options'] = self.state['cursor'].fecthall()
    return self.dropdown(**attrs)

Which means the template code can just concentrate on the arguments which affect how the field is displayed:

form.database_dropdown(table='Color', name='color')

The state argument could also be used to pass a database connection, reference to a memcahced store, SQLAlchemy session or simply configuration information.

James Gardner: Home > Work > Code > FormBuild > 2.2.0 > FormBuild 2 Manual