Flask – 04 Formulare und Datenverarbeitung

Flask – 04 Formulare und Datenverarbeitung

Formulare und Datenverarbeitung

Voraussetzungen

Bevor du mit diesem Modul startest, solltest du bereits vertraut sein mit:

Warum Datenvalidierung?

In den bisherigen Beispielen haben wir Formulardaten einfach angenommen und angezeigt. Aber in echten Anwendungen musst du Daten überprüfen, bevor du sie verarbeitest:

  • Ist die E-Mail-Adresse gültig?
  • Ist das Passwort stark genug?
  • Wurden alle Pflichtfelder ausgefüllt?
  • Ist das Alter eine gültige Zahl?

Warum ist das wichtig?

  • Sicherheit: Verhindere schädliche Eingaben
  • Datenqualität: Stelle sicher, dass Daten verwendbar sind
  • Benutzererfahrung: Gib klares Feedback bei Fehlern
  • Vermeidung von Abstürzen: Verhindere Fehler durch ungültige Daten

Validierungsfunktionen erstellen

Grundprinzip

Validierungsfunktionen prüfen Daten und geben True (gültig) oder False (ungültig) zurück.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# app.py
def validiere_email(email):
    """Prüft, ob eine E-Mail-Adresse gültig aussieht"""
    return "@" in email and "." in email and len(email) >= 5

def validiere_alter(alter_str):
    """Prüft, ob das Alter eine gültige Zahl zwischen 10 und 100 ist"""
    try:
        alter = int(alter_str)
        return 10 <= alter <= 100
    except ValueError:
        return False

def validiere_passwort(passwort):
    """Prüft, ob das Passwort mindestens 8 Zeichen lang ist"""
    return len(passwort) >= 8

So funktioniert’s:

  1. Funktion erhält den zu prüfenden Wert
  2. Führt Prüfungen durch
  3. Gibt True (OK) oder False (Fehler) zurück

Best Practice: Docstrings

Schreibe immer Docstrings (die Texte in """..."""), um zu erklären, was die Funktion prüft. Das hilft dir und anderen, den Code zu verstehen!

Beispiel: E-Mail-Validierung (erweitert)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def validiere_email(email):
    """
    Prüft, ob eine E-Mail-Adresse gültig ist.

    Kriterien:
    - Muss @ enthalten
    - Muss . enthalten
    - @ muss vor dem letzten . kommen
    - Mindestens 5 Zeichen lang
    """
    if not email or len(email) < 5:
        return False

    if "@" not in email or "." not in email:
        return False

    # Finde Position von @ und letztem .
    at_position = email.index("@")
    dot_position = email.rfind(".")

    # @ muss vor dem letzten . kommen
    if at_position >= dot_position:
        return False

    return True

Validierung mit Fehler-Nachrichten

Manchmal möchtest du nicht nur wissen, ob etwas falsch ist, sondern warum:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def validiere_passwort_erweitert(passwort):
    """
    Prüft Passwort und gibt Fehlertext zurück (oder None bei Erfolg)
    """
    if len(passwort) < 8:
        return "Passwort muss mindestens 8 Zeichen lang sein"

    if not any(c.isupper() for c in passwort):
        return "Passwort muss mindestens einen Großbuchstaben enthalten"

    if not any(c.isdigit() for c in passwort):
        return "Passwort muss mindestens eine Zahl enthalten"

    return None  # Kein Fehler

Verwendung:

1
2
3
4
5
fehler = validiere_passwort_erweitert("test")
if fehler:
    print(fehler)  # "Passwort muss mindestens 8 Zeichen lang sein"
else:
    print("Passwort ist gültig!")

Komplexes Registrierungsformular

Lass uns ein vollständiges Registrierungsformular mit Validierung erstellen.

Python-Code (app.py)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# app.py
from flask import Flask, render_template, request

app = Flask(__name__)

# Validierungsfunktionen
def validiere_username(username):
    """Username muss 3-20 Zeichen lang sein, nur Buchstaben und Zahlen"""
    if not username or len(username) < 3 or len(username) > 20:
        return False
    return username.isalnum()  # Nur Buchstaben und Zahlen

