# Adding Flask blueprints
# In this video... (TL;DR)
TIP
List of all code changes made in this lecture: https://diff-store.com/diff/section10__07_flask_blueprints (opens new window)
A blueprint in Flask is basically a collection of routes. We can register zero or more blueprints with an application so that our routes can be split across different files and not all in app.py
.
In the future, if we expand the app, adding more blueprints would also simplify route organization.
# Code written in this lecture
# Flask Blueprints
Let's start with the main reason why we're going to use blueprints: to separate route definition from app configuration.
In app.py
, we'll just keep the barebones to create the Flask application. Later on, when we connect to MongoDB, we'll also put things like MongoDB secrets handling here.
The new app.py
will look like this:
from flask import Flask
from routes import pages
def create_app():
app = Flask(__name__)
app.register_blueprint(pages)
return app
Notice that we've added an import: from routes import pages
. The pages
variable is a Flask blueprint. Inside create_app
we're registering the blueprint.
That makes all the endpoints defined inside the blueprint available to the Flask app.
In routes.py
we'll begin by importing Blueprint
from flask (along with our other imports), and creating the Blueprint
object:
import datetime
from collections import defaultdict
from flask import Blueprint, render_template, request, redirect, url_for
pages = Blueprint(
"habits", __name__, template_folder="templates", static_folder="static"
)
habits = ["Test habit"]
completions = defaultdict(list)
There's the pages
variable we imported in app.py
.
The first argument is the name of the blueprint. This is what we will refer to when we need the names of endpoint functions (more on that in a moment!).
The second argument is the import name, usually __name__
.
Then we have a number of optional keyword arguments, such as the template folder or static asset folder. These can be different for each blueprint if we want, but they must be provided if we want the blueprint to be able to access templates or static assets.
For more information, see the official documentation (opens new window).
Every route in routes.py
is then registered against the pages
blueprint:
@pages.route(...) # or @pages.get(...), @pages.post(...)
So here's the finalised code in routes.py
:
import datetime
from collections import defaultdict
from flask import Blueprint, render_template, request, redirect, url_for
pages = Blueprint(
"habits", __name__, template_folder="templates", static_folder="static"
)
habits = ["Test habit"]
completions = defaultdict(list)
@pages.context_processor
def add_calc_date_range():
def date_range(start: datetime.date):
dates = [start + datetime.timedelta(days=diff) for diff in range(-3, 4)]
return dates
return {"date_range": date_range}
@pages.route("/")
def index():
date_str = request.args.get("date")
if date_str:
selected_date = datetime.date.fromisoformat(date_str)
else:
selected_date = datetime.date.today()
return render_template(
"index.html",
habits=habits,
selected_date=selected_date,
completions=completions[selected_date],
title="Habit Tracker - Home",
)
@pages.route("/complete", methods=["POST"])
def complete():
date_string = request.form.get("date")
date = datetime.date.fromisoformat(date_string)
habit = request.form.get("habitName")
completions[date].append(habit)
return redirect(url_for(".index", date=date_string))
@pages.route("/add", methods=["GET", "POST"])
def add_habit():
if request.form:
habits.append(request.form.get("habit"))
return render_template(
"add_habit.html",
title="Habit Tracker - Add Habit",
selected_date=datetime.date.today(),
)
When registering a blueprint we can also set a prefix so that all routes in that blueprint will share the same URL prefix:
@app.register_blueprint(pages, url_prefix="/pages")
Then routes in our blueprint would be preceded by /pages
:
/
becomes/pages/
/add
becomes/pages/add
/complete
becomes/pages/complete
# Using url_for
The url_for
function takes a function name, and returns its route.
Now that we've got our functions inside blueprints, we need to provide both the blueprint name, and the function name to url_for
, separated by a period (.
).
For example:
url_for("habits.index")
This is great because that means we can have functions with the same name in different blueprints, and we'll still be able to differentiate them.
However, when we are in a blueprint's route and we want to get to another route in the same blueprint, we can skip the blueprint name--but not the period!
We're using url_for
in the complete
function:
@pages.route("/complete", methods=["POST"])
def complete():
date_string = request.form.get("date")
date = datetime.date.fromisoformat(date_string)
habit = request.form.get("habitName")
completions[date].append(habit)
return redirect(url_for(".index", date=date_string))
# Using blueprint endpoints in Jinja2
In our Jinja2 code we're also using url_for
. We must remember to change those instances to use the required syntax.
In index.html
:
--- templates/index.html
+++ templates/index.html
@@ -15,7 +15,7 @@
</div>
{% else %}
<div class="habit">
- <form method="POST" class="habit__form" action="{{ url_for('complete') }}">
+ <form method="POST" class="habit__form" action="{{ url_for('habits.complete') }}">
<input type="hidden" id="habitName" name="habitName" value="{{ habit }}" />
<input type="hidden" id="date" name="date" value="{{ selected_date }}" />
<button type="submit" class="habit__button">
And in layout.html
:
--- templates/layout.html
+++ templates/layout.html
@@ -13,9 +13,9 @@
<body>
<header class="header">
- <h1 class="header__logo"><a href="{{ url_for('index') }}" class="header__link">Habits</a></h1>
+ <h1 class="header__logo"><a href="{{ url_for('habits.index') }}" class="header__link">Habits</a></h1>
{% if "add" not in request.endpoint %}
- <a href="{{ url_for('add_habit') }}" class="header__link">+ Add new</a>
+ <a href="{{url_for('habits.add_habit')}}" class="header__link">+ Add new</a>
{% endif %}
</header>
@@ -24,7 +24,7 @@
{% for date in date_range(selected_date) %}
<a
class="dates__link {{ 'dates__link--current' if loop.index0 == 3 else ''}}"
- href="{{ url_for('index', date=date) }}"
+ href="{{ url_for('habits.index', date=date) }}"
>
<time class="date" datetime="{{ date }}">
<span>{{ date.strftime("%a") }}</span>
And with that, we're done adding blueprints to our application!