mitmario.dev

Vorlagen

Python Sandbox 4 Min Lesezeit 4 BeispieleLektion 3 von 7

Dreizehn Ausgaben soll das Haushaltsbuch zeigen, als Tabelle. Für eine Überschrift und einen Link hat ein Text in Anführungszeichen gereicht, und mit dem, was du bisher kennst, geht auch eine Tabelle. Dabei sieht man allerdings ziemlich schnell, warum man es anders macht.

Eine Tabelle aus f-Strings

Eine Tabelle aus f-Strings
from flask import Flask

from haushalt import AUSGABEN, euro

app = Flask(__name__)


@app.get("/")
def startseite():
    zeilen = ""
    for ausgabe in AUSGABEN:
        zeilen += f"<tr><td>{ausgabe['empfaenger']}</td><td>{euro(ausgabe['betrag'])}</td></tr>"
    return f"<h1>Haushaltsbuch</h1><table>{zeilen}</table>"

Hier wird die Tabelle Zeile für Zeile zusammengebaut, mit einer Schleife und einem f-String je Zeile. In HTML ist eine Tabelle ein <table>, jede Zeile darin ein <tr> und jede Zelle ein <td>. Das funktioniert, im Browser steht die Tabelle. Zwei Dinge sind trotzdem schief.

Das erste sieht man im Code. HTML und Python stecken ineinander. Die Schlüssel stehen in einfachen Anführungszeichen, weil außen doppelte stehen, und die Klammern der Tags stehen zwischen den geschweiften des f-Strings. Eine Seite mit Kopf, Navigation und Fuß wäre eine einzige lange Zeile Text in einer Python-Datei.

Das zweite sieht man im Browser. In der Tabelle steht Flohmarkt fett. Jemand hat den Namen als <b>Flohmarkt</b> eingetippt, der f-String hat das <b> ungeprüft in die Seite gesetzt, und der Browser liest es dort als Anweisung. Hier ist das harmlos. Stünde statt <b> ein <script> mit Code da, liefe der in jedem Browser, der die Seite öffnet.

Die erste Vorlage

Die erste Vorlage
<!doctype html>
<html lang="de">
<head>
    <meta charset="utf-8">
    <title>{{ titel }}</title>
</head>
<body>
    <h1>{{ titel }}</h1>
    <p>Im September sind {{ anzahl }} Ausgaben eingetragen.</p>
</body>
</html>

Eine Vorlage (template) ist eine HTML-Datei mit Platzhaltern. Sie liegt im Ordner templates neben app.py, und dort sucht Flask sie auch. Dafür hat es in der ersten Lektion des Abschnitts __name__ bekommen. Die Sprache der Platzhalter heißt Jinja und kommt mit Flask mit.

render_template("liste.html", titel="Haushaltsbuch", anzahl=len(AUSGABEN)) nimmt die Vorlage, setzt die Werte ein und gibt die fertige Seite als Text zurück, den die Funktion dann wie bisher zurückgibt.

Was hinter dem Dateinamen mit = steht, wird in der Vorlage zu einem Namen. {{ titel }} setzt den Titel ein, sogar zweimal, einmal in den Reiter des Browsers und einmal in die Überschrift. {{ anzahl }} setzt die Zahl ein. Zwischen den doppelten geschweiften Klammern darf ein Ausdruck stehen wie in einem f-String, {{ anzahl + 1 }} ginge also auch.

Das HTML steht jetzt in seiner eigenen Datei und Python in seiner. Um die Anführungszeichen der anderen muss sich keine der beiden mehr kümmern.

Eine Schleife in der Vorlage

Eine Zeile je Ausgabe
<!doctype html>
<html lang="de">
<head>
    <meta charset="utf-8">
    <title>{{ titel }}</title>
