Flask – 02 Jinja Templating Grundlagen

Flask – 02 Jinja Templating Grundlagen

Voraussetzungen

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

Lernpfad-Hinweis

Dieses Modul baut auf Flask – 01 Erste Schritte auf und nutzt Python – 03 Wahrheitswerte und Kontrollstrukturen Konzepte. Wenn du dem empfohlenen Lernpfad folgst, hast du diese Themen bereits durchgearbeitet!

Was ist Jinja?

Jinja ist eine Template-Engine für Python. Diese ermöglicht es dir, HTML-Seiten dynamisch zu gestalten. Statt für jede mögliche Variante einer Webseite eine eigene HTML-Datei zu schreiben, nutzt du Templates mit Platzhaltern, die zur Laufzeit mit echten Daten gefüllt werden.

Beispiel:

  • Statt 100 HTML-Seiten für 100 verschiedene Benutzer zu schreiben, erstellst du ein Template und füllst es mit den jeweiligen Benutzerdaten.

Warum Templating?

  • Wiederverwendbarkeit: Ein Template für viele verschiedene Inhalte
  • Wartbarkeit: Änderungen nur an einer Stelle nötig
  • Dynamik: Inhalte können sich zur Laufzeit ändern
  • Trennung: HTML (Darstellung) getrennt von Python (Logik)

Die Flask_Tutorial_1 Vorlage

In diesem Tutorial bauen wir auf der Flask_Tutorial_1.zip Vorlage auf. Diese enthält bereits:

  • Eine einfache Flask-App (app.py)
  • Ein Basis-Template (base.html)
  • Eine Home-Seite (home.html)
  • Ein Stylesheet (style.css)

Schauen wir uns an, wie Jinja hier funktioniert!

Variablen an Templates übergeben

Grundprinzip

In Flask übergibt man Variablen mit der Funktion render_template() an ein Template:

Grundgerüst

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def hello_world():
    name = "Murat"
    nachname = "Muster"
    geburtsjahr = 2007
    return render_template("home.html", vor=name, nach=nachname, gj=geburtsjahr)

Erklärung:

  1. In Python definierst du Variablen (name, nachname, geburtsjahr)
  2. Mit render_template() lädst du das Template
  3. Die Parameter nach dem Dateinamen (vor=name) übergeben die Werte ans Template
  4. Im Template heißt die Variable dann vor (nicht name!)

Variablen im Template ausgeben

Im Template nutzt du doppelte geschweifte Klammern {{ }}, um Variablen auszugeben:

1
2
3
4
5
6
7
8
<!-- home.html -->
<div class="potray">
    <ul>
        <li>{{ vor }}</li>
        <li>{{ nach }}</li>
        <li>{{ gj }}</li>
    </ul>
</div>

Ausgabe im Browser:

• Murat
• Muster
• 2007

Wichtig zu wissen

  • {{ variable }} gibt den Wert aus
  • Die Variablen-Namen im Template müssen mit den Parametern in render_template() übereinstimmen
  • Du kannst beliebig viele Variablen übergeben

Aufgabe 1: Eigene Variablen

  1. Erstelle eine neue Route /profil
  2. Definiere Variablen für: Name, Alter, Lieblingsfarbe
  3. Übergebe sie an ein neues Template profil.html
  4. Gib die Werte im Template aus

Template-Vererbung (extends und block)

Das Konzept

Stell dir vor, jede Webseite deiner App soll den gleichen Header, Footer und Navigation haben. Ohne Template-Vererbung müsstest du diesen Code auf jeder Seite wiederholen. Mit Jinja erstellst du ein Basis-Template und lässt andere Templates davon erben.

