From small contact forms to whole applications for data collection, forms are an important part of most web applications. Let us look how we can work with forms in Flask.
This post is part of my journey to learn Python. You can find the other parts of this series here.
A simple form
For this example, I use a simple contact form that accepts a name, an email address and a message:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{% 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 }}"> <input name="email" type="text" placeholder="Your email address" class="form-control" value="{{ email }}"> <textarea name="message" type="text" placeholder="Your message" class="form-control">{{ message }}</textarea> <button type="submit" class="btn btn-danger">Submit message</button> <div style="clear: both;"></div> </form> </div> {% endblock %} |
To deliver this form for the URL /contact
I need a view function like this one:
1 2 3 |
@app.route('/contact') def contact_form(): return render_template('contact.html') |
If I now visit the /contact
page I can see my form:
However, if I click on the submit message button, I get this error:
Accepting POST
By default, Flask only allows GET requests on its view functions. To submit this form, we want to use the POST method and need to allow it explicit:
1 2 3 |
@app.route('/contact', methods=['POST']) def contact_post(): ... |
I decided to create another view function to process the form data. You could do that in the same function, but that makes it harder to understand what is going on.
We can access the data from the form using the request.form dictionary:
1 2 3 4 5 6 7 8 |
@app.route('/contact', methods=['POST']) def contact_post(): name = request.form['name'] email = request.form['email'] message = request.form['message'] print(f"{name} [{email}]: {message}") return render_template('thankyou.html') |
We now can store this data or send it by email instead of printing it to the command line. Before we do that, we have to check if everything works in the browser. If we now click on submit, we get a success message:
Despite the working feature, this solution has a severe problem that we will only notice in production. If we hit reload (or F5) in our browser, we get this warning message:
The form data is still in the browser and that could lead to big problems when we use a form in an online store. It is a lot better to fix this problem right away than to hope no one ever hits F5.
Post/Redirect/Get pattern
There is a design pattern called Post/Redirect/Get to solve our problem of resubmitting a form. Instead of handling the POST request and delivering the success message in the same view function, we split those two responsibilities into two different view functions and create a redirect between them. This needs a little bit more code but saves us from the troubles of resubmitted forms.
For this pattern we need to modify our contact_post()
view function and replace the call to render_template()
with a redirect (the Post/Redirect part of the pattern) to another view function:
1 2 3 4 5 6 7 8 9 |
@app.route('/contact', methods=['POST']) def contact_post(): name = request.form['name'] email = request.form['email'] message = request.form['message'] print(f"{name} [{email}]: {message}") resp = flask.redirect('/thanks') return resp |
We can redirect the user to any other existing view function or, as I prefer, to a specific one for this form (that will do the GET part of the pattern):
1 2 3 |
@app.route('/thanks') def thanks(): return render_template('thankyou.html') |
Since we return the same template, the behaviour for the user looks exactly like before. However, when you now make a refresh, you only reload the /thanks
page – and not data is sent to the application:
127.0.0.1 – – [**** 21:00:49] “GET /thanks HTTP/1.1” 200 –
127.0.0.1 – – [**** 21:00:50] “GET /thanks HTTP/1.1” 200 –
127.0.0.1 – – [**** 21:00:51] “GET /thanks HTTP/1.1” 200 –
127.0.0.1 – – [**** 21:00:51] “GET /thanks HTTP/1.1” 200 –
Next
Preventing the user from posting the same data over and over again is only one step to prevent bad data in our application. Another important step is to validate the user input which I cover in the next post.
1 thought on “Python Friday #38: Forms for Your Flask App”