Django Templates for the Designer

There’s no need to know Python or Django to work well with Django templates. A good understanding of the way HTML is generated will take the designer most of the way.

A common shortcut in rendering a form in the template is to use {{ form.as_p }}.  This will include all of the included fields from the form, along with labels and errors.   A nice side effect is that changes to the fields – whether adding a new field, taking one away, or changing its format – are reflected immediately when the app is restarted without any modifications necessary for the template itself.  Here’s an example:

<form action="." method="post">{% csrf_token %} 
{{ form.as_p }} 
<input type="submit" value="Save" /></form>

However, without an understanding of the underlying form, and possibly a related model, it can be difficult for the non-developer to predict which fields will be included. When using a basic Form, one just needs to check the form definition to see the list of fields.  This form is a good example:

class UserSelectForm(Form): 
    user = ChoiceField(required=True)
    reg_date = DateField(required=False)

Just the two fields listed will be included in the generated HTML.

With a ModelForm, things get a little more complicated. Before viewing an example, let’s start with a quick set of rules:

  1. All fields from the related model will be included
  2. UNLESS, the field is marked as editable=False in the model definition
  3. The fields attribute in the ModelForm‘s Meta section can be used to limit the form to a subset of the fields
  4. The exclude attribue, also in the Meta section, is used to omit fields from the form
  5. Additional fields specified in the form that are not part of the model will also be included

For more information, see Creating Forms from Models in the Django documentation.

Here’s an example:

class TrainingScheduleForm(ModelForm):
    class_date = DateFieldMultiFormat(required=True)
    class_time = TimeFieldMultiFormat(required=True)
    send_email = fields.BooleanField(required=False)

    class Meta:
        model = TrainingSchedule
        exclude = ('training_class',)

The model attribute tells us to look at the TrainingSchedule model and include those fields that are editable.

class TrainingSchedule(ModelBase):   
    training_class = models.ForeignKey(TrainingClass,null=False,blank=False)
    class_date = models.DateField(null=False,blank=False)
    class_time = models.TimeField(null=False,blank=False)
    location = models.CharField(max_length=255,null=True,blank=True)
    campus = models.CharField(max_length=255,null=True,blank=True)
    capacity = models.IntegerField(blank=True,null=True)
    display_flag = models.BooleanField(default=True)

Then, we look at the exclude attribute to see that the training_class field is not included. Class_date and class_time are explicitly listed only because of a need to override the default field type, but the send_email is an extra field that is not part of the model. It is included as well. So, the final list of fields in the template is:

  • class_date
  • class_time
  • location
  • campus
  • capacity
  • display_flag
  • send_email

Using the {{ form.as_p }} shortcut, the HTML will include this snippet:

<p><label for="id_class_date">Class date</label><input type="text" name="class_date" id="id_class_date" /> </p>
<p><label for="id_class_time">Class time</label><input type="text" name="class_time" id="id_class_time" /> </p>
<p><label for="id_location">Location</label><input id="id_location" type="text" name="location" maxlength="255" /> </p>
<p><label for="id_campus">Campus</label><input id="id_campus" type="text" name="campus" maxlength="255" /> </p>
<p><label for="id_capacity">Capacity</label><input type="text" name="capacity" id="id_capacity" /> </p>
<p><label for="id_display_flag">Display flag</label><input checked="checked" type="checkbox" name="display_flag" id="id_display_flag" /> </p>
<p><label for="id_send_email">Send email:</label> <input type="checkbox" name="send_email" id="id_send_email" /></p>

Note that both the labels and fields have IDs just waiting to be styled. If there were any errors generated, those sections would also be included, with all of the ID and class goodness.


Sometimes, the designer wants to change the order of the fields, put the errors in a different spot, assign other classes and IDs, and insert additional information in the template. In these cases, one can skip the shortcut, and specify the form pieces individually. Here is an example of a template with individual fields listed:

<form action="." method="post"> {% csrf_token %}

<div id="signup_details">

{{ form.non_field_errors }}

<p>{{ form.training_class.errors }}{{ form.training_class.label_tag }} {{ form.training_class }}</p>
<p>{{ form.training_schedule.errors }}{{ form.training_schedule.label_tag }} {{ form.training_schedule }}</p>

<fieldset id="new_schedule">
<legend>Create New Schedule</legend>
<p>{{ form.class_date.errors }}{{ form.class_date.label_tag }} {{ form.class_date }}</p>
<p>{{ form.class_time.errors }}{{ form.class_time.label_tag }} {{ form.class_time }}</p>
<p>{{ form.location.errors }}{{ form.location.label_tag }} {{ form.location }}</p>
<p>{{ form.campus.errors }}{{ form.campus.label_tag }} {{ form.campus }}</p>

<div id="signup_flags">
<p>{{ form.send_email.errors }}{{ form.send_email.label_tag }} {{ form.send_email }}</p>
<p>{{ form.attended.errors }}{{ form.attended.label_tag }} {{ form.attended }}</p>
<p>{{ form.exempt.errors }}{{ form.exempt.label_tag }} {{ form.exempt }}</p>

<br clear="left" /><br />

<input type="submit" value="Save" />
<INPUT TYPE="BUTTON" VALUE="Cancel" onClick="history.go(-1)">

Note a couple of things. The tag {{ form.non_field_errors }} is where any form level errors would be displayed. Likewise, the {{ form.field_name.errors }} tag holds the place for individual field errors.

Once the tags are in the form, the designer can move them around and add additional HTML – notice the use of fieldset above.

See more information on the individual tags in Working With Forms.

Hope this helps a few designers figure out Django templates.  Please leave any comments or questions below.