Cheatsheet
Snelle referentie voor alles wat je hebt geleerd. Klik op een onderwerp om het te openen.
FastAPI
FastAPI app aanmaken
from fastapi import FastAPI
app = FastAPI()
Server starten
fastapi dev main.py
Open: http://127.0.0.1:8000
Je server openzetten voor het netwerk
fastapi dev main.py --host 0.0.0.0
Zoek je adres met ipconfig (Windows) of ip addr (macOS/Linux) en geef http://<jouw-adres>:8000 door.
Let op: iedereen op hetzelfde netwerk kan er dan bij.
GET endpoint (JSON)
@app.get("/")
async def root():
return {"bericht": "Hallo!"}
GET endpoint (HTML)
from fastapi.responses import HTMLResponse
@app.get("/pagina", response_class=HTMLResponse)
async def pagina():
return """
<html>
<body><h1>Hallo!</h1></body>
</html>
"""
Static files instellen
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")
HTML bestand serveren (FileResponse)
from fastapi.responses import FileResponse
@app.get("/")
async def home():
return FileResponse("static/pages/home.html")
POST endpoint (Form data)
from fastapi import Form
@app.post("/verstuur")
async def verstuur(naam: str = Form(...)):
return {"naam": naam}
Let op: de name in HTML moet matchen met de Python parameter.
Jinja2 template response
from fastapi import Request
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
@app.post("/groet")
async def groet(request: Request, naam: str = Form(...)):
return templates.TemplateResponse(
request,
"resultaat.html",
{"naam": naam}
)
Let op: request is het eerste argument, vóór de bestandsnaam.
Redirect na een POST
from fastapi.responses import RedirectResponse
@app.post("/opslaan")
async def opslaan(naam: str = Form(...)):
return RedirectResponse(url="/lijst", status_code=303)
Let op: zonder status_code=303 krijg je een 405. De standaard is 307, en die herhaalt je POST.
Path-parameter in de URL
@app.get("/bericht/{sleutel}")
async def bericht_detail(sleutel: str):
return {"sleutel": sleutel}
Let op: de naam tussen accolades moet gelijk zijn aan de parameternaam.
404 sturen als iets niet bestaat
from fastapi import HTTPException
@app.get("/bericht/{sleutel}")
async def bericht_detail(sleutel: str):
with SqliteDict("gastenboek.db") as db:
bericht = db.get(sleutel)
if bericht is None:
raise HTTPException(status_code=404, detail="Bestaat niet")
return bericht
Let op: raise, niet return.
DELETE endpoint (verwijderen zonder herladen)
from fastapi.responses import HTMLResponse
@app.delete("/bericht/{sleutel}")
async def bericht_verwijderen(sleutel: str):
with SqliteDict("gastenboek.db") as db:
del db[sleutel]
db.commit()
return HTMLResponse("")
Een formulier zonder htmx kan alleen GET en POST; hx-delete stuurt een DELETE. Een leeg antwoord haalt het doel van de pagina.
Een cookie meegeven
@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...)):
antwoord = RedirectResponse(url="/berichten", status_code=303)
antwoord.set_cookie(key="naam", value=naam, max_age=60 * 60 * 24 * 30)
return antwoord
Let op: maak het antwoord eerst als variabele, anders heb je geen plek om de cookie op te zetten. max_age is de houdbaarheid in seconden.
Een cookie uitlezen
from fastapi import Cookie
@app.get("/gastenboek")
async def gastenboek_form(request: Request, naam: str = Cookie(default="")):
return templates.TemplateResponse(request, "gastenboek.html", {"naam": naam})
Weghalen doe je met antwoord.delete_cookie("naam").
Let op: een cookie staat bij de bezoeker en kan door hem veranderd worden. Gebruik hem niet voor iets waar rechten aan hangen.
Een sessie: gegevens op de server
import secrets
@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), sessie_id: str = Cookie(default="")):
if not sessie_id:
sessie_id = secrets.token_hex(16)
with SqliteDict("sessies.db") as sessies:
sessies[sessie_id] = {"naam": naam}
sessies.commit()
antwoord = RedirectResponse(url="/berichten", status_code=303)
antwoord.set_cookie(key="sessie_id", value=sessie_id, max_age=60 * 60 * 24 * 30)
return antwoord
Uitlezen:
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {})
Let op: in de cookie staat alleen het sessie-id, de gegevens staan op de server. Gebruik .get() met een standaardwaarde: bij een onbekend sessie-id bestaat de sleutel niet.
HTML
Basis HTML pagina
<!DOCTYPE html>
<html>
<head>
<title>Titel</title>
</head>
<body>
<h1>Heading</h1>
<p>Paragraaf</p>
</body>
</html>
CSS koppelen
<link rel="stylesheet" href="/static/css/style.css">
Afbeelding tonen
<img src="/static/foto.jpg" alt="Beschrijving">
Link naar andere pagina
<a href="/about">Ga naar About</a>
Let op: link naar het endpoint, niet naar het bestand.
Formulier (POST)
<form method="post" action="/verstuur">
<input type="text" name="naam" required>
<button type="submit">Verstuur</button>
</form>
Let op: name in HTML moet matchen met de parameter in Python.
Template variabele (Jinja2)
In de template:
<h1>Hallo {{ naam }}!</h1>
Wordt vervangen door de waarde uit Python.
Lijst herhalen in een template (for-lus)
<ul>
{% for bericht in berichten %}
<li>{{ bericht.naam }}: {{ bericht.bericht }}</li>
{% endfor %}
</ul>
Binnen de lus telt {{ loop.index }} vanaf 1.
Lege lijst opvangen (if en else)
{% if berichten %}
<p>Er zijn berichten</p>
{% else %}
<p>Nog geen berichten</p>
{% endif %}
Een stuk template hergebruiken (include)
templates/berichten_lijst.html is een stuk pagina zonder <html> eromheen. In de pagina:
<div id="berichten-lijst">
{% include "berichten_lijst.html" %}
</div>
Hetzelfde bestand geeft je endpoint terug als antwoord op een htmx-verzoek.
JavaScript
JavaScript koppelen aan je pagina
Bestand in static/js/app.js, en in de <head> van je template:
<script src="/static/js/app.js" defer></script>
app.mount("/static", ...) serveert het al; aan main.py verandert niets.
Let op: zonder defer draait je script voordat de pagina er staat, en vindt querySelector niets.
Reageren op typen of klikken
const veld = document.querySelector("#bericht-veld");
const teller = document.querySelector("#teller");
veld.addEventListener("input", function () {
teller.textContent = 80 - veld.value.length + " tekens over";
});
htmx
htmx koppelen aan je pagina
Download htmx.min.js (de link staat bij Zonder herladen met htmx) naar static/js/, en in de <head>:
<script src="/static/js/htmx.min.js" defer></script>
Een verzoek zonder herladen (hx-post, hx-get, hx-delete)
<form hx-post="/gastenboek" hx-target="#berichten-lijst">
<button hx-get="/berichten/lijst" hx-target="#berichten-lijst">Ververs</button>
<button hx-delete="/bericht/{{ sleutel }}" hx-target="#bericht-{{ sleutel }}" hx-swap="outerHTML">Verwijderen</button>
Het endpoint geeft een stukje HTML terug (HTMLResponse("Bedankt") of een template zonder <html> eromheen), nooit een omleiding.
Waar het antwoord komt (hx-target en hx-swap)
hx-target="#id": het element dat het antwoord krijgt. Zonderhx-targetis dat het element met het attribuut zelf.hx-swap="innerHTML"(standaard): vervangt wat er in het doel staat.hx-swap="outerHTML": vervangt het doel zelf. Met een leeg antwoord verdwijnt het.
Wanneer het verzoek gaat (hx-trigger)
<div id="berichten-lijst" hx-get="/berichten/lijst" hx-trigger="every 10s">
Standaard: bij een klik op een knop of het versturen van een formulier.
Bevestiging vooraf (hx-confirm)
<button hx-delete="/bericht/{{ sleutel }}" hx-confirm="Dit bericht verwijderen?">Verwijderen</button>
Database (sqlitedict)
Installatie
python -m pip install sqlitedict
Data opslaan
from sqlitedict import SqliteDict
with SqliteDict("data.db") as db:
db["naam"] = "Jan"
db.commit() # NIET vergeten!
Data uitlezen
with SqliteDict("data.db") as db:
print(db["naam"])
Data veilig uitlezen (met default)
with SqliteDict("data.db") as db:
# Crasht niet als "naam" niet bestaat:
naam = db.get("naam", "Niet gevonden")
Data verwijderen
with SqliteDict("data.db") as db:
del db["naam"]
db.commit()
Alles bekijken
with SqliteDict("data.db") as db:
for key, value in db.items():
print(key, value)
Browser
Ontwikkelaarstools openen
Druk op F12, of klik met de rechtermuisknop op de pagina en kies Inspecteren. Dat laatste opent meteen het tabblad Elementen op het element waar je op klikte.
De tabbladen die je in deze cursus gebruikt:
- Console: fouten uit je JavaScript, met bestand en regelnummer
- Netwerk: elk verzoek van de pagina, met statuscode en herkomst
- App (Application; in Firefox Opslag): de cookies van de site
Herladen zonder cache
Met de ontwikkelaarstools open: rechtermuisknop op de herlaadknop, dan Cache wissen en geforceerd opnieuw laden. Sneltoets: Ctrl+Shift+R.
Zolang je aan je site werkt: tabblad Netwerk, vinkje Cache uitzetten aan.
Verzoeken bekijken
Tabblad Netwerk, herlaad de pagina. Eén regel per verzoek.
- Status
200: gevonden.404: het pad klopt niet, ofapp.mountontbreekt. - Grootte met (schijfcache): de browser heeft de server niet gevraagd.
- Klik een regel aan voor de headers, zoals
Cookie:bij een sessie.
Mappenstructuur
Compleet project
je-project/
├── main.py
├── static/
│ ├── css/
│ │ └── style.css
│ ├── pages/
│ │ ├── home.html
│ │ └── form.html
│ └── foto.jpg
└── templates/
└── resultaat.html