Flask 2 - Templating met Jinja2

Auteur

Fabrice Devaux

Publicatiedatum

16 december 2025

Inleiding

Template: een vooraf gedefinieerd patroon, model of sjabloon dat dient als uitgangspunt voor het maken van iets nieuws. Het bevat vaste elementen en laat ruimte voor variabele onderdelen die ingevuld of aangepast kunnen worden.

Jinja: een Japanse “Shinto” tempel.

Tempel ➡️ Temple ➡️ Template

Jinja is een “Templating Engine” van dezelfde ontwikkelaar als Flask en met als hoofddoel Flask te voorzien van een templating systeem voor HTML pagina’s.

Maar Jinja is op zich een losstaand Python package en wordt ook voor andere doeleinden gebruikt, zoals configuratie management (Ansible) of documentatie-generatie (mkdocs).

Wat leren we in deze les?

  • Templates maken en renderen met Jinja2
  • Variabelen, statements en filters gebruiken
  • Whitespace en escaping controleren
  • Hoe Jinja2 gebruikt wordt in Flask
  • Template inheritance toepassen

Installatie

In een omgeving waar Flask geïnstalleerd is is Jinja die automatisch ook. Het kan ook apart geïnstalleerd worden via het jinja2 package.

Jinja Basics

Jinja Hello World

import jinja2

1env = jinja2.Environment()
2template = env.from_string("Hallo {{ name }}")

3print(template.render(name="Syntra"))
4print(template.render({"name": "Syntra"}))
1
Een jinja2.Environment object vormt de basis waarmee we templates kunnen renderen. Één enkel object kan verschillende templates renderen.
2
Met de from_string methode van dit Environment object wordt een Template object gemaakt van een string. Die string bevat dus de eigenlijke template. Let op: die string is dus geen f-string maar een ‘gewone’ Python string. { name } is een Jinja variabele en zal vervangen worden door tijdens het renderen.
3
De render methode van het Template object zal die specifieke template renderen en geeft een Python string terug. De methode neemt keyword-argumenten die overeenkomen met de variabelen gebruikt in de template.
4
Dezelfde methode kan ook een enkele dictionary als argument gebruiken, met als keys de variabelen.

I.p.v. een string kan de template tekst ook ingelezen worden uit een tekstbestand:

templates/welcome.txt.j2
Hi {{ name }},

In this course you will learn all about {{ course }}.

Good luck!
1env = jinja2.Environment(loader=jinja2.FileSystemLoader("templates"))

2template = env.get_template("welcome.txt.j2")
print(template.render(name="Fabrice", course="Flask"))
1
Om templates uit bestanden in te laden moet een Environment een bepaalde Loader meekrijgen die bepaalt waar de bestanden gezocht worden. FileSystemLoader is een eenvoudige loader die templates zal zoeken in de gegeven directory. Het is conventioneel om alle templates in een templates/ directory te plaatsen.
2
I.p.v. from_string wordt nu de get_template methode gebruikt, met als argument de naam (of pad naar) het te gebruiken bestand (relatief t.o.v. de templates/ directory)

In de praktijk zul je, zeker voor grotere templates, doorgaans bestanden gebruiken. Voor de verdere voorbeelden in deze cursus gebruiken we voor het gemak wel from_string.

Tip: .j2 extensie

Door template bestanden hun normale extensie (hier .txt) gevolgd door de .j2 extensie te geven zullen de meeste editors in staat zijn om syntax highlighting toe te passen zowel voor de taal van het uiteindelijke bestand, als ook voor de Jinja elementen.

Voor VSCode is een extra plugin zoals Better Jinja nodig.

Dit wordt in het bijzonder interessant als we later ingewikkeldere templates maken om HTML bestanden te renderen.

Expressions

In een Jinja template worden variabelen dus tussen dubbele {} geplaatst. In Jinja-terminologie spreken we over een Expression.

Zo een uitdrukking is niet beperkt tot de naam van een variabele die bij het renderen wordt meegeven. Een paar voorbeelden:

print(env.from_string("Math: {{ 84 / 2 }}").render())
print(env.from_string("Comparison: {{ name == 'Syntra' }}").render(name="Syntra"))
print(env.from_string("Logic: {{ True or test }}").render(test=False))
print(env.from_string("Condition: {{ 'groot' if size > 50 else 'klein' }}").render(size=100))

Statements

Naast Expressions kent Jinja ook Statements. Statements worden tussen {% en %} geplaatst en kunnen onder andere voor controle structuren gebruikt worden.

template_text = """
Hallo {{ name }},

1{% if score >= 50 %}
Proficiat, je bent geslaagd!
{% else %}
Je bent helaas niet geslaagd. Volgende keer beter!
2{% endif %}

Je behaalde {{ score }}%.
"""

template = env.from_string(template_text)
print(template.render(name="Fabrice", score=65))
1
Statements worden zelf niet gerenderd maar hebben invloed op de tekst (en expressions) die binnen de statements vallen. Statements bevatten expressions, zoals in dit geval een conditie die de variabele score vergelijkt.
2
De if-else controle-structuur wordt afgesloten met een endif statement. In een gewone Python if-else is dit niet nodig omdat de inspringing (indentation) dit bepaalt.
Vragen
  • Wat valt op in de output?
