Ga naar hoofdinhoud

Een formulier versturen (POST)

Hier bouw je op verder: Formulieren

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

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:

static/pages/naam_form.htmlgaat zoals hij is naar de browser
<!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. Een id, zoals in de web-cursus, gaat niet mee: alleen een name.
  • required laat de browser het formulier pas versturen als het veld is ingevuld. Een placeholder="…" 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:

main.pydraait op de server
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:

main.pydraait op de server
@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}!"}
  1. Regel 1-3:

    Een gewone GET die het formulier toont. Het formulier is een HTML-bestand, dus FileResponse.

  2. Regel 5:

    @app.post in plaats van @app.get: dit endpoint reageert alleen op een POST. Het pad /verstuur is de action van het formulier, en post de method. Die twee moeten kloppen met de HTML, anders komt het verzoek nergens aan.

  3. Regel 6:

    Form(...) haalt het veld naam uit 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 heet naam omdat het veld in de HTML name="naam" heeft; die twee namen moeten gelijk zijn.

  4. 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?​

GETPOST
DoelData ophalenData versturen
Data inDe URLDe body van het verzoek
VoorbeeldPagina laden, zoekenFormulier 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​

  1. Ga naar http://127.0.0.1:8000/naam
  2. Vul je naam in en klik Verstuur
  3. Je ziet: {"bericht":"Hallo Jan!"}
  4. In de terminal: Naam ontvangen: Jan
Gaat er iets mis?

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.

main.pydraait op de server
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}!"}
Controlevraag
Je verandert in naam_form.html action="/verstuur" in action="/naam". De endpoints in main.py laat je zoals ze zijn. Wat krijg je na het versturen?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Opdrachten​

Opdracht 1: Predict - Wat zie je?​

static/pages/begroeting_form.htmlgaat zoals hij is naar de browser
<form method="post" action="/begroeting">
<input type="text" name="naam">
<button type="submit">Verstuur</button>
</form>
main.pydraait op de server
@app.post("/begroeting")
async def begroeting(naam: str = Form(...)):
return {"groet": f"Hallo {naam}!"}
Voorspel
Je vult Sarah in en klikt op Verstuur. Wat zie je?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

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
static/pages/kleur_form.htmlgaat zoals hij is naar de browser
<!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>
main.pydraait op de server
@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
static/pages/aanmelden_form.htmlgaat zoals hij is naar de browser
<!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>
main.pydraait op de server
@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}