Forms are the standard way users input or edit data in web applications. At the their lowest-level, forms are made up of HTML tags with special meaning. While you can directly add HTML form tags to Django templates or Jinja templates, you really want to avoid this and use Django's built-in form support to make form processing easier.
In this chapter you'll learn how to structure Django forms and the workflow forms undergo. You'll also learn the various field types and widgets supported by Django forms, how to validate form data and manage its errors, as well as how to layout forms and their errors in templates.
Once you have a firm understanding of the basics behind Django forms, you'll learn how to create custom form fields and widgets. Finally, you'll learn more complex Django form processing techniques, such as: partial form processing, form processing with AJAX, how to process files sent through Django forms and how to process multiple forms on the same page with Django formsets.
Django form structure and workflow
Django has a special forms package that offers a comprehensive way to work with forms. Among this package's features are the ability to define form functionality in a single location, data validation and tight integration with Django models, among other things. Let's take a first look at a standalone Django form class in listing 6-1 which is used to back a contact form.
Listing 6-1. Django form class definition
# forms.py in app named 'contact' from django import forms class ContactForm(forms.Form): name = forms.CharField(required=False) email = forms.EmailField(label='Your email') comment = forms.CharField(widget=forms.Textarea)
Note There's no specific location Django expects forms to be in. You can equally place Django form classes in their own file inside an app (e.g. forms.py) or place them inside other app files (e.g. models.py, views.py). You can later import Django form classes to where they're needed, just like Django views or Python packages.
The first important aspect to
note in listing 6-1 is a Django form definition is a sub-class of
the forms.Form
class, so it automatically has all the
base functionality of this parent class. Next, you can see the form
class has three attributes, two of the type
forms.CharField
and one of the type
forms.EmailField
. These form field definitions
restrict input to certain characteristics.
For example,
forms.CharField
indicates the input should be a set of
characters and forms.EmailField
indicates the input
should be an email. In addition, you can see each form field
includes properties (e.g.required
) to further restrict
the type of input. For the moment this should be enough detail
about Django form field types, the next section on Django form field types goes into greater detail on just this topic.
Next, let's integrate the Django form in listing 6-1 to a Django view method so it can then be passed and rendered in a Django template. Listing 6-2 illustrates the initial iteration of this view method.
Listing 6-2. Django view method that uses a Django form
# views.py in app named 'contact' from django.shortcuts import render from .forms import ContactForm def contact(request): form = ContactForm() return render(request,'about/contact.html',{'form':form})
The view method in listing 6-2
first instantiates the ContactForm
form class from listing 6-1 and
assigns it to the form
reference. This
form
reference is then passed as an argument to be
made available inside the about/contact.html
template.
Next, inside the Django template
you can output a Django form as a regular variable. Listing 6-3
illustrates how the Django form is rendered if you use the standard
template syntax {{form.as_table}}
.
Listing 6-3. Django form instance rendered in template as HTML
<tr> <th><label for="id_name">Name:</label></th> <td><input id="id_name" name="name" type="text" /></td> </tr> <tr> <th><label for="id_email">Your email:</label></th> <td><input id="id_email" required name="email" type="email" /></td> </tr> <tr> <th><label for="id_comment">Comment:</label></th> <td><textarea cols="40" id="id_comment" required name="comment" rows="10"></textarea></td> </tr>
In listing 6-3 you can see how
the Django form is translated into HTML tags! Notice how the Django
form produces the appropriate HTML <input>
tags
for each form field (e.g. forms.EmailField(label='Your
email')
creates the specified <label>
and
an HTML 5 type="email"
to enforce client-side
validation of an email). In addition, notice the name
field lacks the HTML 5 required
attribute due to the
required=False
statement used in the form field in listing 6-1.
If you look closely at listing
6-3, the HTML output for the Django form instance are just inner
HTML table tags
(i.e.<tr>
,<th>
,<td>
).
The output is missing an HTML <table>
wrapper
tag and supporting HTML form tags (i.e. <form>
tag and action
attribute to indicate where to send the
form, as well as a submit
button). This means you'll
need to add the missing HTML form tags to the template to create a
working web form -- a process that's described shortly in listing 6-4.
In addition, if you don't want to
output the entire form fields surrounded by HTML table elements
like listing 6-3, there are many other syntax variations to output
form fields granularly and stripped of HTML tags. In this case, the
{{form.as_table}}
reference in the template is used to
simplify things, but the upcoming section 'Set up the layout for
Django forms in templates' in this chapter elaborates on the
different syntax variations to output Django forms in
templates.
Next, let's take a look at figure 6-1 which shows a Django form's workflow to better illustrate how Django forms work from start to finish.
Figure 6-1 Django forms workflow
The first two steps of the workflow in Figure 6-1 is what I've described up to this point. It consists of a user hitting a url that's processed by a view method which returns an empty form based on the Django form class definition. Listing 6-3 shows the raw HTML output of the Django form, but as I already mentioned, the form is missing elements in order to become a functional web form, next I'll describe the elements you need to add to get a functional web form.
Functional web form syntax for Django forms
So far you've learned how a Django form class definition can quickly be turned into an HTML form. But this automatic HTML generation is only part of the benefit of using Django form class definitions, you can also validate form values and present errors to end users much more quickly.
To perform these last actions, it's first necessary to have a functional web form. Listing 6-4 illustrates the template syntax to create a functional web form from a Django form.
Listing 6-4. Django form template declaration for functional web form
<form method="POST"> {% csrf_token %} <table> {{form.as_table}} </table> <input type="submit" value="Submit form"> </form>
The first thing to notice about
listing 6-4 is the form is wrapped around the HTML
<form>
tag which is standard for all web forms.
The reason Django forces you to explicitly set the
<form>
tag is because its attributes dictate
much of a web form's behavior and can vary depending on the purpose
of the form.
In listing 6-4, the
method
attribute tells a web browser that when the
form is submitted it POST the data to the server. The POST method
value is standard practice in web forms that process user data --
an alternative method option value is GET, but it's not a typical
choice for transferring user provided data. The use of the POST
method should become clearer shortly, but for more background on
form method
attributes, you can consult many Internet
references on HTTP request methods[1].
Another important
<form>
attribute -- which is actually missing in
listing 6-4 -- is action
which tells a web browser
where to submit the form (i.e. what url is tasked with processing
the form). In this case, because listing 6-4 has no
action
attribute, a browser's behavior is to POST the
data to the same url where it's currently on, meaning if the
browser got the form from the /contact/
url, it will
POST the data to the same /contact/
url. If you wanted
to POST the form to a separate url, then you would add the action
attribute (e.g. action="/urltoprocessform/"
) to the
<form>
tag.
Keep in mind that because the same url delivers the initial form -- via a GET request -- and must also process the form data -- via a POST request -- the backing view method of the url must be designed to handle both cases. I'll describe a modified version of listing 6-2 -- which just handles the GET request case -- to also handle the POST case in the next section.
The {% csrf_token %}
statement in listing 6-4 is a Django tag. {% csrf_token
%}
is a special tag reserved for cases when a web form is
submitted via POST and processed by Django. The csrf initials mean
Cross-Site Request Forgery, which is a default security mechanism
enforced by Django. While it's possible to disable CSRF and not
include the {% csrf_token %}
Django tag in forms, I
would advise against it and recommend you keep adding the {%
csrf_token %}
Django tag to all forms with POST, as CSRF
works as a safeguard and mostly behind the scenes. The last
sub-section in this first section describes the reasoning behind
CSRF and how it works in Django in greater detail.
Next in listing 6-4 is the
{{form.as_table}}
snippet wrapped in an
<table>
tag which represents the Django form
instance and outputs the HTML output illustrated in listing 6-3.
Finally, there's the <input type="submit">
tag
which generates the form's submit button -- that when clicked on by
a user submits the form -- and the closing
</form>
tag.
Django view method to process form (POST handling)
Once you have a functional web form in Django, it's necessary to create a view method to process it. In the previous section, I mentioned how the same url and by extension view method, would both handle generating the blank HTML form, as well as process the HTML form with data. Listing 6-5 illustrates a modified version of the view method in listing 6-2 that does exactly this.
Listing 6-5. Django view method that sends and processes Django form
from django.shortcuts import render from django.http import HttpResponseRedirect from .forms import ContactForm def contact(request): if request.method == 'POST': # POST, generate form with data from the request form = ContactForm(request.POST) # check if it's valid: if form.is_valid(): # process data, insert into DB, generate email,etc # redirect to a new url: return HttpResponseRedirect('/about/contact/thankyou') else: # GET, generate blank form form = ContactForm() return render(request,'about/contact.html',{'form':form})
The most important construct in
the view method of listing 6-5 is the if/else condition that checks
the request
method type. If the request method type is
POST (i.e. data submission or step 3 in figure 6-1) the form's data
is processed, but if the request
method type is
anything else (i.e. initial request or step 1 in figure 6-1) an
empty form is generated to produce the same behavior as listing
6-2. Notice the last line in listing 6-5 is a return
statement that assigns the form instance -- whether empty(a.k.a.
unbound) or populated (a.k.a. bound) -- and
returns it to a template for rendering.
Now let's take a closer look at
the POST logic in listing 6-5. If the request
method
type is POST it means there's incoming user data, so we access the
incoming data with the request.POST
reference and
initialize the Django form with it. But notice how there's no need
to access individual form fields or make piecemeal assignments --
although you could if you wanted to and this process is described
in a later section in this chapter -- using
request.POST
as the argument of a Django form class is
sufficient to populate a Django form instance with user data, it's
that simple! It's worth mentioning that a Django form instance
created in this manner (i.e. with user provided data) is known as a
bound form instance.
At this point, we still don't
know if a user provided valid data with respect to a Django form's
field definitions (e.g. if values are text or a valid email). To
validate a form's data, you must use the is_valid()
helper method on the bound form instance. If
form.is_valid()
is True
the data is
processed and subsequent action is taken, in listing 6-5 this
additional action consists of redirecting control to the
/about/contact/thankyou
url. If
form.is_valid()
is False
it means the
form data has errors, after which point control falls to the last
return
statement which now passes a bound form
instance to render the template. By using a bound form instance in
this last case, the user gets a rendered form filled with his
initial data submission and errors so he's able to correct the data
without reintroducing values from scratch.
I purposely didn't mention any
more details about the is_valid()
helper method or
error message displays, because Django form processing can get a
little complex. The subsequent section 'Django form processing:
Initialization, field access, validation and error handling' covers
all you need to know about Django form processing so it doesn't
interfere in this introductory section.
CSRF: What is it and how does it work with Django ?
CSRF or Cross-Site Request Forgery is a technique used by cyber-criminals to force users into executing unwanted actions on a web application. When users interact with web forms, they make all kinds of state-changing tasks that range from making orders (e.g. products, money transfers) to changing their data (e.g. name, email, address). Most users tend to feel a heightened sense of security when they interact with web forms because they see an HTTPS/SSL security symbol or they've used a username/password prior to interacting with a web form, all of which leads to a feeling there's no way a cyber-criminal could eavesdrop, guess or interfere with their actions.
A CSRF attack relies for the most part on social engineering and lax application security on a web application, so the attack vector is open irrespective of other security measures (e.g. HTTPS/SSL, strong password). Figure 6-2 illustrates a CSRF vulnerable scenario on a web application.
Figure 6-2. Web application with no CSRF protection
After user "X" interacts with web application "A" (e.g. making an order, updating his email) he simply navigates away and goes to other sites. Like most web applications, web application "A" maintains valid user sessions for hours or days, in case users come back and decide to do other things without having to sign-in again. Meanwhile, a cyber-criminal has also used site "A" and knows exactly where and how all of its web forms work (e.g. urls, input parameters such as email, credit card).
Next, a cyber-criminal creates links or pages that mimic the submission of web forms on web application "A". For example, this could be a form that changes a user's email in order to overtake an account or transfers money from a user's account to steal funds. The cyber-criminal then seeds the Internet with these links or pages through email, social media or other web sites with enticing or frightening headlines: "Get a $100 coupon from site 'A'", "Urgent: Change your password on site 'A' because of a security risk'. In reality, these links or pages don't do what they advertise, but instead in a single click mimic web form submissions from site "A" (e.g. change a user's email or transfer funds).
Now lets turn our attention to unsuspecting user "X" that visited site "A" hours or days earlier. He catches a glimpse of these last advertisements and thinks "Wow, I can't pass this up". Thinking what harm can a click do, he clicks on the bogus advertisement, the user is then sent to a legitimate site 'A' page as a façade or the click 'appears' to have done nothing. User "X" thinks nothing of it and goes back to some other task. If site 'A' did not have web forms with CSRF protection, then user "X" just inadvertently -- in a single click -- performed an action on site "A" he wasn't aware of.
As you can see, in order to perform a CSRF attack all that's needed is for a user to have an active session on a given site and a cyber-criminal crafty enough to trick a user into clicking on a link or page that performs actions on said site. Hence the term's name: "Cross-Site", because the request doesn't come from the original site, and "Request Forgery" because it's a forged request by a cyber-criminal.
To protect against web form CSRF attacks, it's isn't sufficient for web applications to trust authenticated users, because as I've just described authenticated users could have inadvertently triggered actions they weren't aware of. Web forms must be equipped with a unique identifier -- often called a CSRF token -- that's unique to a user and has an expiration time, similar to a session identifier. In this manner, if a request is made by an authenticated user to a site, only requests that match his CSRF token are considered valid and all other requests are discarded, as illustrated figure 6-3.
Figure 6-3. Web application with CSRF protection
As you can see in figure 6-3, the inclusion of a CSRF token in a web form makes it very difficult to forge a user's request.
In Django, a CSRF token is
generated in web forms with the {% csrf token %}
tag
that generates an HTML tag in the form <input
type="hidden" name="csrfmiddlewaretoken"
value="32_character_string">
, where the 32-character
string value varies by user. In this manner, if a Django
application makes a POST request -- like those made by web forms --
it will only accept the request if the CSRF token is present and
valid for a given user, otherwise it will generate a '403
Forbidden' page error.
Be aware CSRF is enabled by
default on all Django applications thanks to its out-of-the-box
middleware settings that includes the django.middleware.csrf.CsrfViewMiddleware
class
charged with enforcing CSRF functionality. If you wish to disable
the CSRF support in a Django application completely, you can just
remove the django.middleware.csrf.CsrfViewMiddleware
class from the MIDDLEWARE
variable in
settings.py
.
If you wish to enable or disable
CSRF on certain web forms, you can selectively use the
@csrf_exempt()
and @csrf_protect
decorators on the view methods that process a web form's POST
request.
To enable CSRF on all web forms
and disable CSRF behavior on certain web forms, keep the
django.middleware.csrf.CsrfViewMiddleware
class in
MIDDLEWARE
and decorate the view methods you don't
want CSRF validation on with the @csrf_exempt()
decorator, as illustrated in listing 6-6.
Listing 6-6. Django view method decorated with @csrf_exempt() to bypass CSRF enforcement
from django.views.decorators.csrf import csrf_exempt @csrf_exempt def contact(request): # Any POST processing inside view method # ignores if there is or isn't a CSRF token
To disable CSRF on all web forms
and enable CSRF behavior on certain web forms, remove the
django.middleware.csrf.CsrfViewMiddleware
class from
MIDDLEWARE
and decorate the view methods you want CSRF
validation on with the @csrf_protect()
decorator, as
illustrated in listing 6-7.
Listing 6.7- Django view method decorated with @csrf_protect() to enforce CSRF when CSRF is disabled at the project level
from django.views.decorators.csrf import csrf_protect @csrf_protect def contact(request): # Any POST processing inside view method # checks for the presence of a CSRF token # even when CsrfViewMiddleware is removed