7.4.1. Basic usage
Let's take a look at the code for one of the blueprints from that Facebook example.
# facebook/views/profile.py
from flask import Blueprint, render_template
profile = Blueprint('profile', __name__)
@profile.route('/<user_url_slug>')
def timeline(user_url_slug):
# Do some stuff
return render_template('profile/timeline.html')
@profile.route('/<user_url_slug>/photos')
def photos(user_url_slug):
# Do some stuff
return render_template('profile/photos.html')
@profile.route('/<user_url_slug>/about')
def about(user_url_slug):
# Do some stuff
return render_template('profile/about.html')
To create a blueprint object, we import the Blueprint()
class and
initialize it with the arguments name
and import_name
. Usually
import_name
will just be __name__
, which is a special Python
variable containing the name of the current module.
We're using a functional structure for this Facebook example. If we were using a divisional structure, we'd want to tell Flask that the blueprint has its own template and static directories. This code block shows what that would look like.
profile = Blueprint('profile', __name__,
template_folder='templates',
static_folder='static')
We have now defined our blueprint. It's time to register it on our Flask app.
# facebook/__init__.py
from flask import Flask
from .views.profile import profile
app = Flask(__name__)
app.register_blueprint(profile)
Now the routes defined in facebook/views/profile.py (e.g.
/<user_url_slug>
) are registered on the application and act just as if
you'd defined them with @app.route()
.
7.4.2. Using a dynamic URL prefix
Continuing with the Facebook example, notice how all of the profile
routes start with the <user_url_slug>
portion and pass that value to
the view. We want users to be able to access a profile by going to a URL
like https://facebo-ok.com/john.doe. We can stop repeating ourselves
by defining a dynamic prefix for all of the blueprint's routes.
Blueprints let us define both static and dynamic prefixes. We can tell Flask that all of the routes in a blueprint should be prefixed with /profile for example; that would be a static prefix. In the case of the Facebook example, the prefix is going to change based on which profile the user is viewing. Whatever text they choose is the URL slug of the profile which we should display; this is a dynamic prefix.
We have a choice to make when defining our prefix. We can define the
prefix in one of two places: when we instantiate the Blueprint()
class
or when we register it with app.register_blueprint()
.
# facebook/views/profile.py
from flask import Blueprint, render_template
profile = Blueprint('profile', __name__, url_prefix='/<user_url_slug>')
# [...]
# facebook/__init__.py
from flask import Flask
from .views.profile import profile
app = Flask(__name__)
app.register_blueprint(profile, url_prefix='/<user_url_slug>')
While there aren't any technical limitations to either method, it's nice
to have the prefixes available in the same file as the registrations.
This makes it easier to move things around from the top-level. For this
reason, I recommend setting url_prefix
on registration.
We can use converters to make the prefix dynamic, just like in route()
calls. This includes any custom converters that we've defined. When
using converters, we can pre-process the value given before handing it
off to the view. In this case we'll want to grab the user object based
on the URL slug passed into our profile blueprint. We'll do that by
decorating a function with url_value_preprocessor()
.
# facebook/views/profile.py
from flask import Blueprint, render_template, g
from ..models import User
# The prefix is defined on registration in facebook/__init__.py.
profile = Blueprint('profile', __name__)
@profile.url_value_preprocessor
def get_profile_owner(endpoint, values):
query = User.query.filter_by(url_slug=values.pop('user_url_slug'))
g.profile_owner = query.first_or_404()
@profile.route('/')
def timeline():
return render_template('profile/timeline.html')
@profile.route('/photos')
def photos():
return render_template('profile/photos.html')
@profile.route('/about')
def about():
return render_template('profile/about.html')
We're using the g
object to store the profile owner and g
is
available in the Jinja2 template context. This means that for a
barebones case all we have to do in the view is render the template. The
information we need will be available in the template.
{# facebook/templates/profile/photos.html #}
{% extends "profile/layout.html" %}
{% for photo in g.profile_owner.photos.all() %}
<img src="{{ photo.source_url }}" alt="{{ photo.alt_text }}" />
{% endfor %}
Note
- The Flask documentation has a great tutorial on using prefixes for internationalizing your URLs.
7.4.3. Using a dynamic subdomain
Many SaaS (Software as a Service) applications these days provide users with a subdomain from which to access their software. Harvest, for example, is a time tracking application for consultants that gives you access to your dashboard from yourname.harvestapp.com. Here I'll show you how to get Flask to work with automatically generated subdomains like this.
For this section I'm going to use the example of an application that lets users create their own websites. Imagine that our app has three blueprints for distinct sections: the home page where users sign-up, the user administration panel where the user builds their website and the user's website. Since these three parts are relatively unconnected, we'll organize them in a divisional structure.
sitemaker/ __init__.py home/ __init__.py views.py templates/ home/ static/ home/ dash/ __init__.py views.py templates/ dash/ static/ dash/ site/ __init__.py views.py templates/ site/ static/ site/ models.py
This table explains the different blueprints in this app.
URL | Route | Description |
---|---|---|
sitemaker.com |
sitemaker/home |
Just a vanilla blueprint. Views, templates and static files for index.html, about.html and pricing.html. |
bigdaddy.sitemaker.com |
sitemaker/site |
This blueprint uses a dynamic subdomain and includes the elements of the user's website. We'll go over some of the code used to implement this blueprint below. |
bigdaddy.sitemaker.com/admin |
sitemaker/dash |
This blueprint could use both a dynamic subdomain and a URL prefix by combining the techniques in this section with those from the previous section. |
We can define our dynamic subdomain the same way we defined our URL prefix. Both options (in the blueprint directory or in the top-level init.py) are available, but once again we'll keep the definitions in sitemaker/init.py.
# sitemaker/__init__.py
from flask import Flask
from .site import site
app = Flask(__name__)
app.register_blueprint(site, subdomain='<site_subdomain>')
Since we're using a divisional structure, we'll define the blueprint in sitema-ker/site/init.py.
# sitemaker/site/__init__py
from flask import Blueprint
from ..models import Site
# Note that the capitalized Site and the lowercase site
# are two completely separate variables. Site is a model
# and site is a blueprint.
site = Blueprint('site', __name__)
@site.url_value_preprocessor
def get_site(endpoint, values):
query = Site.query.filter_by(subdomain=values.pop('site_subdomain'))
g.site = query.first_or_404()
# Import the views after site has been defined. The views
# module will needto import 'site' so we need to make
# sure that we import views after site has been defined.
import .views
Now we have the site information from the database that we'll use to display the user's site to the visitor who requests their subdomain.
To get Flask to work with subdomains, we'll need to specify the
SERVER_NAME
configuration variable.
# config.py
SERVER_NAME = 'sitemaker.com'
Note A few minutes ago, as I was drafting this section, somebody in IRC
said that their subdomains were working fine in development, but not
in production. I asked if they had the SERVER_NAME
configured, and
it turned out that they had it in development but not production.
Setting it in production solved their problem.
See the conversation between myself (imrobert in the log) and aplavin: http://dev.pocoo.org/irclogs/%23pocoo.2013-07-30.log
It was enough of a coincidence that I felt it warranted inclusion in the section.
Note You can set both a subdomain and url_prefix. Think about how we would configure the blueprint in sitemaker/dash with the URL structure from the table above.