Tying Forms to Views

Our contact form is not much good to us unless we have some way of displaying it to the user. To do this, we need to first update our mysite\views.py:

# mysite_project\mysite\mysite\views.py

from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import render
import datetime
from mysite.forms import ContactForm    
from django.core.mail import send_mail, get_connection

# ...

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            con = get_connection('django.core.mail.backends.console.EmailBackend')
            send_mail(
                cd['subject'],
                cd['message'],
                cd.get('email', 'noreply@example.com'),
                ['siteowner@example.com'],
                connection=con
            )
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm()

    return render(request, 'contact_form.html', {'form': form})

We’ve added the contact function to our view to handle the form submission. In this new function, we:

  • Check to see if the request have been submitted with the POST method, otherwise we just display the blank form.
  • We then check if the form contains valid data by calling the form’s is_valid() method.
    • If the form contains valid data, an email is sent and the user is redirected to a new view (/contact/thanks/).
    • If the form doesn’t contain valid data, the if block jumps out to the final render() and the form is rendered back to the browser.

Note that we don’t do any error handling in the view – this is all handled by the form class. If the form doesn’t validate, Django will automatically create a list of errors and append them to the response.

A contact form is not much use without some way of sending the contact form information to the site owner. A very common way of doing this is to send an email. Django has the ability to send emails built in to the core. The email functions can be found in the django.core.mail module, which I have imported at the top of our modified views.py.

We are using the send_mail() function to send the email to a dummy email address. You can find out more on send_mail(), and the other email functions and classes in the Django documentation.

To be able to display our contact form, we have to create our contact form (save this to mysite\templates):

# mysite_project\mysite\templates\contact_form.html

<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if form.errors %}
        <p style="color: red;">
            Please correct the error{{ form.errors|pluralize }} below.
        </p>
    {% endif %}

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

Since we’re creating a POST form (which can have the effect of modifying data), we need to worry about Cross Site Request Forgeries. Thankfully, you don’t have to worry too hard, because Django comes with a very easy-to-use system for protecting against it.

In short, all POST forms that are targeted at internal URLs should use the {% csrf_token %} template tag. More details about {% csrf_token %} can be found in Chapter 19.

Keen-eyed readers will also notice the novalidate attribute in the <form> tag. When using HTML5 in some of the latest browsers (notably Chrome), form fields will be automatically validated by the browser. As we want Django to handle form validation, the novalidate attribute tells the browser not to validate the form.

Finally, we need to change our urls.py to display our contact form at /contact/:

 # ...
from mysite.views import hello, current_datetime, hours_ahead, contact

 urlpatterns = [
     # ...
     url(r'^contact/$', contact),
     url(r'^', include('books.urls')),
]

Try running this locally. Load the form, submit it with none of the fields filled out (Figure 6-5), submit it with an invalid e-mail address (Figure 6-6), then finally submit it with valid data.

Django form class provides automatic validation of fields
Figure 6-5. Django form class provides automatic validation of fields.

Django form class provides automatic validation of fields
Figure 6-6. Django also checks for valid email addresses.

Note that you will get a “Page not found (404)” error when you submit the completed form. This is because I have not created the view or URLconf for the redirect to /contact/thanks/. I will leave this to you as a learning exercise.

Changing How Fields Are Rendered

Probably the first thing you’ll notice when you render this form locally is that the message field is displayed as an <input type="text">, and it ought to be a <textarea>. We can fix that by setting the field’s widget:

from django import forms

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

The forms framework separates out the presentation logic for each field into a set of widgets. Each field type has a default widget, but you can easily override the default, or provide a custom widget of your own. Think of the Field classes as representing validation logic, while widgets represent presentation logic.

Setting a Maximum Length

One of the most common validation needs is to check that a field is of a certain size. For good measure, we should improve our ContactForm to limit the subject to 100 characters. To do that, just supply a max_length to the CharField, like this:

from django import forms

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

An optional min_length argument is also available.

Setting Initial Values

As an improvement to this form, let’s add an initial value for the subject field: “I love your site!” (A little power of suggestion can’t hurt.) To do this, we can use the initial argument when we create a Form instance:

def contact(request):
    
    # ...

    else:
        form = ContactForm(
            initial={'subject': 'I love your site!'}
        )
        
    return render(request, 'contact_form.html', {'form':form})

Now, the subject field will be displayed pre-populated with that kind statement. Note that there is a difference between passing initial data and passing data that binds the form. The biggest difference is that if you’re just passing initial data, then the form will be unbound, which means it won’t have any error messages.

Figure 6-7 shows how our form look with a new Textarea widget for the message and a pre-filled subject line.

Django form class provides automatic validation of fields
Figure 6-7. Our Django form with the new textarea widget for the message and a pre-filled subject field.

Custom Validation Rules

Imagine we’ve launched our feedback form, and the e-mails have started tumbling in. There’s just one problem: some of the submitted messages are just one or two words, which isn’t long enough for us to make sense of. We decide to adopt a new validation policy: four words or more, please.

