Doorsturen na opslaan (redirect)
Je gastenboek werkt, maar na het versturen van een bericht sta je op een doodlopende pagina. Druk je daar op F5, dan vraagt je browser of je het formulier nog een keer wilt versturen. Zeg je ja, dan staat hetzelfde bericht twee keer in je database. Daarom stuur je de bezoeker na het opslaan door naar de berichten.
RedirectResponse
Met RedirectResponse stuur je de browser door naar een andere URL. Hij komt
uit fastapi.responses, net als FileResponse en HTMLResponse. Zet hem
daarom in die regel bovenaan je main.py:
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
Vervang dan je POST-endpoint /gastenboek uit Elk bericht bewaren: een eigen sleutel. Alleen de laatste regel verandert:
@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), bericht: str = Form(...)):
with SqliteDict("gastenboek.db") as db:
sleutel = f"bericht_{time.time_ns()}"
db[sleutel] = {"naam": naam, "bericht": bericht}
db.commit()
return RedirectResponse(url="/berichten", status_code=303)
- Regel 3-6:
Het opslaan is hetzelfde als in Elk bericht bewaren: een eigen sleutel: een eigen sleutel per bericht, met
import timebovenaan je bestand. - Regel 7:
Het endpoint geeft nu geen pagina terug, maar een opdracht aan de browser: ga naar
/berichten. De browser doet dat meteen, en de bezoeker ziet zijn eigen bericht achteraan staan. Nu nog als JSON; in de volgende les wordt/berichteneen echte pagina.
Waarom 303
Zonder status_code=303 werkt het niet. RedirectResponse stuurt standaard een 307, en 307 betekent: doe hetzelfde verzoek nog een keer op de nieuwe URL. Jouw verzoek was een POST, dus de browser stuurt een POST naar /berichten. Dat endpoint is een @app.get, dus je krijgt 405 Method Not Allowed.
Een 303 zegt wél wat je bedoelt: ga naar die URL met een GET.
Eerst een POST en dan doorsturen naar een GET: dat heet het post/redirect/get-patroon. Bijna elke website die je gebruikt doet dit na een formulier.
Testen
- Vul het gastenboek in en klik Verstuur
- Je belandt op
/berichtenen ziet je eigen bericht staan, nog als JSON - Druk op F5. De pagina herlaadt, zonder vraag over opnieuw versturen
Krijg je 405 Method Not Allowed meteen na het versturen? Dan mist status_code=303. Meer oplossingen staan op 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.
import random
import time
from fastapi import FastAPI, Form, Request
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
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.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}
)
@app.get("/naam")
async def toon_formulier():
return FileResponse("static/pages/naam_form.html")
@app.post("/verstuur")
async def ontvang_naam(naam: str = Form(...)):
print(f"Naam ontvangen: {naam}")
return {"bericht": f"Hallo {naam}!"}
@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}
)
@app.get("/opslaan")
async def naam_formulier():
return FileResponse("static/pages/naam_opslaan.html")
@app.post("/opslaan")
async def naam_opslaan(naam: str = Form(...)):
with SqliteDict("namen.db") as db:
db["laatste_naam"] = naam
db.commit()
return {"bericht": f"'{naam}' opgeslagen!"}
@app.get("/toon_naam")
async def toon_naam():
with SqliteDict("namen.db") as db:
naam = db.get("laatste_naam", "Nog niets opgeslagen")
return {"naam": naam}
@app.get("/gastenboek")
async def gastenboek_form():
return FileResponse("static/pages/gastenboek_form.html")
@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), bericht: str = Form(...)):
with SqliteDict("gastenboek.db") as db:
sleutel = f"bericht_{time.time_ns()}"
db[sleutel] = {"naam": naam, "bericht": bericht}
db.commit()
return RedirectResponse(url="/berichten", status_code=303)
@app.get("/berichten")
async def berichten():
with SqliteDict("gastenboek.db") as db:
alle_berichten = list(db.values())
return {"berichten": alle_berichten}
Laat je server draaien en typ in een tweede terminal python controleer.py berichten-opslaan. Het script kijkt of alles uit dit hoofdstuk werkt; je downloadt het op Project checken.
Speel na wat er bij één klik heen en weer gaat, met briefjes en zonder computer: Het verzoek-rollenspel. Je docent kan de hand-out printen.
Opdrachten
Opdracht 1: Predict - Waar kom je uit?
@app.post("/inschrijven")
async def inschrijven(naam: str = Form(...)):
return RedirectResponse(url="/bedankt", status_code=303)
@app.get("/bedankt")
async def bedankt():
return {"bericht": "Bedankt"}
Opdracht 2: Run
Pas je /gastenboek POST-endpoint aan zodat het doorstuurt naar /berichten. Verstuur een bericht en kijk of je op de lijst uitkomt.
Opdracht 3: Investigate - Zonder 303
Haal status_code=303 weg en verstuur nog een keer.
Antwoord
Je krijgt 405 Method Not Allowed. Zonder dat getal stuurt FastAPI een 307, en dan herhaalt de browser je POST op /berichten. Daar staat alleen een @app.get, dus het antwoord is: dit endpoint doet geen POST. Je bericht is wel opgeslagen; alleen het doorsturen ging mis. Ga je terug en verstuur je hetzelfde formulier nog een keer, dan staat het er twee keer in.
Opdracht 4: Make - Annuleren
Maak een endpoint /annuleren dat niets opslaat en de bezoeker direct doorstuurt naar /berichten. Zet er een link naartoe op je gastenboekformulier.
Tip
Een bezoeker komt hier via een gewone link, dus het is een @app.get. Een redirect vanuit een GET heeft geen 303 nodig.
Antwoord
@app.get("/annuleren")
async def annuleren():
return RedirectResponse(url="/berichten")
Zet onder het formulier:
<a href="/annuleren">Annuleren</a>
Hier mag de standaard 307 blijven staan: het oorspronkelijke verzoek was al een GET, dus het herhalen daarvan levert precies op wat je wilt.
Door naar Alles tonen: een for-lus in je template.