☑ Jinja Ninja

1 Jun 2013 at 6:25PM in Software
 |   | 

I recently had to do a few not-quite-trivial things with the Jinja2 templating engine, and the more I use it the more I like it.

beach karate

This blog is generated using a tool called Pelican, which generates a set of static HTML files from Markdown pages and other source material. It’s a simple yet elegant tool, and you can customise its output using themes. This site uses a theme I created myself called Graphite. Of course, you’d know all this if you read the little footer at the bottom of the page1.

As it happens, Pelican themes use Jinja2, which is one of the more popular Python templating languages. Since I recently had to do some non-trivial things with the site theme here, I thought I’d post my thoughts on it — the executive summary, for anybody who’s already bored, is that I think it’s rather good.

The main thing I wanted to achieve was to reorganise the archive of old posts into one page per year, with sections for each month. To index this I wanted a top-level page which simply linked to each month of each year, with no posts listed. One thing I didn’t want to do was have to change core Pelican, since I’m trying to keep this theme suitable for anybody (even though it’s unlikely that anyone but myself will ever use it).

Pelican already had some configuration which got me part of the way there. It’s possible for it to put pages into subdirectories according to year and month, and also create index.html pages in them to provide an appropriate index. This was a great starting point, but some work was still needed since the posts were presented to the template as a simple sorted list of objects with appropriate attributes.

Since I wanted the year indices (such as this one) to have links to individual posts organised under headings per month. This was fairly easy to achieve by recording the date of the previous post linked and emitting a header if the month and/or year of the post about to be linked differed. Here’s a snippet from the template which actually generates the links:

{% set last_date = None %}
{% for article in dates %}
    {% if last_date != (article.date.year, article.date.month) %}
      <dt>{{ article.date|strftime("%b %Y") }}</dt>
    {% endif %}
      <a href="{{ SITEURL }}/{{ article.url }}"
         title="{{ article.locale_date }}: {{ article.summary|striptags|escape }}">
        {{ article.title }}
    {% set last_date = (article.date.year, article.date.month) %}
{% endfor %}

Pelican has set dates to an iterable of posts, each of which is a class instance with some appropriate attributes. You can see that setting a tracking variable last_date is simple enough, as is iterating over dates. Then we conditionally emit a <dt> tag containing the date if the current post’s date differs from the previous one2. Since last_date starts at None, this will always compare unequal the first time and emit the month for the first post. Thereafter, the heading is only emitted when the month (or year) changes. This approach does, of course, assume that dates yields in sorted order.

The other points worth noting are the filters, which take the item on the left and transform it somehow. The strftime filter is provided by Pelican, and passes the input date and the format string parameter to strftime() in the obvious way. The striptags and escape filters are available as standard in Jinja2 — their operation should be fairly obvious from the code above.

What I like is the way that I can write fairly natural Pythonic code, referring to attributes and the like, but still have it executed in a fairly secure sandboxed environment instead of just passed to the Python interpreter, where it could cause all sorts of mischief.

Also, there are a few useful extensions to basic Python builtins, such as the ability to refer to the current loop index within a loop, and also refer to the offset from the end of the list as well, to easily identify final and penultimate items for special handling.

The other bit of Jinja2 that’s quite powerful is the concept of inheritance, something that seems to have become increasingly popular in templating engines. The way it’s done here is to be able to declare that one template extends another one:

{% extends base.html %}

The “polymorphism” aspect is handled with the ability to override “blocks”. So, perhaps base.html contains a declaration like this:

  <title>{% block title %}Andy's Blog{% endblock %}</title>

Then, a page which wanted to override the title could do so by extending the base template and simply redeclaring the replacement block:

{% extends base.html %}
{% block title %}Andy's Other Page{% endblock %}

Finally, there’s also the ability to define macros, which essentially parameterised snippets of markup which can be called like functions to be substituted into place with the appropriate arguments included. Here’s a trivial example:

{% macro introduction(name) %}
    Hello, my name is {{ name }}.
{% endmacro %}

Of course, many of these features are provided by other templating engines as well, but I’ve found Jinja2 to be convenient, Pythonic and certainly fast enough for my purposes. I think it’ll be my templating engine of choice for the foreseeable future.

  1. You know, the bit that absolutely nobody ever reads. 

  2. In the original Jinja engine the ifchanged directive provided a more convenient way to do this, but it’s been removed in Jinja2 as it was apparently inefficient

1 Jun 2013 at 6:25PM in Software
 |   |