# Geburtstage

Quelle: https://dev.inf.zone/exercises/13/birthdays/

## Aufgabe

**Auf einen Blick**

- **Was:** Sie vervollständigen eine Web-App mit Flask, in die man Geburtstage von Freunden einträgt (Name, Monat, Tag) und die alle Einträge als Tabelle anzeigt.
- **Codegerüst:** Ordner `birthdays` mit `app.py`, `birthdays.json`, `static/` und `templates/`.
- **Ihre Arbeit:** in `templates/index.html` das Formular und die Tabellenzeilen, in `app.py` die beiden Zweige der Route `/` (GET und POST).
- **Daten:** Die Geburtstage speichern Sie in `birthdays.json` (keine Datenbank). Wie ein Eintrag darin aussieht, entscheiden Sie selbst.
- **Prüfen:** im Browser und mit `check50`.

### So funktioniert ein Formular mit Umleitung

Die ganze App besteht aus einer Route, `/`. Sie beantwortet zwei Arten von Anfragen:

1. **Anzeigen (GET `/`):** `app.py` liest `birthdays.json` ein und übergibt die Einträge an `index.html`. Jinja erzeugt daraus für jeden Eintrag eine Tabellenzeile.
2. **Absenden:** Das Formular hat `action="/"` und `method="post"`. Der Browser schickt die Felder als Paare aus `name` und Wert in einer POST-Anfrage an `/`. Alle Werte kommen als Strings an, auch aus Zahlenfeldern.
3. **Speichern (POST `/`):** `app.py` liest die Felder mit `request.form.get(…)`, ergänzt die Einträge um den neuen und schreibt alles zurück in `birthdays.json`.
4. **Umleiten:** Statt eine Seite zu rendern, antwortet `app.py` mit `redirect("/")`. Der Browser fragt `/` daraufhin von selbst per GET an – weiter mit Schritt 1, jetzt mit dem neuen Eintrag.

Die Umleitung hat einen Grund: Lädt man die Seite danach neu, wiederholt der Browser nur das harmlose GET. Ohne Umleitung würde er das Formular (nach einer Rückfrage) noch einmal senden, und der Eintrag stünde doppelt in der Datei.

**Beispiel:** Die Datei ist leer, jemand trägt Harry mit dem 31. Juli ein.

| Anfrage des Browsers                        | Was `app.py` tut                       | `birthdays.json` danach | Antwort                        |
| ------------------------------------------- | -------------------------------------- | ----------------------- | ------------------------------ |
| GET `/`                                     | liest die Datei, rendert `index.html`  | leer (`[]`)             | Formular und leere Tabelle     |
| POST `/` mit `name=Harry`, `month=7`, `day=31` | liest die Felder, ergänzt, speichert | ein Eintrag (Harry, 7, 31) | Umleitung auf `/`           |
| GET `/` (automatisch)                       | liest die Datei, rendert `index.html`  | unverändert             | Tabelle mit Zeile Harry, 7/31  |

## Demo

![birthdays](https://dev.inf.zone/exercises/13/birthdays/./birthdays.png)

## Aufgabenmaterial

Für diese Aufgabe werden Sie ein von uns zur Verfügung gestelltes Codegerüst vervollständigen.

**Aufgabenmaterial herunterladen**

Öffnen Sie VS Code entsprechend Ihrem [Setup](https://dev.inf.zone/extras/setup/), klicken Sie auf Ihr Terminalfenster und führen Sie `cd` aus. Die Eingabeaufforderung Ihres Terminalfensters sollte ungefähr wie folgt aussehen:

```bash
$
```

Führen Sie den Befehl
```bash
wget https://dev.inf.zone/download/exercises/13/birthdays.zip
```
aus, um in Ihrem Codespace die ZIP `birthdays.zip` herunterzuladen.

Nun können Sie
```bash
unzip birthdays.zip
```
ausführen, um die ZIP in den Ordner `birthdays` zu entpacken.

Sie benötigen die ZIP-Datei nicht mehr, daher können Sie den Befehl
```bash
rm birthdays.zip
```
ausführen und bei der Aufforderung mit „y“ gefolgt von der Eingabetaste antworten, um die heruntergeladene ZIP-Datei zu entfernen.

Geben Sie nun den Befehl
```bash
cd birthdays
```
ein und drücken Sie anschließend die Eingabetaste, um in dieses Verzeichnis zu wechseln (d. h., es zu öffnen). Ihre Eingabeaufforderung sollte nun wie folgt aussehen:
```bash
birthdays/ $
```

Falls alles erfolgreich war, führen Sie den folgenden Befehl aus:
```bash
ls
```
Sie sollten nun die folgenden Dateien und Ordner sehen:
```
app.py  birthdays.json  static/  templates/
```
Falls es zu Problemen kommt, wiederholen Sie dieselben Schritte und versuchen Sie herauszufinden, wo ein Fehler aufgetreten sein könnte!

| Datei                  | Inhalt |
| ---------------------- | ------ |
| `app.py`               | die Route `/` für GET und POST. Der POST-Zweig (nach dem `if`) leitet bisher nur auf `/` um, der GET-Zweig (nach dem `else`) rendert `index.html` ohne Daten. Fehlt `birthdays.json`, legt `app.py` die Datei beim Start mit einer leeren Liste an. |
| `birthdays.json`       | die Geburtstage, anfangs eine leere Liste (`[]`) |
| `static/styles.css`    | das CSS der App – dürfen Sie ändern, müssen Sie aber nicht |
| `templates/index.html` | die einzige Seite: Überschriften und eine Tabelle mit den Spalten „Name“ und „Birthday“. Kommentare mit `TODO` markieren, wo Formular und Tabellenzeilen hingehören. |

## Spezifikation

### GET `/`: Geburtstage anzeigen

- **`app.py`:** Lesen Sie die gespeicherten Geburtstage aus `birthdays.json` und übergeben Sie sie an `render_template("index.html", …)`.
- **`index.html`:** Geben Sie jeden Geburtstag als eine Zeile der Tabelle aus, mit zwei Spalten: Name der Person und Geburtstag (z. B. `7/31`).

### POST `/`: Geburtstag hinzufügen

- **`index.html`:** Fügen Sie ein HTML-Formular hinzu, in das man einen Namen, einen Monat und einen Tag eingibt. Es sendet an `/` (`action`) mit der Methode `POST`.
- **`app.py`:** Lesen Sie die Felder aus, fügen Sie den neuen Geburtstag zu den gespeicherten hinzu und schreiben Sie alles zurück in `birthdays.json`. Danach leitet die Route auf `/` um; `redirect("/")` steht schon im Gerüst.
- Überlegen Sie sich, wie Sie einen Geburtstag in der Datei darstellen.

### Optional

- Geburtstage löschen und bearbeiten. Dafür lohnt es sich, jedem Eintrag eine ID zu geben.
- Die Eingaben im Browser und im Server prüfen (client- und serverseitige Validierung).

## Hilfestellung

Klicken Sie auf die folgenden Tipps, um einige Ratschläge zu erhalten. Versuchen Sie aber zunächst, selbst so weit wie möglich zu kommen.

**JSON-Datei lesen und schreiben**

- `json.load(f)` liest den Inhalt einer geöffneten Datei `f` als Python-Wert ein, aus `[]` wird eine leere Liste.
- `json.dump(wert, f)` schreibt einen Python-Wert in eine Datei, die Sie mit `open(DATA_FILE, "w")` geöffnet haben. `"w"` überschreibt die Datei; schreiben Sie also immer alle Einträge, nicht nur den neuen.
- Für beides lohnen sich zwei kleine Funktionen, die Sie in beiden Zweigen der Route aufrufen.

**Einen Geburtstag darstellen**

Naheliegend ist eine Liste mit einem Dictionary je Geburtstag:

```json
[
    {"name": "Harry", "month": "7", "day": "31"},
    {"name": "Ron", "month": "3", "day": "1"}
]
```

Ein neuer Geburtstag kommt dann mit `append` ans Ende der Liste.

**Formular und Tabelle in `index.html`**

- Die `name`-Attribute der Eingabefelder (etwa `name`, `month`, `day`) sind die Schlüssel, unter denen `request.form.get(…)` die Werte findet.
- Für Monat und Tag eignet sich `<input type="number">` mit `min` und `max`; `required` verhindert leere Felder schon im Browser. Prüfen Sie im Server trotzdem, ob alle Felder ausgefüllt sind – eine POST-Anfrage lässt sich auch ohne Ihr Formular schicken.
- Eine Tabellenzeile pro Eintrag erzeugen Sie mit einer Schleife in Jinja: `{% for birthday in birthdays %} … {% endfor %}`. Mit `{{ birthday.name }}` greifen Sie auch auf den Schlüssel eines Dictionarys zu.

## Testen

Starten Sie den Server im Ordner `birthdays`:

```bash
flask run
```

`app.py` liest und schreibt `birthdays.json` in dem Ordner, in dem Sie `flask run` starten.

Prüfen Sie:

- Tragen Sie einige Geburtstage ein. Erscheint jeder als eigene Zeile mit Name und Geburtstag?
- Laden Sie die Seite nach dem Eintragen neu. Bleibt jeder Eintrag einfach, ohne Doppel?
- Beenden Sie den Server (Strg+C) und starten Sie ihn neu. Sind die Einträge noch da? Stehen sie auch in `birthdays.json`?
- Was passiert, wenn Sie ein Feld leer lassen?

### Korrektheit

Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:

```bash
check50 -l inf-zone/exercises/2026/birthdays
```

## Abgeben

Geben Sie im Ordner `birthdays` ab:

```bash
inf upload birthdays
```

Danach sehen Sie, welche Tests Ihr Programm besteht und ob die Übung für die Bonuspunkte zählt. Wie Sie `inf` installieren und sich anmelden, steht unter [Abgeben mit `inf`](https://dev.inf.zone/faq/uebung-solutions/#inf-installieren).
