Ga naar hoofdinhoud

Templates met Jinja2

Hier bouw je op verder: Dictionaries, Geneste dictionaries en Een module importeren (random)

Weet je dit niet meer zeker? Lees het terug voordat je verdergaat.

Een FileResponse stuurt elke keer hetzelfde bestand. Voor een waarde uit Python op de pagina, zoals een naam, een score of straks de berichten uit je database, heb je een template nodig: een HTML-bestand met open plekken, die Python invult voordat de pagina de server verlaat. Het invullen doet Jinja2, dat al met FastAPI is meegeïnstalleerd.

Een dobbelsteen op een pagina​

Maak naast static een nieuwe map templates, met daarin dobbelsteen.html:

je-project/
├── main.py
├── static/
└── templates/
└── dobbelsteen.html
templates/dobbelsteen.htmlde server vult hem in, de browser toont hem
<!DOCTYPE html>
<html>
<body>
<h1>Dobbelsteen</h1>
<p>Je gooide een {{ worp }}.</p>
</body>
</html>

{{ worp }} is een open plek. Wat daar komt te staan, beslist Python. In je main.py komen daarvoor drie dingen bij: import random helemaal bovenaan, Request en Jinja2Templates bij de imports, en onder app.mount de regel templates = …. De bovenkant van je main.py ziet er dan zo uit:

main.pydraait op de server
import random

from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
  1. Regel 10:

    templates weet waar je templates staan. Vanaf nu vraag je een template op met alleen de bestandsnaam.

Zet het endpoint onder je andere endpoints:

main.pydraait op de server
@app.get("/dobbelsteen")
async def dobbelsteen(request: Request):
worp = random.randint(1, 6)
return templates.TemplateResponse(
request,
"dobbelsteen.html",
{"worp": worp}
)
  1. Regel 2:

    request: Request vult FastAPI zelf in met het verzoek van de browser. Een template heeft dat nodig, dus je geeft hem door.

  2. Regel 3:

    random.randint(1, 6) kiest een willekeurig getal van 1 tot en met 6.

  3. Regel 4-8:

    TemplateResponse vult de template in en stuurt het resultaat als HTML terug. Eerst het verzoek, dan de bestandsnaam in de map templates, dan een dictionary. Elke sleutel wordt in de template een naam: "worp" vult {{ worp }}.

Voorspel
Je gaat naar /dobbelsteen en herlaadt de pagina drie keer. Wat zie je?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Ga naar http://127.0.0.1:8000/dobbelsteen en herlaad een paar keer.

Wat de browser krijgt​

Klik met de rechtermuisknop op de pagina en kies Paginabron weergeven (of druk op Ctrl+U). Het verzoek was:

GET /dobbelsteen

En dit is het antwoord dat de browser kreeg:

Antwoord van de serverdit krijgt de browser van de server
<!DOCTYPE html>
<html>
<body>
<h1>Dobbelsteen</h1>
<p>Je gooide een 4.</p>
</body>
</html>

Nergens staat {{ worp }}. Jinja2 vult de template in op de server, en de browser krijgt gewone HTML. Van de template weet hij niets; hij ziet alleen het getal.

Meer waarden meesturen​

Elke sleutel in het dictionary is een naam die de template kan gebruiken. Stuur je er twee mee, dan heb je twee open plekken:

main.pydraait op de server
return templates.TemplateResponse(
request,
"dobbelsteen.html",
{"worp": worp, "speler": "Sam"}
)
templates/dobbelsteen.htmlde server vult hem in, de browser toont hem
<p>{{ speler }} gooide een {{ worp }}.</p>

Is een waarde zelf een dictionary, dan haal je er in de template een onderdeel uit met een punt:

main.pydraait op de server
{"speler": {"naam": "Sam", "punten": 12}}
templates/dobbelsteen.htmlde server vult hem in, de browser toont hem
<p>{{ speler.naam }} heeft {{ speler.punten }} punten.</p>

In Python schrijf je speler["naam"]; in een template mag speler.naam. Dat kom je weer tegen als je de berichten uit je database op een pagina zet.

Controlevraag
Je zet dobbelsteen.html per ongeluk in static/pages/ in plaats van in templates/. Het endpoint laat je zoals het is. Wat gebeurt er als je naar /dobbelsteen gaat?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Er gaat iets mis​

Een lege plek op de pagina​

Je gooide een .

Oorzaak: de naam in de template staat niet in het dictionary. Jinja2 geeft dan geen foutmelding, maar laat de plek leeg.

Oplossing: vergelijk de naam tussen {{ }} letter voor letter met de sleutel in het dictionary.

main.pydraait op de server
# FOUT: de template vraagt om worp
{"getal": worp}

# GOED
{"worp": worp}

TemplateNotFound​

jinja2.exceptions.TemplateNotFound: 'dobbelsteen.html' not found in search path: 'templates'

Oorzaak: Jinja2 zoekt alleen in de map die je aan Jinja2Templates gaf, en daar staat het bestand niet.

Oplossing: zet het bestand in templates/, naast main.py, en controleer de bestandsnaam. Geef alleen de naam door, zonder templates/ ervoor.

Meer uitleg: Er gaat iets mis

Zo ziet je main.py er nu uit

Dit is je hele main.py na deze les, zonder wat je in de opdrachten maakte.

main.pydraait op de server
import random

from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

@app.get("/")
async def home():
return FileResponse("static/pages/home.html")

