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    
  1. https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods