Dugan Chen's Homepage

Various Things

Rendering Django forms for the Twitter Bootstrap

There seem to be dozens of projects to integrate Django with the Twitter Bootstrap. All of them seem to be large enough to need permanent homes on GitHub. If all you need to do is render a form the way Bootstrap expects, however, then the process is easy. The total amount of code needed is minimal, and very reusable.

Among the techniques we’ll use are:

Here is our forms.py:

from django.forms import CharField, Form
from django.forms.forms import BoundField
from django.forms.util import ErrorList
from django.template import Context, Template

from functools import partial


class BootstrapForm(Form):
    template = \
u'''
{%for field in form%}
<div class="control-group {%if field.errors%}error{%endif%}">
{{field.label_tag}}
<div class="controls">
{{field}}<span class="help-inline">{{field.errors}}</span>
</div>
</div>
{% endfor %}
'''

    def __unicode__(self):
        c = Context({'form': self})
        t = Template(self.template)
        return t.render(c)


def decorate_label_tag(f):

    def bootstrap_label_tag(self, contents=None, attrs=None):
        attrs = attrs or {}
        add_class(attrs, 'control-label')
        return f(self, contents, attrs)

    return bootstrap_label_tag


BoundField.label_tag = decorate_label_tag(
         BoundField.label_tag)


def add_class(attrs, html_class):
    assert type(attrs) is dict

    if 'class' in attrs:
        classes = attrs['class'].split()
        if not html_class in classes:
            classes.append(html_class)
            attrs['class'] = ' '.join(classes)
    else:
        attrs['class'] = html_class


class BootstrapErrorList(ErrorList):

    def __unicode__(self):
        if not self:
            return u''
        return u' '.join(unicode(e) for e in self)


class NameForm(BootstrapForm):
    first_name = CharField()
    last_name = CharField()

NameForm = partial(NameForm, error_class=BootstrapErrorList)

We override the error list format to simply return the errors separated by spaces, rather than wrapping them in an unordered list.

We create a Bootstrap-friendly template to use to render the form. While the current implementations of as_p, as_ul and as_table call _html_output, a template is much more readable.

We monkey patch BoundField.label_tag to be automatically called with attrs={‘class’: ‘control-label’}. This seems to be the best way to add HTML classes to the label. Remember, the classes are specified in a parameter to label_tag, label_tag is called by the template, and the templating language cannot pass parameters.

When we actually create a form, we make the form a subclass of BootstrapForm, and curry the BootstrapErrorList error formatter into its error_class constructor parameter.

The entire bootstrapping (no pun intended) code fits in one screen, and the results are transparent to the rest of the code.