Jinja2 Template Introduction¶
When you install pyramid_jinja2
into your virtualenv, it brings along a
Python-based templating engine called Jinja2
.
In this walkthrough, you’ll see some basics about how templates work, and get to know what sorts of options they provide you for creating HTML from a Python process.
"I enjoy writing HTML in Python"
-- nobody, ever
A good framework will provide some way of generating HTML with a templating system. As a result, there are nearly as many templating systems as there are frameworks. Each comes with advantages and disadvantages.
Pyramid provides add-ons to use a number of different templating systems, including chameleon, mako, and Jinja2. Of these, Jinja2 is arguably the easiest to learn, and has the advantage of strongly resembling the Django templating language. We’ll learn it first.
Jinja2 Template Basics¶
Let’s start with the absolute basics. Begin by activating your learning-journal virtualenv:
$ workon learning-journal
(learning-journal)$
Next, install the Jinja2
template language package using pip
:
(learning-journal)$ pip install jinja2
Collecting jinja2
...
Successfully installed jinja2-2.7.3 markupsafe-0.23
(learning-journal)$
Then, fire up a Python interpreter, with your learning-journal virtualenv active:
(learning-journal)$ python
>>> from jinja2 import Template
For the next steps, you’ll play around a bit with the basic concepts of Jinja2 templating. Feel free to explore a bit, try things out, go beyond what’s suggested and learn from your mistakes.
A template starts life as a simple string:
>>> t1 = Template("Hello {{ name }}, how are you?")
>>>
But it has a bit more to it than that. You can call the render
method of a
template object, providing some context:
>>> t1.render(name="Freddy")
u'Hello Freddy, how are you?'
>>> t1.render({'name': "Roberto"})
u'Hello Roberto, how are you?'
>>>
Context can either be keyword arguments, or a dictionary.
Simple Python values passed in as context will be resolved in the template by
the key they are assigned to in the context. These keys are arbitrary.
Placeholders like {{ name }}
in this example will be replaced by the
corresponding values from the context.
Item and Attribute Access in Templates¶
Dictionaries passed in as part of the context can be addressed with either subscript or dotted notation:
>>> person = {'first_name': 'Frank',
... 'last_name': 'Herbert'}
>>> t2 = Template("{{ person.last_name }}, {{ person['first_name'] }}")
>>> t2.render(person=person)
u'Herbert, Frank'
- Jinja2 will try the correct way first (attr for dotted, item for subscript).
- If nothing is found, it will try the opposite.
- If nothing is found, it will return an undefined object.
The exact same is true of objects passed in as part of context:
>>> t3 = Template("{{ obj.x }} + {{ obj['y'] }} = Fun!")
>>> class Game(object):
... x = 'babies'
... y = 'bubbles'
...
>>> bathtime = Game()
>>> t3.render(obj=bathtime)
u'babies + bubbles = Fun!'
This means your templates can be a bit agnostic as to the nature of the things passed in via context
Filtering values in Templates¶
You can apply filters to the data passed in context with the pipe (‘|’) operator:
t4 = Template("shouted: {{ phrase|upper }}")
>>> t4.render(phrase="this is very important")
u'shouted: THIS IS VERY IMPORTANT'
You can also chain filters together:
t5 = Template("confusing: {{ phrase|upper|reverse }}")
>>> t5.render(phrase="howdy doody")
u'confusing: YDOOD YDWOH'
There are a large number of filters available to use in jinja2
.
Control Flow¶
Jinja2
provides all the expected control structures of a featureful
programming language:
tmpl = """
... {% for item in list %}{{ item }}, {% endfor %}
... """
>>> t6 = Template(tmpl)
>>> t6.render(list=[1,2,3,4,5,6])
u'\n1, 2, 3, 4, 5, 6, '
Any control structure introduced in a template must be paired with an explicit closing tag ({% for %}...{% endfor %})
You can learn more about control structures by reading the documentation.
Conditionals in Templates¶
There are a number of specialized tests available for use with the
if...elif...else
control structure:
>>> tmpl = """
... {% if phrase is upper %}
... {{ phrase|lower }}
... {% elif phrase is lower %}
... {{ phrase|upper }}
... {% else %}{{ phrase }}{% endif %}"""
>>> t7 = Template(tmpl)
>>> t7.render(phrase="FOO")
u'\n\n foo\n'
>>> t7.render(phrase="bar")
u'\n\n BAR\n'
>>> t7.render(phrase="This should print as-is")
u'\nThis should print as-is'
Here’s a list of all the built-in tests in the jinja2
template language.
Python Expressions in Templates¶
You can also use basic Python-like expressions in jinja2
templates. There
are some syntactic differences, though.
tmpl = """
... {% set sum = 0 %}
... {% for val in values %}
... {{ val }}: {{ sum + val }}
... {% set sum = sum + val %}
... {% endfor %}
... """
>>> t8 = Template(tmpl)
>>> t8.render(values=range(1,11))
u'\n\n\n1: 1\n \n\n2: 3\n \n\n3: 6\n \n\n4: 10\n
\n\n5: 15\n \n\n6: 21\n \n\n7: 28\n \n\n8: 36\n
\n\n9: 45\n \n\n10: 55\n \n'
Learn all about expressions, including assignments in the documentation.
Jinja2 Templates in Frameworks¶
The Jinja2 template engine has a concept it calls an Environment. The environment for the template engine is used to:
- Figure out where to look for templates
- Set configuration for the templating system
- Add some commonly used functionality to the template context
In Pyramid, this environment is set up automatically when you include the pyramid_jinja2 configuration. By default, templates will be searched for relative to the file in which they are called. Paths you use to reference templates will begin there unless you use another referencing system.
Once configured, you can use any file ending in .jinja2
as a Pyramid
renderer. Needless to say, the file extension used is also configurable.
from pyramid.config import view_config
@view_config(renderer="templates/hello_world.jinja2")
In this case, Pyramid would expect to find a file called hello_world.jinja2
in a directory called templates
adjacent to the file where this code
appeared.
Let’s look at what a template file like that might look like:
{% extends "layout.jinja2" %}
{% block body %}
<h2>Hello World!</h2>
{% endblock %}
That’s not much to look at. Where’s the rest of the HTML that makes up a page?
Template Inheritance¶
Jinja2
templates allow for inheritance. This means that you can create
shared structure in base templates, and then override or fill in named parts of
that structure in sub-templates.
In the above case, the hello_world.jinja2
sub-template extends the
layout.jinja2
template. What does that file look like?
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>A simple page</h1>
<div class="content">
{% block body %}{% endblock %}
</div>
</body>
</html>
You can see here that the body
block is defined in layout.jinja2
and then
that block is filled by the templating in hello_world.jinja2
.
Inheritance can work the other way, as well. In addition to filling blocks in a
larger structure, you can pull in smaller blocks using the include
template
tag. For example, all the pages on your site might include a common footer
which is defined in footer.jinja2
:
<div id="footer">
I am the footer, seen on all pages.
</div>
Then, we can include this structure in our layout.jinja2
file:
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>A simple page.</h1>
<div class="content">
{% block body %}{% endblock %}
</div>
{% include "footer.jinja2" %}
</body>
</html>
Finally, you can also import template macros from templates where you define them. This can be a convenient way to create libraries of shareable template structures for repetetive elements like form inputs:
{% macro input(name, value='', type='text') -%}
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
{%- endmacro %}
{%- macro textarea(name, value='', rows=10, cols=40) -%}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
}}">{{ value|e }}</textarea>
{%- endmacro %}
Once such a library is established, say in a file called forms.jinja2
, the
macros it contains can be used in other templates:
{% import 'forms.jinja2' as forms %}
<dl>
<dt>Username</dt>
<dd>{{ forms.input('username') }}</dd>
<dt>Password</dt>
<dd>{{ forms.input('password', type='password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>
There’s more to learn about inheritance and importing than we can cover here, so read up.
Common Pyramid Context¶
Keyword arguments you pass to render_template
become the context passed
to the template for rendering.
Pyramid will add values to the context for jinja2 templates, including the
request
object. Within pyramid, the request object is a single location
where you can access other important information like:
- settings:
request.registry.settings
contains all settings for your app. - session: if sessions are configured,
request.session
will hold session data. - route_url: you can easily reverse urls from within your templates with
request.route_url
.
and much much more. The Pyramid request
supports an entire ecosystem of
properties and methods that can come in useful.
Much, Much More¶
Make sure that you bookmark the Jinja2 documentation for later use.