Skip to content

#39: Form Validation for Your Flask App

As soon as you work with forms you want to validate the user input. Otherwise you collect a lot of garbage in no time.

Client-side validation

A user-friendly approach to form validation is to do it on the client side. Here you can inform the user that fields are missing, or that they put in the wrong values without the delay of sending the form to the server. For my simple form of the last post I use the HTML 5 attributes to validate the form data:

{% block main %}
<div class="form-container">
  <form action="" method="POST" class="contact-form">
    <h1>Contact us</h1>
    <p>Please leave us a message</p>

    <input name="name" type="text" placeholder="Your name" 
        class="form-control" 
        value="{{ name }}"
        required>
    <input name="email" type="email" placeholder="Your email address" 
        class="form-control" value="{{ email }}"
        required>
    <textarea  name="message" type="text" placeholder="Your message" 
        required
        class="form-control">{{ message }}</textarea>

    <button type="submit" class="btn btn-danger">Submit message</button>
    <div style="clear: both;"></div>
  </form>
</div>
{% endblock %}

The required attribute in every field makes sure that the user enters some data. If you try to send this form without entering a value for each field, you cannot submit the form and get an explanation on what to do:

The browser tells you that you need to fill in this field

In the field email I want an email address. To enforce that, I changed the type from text to email. If you enter something that has no @ in it, you get this message:

The email field checks that you enter an email address

Everything OK? NO!

As long as you access the form through a modern web browser the validation works. However, if someone uses HTTPie the whole client-side validation is circumvented, and incorrect data goes to the server:

$ http --follow -f POST http://127.0.0.1:5000/contact name='' email='' message=''
HTTP/1.0 200 OK
Content-Length: 2746
Content-Type: text/html; charset=utf-8
Date: Sat, 19 Sep 2020 08:42:46 GMT
Server: Werkzeug/1.0.1 Python/3.8.1

<!doctype html>

    <h1>Thank you!</h1>
    <div class="alert alert-success" role="alert">
        We have received your message and will contact you shortly.
      </div>

We get a successful message on the client, but the form data we processed on the server is empty:

127.0.0.1 - - [19/Sep/2020 08:42:46] "POST /contact HTTP/1.1" 302 - []:

Server-side validation

The only way to prevent bad data from being processed is to validate it on the server. While you can skip the client-side validation (and annoy your users), you never should skip server-side validation.

Even a small form like the contact form requires a lot of validation:

@app.route('/contact', methods=['POST'])
def contact_post():
    name = request.form['name']
    email = request.form['email']
    message = request.form['message']
    error = None

    if not name or not name.strip():
        error = 'name is missing'        
    if not email or not email.strip() or '@' not in email:
        error = 'email is missing' 
    if not message or not message.strip():
        error = 'message is missing' 

    if error:
        return  render_template('contact.html', error = error, name = name, email = email, message = message)

    print(f"{name} [{email}]: {message}")

    resp = flask.redirect('/thanks')
    return resp

If now some input data is not valid, we send the user back to the form where they can add the missing values. The valid values are already in the form, thanks to the attribute value we set for each form field:

1
2
3
4
<input name="name" type="text" placeholder="Your name" 
    class="form-control" 
    value="{{ name }}"
    required>

The last thing we need in our form is to display the error message – if it exists. For that we can add this snipped above the form fields:

1
2
3
{% if error %}
    <div class="error-msg">{{ error }}</div>
{% endif %}

If we now pass the client-side validation of the form but not the validation on the server-side, we get an error message and can correct the data:

Our form now keeps its values and shows us what we need to fix

Next

I hope you agree with the importance of the server-side validation. Unfortunately, the code for validation grows rapidly when you add more fields to your form. In the next post I look at a pattern to unclutter your view functions and still protect your data.