</head>
<body>
    <h1>{{ titel }}</h1>
    {# Eine Zeile je Ausgabe. Was hier steht, kommt nicht in die Seite. #}
    <table>
        {% for ausgabe in ausgaben %}
        <tr><td>{{ ausgabe.empfaenger }}</td><td>{{ ausgabe.betrag | euro }}</td></tr>
        {% endfor %}
    </table>
    <p>Zusammen {{ ausgaben | length }} Ausgaben.</p>
</body>
</html>

Alles zwischen {% for ausgabe in ausgaben %} und {% endfor %} wird einmal je Ausgabe wiederholt, wie bei einer for-Schleife in Python. Das Ende muss ausdrücklich dastehen, weil HTML nichts auf Einrückung gibt.

{{ ausgabe.empfaenger }} holt den Empfänger aus dem Dictionary. Jinja erlaubt dafür den Punkt, aber {{ ausgabe["empfaenger"] }} ginge genauso. Was zwischen {# und #} steht, ist ein Kommentar und kommt nicht in die Seite.

Hinter dem senkrechten Strich steht ein Filter (filter). Bei {{ ausgabe.betrag | euro }} geht der Betrag an die Funktion euro() aus haushalt.py, und in der Zelle steht, was sie zurückgibt: 8,40 € statt 840. Die Vorlage kennt euro(), weil die Funktion in app.py mit app.add_template_filter(euro) angemeldet ist. Jinja bringt auch eigene Filter mit. length unter der Tabelle zählt zum Beispiel die Ausgaben.

Wenn auf einer Seite etwas fehlt, ist die Schreibweise der erste Verdacht. Vertippst du dich in einer Vorlage beim Namen und schreibst ausgabe.empfanger, kommt nämlich kein Fehler. Die Zelle bleibt einfach leer.

Maskiert

Im Browser steht der Flohmarkt diesmal mit seinen spitzen Klammern da, als Text. Jinja schreibt jeden Wert, der aus {{ }} kommt, so um, dass er kein HTML werden kann. Aus < wird &lt;, aus > wird &gt;, und der Browser zeigt dafür wieder die Zeichen an. Das nennt man maskieren (escaping). Auch der Apostroph in „Pizzeria L’Angolo“ steht im Quelltext als &#39;, im Browser merkt man davon nichts.

Das ist der wichtigste Grund für Vorlagen. Eine Seite, die zeigt, was andere eingetippt haben, ist der Normalfall, und mit dem Formular wird das bald auch hier so sein. Ohne Maskieren könnte jeder, der einen Namen eintippt, der Seite eigenen Code unterschieben, und der liefe dann bei allen, die sie danach ansehen.

Diese Lücke ist so verbreitet, dass sie einen eigenen Namen hat. Cross-Site-Scripting. Eine Vorlage schützt von sich aus davor, ein f-String nicht.

Die Vorlage liegt woanders

Die Vorlage liegt woanders
from flask import Flask, render_template

app = Flask(__name__)


@app.get("/")
def startseite():
    return render_template("liste.html", titel="Haushaltsbuch")

Hier liegt liste.html neben app.py statt im Ordner templates. Der Server startet, aber im Browser steht ein Traceback, der so endet:

jinja2.exceptions.TemplateNotFound: liste.html

Übersetzt heißt das, die Vorlage liste.html wurde nicht gefunden. Flask sucht nur in templates, und dort liegt sie nicht. Der Ordner muss genau so heißen und neben app.py liegen. Dieselbe Meldung kommt auch, wenn der Name der Datei nicht genau stimmt, also bei Liste.html oder liste.htm.

Was die Aufgabe will

app.py ist schon fertig und hat zwei Routen. Beide nehmen dieselbe Vorlage, nur mit einem anderen Titel und anderen Ausgaben. Du baust die Vorlage, die für beide passt.

Zum Mitnehmen

Eine Vorlage ist eine HTML-Datei mit Platzhaltern im Ordner templates neben app.py, und render_template("liste.html", titel=…, ausgaben=…) macht daraus die fertige Seite. Mit {{ wert }} kommt ein Wert hinein, {% for … %} bis {% endfor %} wiederholt einen Teil, und hinter dem senkrechten Strich steht ein Filter wie | euro. Maskiert wird dabei jeder Wert, aus <b> wird also &lt;b&gt;, und Eingetipptes bleibt Text.

Jetzt du

Basis Konto, kostenlos

Zu 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.