Better Form Generation with Mako and Pylons

July 01, 2008 at 02:05 PM | Code, Mako/Pylons
11/21/2010 Newly revised for recent Pylons 1.0, Mako 0.3.6

In developing Mako, a primary goal was to make a super-nice version of a particular "component" pattern which I had used for years primarily with HTML::Mason which for me provides a "sweet spot" of obviousness, agility, and succinctness. The focus is around the ability to create "tag libraries" which interact easily with a server-parsed templating language, and which can be implemented within templates themselves. In JSP development, taglibs are now the standard way to indicate dynamic areas of templates, but while they look pretty clean, they are painful to implement (requiring HTML embedded in hand-crafted classes, a few dozen XML pushups for every tag you add, and the obligatory application restart whenever they change), and the EL and OGNL expressions which are standard within taglibs interact terribly with straight Java code.

Mako allows the creation of tags which can be arbitrarily nested and interactive with one another via the <%def> construct, in combination with the <%call> tag, or as is more common with modern versions of Mako, the <%namespace:def> tag. It's been my observation that the <%call> tag as well as the usage of nesting-aware <%defs> hasn't caught on yet, as the examples in the docs are a little dense, so here I will seek to demystify it a bit.

Pylons currently recommends a decent approach to rendering forms, using Webhelpers, which are essentially little functions you can embed in your template to render standard form elements. The handling of the form at the controller level uses FormEncode and routes validation errors through htmlfill. My approach modifies this to use Mako tags to build a site-specific taglib around the webhelpers tags and adds an explicit interaction between those tags and the controller, in a manner similar to a Struts form handler, which replaces htmlfill and allows all layout, including the layout of validation error messages, using the same template system. It also adds a preprocessor that illustrates how to build custom tags in Mako which look as nice as the built-in ones.

A tar.gz of the approach can be downloaded here (works against Pylons 1.0), which contains two templates each illustrating a different approach to laying out the form - one using htmfill, the second using the Mako-defined tags. The final result, present in the file templates/comment_form.html, looks like this:

<%namespace name="form" file="/form_tags.mako"/>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Mako Form Helpers</title>
  <link rel="stylesheet" href="/style.css" type="text/css" />
</head>
<body>

<h3>Using Mako Helpers</h3>

<%form:form name="comment_form" controller="comment" action="post">
<table>
    <tr>
        <th colspan="2">Submit your Comment</th>
    </tr>
    <tr>
        <td>Your Name:</td>
        <td><%form:text name="name"/></td>
    </tr>

    <tr>
        <td>How did you hear about this site ?</td>
        <td>
            <%form:select name="heard" options="${c.heard_choices}">
                <%form:option value="">None</%form:option>
            </%form:select>
        </td>
    </tr>

    <tr>
        <td>Comment:</td>
        <td><%form:textarea name="comment"/></td>
    </tr>

    <tr>
        <td colspan="2"><%form:submit/></td>
    </tr>
</table>
</%form:form>

</body>
</html>

The %form:textarea tag invokes the textarea def inside the form namespace, which is defined in the file form_tags.mako. The def for textarea looks like:

<%def name="textarea(name, default=None, **attrs)" decorator="render_error">\
<%doc>
    Render an HTML <textarea></textarea> tag pair with embedded content.
</%doc>
${h.textarea(name, content=request.params.get(name, default), **attrs)}\
</%def>

Above, the decorator="render_error" is a Mako def decorator, where the render_error function inserts validation messages into the content while also rendering the form control.

The point of form_tags.mako is that all the form tags, their layout and method of rendering is plainly visible and easily customized.

The demo also contains a modified version of Pylons' @validate decorator. Usage is similar, except it requires a name for the form and a formencode schema unconditionally. It also features an optional input_controller parameter, a reference to the local controller method used for input:

from formhelpers.lib.mako_forms import validate

class CommentController(BaseController):
    def index(self):
        if not c.comment_form:
            c.comment_form = CommentForm().from_python({})
        c.heard_choices = HEARD_CHOICES
        return render('/comment_form.html')

    @validate("comment_form", CommentForm, input_controller=index)
    def post(self):
        c.name = self.form_result['name']
        return render('/thanks.html')

Where above, post hits the validator, and on an invalid exception the index controller method is called instead. Validation errors are placed in self.form_errors as well as c.form_errors for template access. The validator as well as the preprocessor are defined in lib/mako_forms.py. The controller also places a comment_form dictionary on c, which the @validate function takes care of on the post side.

Download the formhelpers demo: formhelpers.tar.gz