Django Rendering Fields Manually

In earlier chapter we have learned various ways to display forms in Django. Although methods discussed in the earlier lesson allow us to quickly create form, it also gives us least control over how they are rendered as HTML. If you want to have pixel perfect control over your forms read on.

Open feedback.html in the blog app. It should look something like this:

{% extends "base.html" %}

{% block title %}
    Feedback - {{ block.super }}
{% endblock %}

{% block content %}

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

    <div class="content">
    <div class="section-inner clearfix">

        <h3>Submit Your feedback</h3>       

        <form action="" method="post">
            {% csrf_token %}
            <table>
            {{ form.as_table }}                   
                <tr>
                    <td></td>
                    <td><input type="submit" value="Submit"></td>
                </tr>
            </table>
        </form>

        </div>
    </div>

{% endblock %}

Delete everything and the add the following code:

{% extends "base.html" %}

{% block title %}
    Feedback - {{ block.super }}
{% endblock %}

{% block content %}

    {% if messages %}
    <ul class="messages">
        {% for message in messages %}
        <li>{{ message }}</li>
        {% endfor %}
    </ul>
    {% endif %}

    <div class="content">
    <div class="section-inner clearfix">

        <h3>Submit Your feedback</h3>

        <form action="" method="post">
            {% csrf_token %}
            <table>
                <tr>
                    <td><label for="id_name">Name</label></td>
                    <td><input type="text" id="id_name" name="name"></td>
                </tr>
                <tr>
                    <td><label for="id_email">Email</label></td>
                    <td><input type="email" id="id_email" name="email"></td>
                </tr>
                <tr>
                    <td><label for="id_subject">Subject</label></td>
                    <td><input type="text" id="id_subject" name="subject"></td>
                </tr>
                <tr>
                    <td><label for="id_message">Message</label></td>
                    <td><textarea id="id_message" cols="50" rows="10" name="message"></textarea></td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="Submit"></td>
                </tr>
            </table>
        </form>

        </div>
    </div>

{% endblock %}

Notice that the value of field's name attribute is same as that of field names defined in the Feedback model.

Point your browser to http://127.0.0.1:8000/feedback/, enter some data in the name and email field (it doesn't matter whether the data is valid or not, just fill it) and leave the next two fields empty (i.e subject and comment). Finally hit the submit button. You will be displayed an empty form again.

Wait a minute! What's going on ? All our form fields are required but Why it's now showing any errors ?

The problem is that we are hardcoding our HTML, we are not using form methods like as_p() or as_table() to display the form (form fields as well as errors associated with them). As a result, we are unable to show bound state of the form to the user.

If we had been using f.as_p or f.as_table instead of hardcoding individual form field, we would get the errors as follows:

[form_with_errors]

In addition to errors, the form is also not pre-populating data (valid or invalid) we entered in the name and email field while submitting the form in the last request.

In the following section we will learn how to rectify all these problems.

Displaying field specific errors

To display errors associated with a particular field use form.field_name.errors variable. For example, this is how we can display errors associated with the name field.

{# check whether there are any errors or not #}

{% if form.name.errors %}
    <ul>
    {% for error in form.name.errors %}
        <li>{{ error }}</li>
    {% endfor %}
{% endif %}

Open feedback.html and modify the file as follows:

...
<table>
    <tr>
        <td></td>            
        {% if form.name.errors %}
            {% for error in form.name.errors %}
                <td>{{ error }}</td>
            {% endfor %}
        {% endif %}
    </tr>

    <tr>
        <td><label for="id_name">Name</label></td>
        <td><input type="text" id="id_name" name="name"></td>
    </tr>  

    <tr>
        <td><label for="id_email">Email</label></td>
        <td><input type="email" id="id_email" name="email"></td>
    </tr>
...
</table>
...

Visit http://127.0.0.1:8000/feedback/ and hit submit button. You should see "This field is required." error message just above the name field.

[img_with_errors]

Displaying non field errors

Recall that we can use Form's clean() method to add validation which requires access to more than one fields. The errors raised by clean() method are not specific to any field, infact the errors belong to the whole form. In Django terminology we call such errors Non-field errors. To access non-field errors inside template we use form.non_field_errors variable. For example, we can use the following code to display non-field errors in our feedback form.

{% if form.non_field_errors %}
    <ul>
    {% for error in form.non_field_errors %}
        <li>{{ error }}</li>
    {% endfor %}
{% endif %}

Open feedback.html and add the above code as follows:

...
<h3>Submit Your feedback</h3>

{% if form.non_field_errors %}
<ul>
    {% for error in form.non_field_errors %}
        <li>{{ error }}</li>
    {% endfor %}
</ul>
{% endif %}

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

Django also provides shortcuts to display field errors as well as non-field errors.

{{ form.name.errors }}

in template is same as

{% if form.name.errors %}
    <ul>
    {% for error in form.name.errors %}
        <li>{{ error }}</li>
    {% endfor %}
    </ul>
{% endif %}

Similarly, {{ form.non_field_errors }}

is same as:

{% if form.non_field_errors %}
    <ul>
        {% for error in form.non_field_errors %}
            <li>{{ error }}</li>
        {% endfor %}
    </ul>
{% endif %}

It doesn't matter which shortcut you use the errors will always be printed using unordered list.

Open feedback.html and modify the file as follows:

<h3>Submit Your feedback</h3>

{{ form.non_field_errors }}

<form action="" method="post">
    {% csrf_token %}
    <table>
        <tr>
            <td><label for="id_name">Name</label></td>
            <td>
                {{ form.name.errors }}
                <input type="text" id="id_name" name="name">
            </td>
        </tr>

        <tr>
            <td><label for="id_email">Email</label></td>
            <td>
                {{ form.email.errors }}
                <input type="email" id="id_email" name="email">
            </td>
        </tr>
        <tr>
            <td><label for="id_subject">Subject</label></td>
            <td>
                {{ form.subject.errors }}
                <input type="text" id="id_subject" name="subject">
            </td>
        </tr>
        <tr>
            <td><label for="id_message">Message</label></td>
            <td>
                {{ form.message.errors }}
                <textarea id="id_message" cols="50" rows="10" name="message"></textarea>
            </td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Submit"></td>
        </tr>
    </table>
</form>

Visit http://127.0.0.1:8000/feedback/ and hit submit button without entering anything, this time you should see "This field is required." error message above every field.

Populating Field Values

The biggest usability problem in our form is - In the bound state It doesn't display any data that user has submitted. For example: Enter "django" in name field and hit submit. You should see form like this:

[form_with_no_data]

As you can see the name field has not data in it. Django provides bound field value in the variable form.field_name.value. So to display the data in the name field set it's value attribute to {{ form.name.value }}.

But there is one small problem. If the form is in unbound state {{ form.name.value }} would print None. That's why you must always is if tag to check the existence of any value in form.name.value first. For example:

{% if form.name.value %}
    <input type="text" id="id_name" name="name" value="{{ form.name.value }}">
{% else %}
    <input type="text" id="id_name" name="name">
{% endif %}

Open feedback.html and modify the form as follows:

...
<form action="" method="post">
    {% csrf_token %}
    <table>
        <tr>
            <td><label for="id_name">Name</label></td>
            <td>
                {{ form.name.errors }}
                {% if form.name.value %}
                    <input type="text" id="id_name" name="name" value="{{ form.name.value }}">
                {% else %}
                    <input type="text" id="id_name" name="name">
                {% endif %}
            </td>
        </tr>
    ...

Visit http://127.0.0.1:8000/feedback/ again, enter "django" in the name field and submit.

[]

As expected our bound form is pre-populating data in the name field thanks to form.name.value.

Django also provides {{ form.field_name }} shortcut to print the whole form field in bound as well as in unbound state. In other words {{ form.name }} is same as:

{% if form.name.value %}
    <input type="text" id="id_name" name="name" value="{{ form.name.value }}">
{% else %}
    <input type="text" id="id_name" name="name">
{% endif %}

Open feedback.html and modify the file to use this new shortcut as follows:

...
{{ form.non_field_errors }}

<form action="" method="post">
    {% csrf_token %}
    <table>
        <tr>
            <td><label for="id_name">Name</label></td>
            <td>
                {{ form.name.errors }}
                {{ form.name }}
            </td>
        </tr>

        <tr>
            <td><label for="id_email">Email</label></td>
            <td>
                {{ form.email.errors }}
                {{ form.email }}
            </td>
        </tr>
        <tr>
            <td><label for="id_subject">Subject</label></td>
            <td>
                {{ form.subject.errors }}
                {{ form.subject }}
            </td>
        </tr>
        <tr>
            <td><label for="id_message">Message</label></td>
            <td>
                {{ form.message.errors }}
                {{ form.message }}
            </td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Submit"></td>
        </tr>
    </table>
</form>
...

An important thing to note about this shortcut is that it generates id values of the form id_field_name, so if the name of the field is name, then id attribute would be "id_name".

Displaying Labels

Django provides the following two variables to generate label ids and label name respectively.

  1. form.field_name.id_for_label.
  2. form.field_name.label.

Here is how we can use them inside <label> tag.

<label for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>

This would output:

<label for="id_name">Name</label>

Just like other fields, Django provides a shortcut too to generate the complete <label> tag using
{{ form.field_name.label_tag}}.

So, {{ form.name.label_tag }}

is equivalent to

<label for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>

Open feedback.html and modify the file to use form.field_name.label_tag variable as follows:

...
<form action="" method="post">
    {% csrf_token %}
    <table>
        <tr>
            <td>{{ form.name.label_tag }}</td>
            <td>
                {{ form.name.errors }}
                {{ form.name }}
            </td>
        </tr>

        <tr>
            <td>{{ form.email.label_tag }}</td>
            <td>
                {{ form.email.errors }}
                {{ form.email }}
            </td>
        </tr>
        <tr>
            <td>{{ form.subject.label_tag }}</td>
            <td>
                {{ form.subject.errors }}
                {{ form.subject }}
            </td>
        </tr>
        <tr>
            <td>{{ form.message.label_tag }}</td>
            <td>
                {{ form.message.errors }}
                {{ form.message }}
            </td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Submit"></td>
        </tr>
    </table>
</form>
...

Priting help_text

We can also print the value help_text attribute, if it is defined for the field in the model/form class. To print help_text we use {{ form.field_name.help_text }}. Recall that we have defined help_text attribute for the name field while creating Feedback model as follows:

...
class Feedback(models.Model):
    name = models.CharField(max_length=200, help_text="Name of the sender")
    email = models.EmailField(max_length=200)
    ...

Once again open feedback.html and add {{ form.name.help_text }} just below {{ form.name }} as follows:

...
<form action="" method="post">
    {% csrf_token %}
    <table>
        <tr>
            <td>{{ form.name.label_tag }}</td>
            <td>
                {{ form.name.errors }}
                {{ form.name }}
                {{ form.name.help_text }}
            </td>
        </tr>
        ...
    </table>  
</form>
...

Visit http://127.0.0.1:8000/feedback/ and you should see help_text beside name input field.

[img]

Looping over Form Fields

Django has another trick under its sleeves which allows you shorten the code further. Instead if manually typing individual fields, we can simply loop over form fields using the following code.

...
<form action="" method="post">
            {% csrf_token %}
            <table>
                {% for field in form %}
                    <tr>
                        <td>{{ field.label_tag }}</td>
                        <td>
                            {{ field.errors }}
                            {{ field }}                            
                        </td>
                        <td>
                            {% if field.help_text %}
                            {{ field.help_text }}
                            {% endif %}
                        </td>
                    </tr>
                {% endfor %}
                    <tr>
                        <td></td>
                        <td><input type="submit" value="Submit"></td>
                    </tr>
            </table>
        </form>    
...

As usual this method doesn't output <form> tag and submit button (<input type="submit">) automatically, you have to add them manually in your code.

So as you can see, there a trade off between the amount of code you type and the control you want. The less code affords us least control. We will be using some of the variables we learned here while creating forms for cadmin app.