Templating isn’t just for Web Developers!#

Recently, I found myself rambling on in an unprepared attempt to explain templating to a colleague of mine who had no prior experience with frontend design. It didn’t take me long to realize that my explanation was convoluted and confusing, not that I stopped talking, mind you. Attribute that to being unprepared, or sleep deprived (i.e., child still not sleeping through the night!), or just a bad teacher.

Regardless, I decided to point them to a tutorial online that certainly must exist, but after a brief Google search (which you should read as, “I didn’t find anything in the first page of search results!”), all I could find were tutorials for frontend developers. And if you aren’t an HTML expert, or already have experience with Flask or Django, teaching templating in those contexts can actually be more confusing than illuminating.

So, as I always do when I find myself struggling with something that I think should be easy, I ask my wife for advise. I tried to explain the concept of templating to my wife, who is not a software developer at all. My wife looked at me with this look that said, “What’s not to get?” Then she said, “You mean filling out a form letter automatically…”

Yes. Exactly. And while my palm was hitting my forehead, it occurred to me that I should write a templating tutorial for those who really don’t have any prior experience with frontend development and who might even be scared when they read “less-than-h-t-m-l-greater-than”. Today, we’ll learn Jinja2 templating through an example use case where we want to generate a form letter for a number of different addressees.

Jinja2#

I’ll be using Jinja2 for this tutorial. It’s extremely popular and widespread in the Python developer community, and it has very good documentation… except for the fact that all of their documentation is almost entirely geared toward frontend designers.

You can install Jinja2 with pip or conda as follows:

$ pip install Jinja2

or

$ conda install jinja2

That’s it. You should now be set up with an environment that has (at least) python3 and jinja2 installed in it.

The Use Case#

Imagine that you are the attorney for an oil tycoon with $4.5M held in the Bank of Freedonia. With tensions mounting between Freedonia and neighboring Sylvania, and war looming on the horizon, your client has authorized you to move these assets to another bank outside of Freedonia. Clearly, your only course of action is to email as many people for whom you can find addresses to ask them if they would be willing to hold the assets in their account for the duration of the conflict. Obviously, you will need to pay them for their kind services, and the oil tycoon has agreed to authorize you to gift 20% of the assets to whomever holds them, as payment for their kindness. You just need some personal information from them and some bank routine information. That’s all. You have obtained your list of addressees from a kind person working for the humanitarian WikiLeaks “Foundation,” and now you must craft an email to send to all of these addressees.

So, there is your entirely believable, totally serious, and extremely common scenario motivating your need to learn templating.

The Letter#

Fortunately, you already have a “template” for a formal written business letter sitting around on your hard drive. It looks like this:

[Your Address]

[Date]

[Recipient Name]
[Recipient Title]
[Recipient Company]
[Recipient Address]

[Greeting] [Recipient Name]:

[Letter Body]

[Closing],

[Your Name]
[Your Title]

Each element in this “template” that is enclosed in []-brackets is just a placeholder for some other text. Some of that text is short, like names, titles, or greeting or closing text. Some of that text will me multi-line input, such as the letter’s body and the addresses. In Python, all you would want to do is something like string substitution, for example replacing [Your Name] with Jane Smith.

This is exactly what “templating” is all about. It is, essentially, just string substitution…but with a touch more sophistication.

The Jinja2 Template#

So, to start out, we are going to convert this text template into a Jinja2 template. To do so, the easiest first step would be to just replace every []-bracketted item with a Jinja2 expressions, which are represented with {{}}-brackets:

{{ your_address }}

{{ date }}

{{ recipient_name }}
{{ recipient_title }}
{{ recipient_company }}
{{ recipient_address }}

{{ greeting }} {{ recipient_name }}:

{{ letter_body }}

{{ closing }},

{{ your_name }}
{{ your_title }}

Also, notice that I have converted the text labels inside the []-brackets into valid Python variable names. Let’s assume that this new template file is called letter.j2 (where the .j2 means it is a Jinja2 template, but Jinja2 does not care what extension the file actually has).

We can now “write” this letter using Python variables from a Python script. First, let’s start writing a Python script that can “render” this Jinja2 template:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('./'))
template = env.get_template('letter.j2')

data = {}

print(template.render(**data))

If you were to run this script as it current exists, you would get a lot of blank lines, and some “floating” punctuation. Something like this:

 :



,

This is because the data variable is empty. In other words, all of those {{ var_name }} template variables never got defined! Jinja2 assumes that anything that isn’t defined is an empty string.