def validiere_email(email):
    """Einfache E-Mail-Validierung"""
    return "@" in email and "." in email and len(email) >= 5

def validiere_alter(alter_str):
    """Alter muss zwischen 10 und 100 sein"""
    try:
        alter = int(alter_str)
        return 10 <= alter <= 100
    except:
        return False

def validiere_passwort(passwort):
    """Passwort muss mindestens 8 Zeichen haben"""
    return len(passwort) >= 8


@app.route("/registrierung", methods=['GET', 'POST'])
def registrierung():
    fehler = []  # Liste für Fehlermeldungen

    if request.method == 'POST':
        # Daten aus Formular holen
        username = request.form.get('username', '').strip()
        email = request.form.get('email', '').strip()
        alter = request.form.get('alter', '').strip()
        passwort = request.form.get('passwort', '')

        # Validierung durchführen
        if not username:
            fehler.append("Benutzername darf nicht leer sein")
        elif not validiere_username(username):
            fehler.append("Benutzername muss 3-20 Zeichen lang sein (nur Buchstaben und Zahlen)")

        if not email:
            fehler.append("E-Mail darf nicht leer sein")
        elif not validiere_email(email):
            fehler.append("E-Mail-Adresse ist ungültig")

        if not alter:
            fehler.append("Alter darf nicht leer sein")
        elif not validiere_alter(alter):
            fehler.append("Alter muss zwischen 10 und 100 sein")

        if not passwort:
            fehler.append("Passwort darf nicht leer sein")
        elif not validiere_passwort(passwort):
            fehler.append("Passwort muss mindestens 8 Zeichen lang sein")

        # Wenn keine Fehler: Erfolgreich!
        if not fehler:
            return render_template("registrierung_erfolg.html", username=username)

        # Wenn Fehler: Formular erneut anzeigen mit Fehlern
        return render_template("registrierung.html",
                               fehler=fehler,
                               username=username,
                               email=email,
                               alter=alter)

    # GET-Request: Leeres Formular anzeigen
    return render_template("registrierung.html", fehler=[])


if __name__ == "__main__":
    app.run(debug=True)

Template: Registrierungsformular

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!-- templates/registrierung.html -->
{% extends "base.html" %}

{% block title %}Registrierung{% endblock %}

{% block content %}
<h1>Registrierung</h1>

<!-- Fehler anzeigen, falls vorhanden -->
{% if fehler %}
    <div class="fehler-box">
        <h3>Bitte korrigiere folgende Fehler:</h3>
        <ul>
        {% for fehler_text in fehler %}
            <li>{{ fehler_text }}</li>
        {% endfor %}
        </ul>
    </div>
{% endif %}

<form method="POST" action="/registrierung">
    <div class="form-group">
        <label for="username">Benutzername:</label>
        <input type="text" id="username" name="username"
               value="{{ username }}" required>
        <small>3-20 Zeichen, nur Buchstaben und Zahlen</small>
    </div>

    <div class="form-group">
        <label for="email">E-Mail:</label>
        <input type="email" id="email" name="email"
               value="{{ email }}" required>
    </div>

    <div class="form-group">
        <label for="alter">Alter:</label>
        <input type="number" id="alter" name="alter"
               value="{{ alter }}" min="10" max="100" required>
    </div>

    <div class="form-group">
        <label for="passwort">Passwort:</label>
        <input type="password" id="passwort" name="passwort" required>
        <small>Mindestens 8 Zeichen</small>
    </div>

    <button type="submit" class="btn-primary">Registrieren</button>
</form>
{% endblock %}

Template: Erfolgsseite

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- templates/registrierung_erfolg.html -->
{% extends "base.html" %}

{% block title %}Registrierung erfolgreich{% endblock %}

{% block content %}
<div class="erfolg-box">
    <h1>Willkommen, {{ username }}!</h1>
    <p>Deine Registrierung war erfolgreich.</p>
    <a href="/" class="btn-primary">Zur Startseite</a>
</div>
{% endblock %}

