





















































In this article by Jack Stouffer, author of the book Mastering Flask, the more complex and powerful versions will be introduced, and we will turn our disparate view functions in cohesive wholes. We will also discuss the internals of how Flask handles the lifetime of an HTTP request and advanced ways to define Flask views.
(For more resources related to this topic, see here.)
In some cases, a request-specific variable is needed across all view functions and needs to be accessed from the template as well. To achieve this, we can use Flask's decorator function @app.before_request and the object g. The function @app.before_request is executed every time before a new request is made. The Flask object g is a thread-safe store of any data that needs to be kept for each specific request. At the end of the request, the object is destroyed, and a new object is spawned at the start of a new request. For example, this code checks whether the Flask session variable contains an entry for a logged in user; if it exists, it adds the User object to g:
from flask import g, session, abort, render_template
@app.before_request
def before_request():
if 'user_id' in session:
g.user = User.query.get(session['user_id'])
@app.route('/restricted')
def admin():
if g.user is None:
abort(403)
return render_template('admin.html')
Multiple functions can be decorated with @app.before_request, and they all will be executed before the requested view function is executed. There also exists a decorator @app.teardown_request, which is called after the end of every request. Keep in mind that this method of handling user logins is meant as an example and is not secure.
Displaying browser's default error pages to the end user is jarring as the user loses all context of your app, and they must hit the back button to return to your site. To display your own templates when an error is returned with the Flask abort() function, use the errorhandler decorator function:
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
The errorhandler is also useful to translate internal server errors and HTTP 500 code into user friendly error pages. The app.errorhandler() function may take either one or many HTTP status code to define which code it will act on. The returning of a tuple instead of just an HTML string allows you to define the HTTP status code of the Response object. By default, this is set to 200.
In most Flask apps, views are handled by functions. However, when many views share common functionality or there are pieces of your code that could be broken out into separate functions, it would be useful to implement our views as classes to take advantage of inheritance.
For example, if we have views that render a template, we could create a generic view class that keeps our code DRY:
from flask.views import View
class GenericView(View):
def __init__(self, template):
self.template = template
super(GenericView, self).__init__()
def dispatch_request(self):
return render_template(self.template)
app.add_url_rule(
'/', view_func=GenericView.as_view(
'home', template='home.html'
)
)
The first thing to note about this code is the dispatch_request() function in our view class. This is the function in our view that acts as the normal view function and returns an HTML string. The app.add_url_rule() function mimics the app.route() function as it ties a route to a function call. The first argument defines the route of the function, and the view_func parameter defines the function that handles the route. The View.as_view() method is passed to the view_func parameter because it transforms the View class into a view function. The first argument defines the name of the view function, so functions such as url_for() can route to it. The remaining parameters are passed to the __init__ function of the View class.
Like the normal view functions, HTTP methods other than GET must be explicitly allowed for the View class. To allow other methods, a class variable containing the list of methods named methods must be added:
class GenericView(View):
methods = ['GET', 'POST']
…
def dispatch_request(self):
if request.method == 'GET':
return render_template(self.template)
elif request.method == 'POST':
…
Often, when functions handle multiple HTTP methods, the code can become difficult to read due to large sections of code nested within if statements:
@app.route('/user', methods=['GET', 'POST', 'PUT', 'DELETE'])
def users():
if request.method == 'GET':
…
elif request.method == 'POST':
…
elif request.method == 'PUT':
…
elif request.method == 'DELETE':
…
This can be solved with the MethodView class. MethodView allows each method to be handled by a different class method to separate concerns:
from flask.views import MethodView
class UserView(MethodView):
def get(self):
…
def post(self):
…
def put(self):
…
def delete(self):
…
app.add_url_rule(
'/user',
view_func=UserView.as_view('user')
)
In Flask, a blueprint is a method of extending an existing Flask app. They provide a way of combining groups of views with common functionality and allow developers to break their app down into different components. In our architecture, the blueprints will act as our controllers.
Views are registered to a blueprint; a separate template and static folder can be defined for it, and when it has all the desired content on it, it can be registered on the main Flask app to add blueprints' content. A blueprint acts much like a Flask app object, but is not actually a self-contained app. This is how Flask extensions provide views function. To get an idea of what blueprints are, here is a very simple example:
from flask import Blueprint
example = Blueprint(
'example',
__name__,
template_folder='templates/example',
static_folder='static/example',
url_prefix="/example"
)
@example.route('/')
def home():
return render_template('home.html')
The blueprint takes two required parameters—the name of the blueprint and the name of the package—which are used internally in Flask, and passing __name__ to it will suffice.
The other parameters are optional and define where the blueprint will look for files. Because templates_folder was specified, the blueprint will not look in the default template folder, and the route will render templates/example/home.html and not templates/home.html. The url_prefix option automatically adds the provided URI to the start of every route in the blueprint. So, the URL for the home view is actually /example/.
The url_for() function will now have to be told which blueprint the requested route is in:
{{ url_for('example.home') }}
Also, the url_for() function will now have to be told whether the view is being rendered from within the same blueprint:
{{ url_for('.home') }}
The url_for() function will also look for static files in the specified static folder as well.
To add the blueprint to our app:
app.register_blueprint(example)
Let's transform our current app to one that uses blueprints. We will first need to define our blueprint before all of our routes:
blog_blueprint = Blueprint(
'blog',
__name__,
template_folder='templates/blog',
url_prefix="/blog"
)
Now, because the templates folder was defined, we need to move all of our templates into a subfolder of the templates folder named blog. Next, all of our routes need to have the @app.route function changed to @blog_blueprint.route, and any class view assignments now need to be registered to blog_blueprint. Remember that the url_for() function calls in the templates will also have to be changed to have a period prepended to then to indicate that the route is in the same blueprint.
At the end of the file, right before the if __name__ == '__main__': statement, add the following:
app.register_blueprint(blog_blueprint)
Now all of our content is back on the app, which is registered under the blueprint. Because our base app no longer has any views, let's add a redirect on the base URL:
@app.route('/')
def index():
return redirect(url_for('blog.home'))
Why blog and not blog_blueprint? Because blog is the name of the blueprint and the name is what Flask uses internally for routing. blog_blueprint is the name of the variable in the Python file.
We now have our app working inside a blueprint, but what does this give us? Let's say that we wanted to add a photo sharing function to our site, we would be able to group all the view functions into one blueprint with its own templates, static folder, and URL prefix without any fear of disrupting the functionality of the rest of the site.
Further resources on this subject: