Les 5 · Module 1 · Flask-basis
Formulieren verwerken en valideren
Maak formulieren robuust: valideer op de server, toon foutmeldingen per veld, bewaar ingevulde waarden en gebruik Flask-WTF met CSRF-bescherming.
🎯 Na deze les kun je
- uitleggen waarom validatie op de server verplicht is, ook met HTML-validatie
- een formulier met Flask-WTF definiëren met validators
- foutmeldingen per veld tonen en ingevulde waarden laten staan
- flash-berichten gebruiken na een geslaagde actie
Het formulier van de kassa controleert nu met de hand of product en prijs kloppen. Bij twee velden gaat dat
nog, maar elk nieuw veld betekent nieuwe if-regels, foutteksten en het terugzetten van waarden. In deze les
leer je het patroon dat elke webapp gebruikt: valideren op de server, fouten per veld tonen, daarna
doorsturen met een melding. Je doet het eerst zelf, daarna met Flask-WTF, dat ook meteen CSRF-bescherming geeft.
Waarom op de server?
required, type="number" en pattern in HTML helpen de gebruiker, maar zijn geen beveiliging. Iedereen
kan met curl of de ontwikkelaarstools elk verzoek sturen dat hij wil. De regel is daarom:
Browservalidatie is voor gemak. Servervalidatie is voor correctheid.
Het patroon met de hand
from flask import flash
def lees_bedrag(tekst):
try:
bedrag = Decimal(tekst.strip().replace(",", "."))
except (InvalidOperation, AttributeError):
return None
return bedrag if bedrag >= 0 else None
@app.route("/kassa/toevoegen", methods=["GET", "POST"])
def toevoegen():
fouten, waarden = {}, {}
if request.method == "POST":
waarden = request.form
product = request.form.get("product", "").strip()
prijs = lees_bedrag(request.form.get("prijs", ""))
if not product:
fouten["product"] = "Vul een product in."
elif len(product) > 40:
fouten["product"] = "Maximaal 40 tekens."
if prijs is None:
fouten["prijs"] = "Vul een geldig bedrag in, bijvoorbeeld 2,80."
if not fouten:
bon.append((product, prijs))
flash(f"{product} staat op de bon.")
return redirect(url_for("bon_tonen"))
status = 400 if fouten else 200
return render_template("toevoegen.html", fouten=fouten, waarden=waarden), status
flash() bewaart een bericht in de sessie tot de volgende pagina het ophaalt. Daarvoor heeft Flask een
SECRET_KEY nodig (meer in les 7):
app.config["SECRET_KEY"] = "alleen-voor-ontwikkeling"
En in base.html:
{% with berichten = get_flashed_messages() %}
{% for b in berichten %}<p class="melding">{{ b }}</p>{% endfor %}
{% endwith %}
Voorspel eerst: je stuurt product= (leeg) en prijs=-1. Hoeveel foutmeldingen zie je, en welke statuscode?
Uitkomst
Twee meldingen (product en prijs) en status 400 Bad Request. Er wordt niets aan de bon toegevoegd en er
volgt geen redirect. De ingevulde waarden komen terug via waarden.
Flask-WTF: formulieren als klasse
pip install flask-wtf
from flask_wtf import FlaskForm
from wtforms import StringField, DecimalField, SubmitField
from wtforms.validators import DataRequired, Length, NumberRange
class ProductForm(FlaskForm):
product = StringField("Product", validators=[DataRequired("Vul een product in."), Length(max=40)])
prijs = DecimalField("Prijs", places=2, use_locale=False,
validators=[DataRequired("Vul een prijs in."), NumberRange(min=0)])
opslaan = SubmitField("Toevoegen")
@app.route("/kassa/toevoegen", methods=["GET", "POST"])
def toevoegen():
form = ProductForm()
if form.validate_on_submit():
bon.append((form.product.data, form.prijs.data))
flash(f"{form.product.data} staat op de bon.")
return redirect(url_for("bon_tonen"))
return render_template("toevoegen.html", form=form), (400 if form.errors else 200)
validate_on_submit() is True als het een POST is en alle validators slagen, inclusief de
CSRF-controle. De template:
<form method="post" novalidate>
{{ form.hidden_tag() }}
{% for veld in [form.product, form.prijs] %}
<p>{{ veld.label }} {{ veld() }}
{% for f in veld.errors %}<span class="fout">{{ f }}</span>{% endfor %}</p>
{% endfor %}
{{ form.opslaan() }}
</form>
form.hidden_tag() zet een verborgen veld csrf_token in het formulier. Zonder dat token weigert Flask-WTF
het formulier. Waarom dat belangrijk is, zie je in les 14.
DecimalField kent geen komma. Wil je 2,80 accepteren, maak dan een eigen veld dat in process_formdata
de komma vervangt (oefening 3). Met novalidate zet je de browservalidatie uit, zodat je tijdens het testen
de servermeldingen ziet.
Typische fouten
RuntimeError: A secret key is required:SECRET_KEYontbreekt (nodig voor flash en CSRF).- Formulier doet niets, geen fouten:
{{ form.hidden_tag() }}vergeten. De CSRF-fout staat inform.errors["csrf_token"]; toonform.errorseven om het te zien. curlkrijgt altijd 400: klopt,curlheeft geen CSRF-token. Test formulieren in de browser of in pytest (les 16), waar je CSRF uit kunt zetten.
Oefenen
- Voeg een veld
aantaltoe (geheel getal, 1 t/m 99, standaard 1) en zetaantal × prijsop de bon. - Toon na het leegmaken van de bon een flash-bericht "De bon is leeg." en geef flash-berichten een
categorie (
flash(…, "succes")) die je als CSS-class gebruikt. - Schrijf een veld
KommaDecimalFielddat ook2,80accepteert. - Voeg een eigen validator toe die het product
testweigert met de melding "Kies een echt product".
Modelantwoord oefening 1
from wtforms import IntegerField
aantal = IntegerField("Aantal", default=1, validators=[NumberRange(min=1, max=99)])
In de route: bon.append((f"{form.aantal.data}× {form.product.data}", form.prijs.data * form.aantal.data)).
Later, met een database, bewaar je aantal en stukprijs apart; nu volstaat de tuple.
Modelantwoord oefening 2
flash("De bon is leeg.", "succes")
{% for cat, b in get_flashed_messages(with_categories=true) %}
<p class="melding {{ cat }}">{{ b }}</p>
{% endfor %}
Zonder categorie is die "message".
Modelantwoord oefening 3
class KommaDecimalField(DecimalField):
def process_formdata(self, valuelist):
if valuelist:
valuelist = [valuelist[0].replace(",", ".")]
super().process_formdata(valuelist)
Je past alleen de ruwe tekst aan en laat de rest (omzetten, foutmelding) aan DecimalField.
Modelantwoord oefening 4
from wtforms.validators import ValidationError
class ProductForm(FlaskForm):
...
def validate_product(self, veld):
if veld.data.strip().lower() == "test":
raise ValidationError("Kies een echt product")
Een methode validate_<veldnaam> wordt automatisch als extra validator van dat veld gebruikt.
Lokaal oefenen
Geen live lab. Test in de browser met novalidate aan, zodat je de servermeldingen ziet. Probeer bewust
lege velden, letters in de prijs, een negatieve prijs en een productnaam van 41 tekens.
Samenvatting
- Valideer altijd op de server; HTML-validatie is gemak.
- Patroon:
POST→ valideren → fouten tonen met status 400 of opslaan, flash en redirect. - Flask-WTF geeft formulierklassen, validators en CSRF-tokens;
validate_on_submit()doet alles in één keer. - Volgende les: de groeiende
app.pyopdelen met blueprints.
📝 Quiz: formulieren verwerken en valideren
Dit was de laatste gratis les
Je hebt les 1–5 van Webapps met Flask gratis gevolgd. Wil je verder? Met één aankoop (€ 49,00, eenmalig) krijg je de overige 15 lessen, de live labs en het certificaat. Geen abonnement.