CSS für Fehler und Erfolg

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* static/style.css */
.fehler-box {
    background-color: #f8d7da;
    border: 1px solid #f5c6cb;
    color: #721c24;
    padding: 15px;
    border-radius: 5px;
    margin-bottom: 20px;
}

.fehler-box ul {
    margin: 10px 0 0 0;
    padding-left: 20px;
}

.erfolg-box {
    background-color: #d4edda;
    border: 1px solid #c3e6cb;
    color: #155724;
    padding: 30px;
    border-radius: 5px;
    text-align: center;
}

.form-group {
    margin-bottom: 15px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
}

.form-group input {
    width: 100%;
    padding: 8px;
    border: 1px solid #ddd;
    border-radius: 4px;
    box-sizing: border-box;
}

.form-group small {
    display: block;
    margin-top: 5px;
    color: #666;
    font-size: 0.9em;
}

.btn-primary {
    background-color: #007bff;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1em;
}

.btn-primary:hover {
    background-color: #0056b3;
}

Wichtige Details

Warum value="{{ username }}"?

  • Wenn das Formular Fehler enthält, bleiben die bereits eingegebenen Werte erhalten
  • Der Benutzer muss nicht alles neu eingeben
  • Nur beim Passwort lassen wir das Feld leer (Sicherheit!)

Warum .strip()?

  • Entfernt Leerzeichen am Anfang und Ende
  • Verhindert " " als gültigen Benutzernamen

Daten in Listen speichern (CRUD-Grundlagen)

Jetzt lernen wir, wie man Daten persistent (über mehrere Requests hinweg) speichert. Wir nutzen eine globale Liste (in echten Apps würdest du eine Datenbank verwenden).

Beispiel: Gästebuch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# app.py
from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

# Globale Liste für Gästebuch-Einträge
gaestebuch = []


@app.route("/gaestebuch", methods=['GET', 'POST'])
def gaestebuch_anzeigen():
    if request.method == 'POST':
        name = request.form.get('name', '').strip()
        nachricht = request.form.get('nachricht', '').strip()

        # Validierung
        fehler = []
        if not name:
            fehler.append("Name darf nicht leer sein")
        if not nachricht:
            fehler.append("Nachricht darf nicht leer sein")

        if not fehler:
            # Eintrag zur Liste hinzufügen
            eintrag = {
                "name": name,
                "nachricht": nachricht,
                "id": len(gaestebuch) + 1
            }
            gaestebuch.append(eintrag)

            # Redirect nach POST (Post-Redirect-Get Pattern)
            return redirect(url_for('gaestebuch_anzeigen'))

        # Bei Fehlern: Formular mit Fehlern anzeigen
        return render_template("gaestebuch.html",
                               eintraege=gaestebuch,
                               fehler=fehler,
                               name=name,
                               nachricht=nachricht)

    # GET: Zeige Gästebuch an
    return render_template("gaestebuch.html", eintraege=gaestebuch, fehler=[])


if __name__ == "__main__":
    app.run(debug=True)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!-- templates/gaestebuch.html -->
{% extends "base.html" %}

{% block title %}Gästebuch{% endblock %}

{% block content %}
<h1>Gästebuch</h1>

<!-- Neuen Eintrag erstellen -->
<div class="formular-bereich">
    <h2>Neuer Eintrag</h2>

    {% if fehler %}
        <div class="fehler-box">
            <ul>
            {% for fehler_text in fehler %}
                <li>{{ fehler_text }}</li>
            {% endfor %}
            </ul>
        </div>
    {% endif %}

    <form method="POST" action="/gaestebuch">
        <div class="form-group">
            <label for="name">Name:</label>
            <input type="text" id="name" name="name" value="{{ name }}" required>
        </div>

        <div class="form-group">
            <label for="nachricht">Nachricht:</label>
            <textarea id="nachricht" name="nachricht" rows="4" required>{{ nachricht }}</textarea>
        </div>

        <button type="submit" class="btn-primary">Eintragen</button>
    </form>
</div>