Tip: Online Jinja renderer

Er bestaan verschillende online tools waarmee je ‘live’ Jinja kan renderen, bvb. https://nebula.packetcoders.io/j2-render/.

Dit is bijzonder handig om snel en efficient je templates te testen onder verschillende omstandigheden.

Naast if-else biedt Jinja nog tal van andere controle-structuren zoals bvb. for lussen. In de rest van de cursus komen de belangrijkste aan bod.

Comments

Naast expressions en statements bestaat een derde en laatste syntax om commentaar toe te voegen in Jinja templates. Hiervoor gebruik je {# Wat hier staat wordt dus niet gerenderd #}.

Samengevat hebben we dus:

  • {% ... %}: Statements. (Control Structures, bvb. if, for, …)
  • {{ ... }}: Expressions. -> Output. Meestal gewoon een variabele.
  • {# ... #}: Comments.

Whitespace Control

Inleiding

Whitespace omvat spaties, tabs en newline karakters.

Uit het vorige hoofdstuk is het je misschien opgevallen dat de gerenderde template onverwachte extra lege lijnen bevat. Hoewel statements zelf niet verschijnen in het resultaat worden de newline karakters op het einde van de statements wel behouden.

Bijvoorbeeld bij de volgende template zou je enkel een lijn met het getal 2 verwachten. Tegelijk introduceren we hier de for loop structuur.

{% for num in (1,2,3) %}
  {% if num == 2 %}{{ num }}{% endif %}
{% endfor %}

Het resultaat bevat echter een heleboel lege lijnen:




  2

Om beter in te zien wat er precies gebeurt voegen we wat extra tekst toe op het einde van elke lijn in de template

Start
{% for num in (1,2,3) %}for
  {% if num == 2 %}{{ num }}{% endif %}if
{% endfor %}endfor
Einde

Resultaat:

Start
for
  if
for
  2if
for
  if
endfor
Einde

trim_blocks

trim_blocks is een eerste configuratie die aan de Jinja Environment kan worden meegegeven om bovenstaand gedrag aan te passen.

Met trim_blocks=True zal een newline karakter onmiddellijk na een “block” statement worden verwijderd.

env = jinja2.Environment(trim_blocks=True)

Het resultaat van het vorige voorbeeld wordt dan:

Start
    2  Einde

Wat opvalt is dat we nog 4 spaties (eigenlijk 2 x 2) hebben voor dat 2 geprint wordt, en 2 spaties er na. Dat komt omdat voor elke iteratie doorheen de for lus de twee spaties voor het if statement wel nog geprint worden.

lstrip_blocks

lstrip_blocks is de tweede configuratieoptie. Met lstrip_blocks=true zullen alle spatie en tab karakters net voor (links van) een block statement worden verwijderd.

Als we zowel lstrip_blocks and trim_blocks gebruiken wordt het uiteindelijke resultaat:

Start
2Einde

Manuele Controle

Het is ook mogelijk om het al-dan-niet behouden van whitespace voor een individueel block statement te bepalen.

  • {%- if ... %}: Verwijder whitespace voor het block
  • {% if ... -%}: Verwijder whitespace na het block

Filters

Jinja filters zijn een bijzonder krachtige manier om de waarde van een variable (of van een expression) aan te passen.

Om een filter toe te passen gebruik je | na de naam van de variabele, gevolgd door de naam van de filter.

Een voorbeeld met de upper filter, die een string omzet in uppercase.

{{ "hello" | upper }}

Hier volgen een aantal populaire filters die vaak van pas komen. Voor de meesten zal de functie vrij vanzelfsprekend zijn. De volledige lijst met alle filters vind je hier.

{{ my_variable | default("woops") }}

{{ [1, 2, 3] | first }}

{{ "10" | int + 5 }}

{{ ["red","green","blue"] | join(", ") }}

{{ ["red","green","blue"] | length }}

De output van de ene filter kan de input van de volgende filter vormen (we spreken over chaining).

{{ ["red","green","blue"] | join(", ") | upper }}

Escaping

Syntax Escaping

Zoals bij elke taal waarbij syntactische regels een speciale waarde toekennen aan een bepaalde sequentie karakters moet er een manier bestaan om diezelfde reeks karakters letterlijk te kunnen gebruiken, zonder dat ze door de taal geïnterpreteerd worden.

Bij Jinja hebben bvb {{ of {% een speciale betekenis. Maar wat als je deze letterlijk wil gebruiken in een template?

Een eerste optie is om de reeks te gebruiken binnen een expression.

{{ '{{' }}

Voor groteren stukken tekst kan deze binnen een raw block geplaatst worden.

{% raw %}
Dit is een voorbeeld van een lus in Jinja:

{% for num in (1,2,3) %}
  {% if num == 2 %}{{ num }}{% endif %}
{% endfor %}
{% endraw %}

HTML Escaping

Als we later Jinja met Flask zullen gebruiken om HTML code te genereren bestaat de kans dat er via een variabele ongewenste HTML karakters geïntroduceerd worden.

In de Flask inleidingscursus hebben we reeds geleerd dat the escape functie van het markupsafe package hier een oplossing biedt.

Jinja kan dit automatisch toepassen bij het renderen van een template.

Een eerste optie is om, net als bij syntax escaping, een variabele handmatig te escapen. Dat gebeurt met de escape filter.

<h1>Welcome to {{ "Syntra;</" | escape }}</h1>

Het is ook mogelijk om elke variabele automatisch te escapen door dit in te stellen in het Environment object:

env = jinja2.Environment(autoescape=True)

Als je voor een specifieke variabele dan niet wil escapen kan dat via de safe filter.

Oefeningen

  1. Maak een template waarmee “Hallo <naam>, welkom bij de cursus <cursus>.” geprint kan worden, waarbij naam en cursus variabelen zijn.
  2. Definieer de template in een bestand i.p.v. een string.
  3. Voeg logica toe aan de template om het juiste lokaal mee te geven. Als cursus gelijk is aan Python, dan is het lokaal “C4”, zo niet, dan is het lokaal “C3”.
  4. Is dit een goede aanpak om deze logica in de template te plaatsen? Bespreek een andere/betere manier.
  5. Maak een template die als input variabele een lijst met kleuren (als strings) krijgt en de volgende tekst rendert:
    1. ["rood", "groen"] -> “Kies een van de 2 kleuren: rood of groen”
    2. ["rood", "groen", blauw] -> “Kies een van de 3 kleuren: rood, groen of blauw”

Jinja en Flask

Zoals eerder aangehaald is Jinja dus oorspronkelijk specifiek ontwikkeld om in Flask HTML pagina’s te kunnen templaten. We bekijken hier enkele Jinja instellingen die Flask standaard gebruikt.

Voorbeeld

Tijdens de volgende Flask les maken we uitgebreid gebruik van Jinja Templates dus bekijken we hier slechts een eenvoudig basisvoorbeeld.

hello.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html.j2', person=name)
templates/hello.html.j2
<!doctype html>
<title>Hello from Flask</title>
{% if person %}
  <h1>Hello {{ person }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

Templates

Flask gebruikt automatisch de FileSystemLoader met als zoek-pad de templates/ directory in dezelfde locatie als waar de Flask applicatie (doorgaans app.py zich bevindt)

HTML Escaping

Omdat met Flask in principe hoofdzakelijk HTML getemplate wordt is autoescape natuurlijk noodzakelijk en standaard ingesteld.

Flask doet dit echter enkel voor templates met de extensies .html, .htm, .xml, .xhtml en.svg! We gebruikten eerder in deze cursus de tip om html templates de extensie .html.j2 te geven.

Om ervoor te zorgen dat autoscape ook voor die templates gebruikt wordt kan het jinja_options attribuut van de Flask object als volgt aangepast worden:

app = Flask(__name__)
app.jinja_options["autoescape"] = True

Voor de volledigheid: het is ook mogelijk een lijst specifieke bestand extensies te gebruiken, m.b.v. de Jinja select_autoescape functie.

Whitespace Control

Flask gebruikt de standaard Jinja instellingen voor whitespace control (trim_blocks en lstrip_blocks beiden False). Dit is geen probleem want extra whitespace maakt in dit geval niet veel uit. Waarom is dat?

Template Inheritance

Template Inheritance, of overerving, wordt vaak gezien als de krachtigste functie van Jinja.

Template inheritance maakt het mogelijk om in een eerste “basis” template een algemene structuur te definiëren, waarbij specifieke “blokken” in de template worden ingevuld door afgeleide templates.

Bijvoorbeeld, bij het templaten van de verschillende HTML pagina’s van een website zullen alle pagina’s een identieke basis-structuur hebben.

templates/base.html.j2
1<!doctype html>
<html>
<head><title>{{ title }}</title></head>
<body>
2  {% block body %}Default body{% endblock %}
</body>
</html>
1
Elke template zal altijd dezelfde doctype, html, head, … tags hebben.
2
Maar individuele templates kunnen een verschillende inhoud voor de body definiëren.

Deze “base” template zal nooit rechtstreeks gerenderd worden, maar wel gebruikt om de “echte” templates op te bouwen.

templates/home.html.j2
1{% extends 'base.html.j2' %}

2{% block body %}
<h1>Welcome to {{ title }}</h1>
<p>There are many other like it, but this one is mine.</p>
{% endblock %}
1
Deze template breidt de base template out. Het vertrekpunt is dus de base template.
2
De inhoud van het “body” block uit de base template zal hier door vervangen worden.

Template inheritance biedt nog tal van meer geavanceerde opties, zoals bvb. het overnemen van de block-inhoud van base template, of het nesten van templates. In de praktijk zul je hier echter zelden gebruik van moeten maken. De algemene raad is dan ook om de opbouw van templates zo eenvoudig mogelijk te houden en zoveel mogelijk vrij van ingewikkelde logica.

Extras

Leesvoer en referenties