Das Basis-Template

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Meine Flask App{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <nav>
        <ul>
            <!-- Navigation hier -->
        </ul>
    </nav>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; 2024 Meine Flask App</p>
    </footer>
</body>
</html>

Die wichtigen Teile:

  • {% block title %}...{% endblock %}: Platzhalter für den Titel
  • {% block content %}...{% endblock %}: Platzhalter für den Hauptinhalt
  • Alles andere (Header, Footer, Navigation) wird vererbt

Ein Child-Template

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!-- templates/home.html -->
{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
<div class="hero">
    <h1>Willkommen zu meiner Flask App!</h1>
    <p>Dies ist ein einfaches Beispiel für eine Flask-Webanwendung.</p>
</div>

<div class="potray">
    <ul>
        <li>{{ vor }}</li>
        <li>{{ nach }}</li>
        <li>{{ gj }}</li>
    </ul>
</div>
{% endblock %}

So funktioniert’s:

  1. {% extends "base.html" %} lädt das Basis-Template
  2. {% block title %} überschreibt den Titel-Block
  3. {% block content %} füllt den Content-Block
  4. Header, Footer etc. kommen automatisch vom Basis-Template

Vorteile der Vererbung

  • DRY-Prinzip: Don’t Repeat Yourself
  • Konsistenz: Alle Seiten haben das gleiche Grundgerüst
  • Wartbarkeit: Änderungen am Layout nur in base.html nötig

Styling mit CSS-Klassen

Statische Dateien einbinden

In Jinja nutzt du url_for(), um auf statische Dateien (CSS, JavaScript, Bilder) zuzugreifen:

1
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">

Flask sucht automatisch im static/ Ordner nach der Datei.

CSS-Klassen in Templates verwenden

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- Template -->
<div class="hero">
    <h1>Willkommen!</h1>
</div>

<div class="potray">
    <ul>
        <li>{{ vor }}</li>
    </ul>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* static/style.css */
.hero {
    background-color: aliceblue;
    border-radius: 5px;
    padding: 20px;
}

.potray {
    background-color: gray;
    padding: 20px;
}

.potray li {
    font-size: 20px;
    color: white;
}

Die CSS-Klassen funktionieren genauso wie in normalem HTML!

Bedingungen in Templates

Mit Jinja kannst du Bedingungen direkt im Template verwenden. Das ist nützlich, um unterschiedliche Inhalte je nach Situation anzuzeigen.

Einfache if-Bedingung

1
2
3
4
5
# app.py
@app.route("/alter")
def alter_check():
    alter = 16
    return render_template("alter.html", alter=alter)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- templates/alter.html -->
{% extends "base.html" %}

{% block content %}
<h1>Altersprüfung</h1>

{% if alter >= 18 %}
    <p>Du bist volljährig! 🎉</p>
{% else %}
    <p>Du bist noch minderjährig.</p>
{% endif %}

<p>Dein Alter: {{ alter }}</p>
{% endblock %}

if-elif-else Bedingungen

1
2
3
4
5
6
7
{% if alter >= 18 %}
    <p class="erwachsen">Volljährig</p>
{% elif alter >= 13 %}
    <p class="teenager">Teenager</p>
{% else %}
    <p class="kind">Kind</p>
{% endif %}

Mehrere Bedingungen kombinieren

1
2
3
4
5
6
7
{% if alter >= 18 and name == "Max" %}
    <p>Hallo Max, du bist volljährig!</p>
{% endif %}

{% if vorname or nachname %}
    <p>Name vorhanden: {{ vorname }} {{ nachname }}</p>
{% endif %}

Existenz prüfen

1
2
3
4
5
6
<!-- Prüfen, ob eine Variable existiert/nicht leer ist -->
{% if username %}
    <p>Eingeloggt als: {{ username }}</p>
{% else %}
    <p>Nicht eingeloggt</p>
{% endif %}

Aufgabe 2: Bedingungen üben

Erstelle eine Route /zahl, die eine Zahl als Variable übergibt.

Im Template:

  • Prüfe, ob die Zahl gerade oder ungerade ist
  • Zeige eine entsprechende Nachricht an

Tipp: Eine Zahl ist gerade, wenn zahl % 2 == 0

Werte zwischen Seiten übergeben

Oft möchtest du Daten von einer Seite zur nächsten weitergeben. Dafür gibt es in Webentwicklung zwei Hauptmethoden: GET und POST.

GET-Parameter (URL-Parameter)

GET-Parameter werden in der URL übertragen und sind sichtbar.

Beispiel:

1
2
3
4
5
6
7
8
# app.py
from flask import Flask, render_template, request

@app.route("/gruss")
def gruss():
    # Holt 'name' aus URL, Standard ist 'Gast'
    name = request.args.get('name', 'Gast')
    return render_template("gruss.html", name=name)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!-- templates/gruss.html -->
{% extends "base.html" %}

{% block content %}
<h1>Grüße!</h1>

{% if name == 'Gast' %}
    <p>Hallo Gast! Wie heißt du?</p>
{% else %}
    <p>Hallo {{ name }}! Schön dich zu sehen!</p>
{% endif %}

<p>Du kannst deinen Namen in der URL übergeben:</p>
<ul>
    <li><a href="/gruss?name=Max">Grüße Max</a></li>
    <li><a href="/gruss?name=Anna">Grüße Anna</a></li>
    <li><a href="/gruss">Ohne Name</a></li>
</ul>
{% endblock %}

URL-Beispiele:

  • http://localhost:5000/gruss → zeigt “Hallo Gast!”
  • http://localhost:5000/gruss?name=Max → zeigt “Hallo Max!”
  • http://localhost:5000/gruss?name=Anna → zeigt “Hallo Anna!”

Wofür nutzt man GET?

  • Suchfunktionen
  • Filter
  • Links teilen (URL kann kopiert werden)
  • Daten abrufen (lesen)

POST-Formulare

POST sendet Daten verborgen (nicht in der URL). Das ist sicherer für sensible Daten.

Beispiel:

1
2
3
4
5
6
7
8
# app.py
@app.route("/login", methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        return render_template("willkommen.html", username=username)
    return render_template("login.html")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<!-- templates/login.html -->
{% extends "base.html" %}

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

<form method="POST" action="/login">
    <div>
        <label for="username">Benutzername:</label>
        <input type="text" id="username" name="username" required>
    </div>

    <div>
        <label for="password">Passwort:</label>
        <input type="password" id="password" name="password" required>
    </div>

    <button type="submit">Anmelden</button>
</form>
{% endblock %}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- templates/willkommen.html -->
{% extends "base.html" %}

{% block content %}
<h1>Willkommen!</h1>

{% if username %}
    <p>Hallo {{ username }}! Du hast dich erfolgreich angemeldet.</p>
{% else %}
    <p>Kein Benutzername angegeben.</p>
{% endif %}

<a href="/login">Zurück zum Login</a>
{% endblock %}

Wichtige Teile:

  1. <form method="POST"> → Formular nutzt POST
  2. name="username" → Feldname für request.form.get('username')
  3. methods=['GET', 'POST'] → Route akzeptiert beide Methoden
  4. request.method == 'POST' → Prüft, ob Formular abgesendet wurde

Wofür nutzt man POST?

  • Login-Formulare
  • Registrierung
  • Daten speichern/ändern
  • Sensible Informationen

GET vs POST - Der Unterschied

AspektGETPOST
SichtbarkeitParameter in URL sichtbarNicht in URL sichtbar
DatenmengeBegrenzt (~2000 Zeichen)Praktisch unbegrenzt
VerwendungLinks, Filter, SucheFormulare, Login, Daten speichern
SicherheitWeniger sicher (URL-Verlauf)Sicherer (Daten im Request Body)
Browser-CacheWird gecachtWird nicht gecacht
LesezeichenKann als Lesezeichen gespeichert werdenKann nicht gespeichert werden
Beispiel?suche=Python&kategorie=TutorialLogin-Formulare, Datei-Upload

Faustregel

  • GET: Wenn du Daten nur abrufen/lesen willst
  • POST: Wenn du Daten senden/ändern/speichern willst

Eine Eselsbrücke: GET = “Gucken”, POST = “Posten/Senden”

Übungen

Übung 1: Personalisierte Begrüßung

Erstelle eine Route /willkommen, die einen Namen als GET-Parameter erhält.

Anforderungen:

  • Wenn ein Name übergeben wird: “Hallo [Name]! Willkommen auf meiner Seite.”
  • Wenn kein Name übergeben wird: “Hallo Gast! Bitte gib deinen Namen an.”
  • Nutze {% if %} im Template für die Bedingung
  • Erstelle mindestens 2 Links mit verschiedenen Namen

Tipp: Nutze request.args.get('name') ohne Standard-Wert, dann ist der Wert None wenn kein Parameter übergeben wird.

Lösung zeigen
1
2
3
4
5
# app.py
@app.route("/willkommen")
def willkommen():
    name = request.args.get('name')
    return render_template("willkommen_uebung.html", name=name)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- templates/willkommen_uebung.html -->
{% extends "base.html" %}

{% block title %}Willkommen{% endblock %}

{% block content %}
<h1>Willkommen!</h1>

{% if name %}
    <p>Hallo {{ name }}! Willkommen auf meiner Seite.</p>
{% else %}
    <p>Hallo Gast! Bitte gib deinen Namen an.</p>
{% endif %}

<h2>Versuche diese Links:</h2>
<ul>
    <li><a href="/willkommen?name=Max">Grüße Max</a></li>
    <li><a href="/willkommen?name=Lisa">Grüße Lisa</a></li>
    <li><a href="/willkommen">Ohne Name</a></li>
</ul>
{% endblock %}

Übung 2: Login-System

Erstelle ein einfaches Login-Formular.

Anforderungen:

  1. Route /login mit GET und POST Methoden
  2. Formular mit Feldern für Benutzername und Passwort
  3. Nach dem Absenden: Zeige “Willkommen [Benutzername]!”
  4. Bonus: Prüfe mit {% if %}, ob der Benutzername leer ist

Hinweis: In dieser einfachen Version prüfen wir das Passwort noch nicht! Das kommt eventuell später.

Lösung zeigen
1
2
3
4
5
6
7
# app.py
@app.route("/login_uebung", methods=['GET', 'POST'])
def login_uebung():
    if request.method == 'POST':
        username = request.form.get('username')
        return render_template("login_erfolg.html", username=username)
    return render_template("login_formular.html")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- templates/login_formular.html -->
{% extends "base.html" %}

{% block title %}Login{% endblock %}

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

<form method="POST" action="/login_uebung">
    <div>
        <label for="username">Benutzername:</label>
        <input type="text" id="username" name="username" required>
    </div>

    <div>
        <label for="password">Passwort:</label>
        <input type="password" id="password" name="password" required>
    </div>

    <button type="submit">Anmelden</button>
</form>
{% endblock %}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- templates/login_erfolg.html -->
{% extends "base.html" %}

{% block title %}Willkommen{% endblock %}

{% block content %}
<h1>Login erfolgreich!</h1>

{% if username %}
    <p>Willkommen {{ username }}! Du hast dich erfolgreich angemeldet.</p>
{% else %}
    <p>Fehler: Kein Benutzername angegeben.</p>
{% endif %}

<a href="/login_uebung">Zurück zum Login</a>
{% endblock %}

Übung 3: GET vs POST verstehen

Erstelle zwei verschiedene Routen, um den Unterschied zu verstehen.

Anforderungen:

  1. Route /suche - nutzt GET-Parameter für eine Suchanfrage
    • Template mit einem Link: /suche?query=Python
    • Zeige die Suchanfrage im Template an
  2. Route /nachricht - nutzt POST für eine Nachricht
    • Formular mit einem Textfeld für die Nachricht
    • Zeige die Nachricht nach dem Absenden an

Beobachte: Schaue dir die URL an, nachdem du das Formular abgeschickt hast. Siehst du einen Unterschied?

Lösung zeigen
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# app.py
@app.route("/suche")
def suche():
    query = request.args.get('query', '')
    return render_template("suche.html", query=query)

@app.route("/nachricht", methods=['GET', 'POST'])
def nachricht():
    if request.method == 'POST':
        nachricht_text = request.form.get('nachricht')
        return render_template("nachricht_anzeige.html", nachricht=nachricht_text)
    return render_template("nachricht_formular.html")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- templates/suche.html -->
{% extends "base.html" %}

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

{% if query %}
    <p>Du hast gesucht nach: <strong>{{ query }}</strong></p>
{% else %}
    <p>Keine Suchanfrage angegeben.</p>
{% endif %}

<h2>Probiere diese Suchen:</h2>
<ul>
    <li><a href="/suche?query=Python">Suche nach Python</a></li>
    <li><a href="/suche?query=Flask">Suche nach Flask</a></li>
    <li><a href="/suche?query=Jinja">Suche nach Jinja</a></li>
</ul>

<p><strong>Beachte:</strong> Die Suchanfrage ist in der URL sichtbar!</p>
{% endblock %}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!-- templates/nachricht_formular.html -->
{% extends "base.html" %}

{% block content %}
<h1>Nachricht senden</h1>

<form method="POST" action="/nachricht">
    <div>
        <label for="nachricht">Deine Nachricht:</label>
        <textarea id="nachricht" name="nachricht" rows="4" required></textarea>
    </div>

    <button type="submit">Senden</button>
</form>
{% endblock %}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!-- templates/nachricht_anzeige.html -->
{% extends "base.html" %}

{% block content %}
<h1>Nachricht erhalten!</h1>

{% if nachricht %}
    <p>Deine Nachricht:</p>
    <blockquote>{{ nachricht }}</blockquote>
{% else %}
    <p>Keine Nachricht erhalten.</p>
{% endif %}

<p><strong>Beachte:</strong> Die Nachricht ist NICHT in der URL sichtbar!</p>

<a href="/nachricht">Neue Nachricht senden</a>
{% endblock %}

Zusammenfassung

Das Wichtigste auf einen Blick:

Variablen:

  • In Python übergeben: render_template("file.html", var=value)
  • Im Template ausgeben: {{ variable }}

Template-Vererbung:

  • Basis-Template erstellen mit {% block name %}...{% endblock %}
  • In Child-Template erben: {% extends "base.html" %}
  • Blöcke überschreiben mit gleichem Block-Namen

Bedingungen:

  • {% if bedingung %}...{% endif %}
  • {% if %}...{% elif %}...{% else %}...{% endif %}
  • Mehrere Bedingungen: and, or

Datenweitergabe:

  • GET: Daten in URL sichtbar, für Links und Suche
    • request.args.get('name')
    • Verwendung: /route?name=Max
  • POST: Daten verborgen, für Formulare
    • request.form.get('name')
    • <form method="POST">
    • methods=['GET', 'POST'] in Route nötig

Statische Dateien:

  • {{ url_for('static', filename='style.css') }}

Häufige Fehler vermeiden

  1. Vergessene Imports:

    • from flask import request nicht vergessen für GET/POST!
  2. Template-Namen:

    • Dateinamen in render_template() müssen exakt stimmen
    • Templates müssen im templates/ Ordner liegen
  3. POST ohne methods:

    • Bei POST-Formularen: methods=['GET', 'POST'] in @app.route() nicht vergessen
  4. Jinja-Syntax:

    • {{ }} für Ausgabe
    • {% %} für Befehle (if, for, block, extends)
    • Nicht verwechseln!
  5. Block-Namen:

    • In Child-Templates müssen die Block-Namen exakt wie im Parent sein
Zuletzt aktualisiert am