An Interest In:
Web News this Week
- March 26, 2024
- March 25, 2024
- March 24, 2024
- March 23, 2024
- March 22, 2024
- March 21, 2024
- March 20, 2024
Intro to Flask: Adding a Contact Page
In the previous article in this mini-series, we leveraged Flask to build a simple website that contains “Home” and “About” pages using a generalized workflow that we can apply to other Flask-based web apps. In this lesson, I’ll demonstrate how to add a “Contact” page that allow users to send you messages.
The code used in this article can be found on GitHub. Captions, such as Checkpoint: 05_contact_form, mean that you can switch to the branch named “05_contact_form” and review the code at that point in the article.
Flask Extensions
You can find a full list of extensions in the Flask Extension Registry.
Flask doesn’t come with many features off the shelf, making it easy to pick up and learn. There is no object-relational mapper for database interaction or admin interfaces to add and update content. It only offers a small set of functions, two of which we’ve already used — url_for()
and render_template()
.
Instead of shipping with extra functionality, Flask’s extension model allows you to add functionality as needed. A Flask extension is a package that adds specific functionality to your app. For example, Flask-SQLAlchemy adds database support to your app, whereas Flask-Login adds login/logout support. You can find a full list of extensions in the Flask Extension Registry.
To create a Contact page, we’ll use Flask-WTF to handle and validate form data and Flask-Mail to email the form data to you.
Flask-WTF
Flask-WTF is an exension that handles and validates form data. What does that mean? Look at the following figure:
Fig. 1
- A user issues a GET request for a web page that contains a form.
- The user fills in the form.
- The user clicks the “Send” button, submitting it to the server via a POST request.
- The server validates the information.
- If one or more fields do not validate, the web page containing the form loads again with a helpful error message, prompting the user to try again.
- If all fields validate, the form information is used in the next step in the pipeline.
A contact page will have fields for the user’s name, email, subject, and message. In Flask, we’ll POST the form to a function inside routes.py
. This function is called the form handler. We’ll run a few validation checks, and if any of the input does not pass muster, we’ll refresh the page to display a message that describes the error. Once all validation checks pass, we’ll use the form data for the next step: emailing the message to you, the website owner.
Flask extensions are simple, powerful tools that extend the functionality of your Flask-based app.
That’s how form handling and validation works. Now where do we actually define the form? We could write HTML using the <form>
element and set its action
attribute to a Python script. The Python script would mirror the form in order to capture each form field and validate the form field data. If we use this strategy, however, we’d essentially define the form twice — once for the front-end and once for the back-end.
It would be great to define the form only once: in the Python script. This is exactly what Flask-WTF allows us to do. We’ll define the form just once in a Python script, and then we’ll let Flask-WTF generate the form’s HTML for us. The point of all of this is to separate presentation from content.
Enough chatter. Let’s code.
Creating a Form
As a first step, let’s get back into the isolated development environment we created last time.
$ cd flaskapp$ . bin/activate
Now that we’ve entered and activated our development environment, we can safely install Flask-WTF:
$ pip install flask-wtf
Let’s now define the form in a Python script. We already have routes.py
, which maps URLs to functions. Let’s not clutter it with unrelated code. Instead, create a new file called forms.py
, and place it inside the app/
folder.
app/forms.py
from flask.ext.wtf import Form, TextField, TextAreaField, SubmitFieldclass ContactForm(Form): name = TextField("Name") email = TextField("Email") subject = TextField("Subject") message = TextAreaField("Message") submit = SubmitField("Send")
We just created a form. What did we do? First, we imported a few useful classes from Flask-WTF — the base Form
class, a text field, a textarea field for multi-line text input, and a submit button. Next, we created a new class named ContactForm
, inheriting from the base Form
class. Then we created each field that we want to see in the contact form. Instead of writing <input type="text">Name</input>
in an HTML file, you write name = TextField("Name")
.
Using the Form
Now let’s use our form. We want it to appear when a user visits the contact page. In Flask terms, we want the form to show up in a web template and map a URL to that web template so we can visit it in the browser. This means we need to create a new web template and a new URL mapping. Let’s start by creating a new URL mapping.
This is an action-packed section, and it may be a little confusing. But stick with me and we’ll get through it.
As a first step, open routes.py
and import our newly created form by adding from forms import ContactForm
at the beginning of the script.
app/routes.py
from flask import Flask, render_templatefrom forms import ContactForm
You can prevent a CSRF attack by making sure that the form submission originates from your web app.
Next, configure Flask-WTF to handle a security exploit known as cross-site request forgery (CSRF). In a perfect world, your server would only process forms that belong to your web app. In other words, your server would only handle and validate the forms that you created. However, it is possible for an attacker to create a form on his own website, fill it in with malicious information, and submit it to your server. If your server accepts this malicious information, all sorts of bad things can happen next.
You can prevent a CSRF attack by making sure that the form submission originates from your web app. One way to do this is to keep a unique token hidden inside your HTML <form>
tag that cannot be guessed by attackers. When the form POSTs to your server, the token is checked first. If the token does not match, your server rejects the form submission and does not touch the form data. If the token matches, the server proceeds with form handling and validation.
Flask-WTF does all of this with an easy one-liner. Just configure Flask-WTF with a secret key, and Flask-WTF takes care of generating and managing unique tokens for your forms.
app/routes.py
from flask import Flask, render_template, request, flashfrom forms import ContactFormapp = Flask(__name__) app.secret_key = 'development key'
Here in line six, I set the secret key to ‘development key’. Feel free to make yours more complex, longer, and alphanumeric.
Now that we’ve imported and configured our contact form, we can use it in a URL mapping in routes.py
. Let’s go ahead and create that URL mapping.
app/routes.py
@app.route('/contact')def contact(): form = ContactForm() return render_template('contact.html', form=form)
Now when someone visits the URL /contact
, the function contact()
will execute. Inside contact()
, we first create a new instance of our contact form in line three and sent it to a web template named contact.html
in line four. We will create this web template shortly.
We still have some work to do here though. Figure 1 showed that if a GET request is sent to the server, the web page containing the form should be retrieved and loaded in browser. If the server receives a POST request, a function should capture the form field data and check if it’s valid. In Python terms, this logic can be expressed in an if...else
statement. There is a Flask class for distinguishing between GET and POST requests, so let’s start by importing that class at the beginning of routes.py
and add the if...else
logic to the contact()
function.
app/routes.py
from flask import Flask, render_template, [email protected]('/contact', methods=['GET', 'POST'])def contact(): form = ContactForm() if request.method == 'POST': return 'Form posted.' elif request.method == 'GET': return render_template('contact.html', form=form)
We already imported the Flask class and render_template()
in the previous article, so here we import one more Flask class named request
. request
determines whether the current HTTP method is a GET or a POST. Next is the if...else
logic to the contact()
function (lines 9-13).
In the case of a POST request, a string indicating that the form has been posted will be returned.
This string is a temporary placeholder, and we’ll replace it with real code in the final step of this article. Otherwise, if the request uses GET, we return the web template contact.html
that contains the form.
The next step is to create the web template contact.html
and put it inside the templates/
folder.
app/templates/contact.html
{% extends "layout.html" %}{% block content %} <h2>Contact</h2> <form action="{{ url_for('contact') }}" method=post> {{ form.hidden_tag() }} {{ form.name.label }} {{ form.name }} {{ form.email.label }} {{ form.email }} {{ form.subject.label }} {{ form.subject }} {{ form.message.label }} {{ form.message }} {{ form.submit }} </form>{% endblock %}
As with home.html
and about.html
, the contact.html
template extends layout.html
and fills the ‘content’ block with its own text. We first specify where to send the form data on submission by setting the <form>
element’s action
attribute to the contact()
function we created in routes.py
(line five). Next, we let the Jinja2 template engine generate the bulk of the form for us (lines 6-20). We start by inserting a hidden tag in line six to protect against CSRF exploits. Lastly, we add each label and field of the form.
We are now ready to see the result of all our work. Just type the following:
$ python routes.py
Then go to https://localhost:5000/contact in your favorite web browser.
The contact page containing the form has loaded. Fill in the form fields and click the "Send" button. You’ll see a page that looks like this: