We can implement DRY (Don't Repeat Yourself) principles in our templates
by abstracting snippets of code that appear over and over into
macros. If we're working on some HTML for our app's navigation, we
might want to give a different class to the "active" link (i.e. the link
to the current page). Without macros we'd end up with a block of
if ... else
statements that check each link to find the active one.
Macros provide a way to modularize that code; they work like functions. Let's look at how we'd mark the active link using a macro.
{# myapp/templates/layout.html #}
{% from "macros.html" import nav_link with context %}
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<title>My application</title>
{% endblock %}
</head>
<body>
<ul class="nav-list">
{{ nav_link('home', 'Home') }}
{{ nav_link('about', 'About') }}
{{ nav_link('contact', 'Get in touch') }}
</ul>
{% block body %}
{% endblock %}
</body>
</html>
What we are doing in this template is calling an undefined macro —
nav_link
— and passing it two parameters: the target endpoint (i.e.
the function name for the target view) and the text we want to show.
Note You may notice that we specified with context
in the import
statement. The Jinja context consists of the arguments passed to
the render_template()
function as well as the Jinja environment
context from our Python code. These variables are made available in
the template that is being rendered.
Some variables are explicitly passed by us, e.g.
render_template("index.html", color="red")
, but there are several
variables and functions that Flask automatically includes in the
context, e.g. request
, g
and session
. When we say
{% from ... import ... with context %}
we are telling Jinja to make
all of these variables available to the macro as well.
Note
- All of the global variables that are passed to the Jinja context by Flask: http://flask.pocoo.org/docs/templating/#standard-context}
- We can define variables and functions that we want to be merged into the Jinja context with context processors: http://flask.pocoo.org/docs/templating/#context-processors
Now it's time to define the nav_link
macro that we used in our
template.
{# myapp/templates/macros.html #}
{% macro nav_link(endpoint, text) %}
{% if request.endpoint.endswith(endpoint) %}
<li class="active"><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% else %}
<li><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% endif %}
{% endmacro %}
Now we've defined the macro in myapp/templates/macros.html. In this
macro we're using Flask's request
object — which is available in the
Jinja context by default — to check whether or not the current request
was routed to the endpoint passed to nav_link
. If it was, than we're
currently on that page, and we can mark it as active.
Note The from x import y statement takes a relative path for x. If our
template was in myapp/templates/user/blog.html we would use
from "../macros.html" import nav_link with context
.