<!-- Einträge anzeigen -->
<div class="eintraege-bereich">
    <h2>Einträge ({{ eintraege|length }})</h2>

    {% if eintraege %}
        {% for eintrag in eintraege|reverse %}
            <div class="eintrag">
                <strong>{{ eintrag.name }}</strong>
                <p>{{ eintrag.nachricht }}</p>
                <small>Eintrag #{{ eintrag.id }}</small>
            </div>
        {% endfor %}
    {% else %}
        <p>Noch keine Einträge. Sei der Erste!</p>
    {% endif %}
</div>
{% endblock %}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* static/style.css */
.formular-bereich {
    background-color: #f8f9fa;
    padding: 20px;
    border-radius: 5px;
    margin-bottom: 30px;
}

.eintraege-bereich {
    margin-top: 30px;
}

.eintrag {
    background-color: white;
    border: 1px solid #ddd;
    padding: 15px;
    border-radius: 5px;
    margin-bottom: 15px;
}

.eintrag strong {
    color: #007bff;
    display: block;
    margin-bottom: 10px;
}

.eintrag p {
    margin: 10px 0;
    line-height: 1.5;
}

.eintrag small {
    color: #666;
}

textarea {
    width: 100%;
    padding: 8px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-family: inherit;
    resize: vertical;
}

Post-Redirect-Get Pattern

Nach einem erfolgreichen POST solltest du immer zu einer GET-Seite redirecten:

1
return redirect(url_for('gaestebuch_anzeigen'))

Warum?

  • Verhindert doppeltes Absenden beim Browser-Refresh
  • Bessere User Experience
  • Saubere URL nach dem Absenden

Aufgabe 1: Erweitere das Gästebuch

Füge folgende Features hinzu:

  1. Zeitstempel: Importiere datetime und speichere bei jedem Eintrag das aktuelle Datum/Zeit
  2. Zeige Zeitstempel an: Formatiere mit strftime() für schöne Ausgabe
  3. E-Mail-Feld (optional): Benutzer können E-Mail angeben (Validierung nicht vergessen!)

Tipp: from datetime import datetime und datetime.now()

Umfrage-System

Lass uns ein System erstellen, bei dem mehrere Benutzer abstimmen können.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# app.py
from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

# Umfrage-Daten
umfrage = {
    "frage": "Was ist deine Lieblings-Programmiersprache?",
    "optionen": ["Python", "JavaScript", "Java", "C++", "Go"],
    "stimmen": {}  # {"Python": 5, "JavaScript": 3, ...}
}

# Initialisiere Stimmen auf 0
for option in umfrage["optionen"]:
    umfrage["stimmen"][option] = 0


@app.route("/umfrage", methods=['GET', 'POST'])
def umfrage_anzeigen():
    if request.method == 'POST':
        wahl = request.form.get('wahl')

        # Validierung
        if wahl and wahl in umfrage["optionen"]:
            umfrage["stimmen"][wahl] += 1
            return redirect(url_for('umfrage_ergebnis'))

        # Ungültige Wahl
        fehler = "Bitte wähle eine gültige Option"
        return render_template("umfrage.html", umfrage=umfrage, fehler=fehler)

    # GET: Zeige Umfrage-Formular
    return render_template("umfrage.html", umfrage=umfrage, fehler=None)


@app.route("/umfrage/ergebnis")
def umfrage_ergebnis():
    # Berechne Gesamtstimmen
    gesamt = sum(umfrage["stimmen"].values())

    # Sortiere nach Anzahl der Stimmen (absteigend)
    ergebnisse = sorted(umfrage["stimmen"].items(), key=lambda x: x[1], reverse=True)

    return render_template("umfrage_ergebnis.html",
                           umfrage=umfrage,
                           ergebnisse=ergebnisse,
                           gesamt=gesamt)


if __name__ == "__main__":
    app.run(debug=True)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- templates/umfrage.html -->
{% extends "base.html" %}

{% block title %}Umfrage{% endblock %}

{% block content %}
<h1>Umfrage</h1>

{% if fehler %}
    <div class="fehler-box">{{ fehler }}</div>
{% endif %}

