Update 5/11/2016: Trying to build authentication in Flask? Check out our new article: Flask Auth in One Line of Code!
Flask is an awesome web framework for Python. It’s minimal, it’s simple, and best of all: easy to learn.
Today I’m going to walk you through a tutorial for building your very first Flask web app! Just like the official Flask tutorial, you’ll be building your very own micro blog: Flaskr. Unlike the official Flask tutorial – you’ll be speeding things up by using Stormpath to create and manage user accounts and data. This will dramatically speed up the development process!
Let’s get right to it.
NOTE: This tutorial is meant for new Flask developers, to help give them an understanding of how to build a simple website with Flask and Stormpath. This is a modified version of the official Flask tutorial.
Python Flaskr App Tutorial Goals
- Let a user sign in and out of the micro blog using a previously generated user account (which will be stored by Stormpath).
- Let a logged in user add new entries to a page consisting of a text-only title, and some HTML body text. This HTML won’t be sanitized, as this user is trusted.
- Display all blog entries on the main page of the site, in reverse chronological order (newest on top).
The final site should look like this:
Prerequisites
Before continuing, we’ll need to install several Python packages to make things work! We’ll do this via pip – the Python package manager.
1 2 |
$ pip install flask flask-stormpath |
The above command will install two packages: Flask, and Flask-Stormpath, which will both be used through this tutorial.
Next, you need to create a Stormpath account. You can do so by signing up on the Stormpath website.
Once you’ve created a Stormpath account, and logged into it, you’ll also need to create an API key. You can do this by clicking the Create API Key
button on the dashboard page.
When you create an API key, you will be prompted to download a file named apiKey.properties
. We’ll use this later.
NOTE: Do not check the apiKey.properties
file into your version control system (if you’re using one)! This file holds your Stormpath credentials, so it should be kept safe.
Next, you’ll want to create a new Stormpath application by visiting the Applications page, then clicking Register Application
. Create a new app named flaskr
, with the default options selected.
Lastly, visit the Accounts page and create a new user account in: flaskr Directory
. Any account created here can be used to log into the micro blog you’re about to build.
Directory Structure
The first thing you need to do is create a directory structure to hold your application code. You’ll need to generate several directories, and move your apiKey.properites
file into the new project directory:
1 2 3 4 5 6 7 8 9 10 |
$ mkdir -p flaskr/{static,templates} $ mv ~/path/to/apiKey*.properties flaskr/apiKey.properties $ tree flaskr flaskr ├── apiKey.properties ├── static └── templates 2 directories, 1 file |
The flaskr
folder will be the root of your application. The static
folder will hold all of your static assets (css, javascript, and images). The templates
folder will hold your Jinja templates, which render HTML.
App Setup
Now that you’ve got the directory structure figured out let’s configure the Flask app!
Firstly, create a new file in your flaskr
folder named flaskr.py
. This is where you’ll put your application code.
Here’s what you’ll start with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from datetime import datetime from flask import Flask, abort, flash, redirect, render_template, request, url_for from flask.ext.stormpath import StormpathError, StormpathManager, User, login_required, login_user, logout_user, user app = Flask(__name__) app.config['DEBUG'] = True app.config['SECRET_KEY'] = 'some_really_long_random_string_here' app.config['STORMPATH_API_KEY_FILE'] = 'apiKey.properties' app.config['STORMPATH_APPLICATION'] = 'flaskr' # These settings disable the built-in login / registration / logout functionality # Stormpath provides so we can make our own custom ones later! app.config['STORMPATH_ENABLE_LOGIN'] = False app.config['STORMPATH_ENABLE_REGISTRATION'] = False app.config['STORMPATH_ENABLE_LOGOUT'] = False stormpath_manager = StormpathManager(app) if __name__ == '__main__': app.run() |
There are a few things to note here:
- You’re importing several libraries at the top of the
flaskr.py
file — these will be used down below, through the rest of the tutorial. - You’re creating an
app
object — this is the core of every Flask project. - You’re adding several configuration variables to
app.config
.app.config
is a Python dictionary that allows you to store whatever custom settings you’d like. Here, we’re setting several important variables for usage later on:- The
DEBUG
variable can be set toTrue
orFalse
. This controls Flask’s built in error reporting behavior (it makes Flask display verbose error messages while in development mode). - The
SECRET_KEY
variable is used internally to help keep client-side sessions secure. Make sure this is a long, randomly generated string when deploying a real Flask app. - The
STORMPATH_API_KEY_FILE
variable should point to the location of yourapiKey.properties
file. For more advanced information, see: http://flask-stormpath.readthedocs.io/en/latest/setup.html - The
STORMPATH_APPLICATION
variable should be the name of your Stormpath application, which you created previously. - You’re creating a
stormpath_manager
object. This controls the Stormpath library and allows you to interact easily with users and user data later on. - You’re calling
app.run()
at the bottom. This tells Flask to run your site in development mode for testing.
- The
If you now run the following, you’ll see your Flask app start running on port 5000:
1 2 3 4 |
$ python flaskr.py * Running on http://127.0.0.1:5000/ * Restarting with reloader |
If you visit http://127.0.0.1:5000, however, you’ll get a 404 not found message. This is because we haven’t yet defined any views or URL routes!
Views
Now that you’ve got the setup part finished let’s define the views. This code should go inside the flaskr.py
file, above the:
1 2 3 |
if __name__ == '__main__': app.run() |
Bit.
Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
@app.route('/') def show_posts(): posts = [] for account in stormpath_manager.application.accounts: if account.custom_data.get('posts'): posts.extend(account.custom_data['posts']) posts = sorted(posts, key=lambda k: k['date'], reverse=True) return render_template('show_posts.html', posts=posts) @app.route('/add', methods=['POST']) @login_required def add_post(): if not user.custom_data.get('posts'): user.custom_data['posts'] = [] user.custom_data['posts'].append({ 'date': datetime.utcnow().isoformat(), 'title': request.form['title'], 'text': request.form['text'], }) user.save() flash('New post successfully added.') return redirect(url_for('show_posts')) @app.route('/login', methods=['GET', 'POST']) def login(): error = None if request.method == 'POST': try: _user = User.from_login( request.form['email'], request.form['password'], ) login_user(_user, remember=True) flash('You were logged in.') return redirect(url_for('show_posts')) except StormpathError as err: error = err.message return render_template('login.html', error=error) @app.route('/logout') def logout(): logout_user() flash('You were logged out.') return redirect(url_for('show_posts')) |
Let’s discuss the code above.
You’ll notice the first function defined is show_posts
. This function is what displays blog posts on the front page of the site. As you might have guessed, the decorator, @app.route('/')
, is what tells Flask how to run this function.
Each time a user requests the URL /
, Flask will run the show_posts
function and return the output to the user.
The show_posts
function works simply:
- It iterates over all user accounts, looking for posts. Each post is a simple Python dictionary of the form:
1 2 3 4 5 6 |
{ 'date': '2014-04-01T22:50:49.762475', 'text': 'Blog content.', 'title': 'Post title' } |
- If a post is found, it is added to the
posts
array. - It sorts the
posts
array by date so that newest posts are in front. - It renders an HTML template named
show_posts.html
, passing in theposts
array as input.
The add_posts
view allows logged in users to add new posts to the site. This view introduces several new things:
- The
@app.route('/add', methods=['POST'])
decorator tells Flask to only allow HTTP POST requests on the given URL. By default, Flask only allows GET requests. - The
@login_required
decorator ensures users are logged in before allowing them access to this view. If a user isn’t logged in and tries POST’ing to the view, they’ll get an HTTP 401 UNAUTHORIZED response. - Any view decorated with the
@login_required
decorator can access theuser
variable. This is an object that holds the user’s account details.
The way it works is simple:
- It checks to see if this user has any posts stored in their account yet. This is done by checking to see if
user.custom_data.get('posts')
is notFalse
.user.custom_data
is a Python dictionary that allows you to store any data you’d like into this user’s account. - It grabs the title and text fields from the POST request and creates a new
post
object in the user’sposts
array. - It saves this new post to Stormpath on this user’s account.
- It flashes a message to be displayed to the user later on.
- Lastly, it redirects the user to the
show_posts
view so the newly added post can be displayed. - The
login
andlogout
views are especially simple.
The login
view simply grabs an email address and password from the user’s POST request, then attempts to log the user in by grabbing the user object from Stormpath, and creating a local session.
The logout
view just destroys the user’s session.
Templates
The next thing that needs to be added is the template code. Flask uses the Jinja template language, which makes writing HTML templates very easy.
Let’s start by defining a layout template, templates/layout.html
. This base template will be the parent of all the other templates we write. This strategy is often useful, as it allows you to define a good amount of boilerplate template code in a single place.
Add the following code to your layout.html
template file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<!doctype html> <head> <title>Flaskr</title> <link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"> </head> <body> <div class=page> <h1>Flaskr</h1> <div class=metanav> {% if user.email %} <a href="{{ url_for('logout') }}">log out</a> {% else %} <a href="{{ url_for('login') }}">log in</a> {% endif %} </div> {% for message in get_flashed_messages() %} <div class=flash>{{ message }}</div> {% endfor %} {% block body %}{% endblock %} </div> </body> |
Next is the templates/show_posts.html
file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{% extends "layout.html" %} {% block body %} {% if user.email %} <form action="{{ url_for('add_post') }}" method=post class=add-entry> <dl> <dt>Title: <dd><input type=text size=30 name=title> <dt>Text: <dd><textarea name=text rows=5 cols=40></textarea> <dd><input type=submit value=Share> </dl> </form> {% endif %} <ul class=entries> {% for post in posts %} <li><h2>{{ post['title'] }}</h2>{{ post['text']|safe }} {% else %} <li><em>Unbelievable. No posts here so far!</em> {% endfor %} </ul> {% endblock %} |
And lastly, here’s the templates/login.html
template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{% extends "layout.html" %} {% block body %} <h2>Login</h2> {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %} <form action="{{ url_for('login') }}" method=post> <dl> <dt>Email: <dd><input type=text name=email> <dt>Password: <dd><input type=password name=password> <dd><input type=submit value=Login> </dl> </form> {% endblock %} |
The first thing to note is that the layout.html
template defines a body
block. This can be replaced by a block of the same name in any child template. The layout.html
template displays a login or logout template, and also displays any flashed messages.
Because you’re using Flask-Stormpath, all templates have access to a magical user
variable. If a user is logged in, the user’s details will be available (hence the {% if user.email %}
checks).
The show_posts.html
template iterates over the posts array that was passed into the render_template
call in the show_posts
view. Jinja allows you to loop over any iterable with the for
statement.
It’s also important to note that in order to output variable contents, you need to surround the variable by squiggly braces:
1 2 |
{{ variable }} |
Since we’ve decided to allow users to input arbitrary HTML in their blog posts, we’re outputting the body of the post using the safe
template filter:
1 2 |
{{ post['text']|safe }} |
By default, Jinja will automatically escape any special characters, so we need to use the safe
filter to actually display any user-inputted HTML / Javascript.
Adding Style
The last thing to do is create a static/style.css
file with the following contents:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
body { font-family: sans-serif; background: #eee; } a, h1, h2 { color: #377ba8; } h1, h2 { font-family: 'Georgia', serif; margin: 0; } h1 { border-bottom: 2px solid #eee; } h2 { font-size: 1.2em; } .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; padding: 0.8em; background: white; } .entries { list-style: none; margin: 0; padding: 0; } .entries li { margin: 0.8em 1.2em; } .entries li h2 { margin-left: -1em; } .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } .add-entry dl { font-weight: bold; } .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; margin-bottom: 1em; background: #fafafa; } .flash { background: #cee5F5; padding: 0.5em; border: 1px solid #aacbe2; } .error { background: #f0d6d6; padding: 0.5em; } |
This file is loaded by the layout.html
template, and provides some decent styling.
Testing it Out
Now that we’ve gone over the application code let’s take a look at the final product!
To run your shiny new site, you’ll want to first start up your Flask web server again by running:
1 2 3 4 |
$ python flaskr.py * Running on http://127.0.0.1:5000/ * Restarting with reloader |
Then visit http://127.0.0.1:5000 in your browser. You should now see the flaskr site running, and be able to log in using a Stormpath account, create posts, etc.!
Below is a video of the site running — check it out 🙂
Any questions? Feel free to shoot us an email! We’re happy to help: [email protected]