Ga naar hoofdinhoud

POST met templates

Hier bouw je op verder

In de vorige les gaf je POST endpoint JSON terug. In deze les leer je hoe je een mooie HTML pagina toont met Jinja2 templates.

Templates configureren

Maak een templates folder en configureer Jinja2 in main.py:

je-project/
├── main.py
├── static/
└── templates/
└── resultaat.html
from fastapi import FastAPI, Form, Request
from fastapi.responses import FileResponse
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 1:

    Request is nieuw in de import: een template heeft straks het verzoek zelf nodig.

  2. Regel 8:

    Jinja2Templates krijgt de map waarin je templates staan. Vanaf nu vraag je een template op bij templates, met alleen de bestandsnaam.

Voorbeeld

Formulier (static/pages/groet_form.html):

<!DOCTYPE html>
<html>
<body>
<h1>Hoe heet je?</h1>
<form method="post" action="/groet">
<input type="text" name="naam" required>
<button type="submit">Verstuur</button>
</form>
</body>
</html>

Template (templates/groet_resultaat.html):

<!DOCTYPE html>
<html>
<body>
<h1>Hallo {{ naam }}!</h1>
<a href="/groet">Terug</a>
</body>
</html>

{{ naam }} wordt vervangen door de waarde uit Python.

Endpoints (main.py):

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

@app.post("/groet")
async def groet_verwerk(request: Request, naam: str = Form(...)):
return templates.TemplateResponse(
request,
"groet_resultaat.html",
{"naam": naam}
)
  1. Regel 5:

    Dezelfde URL als de GET erboven, maar met @app.post: dat mag, want een GET en een POST op /groet zijn twee verschillende endpoints.

  2. Regel 6:

    request: Request is een extra parameter die FastAPI zelf invult met het verzoek. Je geeft hem door aan de template.

  3. Regel 7-11:

    TemplateResponse rendert een template: eerst het verzoek, dan de bestandsnaam in de templates-map, dan een dictionary met de waardes. Elke sleutel wordt in de template een naam: {"naam": naam} vult {{ naam }} in.

Waarom moet "request" altijd mee?

FastAPI heeft het request-object nodig om te weten welke gebruiker de pagina opvraagt. Daarom is het het eerste argument, vóór de bestandsnaam.

Kom je online een voorbeeld tegen waarin request juist ín het dictionary staat, zo?

templates.TemplateResponse("groet_resultaat.html", {"request": request, "naam": naam})

Dat is de oude schrijfwijze. Die werkt niet meer, en de foutmelding die je krijgt legt dat niet uit — zie Er gaat iets mis.

Testen

  1. Ga naar http://127.0.0.1:8000/groet
  2. Vul je naam in en klik Verstuur
  3. Je ziet een mooie HTML pagina: "Hallo [jouw naam]."
Gaat er iets mis?

Krijg je een TemplateNotFound error? Check of je template in de templates/ folder staat en of de bestandsnaam klopt. Bekijk de troubleshooting pagina voor meer oplossingen.

Opdrachten

Opdracht 1: Predict - Wat zie je?

Template:

<h1>Welkom {{ naam }}!</h1>
<p>Leeftijd: {{ leeftijd }}</p>

Python:

return templates.TemplateResponse(request, "welkom.html", {
"naam": "Jan", "leeftijd": 16
})

Vraag: Wat zie je op de pagina?

Tip

De {{ }} variabelen worden vervangen door de waarden uit het Python dictionary. Welke waarden staan er voor naam en leeftijd?

Antwoord
Welkom Jan!
Leeftijd: 16

Opdracht 2: Run

Maak het formulier, de template en de endpoints uit het voorbeeld. Test of het werkt door een naam in te vullen.

Opdracht 3: Investigate - De oude schrijfwijze

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

return templates.TemplateResponse("groet_resultaat.html", {"request": request, "naam": naam})
Antwoord

Je krijgt TypeError: unhashable type: 'dict'.

Die melding zegt niets over templates, en dat maakt 'm lastig. Wat er gebeurt: 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 request vooraan.

Opdracht 4: Make - Favoriete eten

Maak een formulier voor je favoriete eten. Na versturen toont een template: "Jouw favoriete eten is: [eten]"

Tip

Je hebt nodig: een HTML formulier met name="eten", een template met {{ eten }}, en een POST endpoint dat TemplateResponse teruggeeft met request vooraan en {"eten": eten} erachter.

Antwoord

static/pages/eten_form.html:

<!DOCTYPE html>
<html>
<body>
<h1>Favoriete eten</h1>
<form method="post" action="/eten">
<input type="text" name="eten" required>
<button type="submit">Verstuur</button>
</form>
</body>
</html>

templates/eten_resultaat.html:

<!DOCTYPE html>
<html>
<body>
<h1>Jouw favoriete eten is: {{ eten }}</h1>
<a href="/eten">Terug</a>
</body>
</html>
@app.get("/eten")
async def eten_form():
return FileResponse("static/pages/eten_form.html")

@app.post("/eten")
async def eten_verwerk(request: Request, eten: str = Form(...)):
return templates.TemplateResponse(
request,
"eten_resultaat.html",
{"eten": eten}
)

Opdracht 5: Make - Profiel pagina

Maak een formulier met naam, leeftijd en hobby. Na versturen toont een template alle drie de waarden.

Tip

Je hebt drie <input> velden nodig, drie Form(...) parameters, en drie {{ }} variabelen in je template. Vergeet niet alle drie de waarden in het dictionary te zetten.

Antwoord

templates/profiel.html:

<!DOCTYPE html>
<html>
<body>
<h1>Profiel van {{ naam }}</h1>
<p>Leeftijd: {{ leeftijd }}</p>
<p>Hobby: {{ hobby }}</p>
</body>
</html>
@app.post("/profiel")
async def profiel(request: Request, naam: str = Form(...), leeftijd: int = Form(...), hobby: str = Form(...)):
return templates.TemplateResponse(
request,
"profiel.html",
{"naam": naam, "leeftijd": leeftijd, "hobby": hobby}
)