<div class="umfrage-box">
    <h2>{{ umfrage.frage }}</h2>

    <form method="POST" action="/umfrage">
        {% for option in umfrage.optionen %}
            <div class="radio-option">
                <input type="radio" id="{{ option }}" name="wahl" value="{{ option }}" required>
                <label for="{{ option }}">{{ option }}</label>
            </div>
        {% endfor %}

        <button type="submit" class="btn-primary">Abstimmen</button>
    </form>

    <p><a href="/umfrage/ergebnis">Ergebnisse anzeigen</a></p>
</div>
{% endblock %}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- templates/umfrage_ergebnis.html -->
{% extends "base.html" %}

{% block title %}Umfrage-Ergebnis{% endblock %}

{% block content %}
<h1>Ergebnisse</h1>

<div class="ergebnis-box">
    <h2>{{ umfrage.frage }}</h2>

    {% if gesamt > 0 %}
        <p><strong>Gesamtstimmen: {{ gesamt }}</strong></p>

        <div class="ergebnisse">
        {% for option, stimmen in ergebnisse %}
            <div class="ergebnis-eintrag">
                <span class="option-name">{{ option }}</span>
                <div class="balken-container">
                    <div class="balken" style="width: {{ (stimmen / gesamt * 100)|round }}%">
                        {{ stimmen }} Stimmen ({{ (stimmen / gesamt * 100)|round }}%)
                    </div>
                </div>
            </div>
        {% endfor %}
        </div>
    {% else %}
        <p>Noch keine Stimmen abgegeben.</p>
    {% endif %}

    <p><a href="/umfrage">Zurück zur Umfrage</a></p>
</div>
{% endblock %}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* static/style.css */
.radio-option {
    margin: 10px 0;
}

.radio-option input {
    margin-right: 10px;
}

.radio-option label {
    cursor: pointer;
}

.ergebnis-eintrag {
    margin: 15px 0;
}

.option-name {
    display: block;
    font-weight: bold;
    margin-bottom: 5px;
}

.balken-container {
    background-color: #f0f0f0;
    border-radius: 5px;
    overflow: hidden;
}

.balken {
    background-color: #007bff;
    color: white;
    padding: 10px;
    text-align: center;
    transition: width 0.3s ease;
}

Prozentrechnung in Jinja

1
{{ (stimmen / gesamt * 100)|round }}%
  • stimmen / gesamt * 100 berechnet den Prozentsatz
  • |round rundet auf ganze Zahlen
  • Das Ergebnis wird direkt als width im CSS genutzt!

Übungen

Übung 1: Kontaktformular mit Validierung

Erstelle eine Route /kontakt mit einem Kontaktformular.

Formular-Felder:

  • Name (Pflicht, mindestens 3 Zeichen)
  • E-Mail (Pflicht, muss @ und . enthalten)
  • Betreff (Pflicht, mindestens 5 Zeichen)
  • Nachricht (Pflicht, mindestens 20 Zeichen)

Anforderungen:

  1. Erstelle Validierungsfunktionen für jedes Feld
  2. Zeige alle Fehler gleichzeitig an (nicht nur den ersten!)
  3. Behalte eingegebene Werte bei Fehlern
  4. Nach erfolgreicher Übermittlung: Zeige Bestätigungsseite

Bonus: Speichere übermittelte Nachrichten in einer Liste und zeige sie auf einer separaten Seite /nachrichten an.

Lösung zeigen
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# app.py
kontakte = []  # Globale Liste

def validiere_name(name):
    return len(name) >= 3

def validiere_email(email):
    return "@" in email and "." in email

def validiere_betreff(betreff):
    return len(betreff) >= 5

def validiere_nachricht(nachricht):
    return len(nachricht) >= 20


