Retail Form Rendering

In the previous chapter we demonstrated how to use Deform to render a complete form, including the input fields, the buttons, and so forth. We used the deform.Field.render() method, and injected the resulting HTML snippet into a larger HTML page in our application. That is an effective and quick way to put a form on a page, but sometimes you need more fine-grained control over the way form HTML is rendered. For example, you may need form elements to be placed on the page side-by-side or you might need the form’s styling to be radically different than the form styling used by the default rendering of Deform forms. Often it’s easier to use Deform slightly differently, where you do more work yourself to draw the form within a template, and only use Deform for some of its features. We refer to this as “retail form rendering”.

Note

This feature is new as of Deform 0.9.6.

A Basic Example

Our schema and form object:

1
2
3
4
5
6
7
8
9
import colander

class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String())
    age = colander.SchemaNode(colander.Integer(),
                              validator=colander.Range(0, 200))

schema = Person()
form = deform.Form(schema, resource_registry=resource_registry)

We feed the schema into a template as the form value. It doesn’t matter what kind of templating system you use to do this, but this example will use ZPT. Below, the name form refers to the form we just created above:

<div class="row"
     tal:repeat="field form">
    <div class="span2">
        ${structure:field.title}
        <span class="req" tal:condition="field.required">*</span>
    </div>
    <div class="span2">
        ${structure:field.serialize()}
    </div>
    <ul tal:condition="field.error">
        <li tal:repeat="error field.error.messages()">
            ${structure:error}
        </li>
    </ul>
</div>

The above template iterates over the fields in the form, using the attributes of each field to draw the title.

You can use the __getitem__ method of a form to grab named form fields instead of iterating over all of its fields. For example:

<div tal:define="field form['name']">
    <div class="span2">
        ${structure:field.title}
        <span class="req" tal:condition="field.required">*</span>
    </div>
    <div class="span2">
        ${structure:field.serialize()}
    </div>
    <ul tal:condition="field.error">
        <li tal:repeat="error field.error.messages()">
            ${structure:error}
        </li>
    </ul>
</div>

You can use as little or as much of the Deform Field API to draw the widget as you like. The above examples use the deform.Field.serialize() method, which is an easy way to let Deform draw the field HTML, but you can draw it yourself instead if you like, and just rely on the field object for its validation errors (if any). Note that the serialize method accepts arbitrary keyword arguments that will be passed as top-level arguments to the Deform widget templates, so if you need to change how a particular widget is rendered without doing things completely by hand, you may want to take a look at the existing widget template and see if your need has been anticipated.

In the POST handler for the form, just do things like we did in the last chapter, except if validation fails, just re-render the template with the same form object.

controls = request.POST.items() # get the form controls

try:
    appstruct = form.validate(controls)  # call validate
except ValidationFailure as e: # catch the exception
     # .. rerender the form .. its field's .error attributes
     # will be set

It is also possible to pass an appstruct argument to the deform.Form constructor to create “edit forms”. Form/field objects are initialized with this appstruct (recursively) when they’re created. This means that accessing form.cstruct will return the current set of rendering values. This value is reset during validation, so after a validation is done you can re-render the form to show validation errors.

Note that existing Deform widgets are all built using “retail mode” APIs, so if you need examples, you can look at their templates.

Other methods that might be useful during retail form rendering are: