Django Form Validation

Simple Validation

Our search example is still reasonably simple, particularly in terms of its data validation; we’re merely checking to make sure the search query isn’t empty. Many HTML forms include a level of validation that’s more complex than making sure the value is non-empty. We’ve all seen the error messages on web sites:

  • “Please enter a valid e-mail address. ‘foo’ is not an e-mail address.”
  • “Please enter a valid five-digit U.S. ZIP code. ‘123’ is not a ZIP code.”
  • “Please enter a valid date in the format YYYY-MM-DD.”
  • “Please enter a password that is at least 8 characters long and contains at least one number.”

Let’s tweak our search() view so that it validates that the search term is less than or equal to 20 characters long. (For sake of example, let’s say anything longer than that might make the query too slow.) How might we do that? The simplest possible thing would be to embed the logic directly in the view, like this:

def search(request):
    error = False
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            error = True
        elif len(q) > 20:
            error = True
        else:
            books = Book.objects.filter(title__icontains=q)
            return render(request, 'search_results.html', {'books': books, 'query': q})
    return render(request, 'search_form.html', {'error': error})

Now, if you try submitting a search query greater than 20 characters long, it won’t let you search; you’ll get an error message. But that error message in search_form.html currently says "Please submit a search term."– so we’ll have to change it to be accurate for both cases:

<html>
<head>
    <title>Search</title>
</head>
<body>
    {% if error %}
        <p style="color: red;">
            Please submit a search term 20 characters or shorter.
        </p>
    {% endif %}

    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>

There’s something ugly about this. Our one-size-fits-all error message is potentially confusing. Why should the error message for an empty form submission mention anything about a 20-character limit? Error messages should be specific, unambiguous and not confusing. The problem is in the fact that we’re using a simple boolean value for error, whereas we should be using a list of error message strings. Here’s how we might fix that:

def search(request):
    errors = []
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            errors.append('Enter a search term.')
        elif len(q) > 20:
            errors.append('Please enter at most 20 characters.')
        else:
            books = Book.objects.filter(title__icontains=q)
            return render(request, 'search_results.html', {'books': books, 'query': q})
    return render(request, 'search_form.html', {'errors': errors})

Then, we need make a small tweak to the search_form.html template to reflect that it’s now passed an errors list instead of an error boolean value:

<html>
<head>
    <title>Search</title>
</head>
<body>
    {% if errors %}
        <ul>
            {% for error in errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>

Making a Contact Form

Although we iterated over the book search form example several times and improved it nicely, it’s still fundamentally simple: just a single field, 'q'. As forms get more complex, we have to repeat the above steps over and over again for each form field we use. This introduces a lot of cruft and a lot of opportunities for human error. Lucky for us, the Django’s developers thought of this and built into Django a higher-level library that handles form- and validation-related tasks.

Your First Form Class

Django comes with a form library, called django.forms, that handles many of the issues we’ve been exploring this chapter – from HTML form display to validation. Let’s dive in and rework our contact form application using the Django forms framework. The primary way to use the forms framework is to define a Form class for each HTML <form> you’re dealing with.

In our case, we only have one <form>, so we’ll have one Form class. Django community convention is to keep Form classes in a separate file called forms.py. Create this file in the same directory as your mysite\views.py, and enter the following:

# mysite_project\mysite\mysite\forms.py

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField()
    email = forms.EmailField(required=False)
    message = forms.CharField()

This is pretty intuitive, and it’s similar to Django’s model syntax. Each field in the form is represented by a type of Field class – CharField and EmailField are the only types of fields used here – as attributes of a Form class. Each field is required by default, so to make email optional, we specify required=False.

Let’s hop into the Python interactive interpreter and see what this class can do. The first thing it can do is display itself as HTML:

(env_mysite) C:\Users\...\mysite> python manage.py shell

>>> from mysite.forms import ContactForm
>>> f = ContactForm()
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input type="text" name="subject" required id="id_subject" /></td></tr>
<tr><th><label for="id_email">Email:</label></th><td><input type="email" name="email" id="id_email" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" required id="id_message" /></td></tr>

Django adds a label to each field, along with <label> tags for accessibility. The idea is to make the default behavior as optimal as possible. This default output is in the format of an HTML <table>, but there are a few other built-in outputs:

>>> print(f.as_ul())
<li><label for="id_subject">Subject:</label> <input type="text" name="subject" required id="id_subject" /></li>
<li><label for="id_email">Email:</label> <input type="email" name="email" id="id_email" /></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" required id="id_message" /></li>

>>> print(f.as_p())
<p><label for="id_subject">Subject:</label> <input type="text" name="subject" required id="id_subject" /></p>
<p><label for="id_email">Email:</label> <input type="email" name="email" id="id_email" /></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" required id="id_message" /></p>

Note that the opening and closing <table>, <ul> and <form> tags aren’t included in the output, so that you can add any additional rows and customization if necessary. These methods are just shortcuts for the common case of displaying the entire form. You can also display the HTML for a particular field:

>>> print(f['subject'])
<input type="text" name="subject" required id="id_subject" />
>>> print f['message']
<input type="text" name="message" required id="id_message" />

The second thing Form objects can do is validate data. To validate data, create a new Form object and pass it a dictionary of data that maps field names to data:

>>> f = ContactForm({'subject': 'Hello', 'email': 'nige@example.com', 'message': 'Nice site!'})

Once you’ve associated data with a Form instance, you’ve created a “bound” form:

>>> f.is_bound
True

Call the is_valid() method on any bound Form to find out whether its data is valid. We’ve passed a valid value for each field, so the Form in its entirety is valid:

>>> f.is_valid()
True

If we don’t pass the email field, it’s still valid, because we’ve specified required=False for that field:

>>> f = ContactForm({'subject': 'Hello', 'message': 'Nice site!'})
>>> f.is_valid()
True 

But, if we leave off either subject or message, the Form is no longer valid:

>>> f = ContactForm({'subject': 'Hello'})
>>> f.is_valid()
False
>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.is_valid()
False 

You can drill down to get field-specific error messages:

>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f['message'].errors
['This field is required.']
>>> f['subject'].errors
[]
>>> f['email'].errors
[]

Each bound Form instance has an errors attribute that gives you a dictionary mapping field names to error-message lists:

>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.errors
{'message'`: ['This field is required.']}

Finally, for Form instances whose data has been found to be valid, a cleaned_data attribute is available. This is a dictionary of the submitted data, “cleaned up”. Django’s forms framework not only validates data; it cleans it up by converting values to the appropriate Python types:

>>> f = ContactForm({'subject': 'Hello', 'email': 'nige@example.com', 'message': 'Nice site!'})
>>> f.is_valid() 
True
>>> f.cleaned_data
{'subject': 'Hello', 'email': 'nige@example.com', 'message': 'Nice site!'}

Our contact form only deals with strings, which are “cleaned” into string objects – but if we were to use an IntegerField or DateField, the forms framework would ensure that cleaned_data used proper Python integers or datetime.date objects for the given fields.

<<< Django Forms | Table of Contents | Tying Forms to Views >>>

Join the Djangobook Family

Updates, freebies, discounts and more!

No spam. Like, ever. Unsubscribe at any time. Powered by ConvertKit