Een formulier versturen (POST)
Hier bouw je op verder: Formulieren
Weet je dit niet meer zeker? Lees het terug voordat je verdergaat.
- Webontwikkeling Formulieren
Tot nu toe stuurde de browser alleen GET-verzoeken: hij haalt iets op, en wat hij meestuurt staat in de URL. Met een formulier stuurt hij iets naar je server, en daarvoor gebruikt hij een tweede soort verzoek: POST.
Het formulier
Maak static/pages/naam_form.html:
<!DOCTYPE html>
<html>
<body>
<h1>Wat is je naam?</h1>
<form method="post" action="/verstuur">
<input type="text" name="naam" required>
<button type="submit">Verstuur</button>
</form>
</body>
</html>
Belangrijke onderdelen
method="post"stuurt de data met POST.action="/verstuur"is het endpoint waar de data naartoe gaat.- Onder
name="naam"komt de waarde in Python binnen. Eenid, zoals in de web-cursus, gaat niet mee: alleen eenname. requiredlaat de browser het formulier pas versturen als het veld is ingevuld. Eenplaceholder="…"is de grijze tekst die in een leeg veld staat.
De endpoints
Form komt uit fastapi, net als FastAPI zelf. Zet hem daarom in die regel
bovenaan je main.py:
from fastapi import FastAPI, Form, Request
Zet daarna twee endpoints onder je andere endpoints: één dat het formulier toont, en één dat het verstuurde formulier ontvangt:
@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}!"}
- Regel 1-3:
Een gewone GET die het formulier toont. Het formulier is een HTML-bestand, dus
FileResponse. - Regel 5:
@app.postin plaats van@app.get: dit endpoint reageert alleen op een POST. Het pad/verstuuris deactionvan het formulier, enpostdemethod. Die twee moeten kloppen met de HTML, anders komt het verzoek nergens aan. - Regel 6:
Form(...)haalt het veldnaamuit het verstuurde formulier, niet uit de URL. De drie puntjes zijn hier echte Python, geen plek om iets in te vullen: ze betekenen dat het veld verplicht is. De parameter heetnaamomdat het veld in de HTMLname="naam"heeft; die twee namen moeten gelijk zijn. - Regel 7:
De print komt in je terminal terecht, niet in de browser. Zo zie je wat er binnenkomt.
Waarom moeten de namen gelijk zijn?
Schrijf je in HTML name="naam", dan stuurt de browser de data als naam=Jan. FastAPI zoekt vervolgens een Python-parameter die ook naam heet. Komen de namen niet overeen, dan vindt FastAPI de data niet en toont je browser:
{"detail":[{"type":"missing","loc":["body","naam"],"msg":"Field required","input":null}]}
"loc":["body","naam"] betekent: in de body van het verzoek, waar het formulier in zit, ontbreekt naam. In je terminal en in Netwerk staat de statuscode 422.
GET of POST?
| GET | POST | |
|---|---|---|
| Doel | Data ophalen | Data versturen |
| Data in | De URL | De body van het verzoek |
| Voorbeeld | Pagina laden, zoeken | Formulier versturen, inloggen |
Grofweg zeg je met GET "Geef me iets" en met POST "Hier is iets". Wat een formulier met POST verstuurt, staat niet in de adresbalk. In het tabblad Netwerk zie je het wel: klik op het verzoek en kies Payload, dan staat het onder Formuliergegevens (Form Data).
Testen
- Ga naar
http://127.0.0.1:8000/naam - Vul je naam in en klik Verstuur
- Je ziet:
{"bericht":"Hallo Jan!"} - In de terminal:
Naam ontvangen: Jan
Zie je "msg":"Field required" in je browser, en een 422 in je terminal? Controleer of de name in je HTML gelijk is aan de naam van de parameter in Python. Krijg je een 405 Method Not Allowed, dan stuur je een GET naar een POST-endpoint of andersom. 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
from fastapi import FastAPI, Form, 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}
)
@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}!"}
Opdrachten
Opdracht 1: Predict - Wat zie je?
<form method="post" action="/begroeting">
<input type="text" name="naam">
<button type="submit">Verstuur</button>
</form>
@app.post("/begroeting")
async def begroeting(naam: str = Form(...)):
return {"groet": f"Hallo {naam}!"}
Opdracht 2: Run
Maak het formulier en de endpoints uit het voorbeeld. Test of je een naam kunt invullen en het bericht terugkrijgt.
Opdracht 3: Investigate - Zonder name
Haal in naam_form.html het attribuut name="naam" helemaal weg, zodat er <input type="text" required> staat. Vul een naam in en verstuur. Wat gebeurt er?
Antwoord
Je krijgt dezelfde weigering als bij een verkeerde naam: "loc":["body","naam"] en "msg":"Field required", en een 422 in je terminal. De browser stuurt alleen velden mee die een name hebben. Zonder name gaat er dus niets mee, ook al heb je iets ingevuld.
Opdracht 4: Investigate - POST via de adresbalk
Wat gebeurt er als je http://127.0.0.1:8000/verstuur rechtstreeks in je adresbalk typt?
Tip
Als je een URL in je adresbalk typt, stuurt de browser altijd een GET-verzoek. Wat voor endpoint is /verstuur?
Antwoord
Je browser toont {"detail":"Method Not Allowed"}, en in Netwerk en je terminal staat de statuscode 405. De browser stuurt een GET, maar /verstuur is een POST-endpoint (@app.post). Een POST-endpoint bereik je via een formulier, niet door de URL in te typen.
Opdracht 5: Make - Eigen formulier
Maak een formulier voor je favoriete kleur, met een GET-endpoint dat het formulier toont en een POST-endpoint dat de kleur ontvangt. Print de kleur in de terminal.
Tip
Maak een HTML bestand met een <form method="post" action="/kleur"> en een <input name="kleur">. Maak twee endpoints: @app.get("/kleur") voor het formulier en @app.post("/kleur") voor het ontvangen.
Antwoord
<!DOCTYPE html>
<html>
<body>
<h1>Favoriete kleur</h1>
<form method="post" action="/kleur">
<input type="text" name="kleur" required>
<button type="submit">Verstuur</button>
</form>
</body>
</html>
@app.get("/kleur")
async def kleur_form():
return FileResponse("static/pages/kleur_form.html")
@app.post("/kleur")
async def kleur_ontvang(kleur: str = Form(...)):
print(f"Kleur ontvangen: {kleur}")
return {"kleur": kleur}
Opdracht 6: Make - Twee velden
Maak een formulier om je aan te melden, met naam en leeftijd, op het adres /aanmelden. Na het versturen krijg je beide waarden terug als JSON.
Tip
Je hebt twee <input> velden nodig in je HTML (name="naam" en name="leeftijd", die laatste met type="number") en twee Form(...) parameters in Python. leeftijd is wel een int en geen str.
Antwoord
<!DOCTYPE html>
<html>
<body>
<h1>Aanmelden</h1>
<form method="post" action="/aanmelden">
<input type="text" name="naam" required>
<input type="number" name="leeftijd" required>
<button type="submit">Verstuur</button>
</form>
</body>
</html>
@app.get("/aanmelden")
async def aanmelden_form():
return FileResponse("static/pages/aanmelden_form.html")
@app.post("/aanmelden")
async def aanmelden(naam: str = Form(...), leeftijd: int = Form(...)):
return {"naam": naam, "leeftijd": leeftijd}