So, if we were to fill in the data dictionary with some data with keys matching the template variables, we should see something different.

from datetime import datetime

data = {
    'your_name': "Jane Smith",
    'your_title': "Attorney at Law",
    'your_address': """1234 Somewhere Street
Atown, XY  12345""",
    'date': datetime.now().strftime("%Y %B %d"),
    'recipient_name': "Joe Smith",
    'recipient_title': "Head of Marketing",
    'recipient_company': "Better Stuff, Inc.",
    'recipient_address': """5678 Nowhere Drive
Someberg, YZ  56789""",
    'greeting': 'Dear',
    'closing': 'Sincerely',
}

And with that change, the output should now look like:

1234 Somewhere Street
Atown, XY  12345

2020 May 11

Joe Smith
Head of Marketing
Better Stuff, Inc.
5678 Nowhere Drive
Someberg, YZ  56789

Dear Joe Smith:



Sincerely,

Jane Smith
Attorney at Law

Does it make sense? Let’s explain what’s going on here.

In our Python code, we are creating some data (literally data) that we want to hand off to the “template engine” to insert into the template, itself. The template engine (jinja2) searches through the template document for all occurrences of {{ key }} for each key in the data dictionary (or each keyward argument in the render() function). And it does simple substition of the string {{ key }} with data[key]. Any {{ key }} not found in the data dictionary will be replaced with an empty string, and any key in the data dictionary not found in the template document will be ignored.

That’s it. That’s a lot of what templating is all about.

But not everything.

Jinja2 Statements#

This letter template is nice, but we want to send out emails, not actual postage. You don’t have time for snail mail!

So, we could write another template for an email, but for the sake of edification, let’s just modify our existing template to be dual purpose. In particular, some of the template variables are just not used when writing emails (your_address, date, recipient_name, recipient_title, recipient_company, recipient_address). But if we leave these variables undeclared, then there will be odd whitespace at the top of our email. So, we will add another template variable for the sole purpose of specifying if the document we are writing is a letter or an email. We’ll label this variable is_letter, and we’ll insert it into our template document with some if statements, like so:

{% if is_letter %}
{{ your_address }}

{{ date }}

{{ recipient_name }}
{{ recipient_title }}
{{ recipient_company }}
{{ recipient_address }}

{% endif %}
{{ greeting }} {{ recipient_name }}:

{{ letter_body }}

{{ closing }},

{{ your_name }}
{{ your_title }}

By default, if we don’t specify is_letter in the data variable in our Python script, it will default to an empty string. And in Python, that evaluates to False in a boolean check. So, the top part of our template will not be displayed if is_letter is False or unset. That is, you must explicitly set is_letter or it will assume it is an email.

There are a lot more kinds of statements ({% %}-syntax) you can place in your templates, but this is just a simple example of what you can do.

Note also that these template variables are all native Python data types, which means you can declare a template variable to be a list, for example, and index into that list in the template itself.

Inheritance#

The last thing to explain that is useful with templating is inheritance. If we wanted to print out the same letter for every recipient, we could just as easily write the letter_body in the Python script, itself. However, since the letter_body could be quite long, we will put it in another template file. One that inherits from the original.

To do this, we will first change the letter_body from a template variable to a template block in our letter.j2 template file, like so:

{% if is_letter %}
{{ your_address }}

{{ date }}

{{ recipient_name }}
{{ recipient_title }}
{{ recipient_company }}
{{ recipient_address }}

{% endif %}
{{ greeting }} {{ recipient_name }}:

{% block letter_body %}
{% endblock %}

{{ closing }},

{{ your_name }}
{{ your_title }}

And then we will write a second template file (called, for example, scam.j2), that looks like this:

{% extends "letter.j2" %}

{% block letter_body %}
(...contents of my letter...)
{% endblock %}

Now, change our script to render the scam.j2 template instead of the letter.j2 template and see what it outputs. You should get something like this:

Dear Joe Smith:


...contents of my letter...


Sincerely,

Jane Smith
Attorney at Law

and if you want to remove those extraneous blank lines, you might want to set trim_blocks=True in the Environment declaration. Then, you should see something like this:

Dear Joe Smith:

...contents of my letter...

Sincerely,

Jane Smith
Attorney at Law

And that’s it! Now, you are an expert in templating with Jinja2! Feel free to attempt your own Spanish Prisoner con. (Not advised!)

Well, maybe not an expert. There’s a lot I didn’t talk about that you can read about in the Jinja2 documentation. Hopefully, though, you can see how something like this can be extremely useful when generating HTML pages for a website.