The problem: simplicity vs. flexibility
The Django forms documentation tells us that the "as_p(), as_ul() and as_table() methods are simply shortcuts for lazy developers." For the software developer, sometimes laziness is a virtue. However, there are times when we need greater flexibility in our form rendering to enable layout and styling. Here, we will look at an approach that improves form styling flexibility while keeping form rendering in your templates relatively clean and simple.
The Goal: looped form fields with custom field wrappers
The resulting rendering I will be aiming for is similar to that shown in the Django documentation which uses the concept of field wrappers. An example from the docs which wraps all the fields in a form looks like this:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
(source: Django docs)
This looped approach to rendering a form is a bit more verbose than the "as_x" shortcuts that Django gives us, but much more succinct than explicitly rendering each field. The problem with this approach is that it does not allow us to style wrappers for different field types differently. As a thought experiment, we could consider basing the field wrapper class on the field's widget input type. Something like so:
{% for field in form %}
<div class="{{ field.widget.input_type }}">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
However, not all widgets have an input type, which makes this approach not broadly applicable. The solution, however, is not far from this approach: base the class on the type name of the widget itself.
The steps
To accomplish this solution, we will need a custom tag that provides the class name for the widget of a field. The following steps give us what we need:
-
In your project, create a custom_tags application, if you do not already have a place defined for custom template tags:
projectdir> python manage.py startapp custom_tags
Add this application to your INSTALLED_APPLICATIONS in settings.py
-
In the custom_tags application directory, create a templatetags directory. Inside templatetags, touch __init__.py and create a file called fieldtypes.py with the following content:
from django import template register = template.Library() @register.simple_tag def field_type(field): return field.field.widget.__class__.__name__Note: If you don't like camel-cased class names in your HTML, you could do some fancy manipulation of the class name before returning it. You could also do some exception handling here, in case you accidentally pass a non-field object during development. My preference is just to let Django throw the default exception that happens when the field.field or field.field.widget does not exist.
For more details about creating custom template tags, go to the Django docs
-
Now in your template, you can do something like this:
{% load fieldtypes %} <form method="POST" action="."> {% for field in form %} {% if field.is_hidden %} {{ field }} {% else %} <div class="{% field_type field %}"> {{ field.errors }} {{ field.label_tag }}: {{ field }} </div> {% endif %} {% endfor %} <input type="submit" name="myform" value="submit"/> </form>The check for hidden fields allows us to render such fields as just hidden fields without errors or labels. In Django 1.1, we could make this a little nicer, by iterating form.visible_fields and form.hidden_fields independently.
Conclusion
The result, by simply exposing the name of the widget type for a form field, is a simple generalized approach to rendering forms with the following field-wrapper classes (plus any custom widget types you have created) applied appropriately to the form fields:
- TextInput
- PasswordInput
- HiddenInput
- MultipleHiddenInput
- FileInput
- DateInput
- DateTimeInput
- TimeInput
- Textarea
- CheckboxInput
- Select
- NullBooleanSelect
- SelectMultiple
- RadioSelect
- CheckboxSelectMultiple
- MultiWidget
- SplitDateTimeWidget
- SelectDateWidget
This approach is certainly not without its limitations. There is not a simple way, for example, to deal with fieldsets -- in which case explicit rendering of fields is probably in order. But, for most of your forms, this should provide a simple means for rendering even large forms in very few lines while maintaining style-ability. With a sub-form, we could simplify things even further, so that rendering a custom field-wrapped form is as succinct as using one of Django's "as_x" shortcuts. I will leave this as an exercise for you and will include that simplification in my next post.
Coming next
In the next post, we will look at how this approach, combined with using the YAML CSS framework, can make quick work of form styling. As mentioned, we will also clean up this approach a bit by taking advantage of include templates to simplify form declaration even more.
-sB