Abschnitt 18 · Lektion 4
Werte aus der Adresse
Das Haushaltsbuch hat dreizehn Ausgaben, und jede soll ihre eigene Seite bekommen. Dafür dreizehn
Routen zu schreiben, /ausgabe/1 bis /ausgabe/13, wäre derselbe Unsinn wie dreizehn Variablen
statt einer Liste. Eine Route kann stattdessen einen Teil ihrer Adresse offenlassen und ihn als Wert
an die Funktion geben.
Ein variabler Teil im Pfad
# Die Ausgaben des Haushaltsbuchs für diesen Abschnitt, dazu zwei Helfer.
# Hier musst du nichts ändern.
from decimal import Decimal, InvalidOperation
# Alle Kategorien, auch die, in denen noch nichts steht.
KATEGORIEN = ["Lebensmittel", "Wohnen", "Mobilität", "Freizeit", "Haushalt", "Kleidung", "Sonstiges"]
# Die Ausgaben im September. Der Betrag steht in Cent wie in Lektion 16.2, 840
# sind 8,40 €. Beim Flohmarkt hat jemand <b> mit eingetippt, damit der Name fett
# dasteht.
AUSGABEN = [
{"empfaenger": "Verkehrsbetriebe Lindenau", "kategorie": "Mobilität", "betrag": 5800},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5218},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 840},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 3450},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 410},
{"empfaenger": "Kino Lichtspiel", "kategorie": "Freizeit", "betrag": 2400},
{"empfaenger": "Hausverwaltung Ahorn", "kategorie": "Wohnen", "betrag": 112000},
{"empfaenger": "Drogerie Seifenblase", "kategorie": "Haushalt", "betrag": 1295},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5000},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 6130},
{"empfaenger": "<b>Flohmarkt</b>", "kategorie": "Freizeit", "betrag": 1500},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 6347},
{"empfaenger": "Stadtwerke Lindenau", "kategorie": "Wohnen", "betrag": 8400},
]
def euro(cent):
"""Schreibt einen Betrag in Cent auf Deutsch: aus 112000 wird "1.120,00 €"."""
vorzeichen = "-" if cent < 0 else ""
ganze, rest = divmod(abs(cent), 100)
return vorzeichen + f"{ganze:,}".replace(",", ".") + f",{rest:02d} €"
def cent_aus_text(text):
"""Liest einen Betrag wie "3,20" oder "1.120,00" und gibt ihn in Cent zurück.
Das Komma trennt die Cent ab, ein Punkt die Tausender. Ist der Text kein
Betrag mit höchstens zwei Stellen nach dem Komma, gibt es einen ValueError.
"""
try:
betrag = Decimal(text.strip().replace(".", "").replace(",", "."))
except InvalidOperation:
raise ValueError(f"Kein Betrag: {text!r}") from None
if not betrag.is_finite() or betrag != round(betrag, 2):
raise ValueError(f"Kein Betrag: {text!r}")
return int(betrag * 100) <!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Ausgabe {{ nummer }}</title>
</head>
<body>
<h1>Ausgabe {{ nummer }} von {{ anzahl }}</h1>
<p>{{ ausgabe.empfaenger }}, {{ ausgabe.kategorie }}: {{ ausgabe.betrag | euro }}</p>
<p><a href="/ausgabe/{{ nummer + 1 }}">Nächste Ausgabe</a></p>
</body>
</html> from flask import Flask, render_template
from haushalt import AUSGABEN, euro
app = Flask(__name__)
app.add_template_filter(euro)
@app.get("/ausgabe/<int:nummer>")
def ausgabe(nummer):
eintrag = AUSGABEN[nummer - 1]
return render_template("ausgabe.html", nummer=nummer, anzahl=len(AUSGABEN), ausgabe=eintrag) <int:nummer> im Pfad ist ein variabler Teil (variable section). Er passt auf jede ganze Zahl
an dieser Stelle, und Flask ruft die Funktion mit dieser Zahl als Parameter nummer auf.
/ausgabe/3 ruft also ausgabe(3) auf, und auf der Seite steht die dritte Ausgabe, die Bäckerei.
Über den Link unten kommst du durch die ganze Liste.
Das int: davor sagt, welcher Typ an dieser Stelle stehen darf, und macht den Wert gleich zu einer
Zahl, wie int() in Lektion 2.5. Ohne die Angabe käme "3" als Text an. Wenn etwas nicht passt,
etwa /ausgabe/drei, gilt die Route gar nicht, und es kommt eine 404.
Ein variabler Teil ohne Angabe, also <name>, nimmt jeden Text bis zum nächsten /, auch einen mit
Umlaut. /kategorie/Mobilität schreibt der Browser beim Fragen um, so wie requests es auch tut, und
Flask macht wieder Mobilität daraus.
Zwei Adressen gehen in diesem Beispiel schief. /ausgabe/14 gibt es nicht, und AUSGABEN[13] endet
mit IndexError: list index out of range. Das ist ein Absturz mit Status 500, und in der
Adresszeile lässt sich das leicht nachprüfen. /ausgabe/0 zeigt dagegen ohne jede Meldung die
letzte Ausgabe, weil AUSGABEN[-1] in Python die letzte ist, wie bei jeder Liste. Beides passiert,
weil niemand nachsieht, ob es zu der Nummer aus der Adresse überhaupt eine Ausgabe gibt.
Eine ehrliche 404
# Die Ausgaben des Haushaltsbuchs für diesen Abschnitt, dazu zwei Helfer.
# Hier musst du nichts ändern.
from decimal import Decimal, InvalidOperation
# Alle Kategorien, auch die, in denen noch nichts steht.
KATEGORIEN = ["Lebensmittel", "Wohnen", "Mobilität", "Freizeit", "Haushalt", "Kleidung", "Sonstiges"]
# Die Ausgaben im September. Der Betrag steht in Cent wie in Lektion 16.2, 840
# sind 8,40 €. Beim Flohmarkt hat jemand <b> mit eingetippt, damit der Name fett
# dasteht.
AUSGABEN = [
{"empfaenger": "Verkehrsbetriebe Lindenau", "kategorie": "Mobilität", "betrag": 5800},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5218},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 840},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 3450},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 410},
{"empfaenger": "Kino Lichtspiel", "kategorie": "Freizeit", "betrag": 2400},
{"empfaenger": "Hausverwaltung Ahorn", "kategorie": "Wohnen", "betrag": 112000},
{"empfaenger": "Drogerie Seifenblase", "kategorie": "Haushalt", "betrag": 1295},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5000},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 6130},
{"empfaenger": "<b>Flohmarkt</b>", "kategorie": "Freizeit", "betrag": 1500},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 6347},
{"empfaenger": "Stadtwerke Lindenau", "kategorie": "Wohnen", "betrag": 8400},
]
def euro(cent):
"""Schreibt einen Betrag in Cent auf Deutsch: aus 112000 wird "1.120,00 €"."""
vorzeichen = "-" if cent < 0 else ""
ganze, rest = divmod(abs(cent), 100)
return vorzeichen + f"{ganze:,}".replace(",", ".") + f",{rest:02d} €"
def cent_aus_text(text):
"""Liest einen Betrag wie "3,20" oder "1.120,00" und gibt ihn in Cent zurück.
Das Komma trennt die Cent ab, ein Punkt die Tausender. Ist der Text kein
Betrag mit höchstens zwei Stellen nach dem Komma, gibt es einen ValueError.
"""
try:
betrag = Decimal(text.strip().replace(".", "").replace(",", "."))
except InvalidOperation:
raise ValueError(f"Kein Betrag: {text!r}") from None
if not betrag.is_finite() or betrag != round(betrag, 2):
raise ValueError(f"Kein Betrag: {text!r}")
return int(betrag * 100) <!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Ausgabe {{ nummer }}</title>
</head>
<body>
<h1>Ausgabe {{ nummer }} von {{ anzahl }}</h1>
<p>{{ ausgabe.empfaenger }}, {{ ausgabe.kategorie }}: {{ ausgabe.betrag | euro }}</p>
<p><a href="/ausgabe/{{ nummer + 1 }}">Nächste Ausgabe</a></p>
</body>
</html> from flask import Flask, abort, render_template
from haushalt import AUSGABEN, euro
app = Flask(__name__)
app.add_template_filter(euro)
@app.get("/ausgabe/<int:nummer>")
def ausgabe(nummer):
if nummer < 1 or nummer > len(AUSGABEN):
abort(404)
eintrag = AUSGABEN[nummer - 1]
return render_template("ausgabe.html", nummer=nummer, anzahl=len(AUSGABEN), ausgabe=eintrag) Wenn es zu einer Adresse nichts gibt, gehört dazu eine 404, „gibt es nicht“. Dafür ist abort(404)
da. Damit endet die Anfrage an genau dieser Stelle, und Flask schickt seine Seite Not Found mit
dem Statuscode 404, so wie bei einem Pfad ohne Route. Die Zeilen danach laufen nicht mehr.
Das if steht deshalb vor dem Zugriff auf die Liste. Für den, der fragt, ist der Unterschied
deutlich. Eine 404 heißt, er hat sich in der Adresse vertan. Eine 500 heißt, der Server ist kaputt.
Ein Programm wie in Abschnitt 17, das deine Seite fragt, kann daran entscheiden, was es tut.
Werte hinter dem Fragezeichen
# Die Ausgaben des Haushaltsbuchs für diesen Abschnitt, dazu zwei Helfer.
# Hier musst du nichts ändern.
from decimal import Decimal, InvalidOperation
# Alle Kategorien, auch die, in denen noch nichts steht.
KATEGORIEN = ["Lebensmittel", "Wohnen", "Mobilität", "Freizeit", "Haushalt", "Kleidung", "Sonstiges"]
# Die Ausgaben im September. Der Betrag steht in Cent wie in Lektion 16.2, 840
# sind 8,40 €. Beim Flohmarkt hat jemand <b> mit eingetippt, damit der Name fett
# dasteht.
AUSGABEN = [
{"empfaenger": "Verkehrsbetriebe Lindenau", "kategorie": "Mobilität", "betrag": 5800},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5218},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 840},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 3450},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 410},
{"empfaenger": "Kino Lichtspiel", "kategorie": "Freizeit", "betrag": 2400},
{"empfaenger": "Hausverwaltung Ahorn", "kategorie": "Wohnen", "betrag": 112000},
{"empfaenger": "Drogerie Seifenblase", "kategorie": "Haushalt", "betrag": 1295},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5000},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 6130},
{"empfaenger": "<b>Flohmarkt</b>", "kategorie": "Freizeit", "betrag": 1500},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 6347},
{"empfaenger": "Stadtwerke Lindenau", "kategorie": "Wohnen", "betrag": 8400},
]
def euro(cent):
"""Schreibt einen Betrag in Cent auf Deutsch: aus 112000 wird "1.120,00 €"."""
vorzeichen = "-" if cent < 0 else ""
ganze, rest = divmod(abs(cent), 100)
return vorzeichen + f"{ganze:,}".replace(",", ".") + f",{rest:02d} €"
def cent_aus_text(text):
"""Liest einen Betrag wie "3,20" oder "1.120,00" und gibt ihn in Cent zurück.
Das Komma trennt die Cent ab, ein Punkt die Tausender. Ist der Text kein
Betrag mit höchstens zwei Stellen nach dem Komma, gibt es einen ValueError.
"""
try:
betrag = Decimal(text.strip().replace(".", "").replace(",", "."))
except InvalidOperation:
raise ValueError(f"Kein Betrag: {text!r}") from None
if not betrag.is_finite() or betrag != round(betrag, 2):
raise ValueError(f"Kein Betrag: {text!r}")
return int(betrag * 100) <!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Suche</title>
</head>
<body>
<h1>Suche nach einem Empfänger</h1>
<form action="/suche">
<input name="wort" value="{{ wort }}">
<button type="submit">Suchen</button>
</form>
<p>{{ treffer | length }} Treffer für „{{ wort }}“</p>
<ul>
{% for ausgabe in treffer %}
<li>{{ ausgabe.empfaenger }}, {{ ausgabe.betrag | euro }}</li>
{% endfor %}
</ul>
</body>
</html> from flask import Flask, render_template, request
from haushalt import AUSGABEN, euro
app = Flask(__name__)
app.add_template_filter(euro)
@app.get("/suche")
def suche():
wort = request.args.get("wort", "")
treffer = [ausgabe for ausgabe in AUSGABEN if wort.lower() in ausgabe["empfaenger"].lower()]
return render_template("suche.html", wort=wort, treffer=treffer) Nicht jeder Wert gehört in den Pfad. Ein Suchwort ist kein Teil der Adresse wie eine Nummer. Es ist
eine Angabe zur Frage, und solche Angaben stehen wie in Abschnitt 17 hinter einem Fragezeichen, etwa
/suche?wort=krume.
Flask legt sie in request.args ab, einem Dictionary für die Parameter der Anfrage. request kommt
aus flask und steht in jeder Route für die Anfrage, die gerade läuft.
request.args.get("wort", "") liest den Wert mit einem Standardwert, wie get an einem Dictionary
in Lektion 7.1. Ohne Fragezeichen gibt es dann ein leeres Suchwort und alle dreizehn Ausgaben.
request.args["wort"] ginge auch, solange der Parameter dasteht. Fehlt er, kommt 400 Bad Request
zurück, und warum, steht nirgends. Deshalb ist get hier fast immer die bessere Wahl.
Das Formular auf der Seite macht übrigens nichts anderes. Wenn du im Browser ein anderes Wort
eintippst und abschickst, siehst du es in der Adresszeile wieder. Der Browser hängt das Feld nämlich
als ?wort=… an und fragt diese Adresse.
Ein anderer Name im Pfad
# Die Ausgaben des Haushaltsbuchs für diesen Abschnitt, dazu zwei Helfer.
# Hier musst du nichts ändern.
from decimal import Decimal, InvalidOperation
# Alle Kategorien, auch die, in denen noch nichts steht.
KATEGORIEN = ["Lebensmittel", "Wohnen", "Mobilität", "Freizeit", "Haushalt", "Kleidung", "Sonstiges"]
# Die Ausgaben im September. Der Betrag steht in Cent wie in Lektion 16.2, 840
# sind 8,40 €. Beim Flohmarkt hat jemand <b> mit eingetippt, damit der Name fett
# dasteht.
AUSGABEN = [
{"empfaenger": "Verkehrsbetriebe Lindenau", "kategorie": "Mobilität", "betrag": 5800},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5218},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 840},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 3450},
{"empfaenger": "Bäckerei Krume", "kategorie": "Lebensmittel", "betrag": 410},
{"empfaenger": "Kino Lichtspiel", "kategorie": "Freizeit", "betrag": 2400},
{"empfaenger": "Hausverwaltung Ahorn", "kategorie": "Wohnen", "betrag": 112000},
{"empfaenger": "Drogerie Seifenblase", "kategorie": "Haushalt", "betrag": 1295},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 5000},
{"empfaenger": "Pizzeria L'Angolo", "kategorie": "Freizeit", "betrag": 6130},
{"empfaenger": "<b>Flohmarkt</b>", "kategorie": "Freizeit", "betrag": 1500},
{"empfaenger": "Supermarkt Sonnenblick", "kategorie": "Lebensmittel", "betrag": 6347},
{"empfaenger": "Stadtwerke Lindenau", "kategorie": "Wohnen", "betrag": 8400},
]
def euro(cent):
"""Schreibt einen Betrag in Cent auf Deutsch: aus 112000 wird "1.120,00 €"."""
vorzeichen = "-" if cent < 0 else ""
ganze, rest = divmod(abs(cent), 100)
return vorzeichen + f"{ganze:,}".replace(",", ".") + f",{rest:02d} €"
def cent_aus_text(text):
"""Liest einen Betrag wie "3,20" oder "1.120,00" und gibt ihn in Cent zurück.
Das Komma trennt die Cent ab, ein Punkt die Tausender. Ist der Text kein
Betrag mit höchstens zwei Stellen nach dem Komma, gibt es einen ValueError.
"""
try:
betrag = Decimal(text.strip().replace(".", "").replace(",", "."))
except InvalidOperation:
raise ValueError(f"Kein Betrag: {text!r}") from None
if not betrag.is_finite() or betrag != round(betrag, 2):
raise ValueError(f"Kein Betrag: {text!r}")
return int(betrag * 100) <!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Ausgabe {{ nummer }}</title>
</head>
<body>
<h1>Ausgabe {{ nummer }} von {{ anzahl }}</h1>
<p>{{ ausgabe.empfaenger }}, {{ ausgabe.kategorie }}: {{ ausgabe.betrag | euro }}</p>
<p><a href="/ausgabe/{{ nummer + 1 }}">Nächste Ausgabe</a></p>
</body>
</html> from flask import Flask, abort, render_template
from haushalt import AUSGABEN, euro
app = Flask(__name__)
app.add_template_filter(euro)
@app.get("/ausgabe/<int:nummer>")
def ausgabe(n):
if n < 1 or n > len(AUSGABEN):
abort(404)
return render_template("ausgabe.html", nummer=n, anzahl=len(AUSGABEN), ausgabe=AUSGABEN[n - 1]) Hier heißt der variable Teil nummer, der Parameter der Funktion aber n. Flask gibt den Wert
unter dem Namen aus dem Pfad weiter, also als ausgabe(nummer=3), und einen Parameter mit diesem
Namen hat die Funktion nicht. Im Browser steht ein Traceback, der so endet:
TypeError: ausgabe() got an unexpected keyword argument 'nummer'
Übersetzt heißt das, ausgabe() hat ein benanntes Argument nummer bekommen, mit dem sie nichts
anfangen kann. Das ist dieselbe Meldung wie bei jedem Aufruf mit einem falschen Namen. Der Name im
Pfad und der des Parameters müssen gleich sein.
Was die Aufgabe will
Jede Kategorie bekommt ihre eigene Seite mit den Ausgaben darin, /kategorie/Lebensmittel und so
weiter. Die Vorlage dafür ist schon fertig. Für eine Kategorie, die es nicht gibt, soll eine
ehrliche 404 kommen. Eine Kategorie, in der noch nichts steht, gibt es trotzdem.
Zum Mitnehmen
Bei @app.get("/ausgabe/<int:nummer>") ist der Teil in spitzen Klammern variabel, die Funktion bekommt den Wert aus der Adresse als nummer, und wegen int: kommen nur ganze Zahlen durch. Werte hinter dem Fragezeichen liegen in request.args und lassen sich mit request.args.get("wort", "") samt Standardwert lesen. Gibt es zu einem Wert nichts, endet die Anfrage mit abort(404) und einer ehrlichen 404.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.