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:

# views.py

from django.shortcuts import render
from mysite.forms import ContactForm
from django.http import HttpResponseRedirect
from django.core.mail import send_mail

# ...

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            send_mail(
                cd['subject'],
                cd['message'],
                cd.get('email', 'noreply@example.com'),
                ['siteowner@example.com'],
            )
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm()
    return render(request, 'contact_form.html', {'form':
form})

Next, we have to create our contact form (save this to 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">
        <table>
            {{ form.as_table }}
        </table>
        {% csrf_token %}
        <input type="submit" value="Submit">
    </form>
</body>
</html>

And 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),
]

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.

Try running this locally. Load the form, submit it with none of the fields filled out, submit it with an invalid e-mail address, then finally submit it with valid data. (Of course, unless you have configured a mail-server, you will get a ConnectionRefusedError when send_mail() is called.)

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):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            send_mail(
                cd['subject'],
                cd['message'],
                cd.get('email', 'noreply@example.com'),
                ['siteowner@example.com'],
            )
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm(
            initial={'subject': 'I love your site!'}
        )
    return render(request, 'contact_form.html', {'form':form})

Now, the subject field will be displayed prepopulated 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.

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. 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. The following CSS really makes our errors stand out:

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

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>

{{ 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). We can also treat form.message.errors as a Boolean or even iterate over it as a list. 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 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).