The “Perfect Form”

Forms can often be a major cause of frustration for the users of your site. Let’s consider the behavior of a hypothetical perfect form:

  • It should ask the user for some information, obviously. Accessibility and usability matter here, so smart use of the HTML <label> element and useful contextual help are important.
  • The submitted data should be subjected to extensive validation. The golden rule of Web application security is “never trust incoming data,” so validation is essential.
  • If the user has made any mistakes, the form should be redisplayed with detailed, informative error messages. The original data should be prefilled, to save the user from having to reenter everything.
  • The form should continue to redisplay until all of the fields have been correctly filled.

Constructing the perfect form seems like a lot of work! Thankfully, Django’s forms framework is designed to do most of the work for you. You provide a description of the form’s fields, validation rules, and a simple template, and Django does the rest. The result is a “perfect form” with very little effort.

Creating a Feedback Form – The best way to build a site that people love is to listen to their feedback. Many sites appear to have forgotten this; they hide their contact details behind layers of FAQs, and they seem to make it as difficult as possible to get in touch with an actual human being.

When your site has millions of users, this may be a reasonable strategy. When you’re trying to build up an audience, though, you should actively encourage feedback at every opportunity. Let’s build a simple feedback form and use it to illustrate Django’s forms framework in action.
We’ll start by adding adding (r’^contact/$’, ‘mysite.books.views.contact’) to the URLconf, then defining our form. Forms in Django are created in a similar way to models: declaratively, using a Python class. Here’s the class for our simple form. By convention, we’ll insert it into a new forms.py file within our application directory:

from django import newforms as forms

TOPIC_CHOICES = (
(‘general’, ‘General enquiry’),
(‘bug’, ‘Bug report’),
(‘suggestion’, ‘Suggestion’),
)

class ContactForm(forms.Form):
topic = forms.ChoiceField(choices=TOPIC_CHOICES)
message = forms.CharField()
sender = forms.EmailField(required=False)

“New” Forms? What? – When Django was first released to the public, it had a complicated, confusing forms system. It made producing forms far too difficult, so it was completely rewritten and is now called “newforms.” However, there’s still a fair amount of code that depends on the “old” form system, so for the time being Django ships with two form packages.
As we write this book, Django’s old form system is still available as django.forms and the new form package as django.newforms. At some point that will change and django.forms will point to the new form package. However, to make sure the examples in this book work as widely as possible, all the examples will refer to django.newforms.

A Django form is a subclass of django.newforms.Form, just as a Django model is a subclass of django.db.models.Model. The django.newforms module also contains a number of Field classes; a full list is available in Django’s documentation at http://www.djangoproject.com/documentation/0.96/newforms/.

Our ContactForm consists of three fields: a topic, which is a choice among three options; a message, which is a character field; and a sender, which is an email field and is optional (because even anonymous feedback can be useful). There are a number of other field types available, and you can write your own if they don’t cover your needs.

The form object itself knows how to do a number of useful things. It can validate a collection of data, it can generate its own HTML “widgets,” it can construct a set of useful error messages and, if we’re feeling lazy, it can even draw the entire form for us. Let’s hook it into a view and see it in action. In views.py:

from django.db.models import Q
from django.shortcuts import render_to_response
from models import Book
from forms import ContactForm

def search(request):
query = request.GET.get(‘q’, ”)
if query:
qset = (
Q(title__icontains=query) |
Q(authors__first_name__icontains=query) |
Q(authors__last_name__icontains=query)
)
results = Book.objects.filter(qset).distinct()
else:
results = [] return render_to_response(“books/search.html”, {
“results”: results,
“query”: query
})

def contact(request):
form = ContactForm()
return render_to_response(‘contact.html’, {‘form’: form})
and in contact.html:

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01//EN”>
<html lang=”en”>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
<form action=”.” method=”POST”>
<table>
{{ form.as_table }}
</table>
<p><input type=”submit” value=”Submit”></p>
</form>
</body>
</html>

The most interesting line here is {{ form.as_table }}. form is our ContactForm instance, as passed to render_to_response. as_table is a method on that object that renders the form as a sequence of table rows (as_ul and as_p can also be used). The generated HTML looks like this:

<tr>
<th><label for=”id_topic”>Topic:</label></th>
<td>
<select name=”topic” id=”id_topic”>
<option value=”general”>General enquiry</option>
<option value=”bug”>Bug report</option>
<option value=”suggestion”>Suggestion</option>
</select>
</td>
</tr>
<tr>
<th><label for=”id_message”>Message:</label></th>
<td><input type=”text” name=”message” id=”id_message” /></td>
</tr>
<tr>
<th><label for=”id_sender”>Sender:</label></th>
<td><input type=”text” name=”sender” id=”id_sender” /></td>
</tr>

Note that the <table> and <form> tags are not included; you need to define those yourself in the template, which gives you control over how the form behaves when it is submitted. Label elements are included, making forms accessible out of the box.

Our form is currently using a <input type=”text”> widget for the message field. We don’t want to restrict our users to a single line of text, so we’ll swap in a <textarea> widget instead:

class ContactForm(forms.Form):
topic = forms.ChoiceField(choices=TOPIC_CHOICES)
message = forms.CharField(widget=forms.Textarea())
sender = forms.EmailField(required=False)

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. At the moment, submitting the form doesn’t actually do anything. Let’s hook in our validation rules:

def contact(request):
if request.method == ‘POST’:
form = ContactForm(request.POST)
else:
form = ContactForm()
return render_to_response(‘contact.html’, {‘form’: form})

A form instance can be in one of two states: bound or unbound. A bound instance is constructed with a dictionary (or dictionary-like object) and knows how to validate and redisplay the data from it. An unbound form has no data associated with it and simply knows how to display itself.
Try clicking Submit on the blank form. The page should redisplay, showing a validation error that informs us that our message field is required.

Try entering an invalid email address as well. The EmailField knows how to validate email addresses, at least to a reasonable level of doubt.

Setting Initial Data – Passing data directly to the form constructor binds that data and indicates that validation should be performed. Often, though, we need to display an initial form with some of the fields prefilled — for example, an “edit” form. We can do this with the initial keyword argument:
form = CommentForm(initial={‘sender’: ‘[email protected]’})
If our form will always use the same default values, we can configure them in the form definition itself:

message = forms.CharField(widget=forms.Textarea(),
initial=”Replace with your feedback”)

Back to Tutorial

Get industry recognized certification – Contact us

Menu