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.
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
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)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>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)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>Template-Vererbung ermöglicht die Erstellung wiederverwendbarer Layout-Strukturen. Ein Base-Template definiert die Grundstruktur der Seite, während Child-Templates spezifische Inhalte bereitstellen.
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>© 2024 Firmenname. Alle Rechte vorbehalten.</p>
</div>
</footer>
<!-- JavaScript Block -->
{% block extra_js %}{% endblock %}
</body>
</html>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 %}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 %}Filter transformieren Variablen vor der Ausgabe und bieten umfangreiche Formatierungsmöglichkeiten. Sie werden mit dem Pipe-Symbol angehängt und können Parameter erhalten.
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>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 }}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>Jinja2 bietet mächtige Kontrollstrukturen für bedingte Anzeige, Schleifen und komplexe Template-Logik.
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 %}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 %}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 %}Makros und Includes fördern die Wiederverwendbarkeit von Template-Code und reduzieren Redundanz in der Präsentationsschicht.
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 %}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 %}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>© {{ 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>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.