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:
- rendering a form via its own template
- overriding the form’s error list format
- monkey patching BoundField.label_tag into a decorated version of itself
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.