6 Templates und Jinja2

6.1 Template-Engine Grundlagen

Jinja2 ist die Standard-Template-Engine von Flask und dient der Trennung von Darstellungslogik und Geschäftslogik. Templates kombinieren statisches HTML mit dynamischen Daten und eliminieren die Notwendigkeit, HTML-Strings im Python-Code zu konstruieren.

6.1.1 Funktionsweise von Jinja2

Jinja2 verarbeitet Template-Dateien und ersetzt spezielle Markierungen durch Daten aus der Flask-Anwendung. Die Engine unterstützt drei grundlegende Syntaxelemente:

Variablen-Ausgabe: {{ variable }} gibt den Wert einer Variable aus Kontrollstrukturen: {% statement %} für Logik wie Schleifen und Bedingungen
Kommentare: {# kommentar #} für Dokumentation im Template

6.1.2 Template-Verarbeitung

Der Prozess der Template-Verarbeitung erfolgt in mehreren Schritten. Flask lädt das Template aus dem templates-Verzeichnis, Jinja2 parst die Syntax und ersetzt die Platzhalter durch die übergebenen Daten, anschließend wird das fertige HTML an den Browser gesendet.

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/artikel/<int:id>')
def artikel_detail(id):
    # Daten aus Datenbank oder anderen Quellen
    artikel_daten = {
        'titel': 'Flask Templates verstehen',
        'autor': 'Max Mustermann',
        'inhalt': 'Ausführlicher Artikel über Templates...',
        'erstellt': '2024-03-15',
        'kategorie': 'Programmierung'
    }
    
    # Template mit Daten rendern
    return render_template('artikel_detail.html', artikel=artikel_daten)

6.1.3 Template-Syntax Grundlagen

Die Jinja2-Syntax unterscheidet zwischen verschiedenen Operationen. Einfache Variablen werden direkt ausgegeben, während komplexere Strukturen spezielle Notation erfordern.

<!-- templates/artikel_detail.html -->
<!DOCTYPE html>
<html lang="de">
<head>
    <title>{{ artikel.titel }}</title>
</head>
<body>
    <article>
        <h1>{{ artikel.titel }}</h1>
        <p>Autor: {{ artikel.autor }}</p>
        <time>{{ artikel.erstellt }}</time>
        
        <div class="inhalt">
            {{ artikel.inhalt }}
        </div>
    </article>
</body>
</html>

6.1.4 Datentypen in Templates

Jinja2 verarbeitet verschiedene Python-Datentypen direkt. Listen, Dictionaries, Objekte und primitive Datentypen können ohne Konvertierung verwendet werden.

@app.route('/dashboard')
def dashboard():
    template_kontext = {
        'benutzer': {
            'name': 'Anna Schmidt',
            'email': 'anna@beispiel.de',
            'rolle': 'Manager'
        },
        'statistiken': {
            'artikel_gesamt': 127,
            'aufrufe_monat': 15420,
            'kommentare_neu': 23
        },
        'kategorien': ['Technik', 'Business', 'Design'],
        'ist_premium': True
    }
    return render_template('dashboard.html', **template_kontext)

6.1.5 Objektzugriff in Templates

Templates ermöglichen sowohl Dictionary-Notation als auch Punktnotation für den Zugriff auf Objekteigenschaften. Dies bietet Flexibilität bei der Datenstrukturierung.

<!-- Beide Schreibweisen sind äquivalent -->
<h2>{{ benutzer.name }}</h2>
<h2>{{ benutzer['name'] }}</h2>

<!-- Listenzugriff mit Index -->
<p>Erste Kategorie: {{ kategorien[0] }}</p>

<!-- Sichere Zugriffe mit get() -->
<p>Telefon: {{ benutzer.get('telefon', 'Nicht angegeben') }}</p>

6.2 Template-Vererbung

Template-Vererbung ermöglicht die Erstellung wiederverwendbarer Layout-Strukturen. Ein Base-Template definiert die Grundstruktur der Seite, während Child-Templates spezifische Inhalte bereitstellen.

6.2.1 Base-Template erstellen

Das Base-Template enthält die gemeinsame HTML-Struktur und definiert Blöcke, die von Child-Templates überschrieben werden können.

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Standard Titel{% endblock %} - Firma</title>
    
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <!-- Custom CSS Block -->
    {% block extra_css %}{% endblock %}
</head>
<body>
    <!-- Navigation -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{{ url_for('index') }}">Firmenname</a>
            
            <div class="navbar-nav">
                <a class="nav-link" href="{{ url_for('index') }}">Start</a>
                <a class="nav-link" href="{{ url_for('produkte') }}">Produkte</a>
                <a class="nav-link" href="{{ url_for('kontakt') }}">Kontakt</a>
            </div>
        </div>
    </nav>
    
    <!-- Hauptinhalt -->
    <main class="container mt-4">
        {% block content %}
        <!-- Standardinhalt, falls kein Child-Template content definiert -->
        <p>Kein spezifischer Inhalt definiert.</p>
        {% endblock %}
    </main>
    
    <!-- Footer -->
    <footer class="bg-light mt-5 py-3">
        <div class="container text-center">
            <p>&copy; 2024 Firmenname. Alle Rechte vorbehalten.</p>
        </div>
    </footer>
    
    <!-- JavaScript Block -->
    {% block extra_js %}{% endblock %}
</body>
</html>

6.2.2 Child-Template implementieren

Child-Templates erweitern das Base-Template und überschreiben definierte Blöcke mit spezifischem Inhalt.

<!-- templates/produkte.html -->
{% extends "base.html" %}

{% block title %}Produktkatalog{% endblock %}

{% block extra_css %}
<style>
    .produkt-karte {
        border: 1px solid #ddd;
        border-radius: 8px;
        padding: 15px;
        margin-bottom: 20px;
    }
</style>
{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-12">
        <h1>Unser Produktkatalog</h1>
        <p>Entdecken Sie unsere hochwertigen Produkte.</p>
    </div>
</div>

<div class="row">
    {% for produkt in produkte %}
    <div class="col-md-4">
        <div class="produkt-karte">
            <h3>{{ produkt.name }}</h3>
            <p>{{ produkt.beschreibung }}</p>
            <p class="preis">{{ produkt.preis }} €</p>
            <a href="{{ url_for('produkt_detail', id=produkt.id) }}" class="btn btn-primary">
                Details anzeigen
            </a>
        </div>
    </div>
    {% endfor %}
</div>
{% endblock %}

{% block extra_js %}
<script>
    // Produktspezifisches JavaScript
    console.log('Produktseite geladen');
</script>
{% endblock %}

6.2.3 Block-Hierarchie und super()

Blöcke können verschachtelt werden und der Inhalt von Parent-Blöcken kann mit der super()-Funktion erweitert werden.

<!-- templates/admin_base.html -->
{% extends "base.html" %}

{% block title %}Admin - {{ super() }}{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-3">
        <nav class="admin-sidebar">
            <h4>Admin-Bereich</h4>
            <ul class="nav flex-column">
                <li class="nav-item">
                    <a class="nav-link" href="{{ url_for('admin_dashboard') }}">Dashboard</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{{ url_for('admin_benutzer') }}">Benutzer</a>
                </li>
            </ul>
        </nav>
    </div>
    
    <div class="col-md-9">
        {% block admin_content %}
        <p>Admin-Inhalt wird hier angezeigt</p>
        {% endblock %}
    </div>
</div>
{% endblock %}

6.3 Variablen und Filter

Filter transformieren Variablen vor der Ausgabe und bieten umfangreiche Formatierungsmöglichkeiten. Sie werden mit dem Pipe-Symbol angehängt und können Parameter erhalten.

6.3.1 Standard-Filter

Jinja2 enthält eine Vielzahl eingebauter Filter für häufige Formatierungsaufgaben.

<!-- String-Filter -->
<p>Name: {{ benutzer.name|upper }}</p>
<p>E-Mail: {{ benutzer.email|lower }}</p>
<p>Titel: {{ artikel.titel|title }}</p>
<p>Gekürzt: {{ beschreibung|truncate(50) }}</p>

<!-- Zahlen-Filter -->
<p>Preis: {{ produkt.preis|round(2) }} €</p>
<p>Absolut: {{ differenz|abs }}</p>

<!-- Listen-Filter -->
<p>Anzahl Tags: {{ tag_liste|length }}</p>
<p>Erstes Element: {{ artikel_liste|first }}</p>
<p>Letztes Element: {{ artikel_liste|last }}</p>
<p>Verbunden: {{ kategorien|join(', ') }}</p>

<!-- Datum-Filter -->
<p>Erstellt: {{ artikel.datum|strftime('%d.%m.%Y') }}</p>

6.3.2 Benutzerdefinierte Filter

Custom Filter erweitern die Funktionalität von Jinja2 um anwendungsspezifische Formatierungen.

from datetime import datetime

@app.template_filter('currency')
def currency_filter(amount):
    """Formatiert Beträge als Währung"""
    return f"{amount:,.2f} €"

@app.template_filter('timeago')
def timeago_filter(dt):
    """Zeigt relative Zeit an"""
    now = datetime.utcnow()
    diff = now - dt
    
    if diff.days > 0:
        return f"vor {diff.days} Tagen"
    elif diff.seconds > 3600:
        hours = diff.seconds // 3600
        return f"vor {hours} Stunden"
    elif diff.seconds > 60:
        minutes = diff.seconds // 60
        return f"vor {minutes} Minuten"
    else:
        return "gerade eben"

# Verwendung in Templates:
# {{ produkt.preis|currency }}
# {{ artikel.erstellt|timeago }}

6.3.3 Filter-Verkettung

Filter können miteinander verkettet werden, wobei das Ergebnis eines Filters als Eingabe für den nächsten dient.

<!-- Filter-Kette: erst kürzen, dann HTML escapen -->
<p>{{ artikel.text|truncate(100)|e }}</p>

<!-- Komplexere Verkettung -->
<p>{{ benutzer_liste|selectattr('aktiv')|list|length }} aktive Benutzer</p>

<!-- Mit Parametern -->
<p>{{ nachricht|wordwrap(40)|indent(4, true) }}</p>

6.4 Kontrollstrukturen in Templates

Jinja2 bietet mächtige Kontrollstrukturen für bedingte Anzeige, Schleifen und komplexe Template-Logik.

6.4.1 Bedingte Anzeige

if-Statements ermöglichen die bedingte Ausgabe von HTML-Elementen basierend auf Variablenwerten oder Ausdrücken.

{% if benutzer.ist_angemeldet %}
    <div class="benutzer-info">
        <h3>Willkommen, {{ benutzer.name }}!</h3>
        
        {% if benutzer.rolle == 'admin' %}
            <a href="{{ url_for('admin_panel') }}" class="btn btn-danger">
                Admin-Bereich
            </a>
        {% elif benutzer.rolle == 'moderator' %}
            <a href="{{ url_for('moderator_panel') }}" class="btn btn-warning">
                Moderator-Bereich
            </a>
        {% else %}
            <p>Standard-Benutzerrechte</p>
        {% endif %}
    </div>
{% else %}
    <div class="anmelde-aufforderung">
        <p>Bitte melden Sie sich an, um fortzufahren.</p>
        <a href="{{ url_for('login') }}" class="btn btn-primary">Anmelden</a>
    </div>
{% endif %}

6.4.2 Schleifen und Iteration

for-Schleifen iterieren über Listen, Dictionaries und andere iterierbare Objekte. Die loop-Variable bietet zusätzliche Informationen über den aktuellen Schleifendurchlauf.

<!-- Einfache Liste -->
{% for artikel in artikel_liste %}
    <div class="artikel-vorschau">
        <h4>{{ artikel.titel }}</h4>
        <p>{{ artikel.zusammenfassung }}</p>
        
        <!-- Loop-Variablen nutzen -->
        <small>
            Artikel {{ loop.index }} von {{ loop.length }}
            {% if loop.first %} (Neuester){% endif %}
            {% if loop.last %} (Ältester){% endif %}
        </small>
    </div>
{% else %}
    <!-- Wird ausgeführt, wenn artikel_liste leer ist -->
    <p>Keine Artikel vorhanden.</p>
{% endfor %}

<!-- Dictionary-Iteration -->
{% for schluessel, wert in einstellungen.items() %}
    <div class="einstellung">
        <label>{{ schluessel|replace('_', ' ')|title }}:</label>
        <span>{{ wert }}</span>
    </div>
{% endfor %}

<!-- Verschachtelte Schleifen -->
{% for kategorie in kategorien %}
    <div class="kategorie-gruppe">
        <h3>{{ kategorie.name }}</h3>
        {% for artikel in kategorie.artikel %}
            <div class="artikel-eintrag">
                <a href="{{ url_for('artikel_detail', id=artikel.id) }}">
                    {{ artikel.titel }}
                </a>
                <span class="datum">({{ artikel.datum|strftime('%d.%m.%Y') }})</span>
            </div>
        {% endfor %}
    </div>
{% endfor %}

6.4.3 Erweiterte Template-Logik

Jinja2 unterstützt komplexere Kontrollstrukturen für anspruchsvolle Template-Logik.

<!-- Set-Statement für Variablen -->
{% set benutzer_vollname = benutzer.vorname + ' ' + benutzer.nachname %}
{% set ist_wochenende = datum.weekday() >= 5 %}

<!-- With-Statement für Kontext -->
{% with aktuelle_nachrichten = nachrichten|selectattr('wichtig')|list %}
    {% if aktuelle_nachrichten %}
        <div class="wichtige-nachrichten">
            {% for nachricht in aktuelle_nachrichten %}
                <div class="alert alert-warning">{{ nachricht.text }}</div>
            {% endfor %}
        </div>
    {% endif %}
{% endwith %}

<!-- Bedingte Schleifen -->
{% for produkt in produkte if produkt.verfuegbar and produkt.preis > 0 %}
    <div class="verfuegbares-produkt">
        <h4>{{ produkt.name }}</h4>
        <p>{{ produkt.preis|currency }}</p>
    </div>
{% endfor %}

6.5 Makros und Includes in Templates

Makros und Includes fördern die Wiederverwendbarkeit von Template-Code und reduzieren Redundanz in der Präsentationsschicht.

6.5.1 Makro-Definition und -verwendung

Makros funktionieren wie Funktionen in Templates und ermöglichen parametrisierte, wiederverwendbare Template-Fragmente.

<!-- templates/macros/form_helpers.html -->
{% macro input_field(name, type="text", label="", value="", required=false, class="form-control") %}
    <div class="mb-3">
        {% if label %}
            <label for="{{ name }}" class="form-label">
                {{ label }}
                {% if required %}<span class="text-danger">*</span>{% endif %}
            </label>
        {% endif %}
        <input type="{{ type }}" 
               id="{{ name }}" 
               name="{{ name }}" 
               value="{{ value }}"
               class="{{ class }}"
               {% if required %}required{% endif %}>
    </div>
{% endmacro %}

{% macro submit_button(text="Speichern", class="btn btn-primary") %}
    <button type="submit" class="{{ class }}">{{ text }}</button>
{% endmacro %}

6.5.2 Makro-Import und -verwendung

Makros werden importiert und können dann in anderen Templates verwendet werden.

<!-- templates/benutzer_formular.html -->
{% from 'macros/form_helpers.html' import input_field, submit_button %}

{% extends "base.html" %}

{% block content %}
<form method="POST">
    {{ input_field('vorname', label='Vorname', value=benutzer.vorname, required=true) }}
    {{ input_field('nachname', label='Nachname', value=benutzer.nachname, required=true) }}
    {{ input_field('email', type='email', label='E-Mail-Adresse', value=benutzer.email, required=true) }}
    {{ input_field('telefon', label='Telefonnummer', value=benutzer.telefon) }}
    
    <div class="mt-3">
        {{ submit_button('Benutzer speichern') }}
        <a href="{{ url_for('benutzer_liste') }}" class="btn btn-secondary">Abbrechen</a>
    </div>
</form>
{% endblock %}

6.5.3 Include-Direktiven

Include fügt den Inhalt anderer Template-Dateien direkt ein und eignet sich für statische Template-Teile.

<!-- templates/partials/header.html -->
<header class="site-header">
    <div class="container">
        <h1>{{ app_name }}</h1>
        <nav>
            <a href="{{ url_for('index') }}">Start</a>
            <a href="{{ url_for('about') }}">Über uns</a>
        </nav>
    </div>
</header>

<!-- templates/partials/footer.html -->
<footer class="site-footer">
    <div class="container">
        <p>&copy; {{ current_year }} {{ firma_name }}</p>
        <p>Kontakt: {{ kontakt_email }}</p>
    </div>
</footer>

<!-- Haupttemplate -->
<!-- templates/layout.html -->
<!DOCTYPE html>
<html lang="de">
<head>
    <title>{{ title }}</title>
</head>
<body>
    {% include 'partials/header.html' %}
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    {% include 'partials/footer.html' %}
</body>
</html>

6.5.4 Makros mit Call-Blöcken

Call-Blöcke ermöglichen es Makros, Template-Inhalte als Parameter zu erhalten.

<!-- templates/macros/layout_helpers.html -->
{% macro modal(id, title, size="") %}
    <div class="modal fade" id="{{ id }}" tabindex="-1">
        <div class="modal-dialog {{ 'modal-' + size if size }}">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">{{ title }}</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    {{ caller() }}
                </div>
            </div>
        </div>
    </div>
{% endmacro %}

<!-- Verwendung mit Call-Block -->
{% from 'macros/layout_helpers.html' import modal %}

{% call modal('benutzer-modal', 'Benutzer bearbeiten', 'lg') %}
    <form>
        <input type="text" name="name" placeholder="Name">
        <input type="email" name="email" placeholder="E-Mail">
    </form>
{% endcall %}

Diese Template-Konzepte bilden das Fundament für saubere, wartbare Benutzeroberflächen in Flask-Anwendungen und ermöglichen die effiziente Entwicklung konsistenter Web-Interfaces.