Understand Django: User Interaction With Forms
Tags: python, django, forms
Published: 2020-05-05 14:39:29

In the previous [Understand Django](https://www.mattlayman.com/understand-django/) article, we saw how Django templates work to produce a user interface.  That's fine if you only need need to display a user interface, but what do you do if you need your site to interact with users?  You use Django's form system!  In this article, we'll focus on how to work with web forms using the Django form system.

## Web Forms 101

Before we can dive into how Django handles forms, we need to have an understanding of HTML forms in general.  Django's form functionality builds upon web forms so this topic won't make sense without a baseline knowledge of the topic.

HTML can describe the type of data that you may want your users to send to your site.  Collecting this data is done with a handful of tags.  The primary HTML tags to consider are `form`, `input`, and `select`.

A `form` tag is the container for all the data that you want a user to send to your application.  The tag has two critical attributes that tell the browser how to send data: `action` and `method`.

`action` would be better named as "destination" or "url." Alas, we are stuck with `action`.  This attribute of the `form` tag is where user data should be sent to.  It's also useful to know that leaving out `action` or using `action=""` will send any form data as an HTTP request to the same URL that the user's browser is on.  The default behavior of `action` is sometimes handy to know.

The `method` attribute dictates which HTTP method to use and can have a value of `GET` or `POST`.  When paired with `action`, the browser knows how to send a properly formatted HTTP request.

Let's say we have this example.

```html
<form method="GET" action="/some/form/">
    <input type="text" name="message">
    <button type="submit">Send me!</button>
</form>
```

When the form's method is `GET`, the form data will be sent as part of the URL in a querystring.  The GET request sent to the server will look like `/some/form/?message=Hello`.  This type of form submission is most useful when we don't need to save data and are trying to do some kind of query.  For instance, you could give your application some search functionality with a URL like `/search/?q=thing+to+search`.  These links could be bookmarked easily and are a natural fit for that kind of function.

The `POST` method of sending form data is for data that we want to be secure or saved within an application.  With a GET request, form data in the querystring is exposed in a number of places (see [more information](https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url) from the Open Web Application Security Project (OWASP)).  On the other hand, POST sends the data in the body of the HTTP request.  This means that if your site is secure (i.e., using HTTPS), then data is encrypted while traveling from a browser to a server.

If you ever login to a website and submit a password in a form, you can be nearly certain that the form is sent with the POST method option (and if it's not, run away!).

We've seen that `form` is the container that guides how to send form data.  `input` and `select` are the tags that let us display a meaningful form to the user.

The more prevalent tag is `input`.  With the `input` tag, form authors will set `type` and `name` primarily.  The `type` attribute tells the browser which kind of input to display.

* Do we need a checkbox? `type="checkbox"`
* Do we need a password field that hides characters? `type="password"`
* How about a classic text box? `type="text"`

You can see a full list of types on the [MDN input documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) page.

The other attribute, `name`, is the identifier that the form will pair with the user data.  The server uses the identifier so it can distinguish between the pieces of data that a form submission may include.

Another tag that your forms may use is the `select` tag.  This kind of tag is less frequent than the `input` tag.  The `select` tag lets users make a choice from a list of options.  The default browser user interface for this tag is a dropdown menu.

With these core elements of HTML forms, we are equipped to understand Django's form capabilities.  Let's dive in!

## Django Forms

Django's form features act as a bridge between HTML forms and Python classes and data types.  When presenting a form to a user in view, the form system is able to display the proper HTML form tags and structure.  When receiving this form data from a user's submission, the form system can translate the browser's raw form data into native Python data that we can use.

We can begin with the `Form` class.  A form class acts as the data declaration of what data we need from the user.  Here's an example that we can examine.

```python
# application/forms.py

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    message = forms.CharField(max_length=1000)
```

1. Django forms are sub-classes of the `Form` class.  This class adds a lot of powerful functionality that will aid us as we explore more.
2. The data that we want to collect is listed as class level attributes.  Each field of the form is a certain field type that has its own characteristics to help translate raw form data into the data types that we want to work with in views.

If we take this form and add it to a view's context as `form`, then we can render it in a template.  The default rendering of the form uses an HTML table, but we can render the fields in a simpler format with the `as_p` method.  This method will use paragraph tags instead for the form elements.  If the template looks like:

```django
{{ form.as_p }}
```

Then Django will render:

```html
<p><label for="id_name">Name:</label>
  <input type="text" name="name" maxlength="100" required id="id_name"></p>
<p><label for="id_email">Email:</label>
  <input type="email" name="email" required id="id_email"></p>
<p><label for="id_message">Message:</label>
  <input type="text" name="message" maxlength="1000" required id="id_message">
</p>
```

To make it possible submit the form, we need to wrap this rendered output with a `form` tag and include a submit button and a CSRF token.

Huh? *CSRF token?* Sadly, the world is full of nefarious people who would love to hack your application to steal data from others.  A CSRF token is security measure that Django includes to make it harder for malicious actors to tamper with your form's data.  We'll talk more about security in a future article.  For now, sprinkle the token into your forms with Django's built-in template tag and everything should work.

```django
<form action="{% url "some-form-view" %}" method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <p><input type="submit" value="Send the form!"></p>
</form>
```

That's how a form gets displayed.  Now let's look at a view that handles the form properly.  When working with form views, we will often use a view that is able to handle both `GET` and `POST` HTTP requests.  Here's a full form that we can break down piece by piece.  The example uses a function view for simplicity, but you could do something similar with a class-based view.

```python
# application/views.py

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import ContactForm

def contact_us(request):
    if request.method == "POST":
        form = ContactForm(request.POST)
        if form.is_valid():
            # Do something with the form data like send an email.
            return HttpResponseRedirect(reverse('some-form-success-view'))
    else:
        form = ContactForm()

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

If we start by thinking about the `else` branch, we can see how little this view does on a `GET` request.  When the HTTP method is a `GET`, it creates an empty form with no data passed to the constructor and renders a template with the `form` in the context.

`contact_form.html` would contain the Django template that is above to display the HTML form to the user.  When the user clicks "Send the form!", another request comes to the same view, but this time the method is an HTTP `POST` and contains the submitted data.

The `POST` request creates a `form`, but there is a difference in how it is constructed.  The form submission data is stored in `request.POST`, which is a dictionary-like object that we first encountered in the views article.  By passing `request.POST` to the form's constructor, we create a form with data.  In the Django documentation, you will see this called a *bound form* because data is *bound* to the form.

With the form ready, the view checks if the data is valid.  We'll talk about form validation in detail later in this article.  In this instance, you can see that `is_valid` could return `False` if the form data contained "I am not an email address" in the `email` field, for instance.

* When the form is valid, the view does the extra work represented by the comment and redirects to a new view that can show some kind of success message.
* When the form is invalid, the view goes out of the `if` clause and calls `render`.  Since the data is bound to the `form`, the contact form has enough information to show which form fields caused the errors that made the form invalid.

That's the core of form handling!  The presented view is a common pattern for handling form views in Django.  In fact, this view pattern is so common that Django provides a built-in view to implement what is done in the example named `FormView`.

```python
# application/views.py

from django.views.generic import FormView

from .forms import ContactForm

class ContactUs(FormView):
    form = ContactForm
    template = 'contact_form.html'

    def get_success_url(self):
        return reverse('some-form-success-view')

    def form_valid(self, form):
        # Do something with the form data like send an email.
        return super().form_valid(form)
```

## Form Fields

With the basics of form handling done, we can turn our attention to the kinds of fields that forms can use.  The extensive list of fields is in [the Django documentation](https://docs.djangoproject.com/en/3.0/ref/forms/fields/), and we will look at a few of the most common ones in this article.

The first thing to remember about Django form fields is that they convert HTML form data into native Python data types.  If you examine the data of a form submission, you'll discover that each value is essentially a string by default.  If Django did nothing for you, then you would constantly have to convert to the data types that you want.  By working with form fields, that data conversion is automatically handled for you.  For instance, if you choose a `BooleanField`, after the Django form is validated, that field value will be either `True` or `False`.

Another important item to know about fields is that they are associated with particular Django widgets.  Widgets are the way to control what Django renders when you render a form.  Each form field has a default widget type.  Sticking with `BooleanField`, its default widget is a `CheckboxInput` which will render an `input` tag with a type of `checkbox` (i.e., your standard form checkbox).

Fields are the critical intersection between the world of the browser and HTML and the Python world with all of its robust data types.

What fields are you most likely to reach for?  And what do you need to set on those fields?

### CharField

`CharField` is a real workhorse for Django forms.  The `CharField` captures text input and uses a standard `input` tag with a type of `text`.  If you want to collect more text, like in a feedback form, you can switch from the default `TextInput` widget to a `Textarea` widget.  This will make your form render a `textarea` tag that will give far more space for any input.

```python
# application/forms.py

from django import forms

class FeedbackForm(forms.Form):
    email = forms.EmailField()
    comment = forms.CharField(widget=forms.Textarea)
```

### EmailField

The `EmailField` is like a specialized version of the `CharField`.  The field uses an `input` tag with a type of `email`.  Many modern browsers can help to check that valid email addresses are provided.  Also, when this field is validated within the framework, Django will attempt to validate the email address too in case the browser wasn't able to do it.

### DateField

A `DateField` is another field that is mostly like a `CharField`.  The field even uses the `input` type of `text` when rendered.  The difference with this field comes from the data type that the form will provide after it is validated.  A `DateField` will convert [a variety of string formats](https://docs.djangoproject.com/en/3.0/ref/settings/#datetime-input-formats) into a Python `datetime.date` object.

### ChoiceField

A `ChoiceField` is useful when you want a user to make a choice from a list of options.  For this field type, we must provide a list of choices that the user can pick from.  Imagine that we want to ask users what their favorite meal of the day is.  Here's a form that can do that.

```python
# application/forms.py

from django import forms

class SurveyForm(forms.Form):
    MEALS = [("b", "Breakfast"), ("l", "Lunch"), ("d", "Dinner")]
    favorite_meal = forms.ChoiceField(choices=MEALS)
```

This will contain a form with a `select` tag that looks like:

```html
<p>
  <label for="id_favorite_meal">Favorite meal:</label>
  <select name="favorite_meal" id="id_favorite_meal">
    <option value="b">Breakfast</option>
    <option value="l">Lunch</option>
    <option value="d">Dinner</option>
  </select>
</p>
```

This handful of fields will deal with most form needs.  Be sure to explore the full list of what is available to equip yourself with other beneficial types.

Form fields share some common attributes for things that each field needs.

The `required` attribute is a boolean that specifies whether a field must have a value or not.  For instance, it wouldn't make much sense if your site had a support form that you planned to use to contact people via email, and an `email` field on the form was optional.

`label` sets what text is used for the `label` tag that is rendered with a form `input`.  In the meal example, we could use the `label` attribute to change "Favorite meal" into "What is your favorite meal?" That would make a much better survey experience.

Sometimes forms may not be clear and users need help.  You can add a `help_text` attribute that will render additional text by your form field wrapped in a `span` tag with a `helptext` class if you want to style it with CSS.

Because forms are one of the main ways that users will provide information to your application, the forms system is rich with features.  You can learn a lot about Django by diving deeply into that portion of the documentation.

Let's shift our focus to form validation since I've mentioned it a few times in passing now.

## Validating Forms

In the view example, I showed a form that calls the `is_valid` method.  At a high level, we can understand what that method is doing; it's determining whether the form is valid or not.

But what is `is_valid` actually doing?  It does a lot!

The method handles each of the fields.  As we saw, fields can have a final data type (like with `BooleanField`) or an expected structure (like with `EmailField`).  This process of converting data types and validating the field data is called cleaning.  In fact, each field must have a `clean` method that the form will call when `is_valid` is called.

When `is_valid` is `True`, the form's data will be in a dictionary named `cleaned_data` with keys that match the field names declared by the forms.  With the validated data, you access `cleaned_data` to do your work.  For instance, if we had some kind of integration with a support ticket system, perhaps our `FeedbackForm` above is handled in the view like:

```python
if form.is_valid():
    email = form.cleaned_data['email']
    comment = form.cleaned_data['comment']
    create_support_ticket(email, comment)
    return HttpReponseRedirect(reverse('feedback-received'))
```

When `is_valid` is `False`, Django will store the errors it found in an `errors` attribute.  This attribute will be used when the form is re-rendered on the page (because, if you recall from the view example, the form view pattern sends a bound form back through a `render` call in the failure case).

Once again, Django is doing a lot of heavy lifting for you to make working with forms easier.  The system *also* permits developers to add custom validation logic.

If you have a form field, you can add customization by writing a method on the form class.  The format of the method must match with the field name and prepend `clean_`.  Let's imagine that we want a website for Bobs.  In order to sign up for the website, your email address must have "bob" in it.  We can write a clean method to check for that.

```python
# application/forms.py

from django import forms

class SignUpForm(forms.Form):
    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)

    def clean_email(self):
        email = self.cleaned_data['email']
        if 'bob' not in email:
            raise forms.ValidationError('Sorry, you are not a Bob.')
        return email
```

There are a few important points about this:

* `clean_email` will only try to clean the `email` field.
* If validation fails, your code should raise a `ValidationError`.  Django will handle that and put the error in the right format in the `errors` attribute of the form.
* If everything is good, be sure to return the cleaned data.  That is part of the interface that Django expects for clean methods.

These `clean_<field name>` methods are hooks that let you include extra checking.  This hook system gives you the perfect place to put validation logic for data that is specific to your application.  But what about validating multiple pieces of data?  This might happen when data has some kind of interrelationship.  For instance, if you're putting together a genealogy website, you may have a form that records birth and death dates.  You might want to check those dates.

```python
# application/forms.py

from django import forms

class HistoricalPersonForm(forms.Form):
    name = forms.CharField()
    date_of_birth = forms.DateField()
    date_of_death = forms.DateField()

    def clean(self):
        cleaned_data = super().clean()
        date_of_birth = cleaned_date.get('date_of_birth')
        date_of_death = cleaned_date.get('date_of_death')
        if date_of_birth and date_of_death and date_of_birth > date_of_death:
            raise forms.ValidationError('Birth date must be before death date.')
        return cleaned_data
```

This method is similar to `clean_<field name>`, but we must be more careful.  Individual field validations run first, but they may have failed!  When clean methods fail, the form field is removed `cleaned_data` so we can't do a direct key access.  The clean method checks if the two dates are truthy and each has a value, then does the comparison between them.

Custom validation is a great feature to improve the quality of the data you collect from users of your application.

## Summary

That's how forms make it possible to collect data from your users so your site can interact with them.  We've seen:

* Web forms and the `form` HTML tag
* The `Form` class that Django uses to handle form data in Python
* How forms are rendered to users by Django
* Controlling what fields are in forms
* How to do form validation

Now that we know how to collect data from users, how can we make the application hold onto that data for them?  In the next article, we will begin to store data in a database.  We'll work with:

* How to set up a database for your project.
* How Django uses special classes called models to keep data.
* Running the commands that will prepare a database for the models you want to use.
* Saving new information into the database.
* Asking the database for information that we stored.

If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content.  If you have other questions, you can reach me online on Twitter where I am [@mblayman](https://twitter.com/mblayman).

This article first appeared on [mattlayman.com](https://www.mattlayman.com/understand-django/user-interaction-forms/).