@app.route("/kontakt", methods=['GET', 'POST'])
def kontakt():
    fehler = []

    if request.method == 'POST':
        name = request.form.get('name', '').strip()
        email = request.form.get('email', '').strip()
        betreff = request.form.get('betreff', '').strip()
        nachricht = request.form.get('nachricht', '').strip()

        # Validierung
        if not name:
            fehler.append("Name darf nicht leer sein")
        elif not validiere_name(name):
            fehler.append("Name muss mindestens 3 Zeichen lang sein")

        if not email:
            fehler.append("E-Mail darf nicht leer sein")
        elif not validiere_email(email):
            fehler.append("Ungültige E-Mail-Adresse")

        if not betreff:
            fehler.append("Betreff darf nicht leer sein")
        elif not validiere_betreff(betreff):
            fehler.append("Betreff muss mindestens 5 Zeichen lang sein")

        if not nachricht:
            fehler.append("Nachricht darf nicht leer sein")
        elif not validiere_nachricht(nachricht):
            fehler.append("Nachricht muss mindestens 20 Zeichen lang sein")

        if not fehler:
            # Speichern
            kontakte.append({
                "name": name,
                "email": email,
                "betreff": betreff,
                "nachricht": nachricht
            })
            return render_template("kontakt_erfolg.html", name=name)

        return render_template("kontakt.html",
                               fehler=fehler,
                               name=name,
                               email=email,
                               betreff=betreff,
                               nachricht=nachricht)

    return render_template("kontakt.html", fehler=[])


@app.route("/nachrichten")
def nachrichten():
    return render_template("nachrichten.html", kontakte=kontakte)

Übung 2: To-Do-Liste (CRUD)

Erstelle eine einfache To-Do-Listen-App.

Anforderungen:

  1. Create: Formular zum Hinzufügen neuer To-Dos
  2. Read: Alle To-Dos anzeigen
  3. Jedes To-Do hat: Titel, Beschreibung, Status (offen/erledigt)
  4. Validierung: Titel muss mindestens 3 Zeichen haben

Bonus:

  • Zeige Anzahl offener und erledigter To-Dos
  • Markiere erledigte To-Dos visuell anders (z.B. durchgestrichen)
Lösung zeigen
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# app.py
todos = []

@app.route("/todos", methods=['GET', 'POST'])
def todo_liste():
    fehler = []

    if request.method == 'POST':
        titel = request.form.get('titel', '').strip()
        beschreibung = request.form.get('beschreibung', '').strip()

        # Validierung
        if not titel:
            fehler.append("Titel darf nicht leer sein")
        elif len(titel) < 3:
            fehler.append("Titel muss mindestens 3 Zeichen lang sein")

        if not fehler:
            todo = {
                "id": len(todos) + 1,
                "titel": titel,
                "beschreibung": beschreibung,
                "erledigt": False
            }
            todos.append(todo)
            return redirect(url_for('todo_liste'))

        return render_template("todos.html",
                               todos=todos,
                               fehler=fehler,
                               titel=titel,
                               beschreibung=beschreibung)

    # Statistiken berechnen
    offen = sum(1 for t in todos if not t["erledigt"])
    erledigt = sum(1 for t in todos if t["erledigt"])

    return render_template("todos.html",
                           todos=todos,
                           fehler=[],
                           offen=offen,
                           erledigt=erledigt)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!-- templates/todos.html -->
{% extends "base.html" %}

{% block content %}
<h1>To-Do-Liste</h1>

<div class="statistik">
    <span>Offen: {{ offen }}</span> |
    <span>Erledigt: {{ erledigt }}</span> |
    <span>Gesamt: {{ todos|length }}</span>
</div>

{% if fehler %}
    <div class="fehler-box">
        <ul>
        {% for f in fehler %}
            <li>{{ f }}</li>
        {% endfor %}
        </ul>
    </div>
{% endif %}

<form method="POST" action="/todos">
    <div class="form-group">
        <input type="text" name="titel" placeholder="Titel" value="{{ titel }}" required>
    </div>
    <div class="form-group">
        <textarea name="beschreibung" placeholder="Beschreibung (optional)">{{ beschreibung }}</textarea>
    </div>
    <button type="submit" class="btn-primary">Hinzufügen</button>
</form>

<div class="todo-liste">
    {% for todo in todos %}
        <div class="todo-item {% if todo.erledigt %}erledigt{% endif %}">
            <h3>{{ todo.titel }}</h3>
            {% if todo.beschreibung %}
                <p>{{ todo.beschreibung }}</p>
            {% endif %}
        </div>
    {% else %}
        <p>Keine To-Dos. Erstelle dein erstes!</p>
    {% endfor %}
</div>
{% endblock %}

Übung 3: Quiz-App

Erstelle eine einfache Quiz-App mit mehreren Fragen.

Anforderungen:

  1. Erstelle eine Liste mit mindestens 3 Quiz-Fragen
  2. Jede Frage hat: Frage-Text, 4 Antwortmöglichkeiten, richtige Antwort
  3. Zeige alle Fragen in einem Formular an (Radio-Buttons)
  4. Nach dem Absenden: Zähle richtige Antworten und zeige Ergebnis
  5. Zeige welche Antworten richtig/falsch waren

Bonus: Berechne und zeige Prozent-Punktzahl

Lösung zeigen
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# app.py
quiz_fragen = [
    {
        "frage": "Was ist die Hauptstadt von Deutschland?",
        "optionen": ["Berlin", "München", "Hamburg", "Köln"],
        "richtig": "Berlin"
    },
    {
        "frage": "Welche Programmiersprache nutzt Flask?",
        "optionen": ["JavaScript", "Python", "Java", "Ruby"],
        "richtig": "Python"
    },
    {
        "frage": "Was bedeutet HTML?",
        "optionen": ["Hyper Text Markup Language", "High Tech Modern Language", "Home Tool Markup Language", "Hyperlinks and Text Markup Language"],
        "richtig": "Hyper Text Markup Language"
    }
]


@app.route("/quiz", methods=['GET', 'POST'])
def quiz():
    if request.method == 'POST':
        richtig = 0
        ergebnisse = []

        for i, frage in enumerate(quiz_fragen):
            antwort = request.form.get(f'frage_{i}')
            ist_richtig = antwort == frage["richtig"]

            if ist_richtig:
                richtig += 1

            ergebnisse.append({
                "frage": frage["frage"],
                "deine_antwort": antwort,
                "richtige_antwort": frage["richtig"],
                "ist_richtig": ist_richtig
            })

        prozent = round((richtig / len(quiz_fragen)) * 100)

        return render_template("quiz_ergebnis.html",
                               ergebnisse=ergebnisse,
                               richtig=richtig,
                               gesamt=len(quiz_fragen),
                               prozent=prozent)

    return render_template("quiz.html", fragen=quiz_fragen)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- templates/quiz.html -->
{% extends "base.html" %}

{% block content %}
<h1>Quiz</h1>

<form method="POST" action="/quiz">
    {% for i, frage in fragen|enumerate %}
        <div class="quiz-frage">
            <h3>{{ i + 1 }}. {{ frage.frage }}</h3>
            {% for option in frage.optionen %}
                <div class="radio-option">
                    <input type="radio" id="f{{ i }}_{{ loop.index }}"
                           name="frage_{{ i }}" value="{{ option }}" required>
                    <label for="f{{ i }}_{{ loop.index }}">{{ option }}</label>
                </div>
            {% endfor %}
        </div>
    {% endfor %}

    <button type="submit" class="btn-primary">Auswerten</button>
</form>
{% endblock %}

Zusammenfassung

Das Wichtigste auf einen Blick:

Validierungsfunktionen:

  • Prüfen Daten vor der Verarbeitung
  • Geben True (gültig) oder False (ungültig) zurück
  • Können auch Fehler-Texte zurückgeben

Fehlerbehandlung:

  • Sammle alle Fehler in einer Liste
  • Zeige Fehler im Template mit Jinja-Schleife
  • Behalte eingegebene Werte bei (außer Passwörter)

CRUD-Operationen:

  • Create: Daten aus Formular in Liste speichern
  • Read: Liste durchlaufen und anzeigen
  • Nutze globale Liste (oder später: Datenbank)
  • Post-Redirect-Get Pattern nach erfolgreicher Übermittlung

Best Practices:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Validierung
if not validiere_email(email):
    fehler.append("Ungültige E-Mail")

# Erfolg: Redirect
if not fehler:
    liste.append(daten)
    return redirect(url_for('route_name'))

# Fehler: Formular erneut mit Fehlern
return render_template("form.html", fehler=fehler, **daten)

Nächste Schritte

Du kennst jetzt Formulare und Validierung! Im nächsten Modul:

Zuletzt aktualisiert am