Monthly Archives: December 2013

Jinja2 templates and Bottle

Although [Bottle’s][bottle] built-in mini-template language is remarkably useful, I nearly always prefer to use [Jinja2 templates][jinja2] because the syntax is very close to [Django’s template syntax][django] (which I am more familiar with) and because the Bottle template syntax for filling in blocks from a parent template is a bit limiting (but that’s kind of the point).

Bottle provides a nice jinja2_view decorator that makes it easy to use Jinja2 templates, but it isn’t that obvious how to configure the template environment for auto-escaping and default context, etc.

(The rest of this relates to Bottle version 0.11 and Jinja2 version 2.7.)

Template paths
————–

Bottle’s view decorator takes an optional `template_lookup` keyword argument. The default is to look in the current working directory and in a ‘views’ directory, i.e. `template_lookup=(‘.’, ‘./views/’)`.

You can override the template path like so:

from bottle import jinja2_view, route

@route(‘/’, name=’home’)
@jinja2_view(‘home.html’, template_lookup=[‘templates’])
def home():
return {‘title’: ‘Hello world’}

Which will load `templates/home.html`.

Most likely you will want to use the same template path for every view, which can be done by wrapping `jinja2_view`:

import functools
from bottle import jinja2_view, route

view = functools.partial(jinja2_view, template_lookup=[‘templates’])

@route(‘/’, name=’home’)
@view(‘home.html’)
def home():
return {‘title’: ‘Hello world’}

@route(‘/foo’, name=’foo’)
@view(‘foo.html’)
def foo():
return {‘title’: ‘Foo’}

That would have loaded `templates/home.html` and `templates/foo.html`.

Another way of setting a global template path for the view decorator is to fiddle with Bottle’s global default template path:

from bottle import TEMPLATE_PATH, jinja2_view, route

TEMPLATE_PATH[:] = [‘templates’]

@route(‘/’, name=’home’)
@jinja2_view(‘home.html’)
def home():
return {‘title’: ‘Hello world’}

N.B. I used `TEMPLATES_PATH[:]` to update the global template path directly rather than re-assigning it with `TEMPLATE_PATH = [‘templates’]`.

Template defaults
—————–

Bottle has a useful `url()` function to generate urls in your templates using named routes. But it isn’t in the template context by default. You can modify the default context on the Jinja2 template class provided by Bottle:

from bottle import Jinja2Template, url

Jinja2Template.defaults = {
‘url’: url,
‘site_name’: ‘My blog’,
}

Jinja2 version 2.7 by default does *not* escape variables. This surprises me, but it is easy to configure a template environment to auto-escape variables.

from bottle import Jinja2Template

Jinja2Template.settings = {
‘autoescape’: True,
}

Any of [the Jinja2 environment keyword arguments][environment] can go in this settings dictionary.

Using your own template environment
———————————–

Bottle’s template wrappers make a new instance of a Jinja2 template environment for each template (although if two views use the same template then they will share the compiled template and its environment).

You can avoid this duplication of effort by creating the Jinja2 template environment yourself, however this approach means you also need to write your own view decorator to use the custom template environment. No biggie.

Setting up a global Jinja2 template environment to look for templates in a “templates” directory:

from bottle import url
import jinja2

env = jinja2.Environment(
loader=jinja2.FileSystemLoader(‘templates’),
autoescape=True,
)
env.globals.update({
‘url’: url,
‘site_name’: ‘My blog’,
})

You then need a replacement for Bottle’s view decorator that uses the previously configured template environment:

import functools

# Assuming env has already been defined in the module’s scope.
def view(template_name):
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(*args, **kwargs):
response = view_func(*args, **kwargs)

if isinstance(response, dict):
template = env.get_or_select_template(template_name)
return template.render(**response)
else:
return response

return wrapper

return decorator

@route(‘/’, name=’home’)
@view(‘home.html’)
def home():
return {‘title’: ‘Hello world’}

Conclusion
———-

It’s easy to customize the template environment for Jinja2 with Bottle and keep compatibility with Bottle’s own view decorator, but at some point you may decide it is more efficient to by-pass things and setup a custom Jinja2 environment.

Bottle is nice like that.

[bottle]: http://bottlepy.org/
[jinja2]: http://jinja.pocoo.org/
[django]: https://www.djangoproject.com/
[environment]: http://jinja.pocoo.org/docs/api/#jinja2.Environment