21.7. Form Validation with Flask

Whenever we ask users for input, it is critical that we validate the information they submit. Users will often submit something that doesn’t fit our requirements or doesn’t make sense in some way. Most of the time, this happens by accident. Sometimes, however, users will try to mess with our application by sending bad data.

Our applications should respond by rejecting forms that contain invalid information. We should also include code that tells the user what they did wrong.

21.7.1. Validation

Validation checks to make sure that data submitted by a user meets the requirements for our program. For example, if we want the user to schedule an appointment, then we need to make sure they enter a date and time within a certain range.

Invalid data might just produce incorrect, harmless results. However, it might cause our program to crash, or allow outside users to access our code. Either way, we want to make sure our applications catch any errors and deal with them in a smooth way.

Client-side validation takes place inside the browser window, and it prevents a form from being submitted. In this case, the invalid data never leaves the user’s screen. In the Forms chapter, we added client-side validation by including the type and/or required attributes inside an <input> tag.

Server-side validation occurs after the user clicks Submit and sends an HTTP request over the internet. In this case, the server receives the request and runs some code to check the data. If a problem is found, the server sends a response back to the user’s browser with information about what went wrong. Although the faulty data reaches the server, it doesn’t make it very far. The program catches and responds to a mistake before it can cause any harm.

Whenever we build a web application that requires user input, we should include both types of validation.

21.7.1.1. Why Do Both?

Each type of validation has its strengths and weaknesses, so using both helps us cover the bases. Here’s a summery of what each type brings to our application:

Client-side:

  1. Pro: Checks data in real-time, and prevents bad data from being sent to the server.

  2. Pro: HTML attributes make it simple to put in place.

  3. Con: Can be easily fooled, turned off, or bypassed completely by the user.

Server-side:

  1. Pro: Cannot be bypassed by the user.

  2. Pro: Allows programmers to write much more specific checks for the data.

  3. Con: Requires more work to put in place, and it only operates after a request reaches the server.

Whenever our applications collect user input, we should never assume that the information will be valid or safe. We must always check the data to make sure it will work with our program. By combining client and server-side validation, we decrease the chances that our program will crash when it receives bad data.

21.7.2. Server-Side Validation

In our favorite_form template, we can add client-side validation to the Favorite Pixar Movie field by including the required attribute. However, this just checks to make sure the user types something into the field. They could easily submit something other than a Pixar film title.

Let’s add some server-side validation to make sure the user chooses an actual Pixar movie. Submitting the form sends data to the /results route, so that’s where we need to add some new Python code.

Try It!

  1. Open hello.py and find the results() function. Your code should include a request.form statement for each of the four input fields. However, in this exercise we’re only going to pay attention to the movie title.

    23
    24
    25
    26
    27
    28
    @app.route('/results', methods=["POST"])
    def results():
       best_pix = request.form['best_pix']
       # Other request.form statements...
    
       return render_template('form_results.html', best_pix = best_pix)
    
  2. To help with the validation, it’s useful to cast text entries into all lowercase or uppercase. We should also remove any leading or trailing whitespace. Modify line 25 as follows:

    23
    24
    25
    26
    27
    28
    @app.route('/results', methods=["POST"])
    def results():
       best_pix = request.form['best_pix'].lower().strip()
       # Other request.form statements...
    
       return render_template('form_results.html', best_pix = best_pix)
    
  3. As of December, 2020, Pixar had released 23 full-length movies. Copy/paste the list of the titles into your code.

    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @app.route('/results', methods=["POST"])
    def results():
       best_pix = request.form['best_pix'].lower().strip()
       # Other request.form statements...
       films = ["toy story","a bug's life","toy story 2","monsters, inc.",
          "finding nemo", "the incredibles","cars","ratatouille","wall-e","up",
          "toy story 3","cars 2", "brave","monsters university","inside out",
          "the good dinosaur","finding dory", "cars 3","coco","incredibles 2",
          "toy story 4","onward","soul"]
    
       return render_template('form_results.html', best_pix = best_pix)
    

    Note that all of the titles in the list are lowercase. This matches the format of the string assigned to best_pix.

  4. Add a conditional to check if the title entered by the user is NOT part of the films list:

    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    @app.route('/results', methods=["POST"])
    def results():
       best_pix = request.form['best_pix'].lower().strip()
       # Other request.form statements...
       films = ["toy story","a bug's life","toy story 2","monsters, inc.",
          "finding nemo", "the incredibles","cars","ratatouille","wall-e","up",
          "toy story 3","cars 2", "brave","monsters university","inside out",
          "the good dinosaur","finding dory", "cars 3","coco","incredibles 2",
          "toy story 4","onward","soul"]
    
       if best_pix not in films:
          best_pix = "Sorry, '{0}' isn't a Pixar film.".format(best_pix.title())
       else:
          best_pix = best_pix.title()
    
       return render_template('form_results.html', best_pix = best_pix)
    

    If the boolean expression in line 33 returns True, then we assign an error message to the best_pix variable. If False, line 36 casts the user’s entry into Title Case.

  5. Save your code, then launch the application and navigate to http://127.0.0.1:5000/form. Test out the validation code by entering different options in the Favorite Pixar Movie field. Be sure to test cASe differences, leading/trailing whitespace, non-Pixar movies, and (of course) your favorite Pixar film.

    Before and after pages showing the form and the results page (with an error message).

    The user sees an error message on the results page.

Following the example above, add validation for the color input. Limit the user to only 1 of 3 choices: red, green, or blue.

Note

You could replace the color input with either radio buttons or a select menu. This would fit well with client-side validation.

However, you need to practice server-side validation! For now, keep the input as type="text".

21.7.3. A Better User Experience

With server-side validation, the user only finds out if they did something wrong AFTER they submit the form. Also, they will be on the results page, which means they need to navigate back to the form in order to fix their mistakes. Many users will find this annoying, or won’t know how to go back a step.

We want to give our users a smoother experience if they make a mistake. One thing we can do is to return them to the original form instead of rendering the results page. We will handle this in the next chapter.

21.7.4. Video Summary

Always validate data collected from a user.

21.7.5. Check Your Understanding

Question

Which type of validation prevents form submission?

  1. Client-side
  2. Server-side
  3. Both
  4. Neither

Question

Which type of validation can be used to display error messages on a webpage?

  1. Client-side
  2. Server-side
  3. Both
  4. Neither

Question

Which type of validation provides more detailed control over checking user input?

  1. Client-side
  2. Server-side