# Creating a form with WTForms

# Dependencies

To use WTForms in a Flask application, the easiest thing to do is install two dependencies: wtforms and flask-wtf.

WTForms will help us define the forms, and Flask-WTF will add extra functionality such as that to deal with CSRF tokens.

pip install wtforms
pip install flask-wtf

# What is a CSRF token?

Cross-Site Request Forgery (CSRF)[1] is a web security vulnerability. It allows an attacker to trick the browser into submitting a form that sends data to your application, without the user noticing it.

By including a unique token generated by the Flask server in all requests, it prevents the possibility of a CSRF attack because the attacker will need the server to generate the form, which comes with the whole HTML page and usually requires the user to log-in first.

# Create a WTForm Class

Let's create a new file called movie_library/forms.py. There, we will create a Python class that represents our form.

from flask_wtf import FlaskForm


class MovieForm(FlaskForm):
    pass

Note that the import is from flask_wtf, as that will give the form CSRF protection (as long as the necessary field is included in the rendered HTML, more on that later).

# Define Form Fields

Next, we need to define our form fields. This form is for creating new movies, so it will only include four fields: title, director, year, and submit. The first two will be strings, and the year will be an integer. The submit field will be rendered as a button to submit the form.

Giving our fields appropriate types will do two things:

  • Render the appropriate field type in HTML
  • Make validation easier
from flask_wtf import FlaskForm
from wtforms import IntegerField, StringField, SubmitField


class MovieForm(FlaskForm):
    title = StringField("Title")
    director = StringField("Director")

    year = IntegerField("Year")

    submit = SubmitField("Add Movie")

With just this, we can go and render the form and it will work just fine!

However, one of the key benefits of WTForms is how easy it is to include validation in our forms. Let's look at that.

# Data Validation with WTForms

We will be including two types of validation in this form:

  • InputRequired, which makes it so the field cannot be empty when submitted
  • NumberRange, which makes it so the field must have a value between two provided numbers

WTForms ships with many more validators[2] that you can use.

For now though, let's add the validation to our fields:

from flask_wtf import FlaskForm
from wtforms import IntegerField, StringField, SubmitField

from wtforms.validators import InputRequired, NumberRange


class MovieForm(FlaskForm):
    title = StringField("Title", validators=[InputRequired()])
    director = StringField("Director", validators=[InputRequired()])

    year = IntegerField(
        "Year",
        validators=[
            InputRequired(),
            NumberRange(min=1878, message="Please enter a year in the format YYYY."),
        ],
    )

    submit = SubmitField("Add Movie")

I've made all three input fields required, and I've made the year field have a minimum value of 1878, since that's when the first movie was released.

A few things to note:

  • validators is a keyword argument which takes a list of validator objects.
  • Each validator object takes an optional message keyword argument which will be displayed to users if the validation fails at that step.
  • Validators are validated in the order in which they are defined, so that's why I put InputRequired() first.

Now that we've defined our form using WTForms, a few questions still remain!

  1. How do we render this form in our template?
  2. How do we receive form data?
  3. How do we validate the form data?
  4. If validation fails, how do we display the error messages to the user?

We will tackle all these questions over the next 2 lectures. Let's go!


  1. Cross-site request forgery (PortSwigger) (opens new window) ↩︎

  2. List of built-in validators (WTForms) (opens new window) ↩︎