There are a number of ways to hook custom validation into a Django form. If our rule is something we will reuse again and again, we can create a custom field type. Most custom validations are one-off affairs, though, and can be tied directly to the Form class. We want additional validation on the message field, so we add a clean_message() method to our Form class:

from django import forms

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

    def clean_message(self):
        message = self.cleaned_data['message']
        num_words = len(message.split())
        if num_words < 4:
            raise forms.ValidationError("Not enough words!")
        return message

Django’s form system automatically looks for any method whose name starts with clean_ and ends with the name of a field. If any such method exists, it’s called during validation. Specifically, the clean_message() method will be called after the default validation logic for a given field (in this case, the validation logic for a required CharField). Because the field data has already been partially processed, we pull it out of self.cleaned_data.

Also, we don’t have to worry about checking that the value exists and is non-empty; that’s done by the default validator. We naively use a combination of len() and split() to count the number of words. If the user has entered too few words, we raise a forms.ValidationError. The string attached to this exception will be displayed to the user as an item in the error list (Figure 6-8).

Django form custom validation of fields
Figure 6-8. Our custom validator for the message field will display and error when less than 4 words are entered.

It’s important that we explicitly return the cleaned value for the field at the end of the method. This allows us to modify the value (or convert it to a different Python type) within our custom validation method. If we forget the return statement, then None will be returned, and the original value will be lost.

Specifying labels

By default, the labels on Django’s auto-generated form HTML are created by replacing underscores with spaces and capitalizing the first letter – so the label for the email field is “Email“. (Sound familiar? It’s the same simple algorithm that Django’s models use to calculate default verbose_name values for fields. We covered this in Chapter 4.) But, as with Django’s models, we can customize the label for a given field. Just use label, like so:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False, label='Your e-mail address')
    message = forms.CharField(widget=forms.Textarea)

Customizing Form Design

Our contact_form.html template uses {{ form.as_table }} to display the form, but we can display the form in other ways to get more granular control over display. The quickest way to customize forms’ presentation is with CSS. Error lists, in particular, could do with some visual enhancement, and the auto-generated error lists use <ul class="errorlist"> precisely so that you can target them with CSS. Add the following CSS in the <head></head> section of contact_form.html to really make our errors stand out (Figure 6-9):

<style type="text/css">
    ul.errorlist {
        margin: 0;
        padding: 0;
    }
    .errorlist li {
        background-color: red;
        color: white;
        display: block;
        font-size: 1.2em;
        margin: 0 0 3px;
        padding: 4px 5px;
    }
</style>

Django adding styles to forms
Figure 6-9. Adding styles to our error list.

While it’s convenient to have our form’s HTML generated for us, in many cases you’ll want to override the default rendering. {{ form.as_table }} and friends are useful shortcuts while you develop your application, but everything about the way a form is displayed can be overridden, mostly within the template itself, and you’ll probably find yourself doing this.

Each field’s widget (<input type="text">, <select>, <textarea>, etc.) can be rendered individually by accessing {{ form.fieldname }} in the template, and any errors associated with a field are available as {{ form.fieldname.errors }}. With this in mind, we can construct a custom template for our contact form with the following template code:

<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if form.errors %}
        <p style="color: red;">
            Please correct the error{{ form.errors|pluralize }} below.
        </p>
    {% endif %}

    <form action="" method="post">
        <div class="field">
            {{ form.subject.errors }}
            <label for="id_subject">Subject:</label>
            {{ form.subject }}
        </div>
        <div class="field">
            {{ form.email.errors }}
            <label for="id_email">Your e-mail address:</label>
            {{ form.email }}
        </div>
        <div class="field">
            {{ form.message.errors }}
            <label for="id_message">Message:</label>
            {{ form.message }}
        </div>
        {% csrf_token %}
        <input type="submit" value="Submit">
    </form>
</body>
</html>

In the above template code, {{ form.message.errors }} displays a <ul class="errorlist"> if errors are present and a blank string if the field is valid (or the form is unbound). This is the same for the other fields on our contact form.

We can also treat form.message.errors as a Boolean or even iterate over it as a list. This is useful when there can be multiple errors associated with a field. For example:

<div class="field{% if form.message.errors %} errors{% endif %}">
    {% if form.message.errors %}
        <ul>
        {% for error in form.message.errors %}
            <li><strong>{{ error }}</strong></li>
        {% endfor %}
        </ul>
    {% endif %}
    <label for="id_message">Message:</label>
    {{ form.message }}
</div>

In the case of validation errors, this will add an “errors” class to the containing <div> and display the list of errors associated with the message field in an unordered list.

What’s Next?

This chapter concludes the introductory material in this book – the so-called “core curriculum.” The next section of the book, Chapters 7 to 13, goes into more detail about advanced Django usage, including how to deploy a Django application (Chapter 13). After these first seven chapters, you should know enough to start writing your own Django projects. The rest of the material in this book will help fill in the missing pieces as you need them. We’ll start in Chapter 7, by doubling back and taking a closer look at views and URLconfs (introduced first in Chapter 2).

<<< Form Validation | Table of Contents | Advanced Views and URLconfs >>>

Join the Djangobook Family

Updates, freebies, discounts and more!

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