A web application that only returns plain strings is boring. To turn your Flask application into something optical pleasing, we need a template engine like Jinja.
This post is part of my journey to learn Python. You can find the other parts of this series here.
Install Jinja
Jinja is a modern and designer-friendly templating language for Python. Flask depends on Jinja and is therefore the obvious choice for a template engine. Luckily for us, we need to learn only a few tags to use it.
You can install Jinja with this command in a virtual environment:
1 |
pip install Jinja2 |
Reuse your HTML and put the logic away from the view
The main benefit for Jinja, at least in my opinion, is that we can reuse our HTML. We only need to add a few tags to turn a static site into something dynamic. This simple behaviour is only possible if we keep the complexity and the whole logic outside of our HTML page.
Where to put your views?
Flask has a few principles and conventions, that simplify your developer life. One of those conventions is that you put your templates into a folder called template. Create a folder called template next to the file that contains your Flask application:
1 |
> mkdir template |
Render your template
In my minimalistic example from the first steps with Flask post, my view function index()
returned a string. To use a template instead of a string, we need to call the function render_template(). The first argument is the name of the template (relative to the template folder), while everything else that we want to use in our view goes in this function call as named parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def get_latest_tasks(): return [ {'id': 1, 'description': 'write post', 'done': False}, {'id': 2, 'description': 'create examples', 'done': True}, {'id': 3, 'description': 'simplify examples', 'done': True}, {'id': 4, 'description': 'create images', 'done': False}, ] @app.route('/tasks') def tasks(): my_tasks = get_latest_tasks() return render_template('tasks.html', name='Johnny', tasks=my_tasks) |
The get_latest_tasks()
function helps me to create a bit more complex data, that I will use later on.
Access variables in your HTML template
We can access the variables we used in our call to the render_template() function by putting them inside {{ }}. To access name, we can write {{ name }} and Jinja will replace this snipped with the value of that variable:
1 |
<div class="container">Hello {{ name }}div> |
For loops
We often want to iterate through a list of items. We need to tell our template where the for loop starts (line 3) and where it ends (line 5). Inside those two blocks we can access the data as we would in any other Python for loop:
1 2 3 4 5 6 7 |
<div class="container"> <ul> {% for t in tasks %} <li>{{ t.id }} - {{ t.description }}</li> {% endfor %} </ul> </div> |
If statements
Even when we put all our logic away from the view, a few if statements will stay. With an if statement we can show or hide blocks. As with the for loop, we need to tell where our if starts and where it ends:
1 2 3 4 5 |
<div class="container"> {% if name %} Welcome {{ name }}! {% endif %} </div> |
We can extend this if statement to include an else branch:
1 2 3 4 5 6 7 |
<div class="container"> {% if name %} Welcome {{ name }}! {% else %} Welcome! {% endif %} </div> |
It is even possible to use the full Python syntax for if blocks and add as many elif as you like. However, I suggest you try not to use everything that is possible and aim for a maintainable application.
Everything put together
When we put all the concepts of this post together, we get this HTML template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<div class="container"> <h1>Hello {{ name }}</h1> <p>Your current tasks are: <ul> {% for t in tasks %} <li>[{{ t.id }}] {{ t.description }} {% if t.done %} <i class="fa fa-check" aria-hidden="true"></i> {% else %} <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> {% endif %} </li> {% endfor %} </ul> </p> </div> |
The view function for /task is unchanged:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def get_latest_tasks(): return [ {'id': 1, 'description': 'write post', 'done': False}, {'id': 2, 'description': 'create examples', 'done': True}, {'id': 3, 'description': 'simplify examples', 'done': True}, {'id': 4, 'description': 'create images', 'done': False}, ] @app.route('/tasks') def tasks(): my_tasks = get_latest_tasks() return render_template('tasks.html', name='Johnny', tasks=my_tasks) |
The rendered template looks a lot better in our browser that just a string:
With these parts you can go a long way. There is a lot more to Jinja than that, and if you hit a problem or want to know more, there is an extensive official documentation with all the details.
Next
With these parts you can write dynamic HTML pages. However, if you have multiple pages it quickly gets annoying when you have to change the layout. In the next post I explain how you can reuse your layout and only keep the specific parts of a site in your template.
Thank you for sharing these resources. Read through some of your other Python Friday articles. I am definitely including these in my team training reading recommendations.