Flask 2 - Templating met Jinja2
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.Environmentobject vormt de basis waarmee we templates kunnen renderen. Één enkel object kan verschillende templates renderen. - 2
-
Met de
from_stringmethode van ditEnvironmentobject wordt eenTemplateobject 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
rendermethode 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
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
Environmenteen bepaaldeLoadermeekrijgen die bepaalt waar de bestanden gezocht worden.FileSystemLoaderis een eenvoudige loader die templates zal zoeken in de gegeven directory. Het is conventioneel om alle templates in eentemplates/directory te plaatsen. - 2
-
I.p.v.
from_stringwordt nu deget_templatemethode gebruikt, met als argument de naam (of pad naar) het te gebruiken bestand (relatief t.o.v. detemplates/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.
.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
scorevergelijkt. - 2
-
De
if-elsecontrole-structuur wordt afgesloten met eenendifstatement. In een gewone Pythonif-elseis dit niet nodig omdat de inspringing (indentation) dit bepaalt.
- Wat valt op in de output?
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.
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.
Het resultaat bevat echter een heleboel lege lijnen:
Om beter in te zien wat er precies gebeurt voegen we wat extra tekst toe op het einde van elke lijn in de template
Resultaat:
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.
Het resultaat van het vorige voorbeeld wordt dan:
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:
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.
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).
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.
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.
Het is ook mogelijk om elke variabele automatisch te escapen door dit in te stellen in het Environment object:
Als je voor een specifieke variabele dan niet wil escapen kan dat via de safe filter.
Oefeningen
- Maak een template waarmee “Hallo <naam>, welkom bij de cursus <cursus>.” geprint kan worden, waarbij
naamencursusvariabelen zijn. - Definieer de template in een bestand i.p.v. een string.
- Voeg logica toe aan de template om het juiste lokaal mee te geven. Als
cursusgelijk is aanPython, dan is het lokaal “C4”, zo niet, dan is het lokaal “C3”. - Is dit een goede aanpak om deze logica in de template te plaatsen? Bespreek een andere/betere manier.
- Maak een template die als input variabele een lijst met kleuren (als strings) krijgt en de volgende tekst rendert:
["rood", "groen"]-> “Kies een van de 2 kleuren: rood of groen”["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
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:
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
- 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
- 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
- https://jinja.palletsprojects.com/en/stable/
- https://jinja.palletsprojects.com/en/stable/templates/#list-of-control-structures
- https://jinja.palletsprojects.com/en/stable/templates/#expressions
- https://jinja.palletsprojects.com/en/stable/templates/#list-of-builtin-filters
- https://jinja.palletsprojects.com/en/stable/templates/#builtin-tests
- https://jinja.palletsprojects.com/en/stable/templates/#whitespace-control
- https://jinja.palletsprojects.com/en/stable/templates/#template-inheritance
- https://flask.palletsprojects.com/en/stable/quickstart/#rendering-templates
- https://nebula.packetcoders.io/j2-render/
- https://www.youtube.com/watch?v=OraYXEr0Irg
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.