@app.get("/info")
async def info():
return {"naam": "Jouw Naam", "leeftijd": 16}

@app.get("/pagina", response_class=HTMLResponse)
async def pagina():
return """
<!DOCTYPE html>
<html>
<head>
<title>Mijn Pagina</title>
</head>
<body>
<h1>Hallo!</h1>
<p>Dit is mijn eerste HTML pagina.</p>
</body>
</html>
"""

@app.get("/styled", response_class=HTMLResponse)
async def styled():
return """
<!DOCTYPE html>
<html>
<head>
<title>Met CSS</title>
<style>
h1 { color: blue; }
p { color: gray; }
</style>
</head>
<body>
<h1>Gestylede pagina</h1>
<p>Deze tekst is grijs.</p>
</body>
</html>
"""

@app.get("/about")
async def about():
return FileResponse("static/pages/about.html")

DIEREN = {
"kat": "Een kat spint als hij tevreden is.",
"hond": "Een hond kwispelt als hij blij is.",
"vis": "Een vis ademt met kieuwen.",
}

@app.get("/dier/{naam}", response_class=HTMLResponse)
async def dier(naam: str):
tekst = DIEREN.get(naam, "Dit dier staat nog niet op de site.")
return f"""
<!DOCTYPE html>
<html>
<body>
<h1>Over dieren</h1>
<p>{tekst}</p>
<a href="/">Terug naar Home</a>
</body>
</html>
"""

@app.get("/zoek")
async def zoek(term: str):
return {"je_zocht": term}

@app.get("/foto")
async def foto():
return FileResponse("static/pages/foto.html")

@app.get("/dobbelsteen")
async def dobbelsteen(request: Request):
worp = random.randint(1, 6)
return templates.TemplateResponse(
request,
"dobbelsteen.html",
{"worp": worp}
)
Eerst op papier

Sorteer eerst zonder computer welke regels op de server draaien en welke in de browser: Welke taal, waar?. Je docent kan de hand-out printen.

Opdrachten​

Opdracht 1: Predict - Wat zie je?​

templates/welkom.htmlde server vult hem in, de browser toont hem
<h1>Welkom {{ naam }}</h1>
<p>Leeftijd: {{ leeftijd }}</p>
<p>Klas: {{ klas }}</p>
main.pydraait op de server
return templates.TemplateResponse(request, "welkom.html", {
"naam": "Jan", "leeftijd": 16, "Klas": "3B"
})
Voorspel
Wat zie je op de pagina?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Opdracht 2: Run​

Maak de map templates, het bestand dobbelsteen.html en het endpoint /dobbelsteen. Herlaad een paar keer en bekijk de paginabron.

Opdracht 3: Investigate - De oude schrijfwijze​

Veel voorbeelden die je online vindt, zetten request ín het dictionary in plaats van ervoor. Probeer dat eens:

main.pydraait op de server
return templates.TemplateResponse("dobbelsteen.html", {"request": request, "worp": worp})
Antwoord

De browser zegt alleen Internal Server Error. Onderaan de lange foutmelding in je terminal staat TypeError: unhashable type: 'dict'.

Die melding zegt niets over templates, en dat maakt hem lastig. Het eerste argument is tegenwoordig request, dus jouw bestandsnaam belandt op de plek van request en jouw dictionary op de plek van de bestandsnaam. FastAPI probeert dan een template op te zoeken met een dictionary als naam, en dat kan niet.

Zie je deze fout in je eigen code of in een voorbeeld dat je overneemt, zet dan request vooraan.

Opdracht 4: Make - Twee dobbelstenen​

Gooi met twee dobbelstenen en laat op de pagina beide worpen zien, en wat ze samen zijn: "Je gooide 3 en 5, samen 8."

Tip

Reken alles uit in Python en stuur drie waarden mee. De template telt niet zelf op; die vult alleen in.

Antwoord
templates/twee.htmlde server vult hem in, de browser toont hem
<!DOCTYPE html>
<html>
<body>
<p>Je gooide {{ eerste }} en {{ tweede }}, samen {{ totaal }}.</p>
</body>
</html>
main.pydraait op de server
@app.get("/twee")
async def twee(request: Request):
eerste = random.randint(1, 6)
tweede = random.randint(1, 6)
return templates.TemplateResponse(
request,
"twee.html",
{"eerste": eerste, "tweede": tweede, "totaal": eerste + tweede}
)

Opdracht 5: Make - Een profielkaart​

Maak een endpoint /kaart dat een dictionary met je naam, je leeftijd en je hobby als één waarde meestuurt, en toon ze op de pagina met de punt.

Tip

Stuur {"ik": {"naam": …, "leeftijd": …, "hobby": …}} mee. In de template schrijf je dan {{ ik.naam }}.

Antwoord
templates/kaart.htmlde server vult hem in, de browser toont hem
<!DOCTYPE html>
<html>
<body>
<h1>{{ ik.naam }}</h1>
<p>Leeftijd: {{ ik.leeftijd }}</p>
<p>Hobby: {{ ik.hobby }}</p>
</body>
</html>
main.pydraait op de server
@app.get("/kaart")
async def kaart(request: Request):
ik = {"naam": "Sam", "leeftijd": 15, "hobby": "gamen"}
return templates.TemplateResponse(request, "kaart.html", {"ik": ik})

Je template vult nu waarden uit Python in. In Een formulier beantwoorden met een template komt die waarde van de bezoeker.