As soon as you work with forms you want to validate the user input. Otherwise you collect a lot of garbage in no time.
This post is part of my journey to learn Python. You can find the other parts of this series here.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{% 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:
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:
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@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:
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.
2 thoughts on “Python Friday #39: Form Validation for Your Flask App”