Create reusable Jinja templates
Templates tend to have common sections that are equally used across multiple instances. For example, the header and footer sections on all templates rarely changes, whether a project has five or one hundred templates. Other template sections like menus and advertisements, also fall into this category of content that's constant across multiple templates. All of this can lead to repetition over multiple templates, which can be avoided by creating reusable templates.
With reusable Jinja templates you can define common sections on separate templates and reuse them inside other templates. This process makes it easy to create and manage a project's templates because a single template update takes effect on all templates.
Reusable Jinja templates also allow you to define page blocks to override content on a page by page basis. This process makes a project's templates more modular because you define top level blocks to establish the overall layout and define content on a page by page basis.
Lets take the first step toward
building reusable Jinja templates by exploring Jinja's built-in
{% block %}
tag. Listing 4-5 illustrates the first
lines of a template called base.html
with several
{% block %}
tags.
Listing 4-5. Jinja template with {% block %} tags
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>{% block title%}Default title{% endblock title %}</title> <meta name="description" content="{% block metadescription%}{% endblock metadescription %}"> <meta name="keywords" content="{% block metakeywords%}{% endblock metakeywords %}">
Notice the syntax {% block
<name>%}{% endblock <name>%}
in listing 4-5.
Each {% block %}
tag has a reference name. The
reference name is used by other Jinja templates to override the
content for each block. For example, the {% block title
%}
tag within the HTML <title>
tags
defines a web page title. If another template reuses the template
in listing 4-5, it can define its own web page title by overriding
the title block. If a block is not overridden on a template, the
block receives the default content within the block. For the title
block the default content is Default title
, for the
metadescription
and metakeywords
blocks
the default content is an empty string.
The same mechanism illustrated in
listing 4-5 can be used to define any number of blocks (e.g.
content, menu, header, footer). It's worth mentioning the
<name>
argument of {% endblock
<name> %}
is optional and it's valid to just use
{% endblock %}
to close a block statement, however,
the former technique makes it clearer where a block statement ends
which is specially helpful when a template has multiple blocks.
Although it's possible to call
the template in listing 4-5 directly by a Django view method or url
request, the purpose of this kind of template is to use it as a
base template for other templates. To reuse a Jinja template you
use the Jinja built-in {% extends %}
tag.
The {% extends %}
tag uses the syntax {% extends <name> %}
to
reuse the layout of another template. This means that in order to
reuse the layout in listing 4-5 defined in a file
base.html
, you use the syntax {% extends
"base.html" %}
, as illustrated in listing 4-6.
Listing 4-6. Jinja template with {% extends %} and {% block %} tag
{% if user %}{% extends "base.html" %}{% else %}{% extends "signup_base.html" %}{% endif %} {% block title %}Coffeehouse home page{% endblock %}
Look how listing 4-6 uses the
{% extends "base.html" %}
wrapped around the {%
if user %}
statement. If the user
variable is
defined, Jinja extends the base.html
template,
otherwise it extends the signup_base.html
template.
This conditional syntax is not possible in Django templates.
In addition, notice how listing
4-6 defines the {% block title %}
tag with the content
Coffeehouse home page
. The block in listing 4-6
overrides the title
block from the
base.html
template. So where are the HTML
<title>
tags in listing 4-6 ? There aren't any
and you don't need them. Jinja automatically reuses the layout from
either the base.html
or signup_base.html
templates and substitutes the blocks content where necessary.
Jinja templates that reuse other templates tend to have limited layout elements (e.g. HTML tags) and more Jinja block statements to override content. This is beneficial because as I outlined previously, it lets you establish the overall layout once and define content on a page by page basis.
The re-usability of Jinja
templates can occur multiple times. For example, you can have
templates A, B and C, where B requires to reuse A, but C requires
to reuse parts of B. The only difference is template C needs to use
the {% extends "B" %}
tag instead of the {%
extends "A"%}
tag. But since template B reuses A, template C
also has access to the same elements in template A.
When reusing Jinja templates,
it's also possible to access the block content from a parent
template. Jinja exposes the block content from a parent template
through the super()
method. Listing 4-7 illustrates
three templates that show this mechanism for a block containing web
page paths or 'breadcrumbs'.
Listing 4-7. Jinja templates use of super() with three reusable templates
# base.html template <p>{% block breadcrumb %}Home{% endblock %}</p> # index.html template {% extends "base.html" %} {% block breadcrumb %}Main{% endblock %} # detail.html template {% extends "index.html" %} {% block breadcrumb %} {{super()}} : Detail {% endblock %}
The base.html
template in listing 4-7 defines the breadcrumb
block
with a default value of Home
. Next, the
index.html
template reuses the base.html
template and overrides the breadcrumb block with a value of
Main
. Finally, the detail.html
template
reuses the index.html
template and overrides the
breadcrumb
block value. However, notice the
{{super()}}
statement in the final block override.
Since {{super()}}
is inside the
breadcrumb
block, {{super()}}
tells Jinja
to get the content from the parent template block.
Another re-usability
functionality supported by Jinja templates is the inclusion of a
Jinja template inside another Jinja template. Jinja supports this
functionality through the {% include %}
tag.
By default, the {% include
%}
tag expects the name of a template. For example, {%
include "footer.html" %}
inserts the contents of the
footer.html
template in the position of the template
where it's declared. The {% include %}
tag also makes
the underlying template aware of variables. This meas the
footer.html
template can have variable definitions
(e.g.{{year}}
) and if the calling template has these
variable definitions, the {% include %}
tag
automatically substitutes these values.
In addition, it's possible to
provide a list of templates as a fall-back mechanism. For example,
{% include ['special_sidebar.html', 'sidebar.html'] ignore
missing %}
tells Jinja to first attempt to locate the
special_sidebar.html
template and if it isn't found to
attempt to locate the sidebar.html
template, if
neither template is found the last argument ignore
missing
tells Jinja to render nothing. Note the ignore
missing
argument can also be used in individual statements
(e.g. {% include "footer.html" ignore missing %}
, as
well as lists). In addition, if the ignore missing
statement is not used and Jinja can't find a matching template
declared in {% include %}
Jinja raises an
exception.
The {% macro %}
tag
allows the definition of reusable content snippets across
templates. For example, if you need to incorporate elaborate markup
to display elements that have common characteristics, you can
define the elaborate markup once in a {% macro %}
statement and then re-use this {% macro %}
to output
the markup customized to each element instance.
Macros are helpful because if you
decide to change the markup, you only need to change it in a single
location and the changes propagate to other locations. Listing 4-8
illustrates the definition of a {% macro %}
statement
and its usage in templates.
Listing 4-8. Jinja {% macro %} definition and use of {% import %}
# base.html template {% macro coffeestore(name, id='', address='', city='San Diego', state='CA', email=None) -%} <a id="{{id}}"></a> <h4>{{name}}</h4> <p>{{address}} {{city}},{{state}}</p> {% if email %}<p><a href='mailto:{{email}}'>{{email}}</a></p>{% endif %} {%- endmacro %} # index.html template calls inherited macro directly {% extends "base.html" %} {{coffeestore('Downtown',1,'Horton Plaza','San Diego','CA','downtown@coffeehouse.com')}} # detail.html template with no extends, uses {% import %} to access macro in base.html {% import 'base.html' as base %} {{base.coffeestore('Downtown',1,'Horton Plaza','San Diego','CA','downtown@coffeehouse.com')}} # otherdetail.html template with no extends, uses {% from import %} to access macro in base.html {% from 'base.html' import coffeestore as mycoffeestoremacro %} {{mycoffeestoremacro('Downtown',1,'Horton Plaza','San Diego','CA','downtown@coffeehouse.com')}}
The first thing that's done in
listing 4-8 is the {% macro %}
definition declared in
the base.html
template. Notice that after the {%
macro
snippet, there's what appears to be a regular method
named coffeestore
, which corresponds to the name of
the macro with six input arguments, five of which have default
values. Next, inside the {% macro %}
and {%
endmacro %}
statements you can see some elaborate HTML
markup that makes use of the standard {{ }}
syntax to
output whatever variable values are passed on a given instance of
the macro.
Since the {% macro
%}
in listing 4-8 is defined inside the
base.html
template, any other template that uses the
base.html
template can access the macro and call the
macro with an instance (e.g.
{{coffeestore('Downtown',1,'Horton Plaza','San
Diego','CA','downtown@coffeehouse.com')}}
-- hard-coded
values for simplicity) for Jinja to render the HTML markup
customized with the instance values.
If you want to access a {%
macro %}
in other templates you have three alternatives
which are also presented in listing 4-8. If a template extends
another template (e.g. {% extends "base.html" %}
) then
by default it will also gain access to the parent's template
{% macro %}
definitions. It's also possible to access
another template's {% macro %}
definitions with the
{% import %}
statement. For example, the statement
{% import 'base.html' as base %}
imports the
base.html
definitions into another template with the
base namespace, in which case to invoke the {% macro
%}
called coffeestore
you would use the
{{base.coffeestore(...}}
syntax. Finally, it's also
possible to selectively import a {% macro %}
definition with the {% from import %}
statement. For
example, the statement {% from 'base.html' import coffeestore
as mycoffeestoremacro %}
imports the
coffeestore
definition from the base.html
template and places it under the mycoffeestoremacro
name, in which case you would use the
{{mycoffeehousemacro(...}}
syntax to invoke the
{% macro %}
.
The {% call %}
tag
is another option that used in conjunction with the {% macro
%}
tags favors the re-usability of macros themselves. The
first usage scenario of the {% call %}
tag is to
invoke a {% macro %}
that requires a placeholder for
content that's defined until the invocation of the macro. Listing
4-9 illustrates this basic scenario of the {% call %}
tag along with a {% macro %}
.
Listing 4-9. Jinja {% call %} and {% macro %} use
# macro definition {% macro contentlist(adcolumn_width=3,contentcolumn_width=6) -%} <div class="col-md-{{adcolumn_width}}"> Sidebar ads </div> <div class="col-md-{{contentcolumn_width}}"> {{ caller() }} </div> <div class="col-md-{{adcolumn_width}}"> Sidebar ads </div> {%- endmacro %} # macro call/invocation {% call contentlist() %} <ul> <li>This is my list</li> </ul> {% endcall %} # rendering <div class="col-md-3"> Sidebar ads </div> <div class="col-md-6"> <ul> <li>This is my list</li> </ul> </div> <div class="col-md-3"> Sidebar ads </div>
In listing 4-9 we first define a
{% macro %}
with a similar structure to that of
listing 3-8, however, notice inside the {% macro %}
the {{ caller() }}
statement. The
caller()
method inside {% macro %}
serves
as placeholder to be substituted by the calling entity.
Next, in listing 4-9 you can see
the {% call %}
statement is declared with the macro
call -- in this case contentlist()
-- and the body of
the {% call %}
statement contains an HTML list. When
Jinja executes the {% call %}
statement, the {%
call %}
contents are placed in the location of the {%
macro %} {{caller()}}
declaration.
A more advanced scenario of the
{% call %}
tag with a {% macro %}
is for
the caller()
statement to use references, a process
that's more natural to data that's recursive in nature (i.e. a
macro over a macro). Listing 4-10 illustrates this recursive
scenario of the {% call %}
tag along with a {%
macro %}
.
Listing 4-10 Jinja {% call %} and {% macro %} recursive calls
# macro definition {% macro contentlist(itemlist,adcolumn_width=3,contentcolumn_width=6) -%} <div class="col-md-{{adcolumn_width}}"> Sidebar ads </div> <div class="col-md-{{contentcolumn_width}}"> {% for item in itemlist %} {{ caller(item) }} {% endfor %} </div> <div class="col-md-{{adcolumn_width}}"> Sidebar ads </div> {%- endmacro %} # variable definition {% set coffeestores=[{'id':0,'name':'Corporate','address':'624 Broadway','city':'San Diego','state':'CA', 'email':'corporate@coffeehouse.com'},{'id':1,'name':'Downtown','address':'Horton Plaza','city':'San Diego', 'state':'CA','email':'downtown@coffeehouse.com'},{'id':2,'name':'Uptown','address':'1240 University Ave', 'city':'San Diego','state':'CA','email':'uptown@coffeehouse.com'},{'id':3,'name':'Midtown', 'address':'784 W Washington St','city':'San Diego','state':'CA','email':'midtown@coffeehouse.com'}] %} # macro call/invocation {% call(item) contentlist(coffeestores) %} <a id="{{item.id}}"></a> <h4>{{item.name}}</h4> <p>{{item.address}} {{item.city}},{{item.state}}</p> {% if item.email %}<p><a href='mailto:{{item.email}}'>{{item.email}}</a></p>{% endif %} {% endcall %} # rendering <div class="col-md-3"> Sidebar ads </div> <div class="col-md-6"> <a id="0"></a> <h4>Corporate</h4> <p>624 Broadway San Diego,CA</p> <p><a href="mailto:corporate@coffeehouse.com">corporate@coffeehouse.com</a></p> <a id="1"></a> <h4>Downtown</h4> <p>Horton Plaza San Diego,CA</p> <p><a href="mailto:downtown@coffeehouse.com">downtown@coffeehouse.com</a></p> <a id="2"></a> <h4>Uptown</h4> <p>1240 University Ave San Diego,CA</p> <p><a href="mailto:uptown@coffeehouse.com">uptown@coffeehouse.com</a></p> <a id="3"></a> <h4>Midtown</h4> <p>784 W Washington St San Diego,CA</p> <p><a href="mailto:midtown@coffeehouse.com">midtown@coffeehouse.com</a></p> </div> <div class="col-md-3"> Sidebar ads </div>
As you can see in listing 4-10,
the {% macro %}
definition now has an argument called
itemlist
on which it creates an iteration and for each
item it invokes {{caller(item)}}
. Also notice in
listing 4-10 the {% call %}
statement is now {%
call(item) contentlist(coffeestores) %}
, where item
represents the callback item sent from the macro and
contentlist(coffeestores)
is the actual call to the
macro named contentlist
along with its input
coffeestores
that's a list of dictionaries. When Jinja
executes the {% call %}
statement, the {% call
%}
contents are run recursively over each item resulting in
the output presented at the bottom of listing 4-10.
Tip The built-in {% set %}
statement
-- described in the Jinja built-in filters section -- provides
simpler re-use functionality for static content blocks compared to
{% macro %} statements that use variables. (e.g. {% set
advertisement %}<div class='banner'><img
src=...></div>{% endset %} creates the advertisement
variable that can output the contents between {% set %} and {%
endset %} anywhere in a template).