HTML van een bezoeker: templates escapen vanzelf
Met escape uit les 2 moet je aan elke plek denken, en in de "Er
gaat iets mis" van die les zag je hoe makkelijk je er een vergeet. Een template
van Jinja doet het voor je: alles tussen {{ }} escapet hij, zonder dat je
erom vraagt.
De lijst met een template
Maak templates/berichten_lijst.html, zoals in het recept Verversen van het
htmx-overzicht:
<ul>
{% for bericht in berichten %}
<li>{{ bericht.naam }}: {{ bericht.bericht }}</li>
{% endfor %}
</ul>
Het endpoint geeft nu het template terug, in plaats van zelf HTML te bouwen:
@app.get("/berichten/lijst")
async def berichten_lijst(request: Request):
with SqliteDict("gastenboek.db") as db:
alle_berichten = list(db.values())
return templates.TemplateResponse(request, "berichten_lijst.html", {"berichten": alle_berichten})
- Regel 2:
Een
TemplateResponseheeft derequestnodig, net als in de andere lessen met templates. - Regel 5:
Geen f-string en geen
escapemeer: de berichten gaan naar het template, en Jinja zet ze erin.
Zet bovenaan main.py wat daarvoor nodig is:
from fastapi import Request
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
Het antwoord na het versturen blijft één zin in een f-string, met escape(naam).
Voor één zin is een template wat veel.
Doe de test opnieuw
Gooi gastenboek.db weg, start de server opnieuw, en vul als Sara weer
<b>vet</b> in. lijst.py uit les 1 geeft nu:
<ul>
<li>Sara: <b>vet</b></li>
</ul>
De witruimte komt uit het template: de regels met {% for %} en {% endfor %}
worden elk een regel met alleen vier spaties. Wat Sara typte staat er, maar als
tekst, net als met escape.
Er gaat iets mis
Je noemt het template berichten_lijst.jinja, en <b>vet</b> is weer vet.
Oorzaak: Jinja escapet in FastAPI alleen templates die op .html, .htm of
.xml eindigen. Een andere extensie zet het escapen stil uit: geen foutmelding,
geen waarschuwing.
Oplossing: houd je templates op .html, en pas de naam in
TemplateResponse mee aan.
Opdrachten
Opdracht 1: Modify - Namen vet, veilig
Maak in de lijst de naam van elke schrijver vet, zonder dat een bezoeker zelf HTML kan invullen.
Tip
De <b> mag in het template staan. Het is jouw HTML, niet die van de bezoeker.
Antwoord
<ul>
{% for bericht in berichten %}
<li><b>{{ bericht.naam }}</b>: {{ bericht.bericht }}</li>
{% endfor %}
</ul>
De tags komen van jou, de inhoud van de bezoeker. Jinja escapet alleen wat
tussen {{ }} staat, dus de naam blijft tekst en jouw <b> blijft HTML.
Opdracht 2: Investigate - Waarom nog escape?
De lijst gebruikt nu een template. Waarom staat er in gastenboek_opslaan nog
steeds escape(naam)?
Antwoord
Omdat dat antwoord geen template is maar een f-string in een HTMLResponse.
Een template escapet alleen wat door het template gaat. Haal je daar escape
weg, dan is de naam na het versturen weer HTML.
Zo ziet je main.py er nu uit
import time
from html import escape
from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlitedict import SqliteDict
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), bericht: str = Form(...)):
with SqliteDict("gastenboek.db") as db:
db[f"bericht_{time.time_ns()}"] = {"naam": naam, "bericht": bericht}
db.commit()
return HTMLResponse(f"Bedankt, {escape(naam)}. Je bericht staat in het gastenboek.")
@app.get("/berichten/lijst")
async def berichten_lijst(request: Request):
with SqliteDict("gastenboek.db") as db:
alle_berichten = list(db.values())
return templates.TemplateResponse(request, "berichten_lijst.html", {"berichten": alle_berichten})
Naast main.py staan static/gastenboek.html, static/js/htmx.min.js en
templates/berichten_lijst.html.
Door naar les 4: |safe, de bescherming uitzetten.