Set up the layout for Django forms in templates
When you pass a Django form -- unbound or bound -- to a template there are many options to generate its layout. You can use one of Django's pre-built HTML helpers to quickly generate a form's output or granularly output each field to create an advanced form layout (e.g. responsive design[3]).In addition, there can also be many ways to output form errors (e.g. besides the fields themselves or at the top of a form). Up next, I'll describe the various options to output Django forms in templates.
Listing 6-19 shows the Django form I'll use throughout the remaining layout sections -- which is the same form used throughout this chapter.
Listing 6-19. Django form class definition
from django import forms class ContactForm(forms.Form): name = forms.CharField(required=False) email = forms.EmailField(label='Your email') comment = forms.CharField(widget=forms.Textarea)
Output form fields: form.as_table, form.as_p, form.as_ul & granularly by field
Django forms offer three helper
methods to simplify the output of all form fields. The syntax
form.as_table
outputs a form's fields to accommodate
an HTML <table>
as illustrated in listing 6-20.
The syntax form.as_p
outputs a form's fields with HTML
<p>
tags as illustrated in listing 6-21. Where
as the syntax form.as_ul
outputs a form's fields to
accommodate an HTML <ul>
list tag, as
illustrated in listing 6-22.
Caution If you use form.as_table, form.as_p, form.as_ul you must declare opening/closing HTML tags, a wrapping <form> tag, a Django {% csrf_token %} tag and an <input type="submit"> button, as described in the initial section of this chapter 'Functional web form syntax for Django forms'
Listing 6-20. Django form output with form.as_table
<tr> <th><label for="id_name">Name:</label></th> <td><input id="id_name" name="name" type="text" /></td> </tr>\n <tr> <th><label for="id_email">Your email:</label></th> <td><input id="id_email" name="email" type="email" required/></td> </tr>\n <tr> <th><label for="id_comment">Comment:</label></th> <td><textarea cols="40" id="id_comment" name="comment" rows="10" required>\r\n</textarea></td> </tr>
Listing 6-21. Django form output with form.as_p
<p> <label for="id_name">Name:</label> <input id="id_name" name="name" type="text" /> </p>\n <p> <label for="id_email">Your email:</label> <input id="id_email" name="email" type="email" required/> </p>\n <p> <label for="id_comment">Comment:</label> <textarea cols="40" id="id_comment" name="comment" rows="10" required>\r\n</textarea> </p>'
Listing 6-22 Django form output with form.as_ul
<li> <label for="id_name">Name:</label> <input id="id_name" name="name" type="text" /> </li>\n <li> <label for="id_email">Your email:</label> <input id="id_email" name="email" type="email" required/> </li>\n <li><label for="id_comment">Comment:</label> <textarea cols="40" id="id_comment" name="comment" rows="10" required>\r\n</textarea> </li>
Tip Theform.as_table
,form.as_p
&form.as_ul
output can be made less verbose -- omitting label tags and id attributes -- by initializing the form withauto_id=False
. In addition, you can also change the symbol that separates label names (by default:
) with another symbol by initializing the form with thelabel_suffix
variable. It's also possible to use thefield_order
option to alter the output field order.
Under certain circumstances, none
of the previous helper methods may be sufficient to achieve certain
form layouts. For example, to create a responsive design you'll
need to output each of field manually to accommodate specific
layout requirement (e.g. Bootstrap CSS grid columns). To achieve
the custom output of fields, every form instance permits access to
its fields through the form.<field_name>
syntax
using the attributes in table 6-4.
Table 6-4. Django form field attributes accessible in templates
Attribute name | Description |
---|---|
{{form.<field_name>}} (i.e. No attribute, just the field name by itself) |
Outputs the HTML form tag -- technically known as the Django widget -- associated with the field (e.g. <input type="text">) |
{{form.<field_name>.name}} |
Outputs the name of a field, as defined in the form class. |
{{form.<field_name>.value}} |
Outputs the value of the field assigned with initial or user provided data. Useful if you need to separately output the HTML form tag's value attribute (e.g. for <input type="text" name="name" value="John Doe">, {{form.name.value}} outputs John Doe) |
{{form.<field_name>.label}} |
Outputs the label of a field, which by default uses the syntax "Your <field_name>" (e.g. for the email field, {{form.email.label}} outputs Your email). |
{{form.<field_name>.id_for_label}} |
Outputs the label id of a field, which by default uses the syntax id_<field_name> (e.g. for the email field, {{form.email.id_for_label}} outputs id_email). |
{{form.<field_name>.auto_id}} |
Outputs the auto id of a field, which by default uses the syntax id_<field_name> (e.g. for the email field, {{form.email.auto_id}} outputs id_email). |
{{form.<field_name>.label_tag}} |
Helper method to output the HTML <label> tag along with id_for_label and label(e.g. for the email field, {{form.email.label_tag}} outputs <label for="id_email">Your email:</label>). |
{{form.<field_name>.help_text}} |
Outputs the help text associated with a field. |
{{form.<field_name>.errors}} |
Outputs the errors associated with a field. |
{{form.<field_name>.css_classes}} |
Outputs the CSS classes associated with a field. |
{{form.<field_name>.as_hidden}} |
Outputs the HTML of a field as a hidden HTML field (e.g. <input type="hidden" >) |
{{form.<field_name>.is_hidden}} |
Boolean result of a field's hidden status. |
{{form.<field_name>.as_text}} |
Outputs the HTML of a field as a text HTML field (e.g. <input type="text">) |
{{form.<field_name>.as_textarea}} |
Outputs the HTML of a field as a textarea HTML field (e.g. <textarea></textarea>) |
{{form.<field_name>.as_widget}} |
Outputs the Django widget associated with a field; technically produces the same output as calling the standalone field with the syntax {{form.<field_name>}} -- shown at the top of this table. |
Tip You can override the default output for{{form.<field_name>.label}}
, the suffix for{{form.<field_name>.label_tag}}
and the default output for{{form.<field_name>.help_text}}
in table 6-4, by using thelabel
,label_suffix
andhelp_text
options on form fields. This process is described in the previous section on 'Django form field types: Widgets, options and validations'.
As you can see in table 6-4, there are many field attributes available to customize the layout of a form. Just be careful that if you output form fields granularly you don't miss a field, because if you do miss a field, the most likely outcome is Django won't be able to process the form as it won't receive values from missing fields.
Listing 6-23 illustrates a
standard {% for %}
loop which ensures you don't miss
any field and provides more flexibility than the previous
form.as_table
, form.as_p
&
form.as_ul
methods.
Listing 6-23 Django form {% for %} loop over all fields
{% for field in form %} <div class="row"> <div class="col-md-2"> {{ field.label_tag }} {% if field.help_text %} <sup>{{ field.help_text }}</sup> {% endif %} {{ field.errors }} </div><div class="col-md-10 pull-left"> {{ field }} </div> </div> {% endfor %}
In listing 6-23, a loop is created
over the form
reference to ensure no fields are
missed. If you want to avoid presenting a field in certain form
layouts, then I recommend you use the
{{field.as_hidden}}
vs. {{field}}
, as
this ensures the field still forms part of the form for validation
purposes and is simply hidden from a user -- more details about
this scenario are provided in the upcoming section on advanced form processing and partial forms.
Output field order: field_order and order_fields.
If you use any of the techniques presented in listings 6-20, 6-21, 6-22 or 6-23, the form fields are output in the same order as they're declared in the form class in listing 6-19 (i.e. name,email,comment). However, you can use several techniques to alter the order in which form fields are output.
The first and obvious approach is
to change the form field order directly in the form class
definition. Because this last technique requires altering a form's
source code, Django also offers the field_order
option. The field_order
option accepts a list of form
field names in the order you want them output (e.g.
field_order=['email','name','comment']
outputs the
email
field first, followed by name
and
comment
). The field_order
option is
flexible enough that you can provide a partial list of form fields
(e.g. field_order=['email']
outputs the email field
first and the remaining form fields in their declared order) as
well as declare non-existent field names which are ignored and is
helpful when using form inheritance.
The field_order
option can be declared in two locations. First, it can be declared
as part of a form class definition, as illustrated in listing
6-24.
Listing 6-24 Django form field_order option to enforce field order
from django import forms class ContactForm(forms.Form): name = forms.CharField(required=False) email = forms.EmailField(label='Your email') comment = forms.CharField(widget=forms.Textarea) field_order = ['email','comment','name']
As you can see in listing 6-24,
field_order
is declared as any other form field and
assigned a list of field names to ensure the fields are output in
the order: email, comment and name. It's also possible to use the
field_order
option as part of a form's initialization
processes -- described in detail in the form processing section.
It's worth mentioning that if you use the field_order
option on both the class definition -- as shown in listing 6-24 --
and form instance initialization, the latter value takes precedence
over the former.
In addition to the
field_order
option, Django also offers
order_fields
which also expects a list of field names
to alter a form's output field order. But unlike the
field_order
option which must be declared in a form
class or as part of the initialization of a form instance,
order_fields
can be called directly on a form instance
which makes it a good option to use in a view method or template
(e.g. form.order_fields(['email'])
).
Output CSS classes, styles & field attributes: error_css_class, required_css_class, widget customization and various form field options.
By default, when you output form
fields and labels there are no CSS classes or styles associated
with them. Django offers several mechanisms to associate CSS
classes with form fields. The first two approaches are the
error_css_class
and required_css_class
fields which are declared directly in a Django form, as illustrated
in listing 6-25.
Listing 6-25. Django form error_css_class and required_css_class fields to apply CSS formatting
from django import forms class ContactForm(forms.Form): name = forms.CharField(required=False) email = forms.EmailField(label='Your email') comment = forms.CharField(widget=forms.Textarea) error_css_class = 'error' required_css_class = 'bold'
As you can see in listing 6-25,
the error_css_class
and
required_css_class
fields are added just like regular
form fields. When a field associated with a form instance of this
kind is rendered on a template, Django adds the error
CSS class to all fields marked with an error and adds the
bold
CSS class to all fields marked as required.
For example, all form fields are
treated as required except when they explicitly use
required=False
. This means if you output an unbound
form instance from listing 6-25 using form.as_p
, the
comment field is output as <p class="bold">Comment:
<textarea cols="40" name="comment" rows="10"
required>\r\n</textarea></p>
-- note the
class
in the <p>
tag. Similarly, if
a field associated with a bound form instance from listing 6-25
raises an error, Django adds the error
CSS class to
the field (e.g. if the email field value is not valid, the email
field is output as <p class="bold error">Your email:
<input name="email" type="email" value="aninvalidemail" required
/></p>
, note the bold
CSS class
remains because the form field is also required).
As helpful as the
error_css_class
and required_css_class
fields are, they still offer limited CSS formatting functionality.
To gain full control over CSS class output, you'll need to either
use some of the more granular output options for fields in table
6-4 or customize a form field's widget as illustrated in listing
6-26.
Listing 6-26. Django form with inline widget definition to add custom CSS class
from django import forms class ContactForm(forms.Form): name = forms.CharField(required=False) email = forms.EmailField(label='Your email', widget=forms.TextInput(attrs={'class' : 'myemailclass'})) comment = forms.CharField(widget=forms.Textarea)
Notice in listing 6-26 how the
email field is declared with the
widget=forms.TextInput(attrs={'class' :
'myemailclass'})
argument. This last statement tells Django
that when it outputs the email
field, it use the
custom forms.TextInput
widget which declares the CSS
class
attribute with the myemailclass
value.
By using the form definition in
listing 6-26, the email
field is output as
<input class="myemailclass" type="text"...>
. If
you don't know what a Django widget is, see the earlier section
entitled 'The relationship between widgets and form fields'.
The approach presented in listing
6-26 is a powerful technique, because just as you can declare the
CSS class
attribute, you can also declare any other
form field HTML attribute. For example, if you wanted to declare
custom HTML attributes -- such as those used by frameworks like
jQuery or Bootstrap -- you can easily use this same technique
(e.g.widget=forms.TextInput(attrs={'role' : 'dialog'})
would output <input role="dialog"
type="text"...>
).
However, a word of caution now
that you know how easy it's to output any HTML attribute alongside
a Django form field. Be aware that nearly all Django form field
data types come with built-in options that get translated into HTML
attributes. For example, the
forms.CharField(max_length=25)
statement gets output
to <input type="text" maxlength="25"...>
, which
means the form field max_length
option automatically
generates the HTML maxlength="25"
attribute. So be
careful to start adding HTML attributes indiscriminately using the
approach in listing 6-26, as they may already be supported through
built-in data type options. See the previous section on 'Django
form field types: Widgets, options and validations' for more
details on these built-in data type options.
Output form field errors: form.<field_name>.errors, form.errors, form.non_field_errors
Just as form fields can be output
in different ways, form field errors can also be output in
different ways. Toward the end of the first section in listing
6-23, you can see how we use the {{field.errors}}
syntax to output errors associated with a particular field.
However, an important thing to keep in mind when outputting a
field's errors
value in this manner is the output is
generated as an HTML formatted list:
<ul class="errorlist"> <li>Name is required.</li> </ul>
As you can see in listing 6-27,
the {{fields.errors}}
value is list with the
errorlist
CSS class -- which allows you to provide CSS
behaviors like a background color or borders -- and the values are
pre-wrapped as list elements.
If you want to strip these
wrapping HTML list tags to gain more control over the error layout
(e.g. creating a responsive design or CSV list) you can do so
creating a loop on each field.errors
as illustrated in
listing 6-27.
Listing 6-27. Django loop over form.<field_name>.errors
{% for field in form %} <div class="row"> <div class="col-md-2"> {{ field.label_tag }} {% if field.help_text %} <sup>{{ field.help_text }}</sup> {% endif %} {% for error in field.errors %} <div class="row"> <div class="alert alert-danger">{{error}}</div> </div> {% endfor %} </div><div class="col-md-10 pull-left"> {{ field }} </div> </div> {% endfor %}
You can see in listing 6-27 that
inside the loop for each field, another loop is made on the
field.errors
reference to granularly output and assign
custom markup to each field error.
As granular as the error output
in 6-27 is, this type of layout assumes you want to display a
form's error messages besides each field, in addition to requiring
a loop over a form's fields. But what if you want to display a
form's errors at the top or besides the main form ? Or if you want
to keep using Django's short-cut methods (i.e.
form.as_table
, form.as_p
&
form.as_ul
) and still display errors ?
Besides the
form.<field_name>.errors
syntax in listing 6-27
to access field errors, Django also can output form's errors with
the errors
and non_field_errors
dictionaries as illustrated in listing 6-28.
Listing 6-28. Django form.errors and form.non_field_errors with custom HTML output
<!-- Field errors --> {% if form.errors %} <div class="row"> {% for field_with_error,error_messages in form.errors.items %} <div class="alert alert-danger">{{field_with_error}} {{error_messages}}</div> {% endfor %} </div> {% endif %}
<!-- Non-field errors -->
{% if form.non_field_errors %} <div class="row"> {% for error in form.non_field_errors %} <div class="alert alert-danger">{{error}}</div> {% endfor %} </div> {% endif %}
As you can see in listing 6-28,
the form.errors
dictionary provides an aggregated
version of all the form.<field_name>.errors
,
where each dictionary key represents the form field name and the
value is a list of error messages with error code (e.g.
required
) to further filter the error list. If you
want to output every form error at the top of a form/page, require
error filtering by code type or want to keep using Django's
shortcut output form methods (e.g. form.as_table
) and
obtain form errors, then using form.errors
on a
template is the way to go.
In addition, notice toward the
top of listing 6-28 the loop over the
form.non_field_errors
dictionary. The
form.non_field_errors
contains errors that don't
belong to a specific form field -- as discussed earlier in the
'Error form values: errors' section and the special error
placeholder field named __all__
. Because non-field
errors don't apply to a specific form field, it's common to output
these type of errors at the top of a form accessing the
non_field_errors
dictionary.
Be aware that if you use
form.errors
or form.non_field_errors
to
output errors, by default the error reference --
{{error_messages}}
and {{error}}
in
listing 6-28 -- are wrapped as an HTML formatted list (e.g.
<ul
class="errorlist"><li>...</li></ul>
)
but you can add an additional for loop to the error list -- as in
listing 6-27 -- to create a custom HTML error layout.
Finally, it's worth mentioning there are a series of auxiliary methods designed to facilitate error output (e.g. in JSON format), table 6-1 in the Django form processing section describes these methods.