Cours Python · Module 5: Flask et applications web

Templates Jinja2 - interfaces dynamiques

13 min de lecture
Dans cette leçon11

Bon retour ! Darwin est là avec un nouvel outil - les templates Jinja2 !

Jusqu'ici tu écrivais le HTML sous forme de chaînes codées en dur dans des fonctions Python. Ça marche, mais c'est illisible, difficile à maintenir et peu pratique pour de plus grandes applications !

Analogie Safari : Le HTML codé en dur, c'est comme dessiner chaque carte de safari à la main depuis zéro - fatigant et chronophage ! Les templates Jinja2 sont comme des modèles de cartes - tu prépares la structure une fois, puis tu n'as plus qu'à remplir les données !

Le problème du HTML codé en dur

Souviens-toi du code de la leçon précédente :

1@app.route('/species')
2def list_species():
3    species_html = ""
4    for spec in species_db.values():
5        species_html += f"""
6        <div style="background: white; padding: 15px;">
7            <h3>{spec['name']}</h3>
8            <p><strong>Population :</strong> {spec['population']}</p>
9        </div>
10        """
11
12    return f"""
13    <!DOCTYPE html>
14    <html>
15    <head><title>Liste des espèces</title></head>
16    <body>
17        <h1>Liste des espèces</h1>
18        {species_html}
19    </body>
20    </html>
21    """

Problèmes : HTML mélangé à la logique Python Difficile à lire et à modifier Pas de coloration syntaxique HTML Duplication de code (navigation, pied de page) Collaboration difficile avec les designers Aucune réutilisabilité

Solution - les templates Jinja2

Jinja2 est un template engine - un moteur de templates qui permet de séparer le HTML de la logique Python.

Comment ça marche ?

  1. Tu crées des fichiers HTML dans le dossier templates/
  2. Tu utilises {{ variable }} pour afficher des données
  3. Tu utilises {% if %}, {% for %} pour la logique
  4. Flask rend le template + les données → HTML terminé

Analogie : Jinja2 est comme un formulaire à remplir - tu as une structure prête (HTML), tu n'as qu'à insérer les données (variables Python) !

Structure d'un projet avec templates

1safari_web/
2│
3├── app.py
4│
5├── templates/          ← Dossier pour les templates HTML
6│   ├── base.html
7│   ├── home.html
8│   ├── species_list.html
9│   └── species_detail.html
10│
11└── static/             ← Dossier pour les fichiers statiques
12    ├── css/
13    │   └── style.css
14    ├── js/
15    │   └── script.js
16    └── images/
17        └── logo.png

Flask cherche automatiquement les fichiers dans le dossier templates/ !

Les bases de Jinja2 - la syntaxe

1. Variables - {{ }}

Afficher la valeur d'une variable :

1<h1>{{ species_name }}</h1>
2<p>Population : {{ population }}</p>

2. Expressions

1<p>{{ 2 + 3 }}</p>                    <!-- 5 -->
2<p>{{ "Bonjour " + name }}</p>        <!-- Bonjour Darwin -->
3<p>{{ species.name }}</p>             <!-- Accès à un attribut -->
4<p>{{ species['name'] }}</p>          <!-- Accès à un dict -->
5<p>{{ species_list[0] }}</p>          <!-- Accès à un élément de liste -->

3. Structures de contrôle - {% %}

Instruction if :

1{% if population > 1000 %}
2    <span style="color: green;">Non menacée</span>
3{% elif population > 100 %}
4    <span style="color: orange;">En danger</span>
5{% else %}
6    <span style="color: red;">En danger critique</span>
7{% endif %}

Boucle for :

1<ul>
2{% for species in species_list %}
3    <li>{{ species.name }} - {{ species.population }} individus</li>
4{% endfor %}
5</ul>

Boucle for avec index :

1{% for species in species_list %}
2    <p>{{ loop.index }}. {{ species.name }}</p>
3{% endfor %}

4. Filtres - |

Les filtres modifient les variables :

1<p>{{ name | upper }}</p>                    <!-- LION -->
2<p>{{ name | lower }}</p>                    <!-- lion -->
3<p>{{ name | title }}</p>                    <!-- Lion -->
4<p>{{ species_list | length }}</p>           <!-- 3 -->
5<p>{{ price | round(2) }}</p>                <!-- 19.99 -->
6<p>{{ text | truncate(50) }}</p>             <!-- Tronquer à 50 caractères -->
7<p>{{ published_date | default('Aucune') }}</p> <!-- Valeur par défaut -->

Filtres populaires :

  • upper, lower, title - changement de casse
  • length - longueur d'une liste/chaîne
  • default(value) - valeur par défaut si None
  • round(n) - arrondir à n décimales
  • truncate(n) - tronquer le texte
  • join(separator) - joindre une liste
  • safe - désactiver l'échappement HTML (à utiliser avec précaution !)

5. Commentaires - {# #}

1{# Ceci est un commentaire - il ne sera pas visible dans le HTML #}
2<h1>{{ species_name }}</h1>

Rendre des templates dans Flask

Utilise la fonction render_template() :

1from flask import Flask, render_template
2
3app = Flask(__name__)
4
5@app.route('/')
6def home():
7    # Rendre le template + passer des variables
8    return render_template('home.html',
9                         title='Safari Database',
10                         species_count=3)
11
12@app.route('/species/<int:species_id>')
13def show_species(species_id):
14    species = species_db.get(species_id)
15
16    return render_template('species_detail.html',
17                         species=species)

Flask :

  1. Cherche le fichier templates/home.html
  2. Rend le template avec les variables passées
  3. Renvoie le HTML terminé

Premier template - home.html

Crée un dossier templates/ et un fichier home.html :

1<!DOCTYPE html>
2<html lang="fr">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>{{ title }}</title>
7    <style>
8        body {
9            font-family: Arial, sans-serif;
10            max-width: 800px;
11            margin: 50px auto;
12            padding: 20px;
13            background-color: #f4f1de;
14        }
15        h1 { color: #2c5f2d; }
16    </style>
17</head>
18<body>
19    <h1>{{ title }}</h1>
20    <p>Bienvenue sur Safari Database !</p>
21    <p>La base de données contient <strong>{{ species_count }} espèces</strong>.</p>
22</body>
23</html>

app.py :

1from flask import Flask, render_template
2
3app = Flask(__name__)
4
5@app.route('/')
6def home():
7    return render_template('home.html',
8                         title='Safari Database',
9                         species_count=3)
10
11if __name__ == '__main__':
12    app.run(debug=True)

Lance : http://localhost:5000/ - tu verras le template rendu !

Héritage de templates

La fonctionnalité Jinja2 la plus importante ! Elle permet de créer un template de base (layout) et de l'étendre dans d'autres templates.

Analogie : L'héritage de templates est comme l'héritage de l'ADN - une espèce descendante (template enfant) hérite des traits de son parent (template de base), mais peut avoir ses propres caractéristiques uniques !

base.html - template de base

1<!DOCTYPE html>
2<html lang="fr">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>{% block title %}Safari Database{% endblock %}</title>
7    <style>
8        body {
9            font-family: Arial, sans-serif;
10            max-width: 900px;
11            margin: 0 auto;
12            padding: 20px;
13            background-color: #f4f1de;
14        }
15        header {
16            background-color: #2c5f2d;
17            color: white;
18            padding: 20px;
19            border-radius: 8px;
20            margin-bottom: 20px;
21        }
22        nav {
23            margin: 20px 0;
24            padding: 15px;
25            background: white;
26            border-radius: 8px;
27        }
28        nav a {
29            margin-right: 15px;
30            color: #2c5f2d;
31            text-decoration: none;
32            font-weight: bold;
33        }
34        nav a:hover {
35            text-decoration: underline;
36        }
37        main {
38            background: white;
39            padding: 30px;
40            border-radius: 12px;
41        }
42        footer {
43            margin-top: 30px;
44            text-align: center;
45            color: #666;
46        }
47    </style>
48    {% block extra_css %}{% endblock %}
49</head>
50<body>
51    <header>
52        <h1>Safari Database</h1>
53        <p>Système de gestion des données d'espèces africaines</p>
54    </header>
55
56    <nav>
57        <a href="{{ url_for('home') }}">Accueil</a>
58        <a href="{{ url_for('list_species') }}">Liste des espèces</a>
59        <a href="{{ url_for('search') }}">Rechercher</a>
60    </nav>
61
62    <main>
63        {% block content %}
64        <!-- Le contenu des templates enfants va ici -->
65        {% endblock %}
66    </main>
67
68    <footer>
69        <p>&copy; 2024 Safari Database par Darwin</p>
70    </footer>
71
72    {% block extra_js %}{% endblock %}
73</body>
74</html>

Blocs ({% block %}) : Emplacements qui peuvent être redéfinis dans les templates enfants.

home.html - étend base.html

1{% extends 'base.html' %}
2
3{% block title %}Accueil - Safari Database{% endblock %}
4
5{% block content %}
6    <h2>Bienvenue sur Safari Database !</h2>
7    <p>La base de données contient <strong>{{ species_count }} espèces</strong>.</p>
8
9    <div style="margin-top: 30px;">
10        <h3>Fonctionnalités du système :</h3>
11        <ul>
12            <li>Parcourir la liste des espèces</li>
13            <li>Rechercher des espèces</li>
14            <li>Statistiques de population</li>
15            <li>Ajouter de nouvelles observations</li>
16        </ul>
17    </div>
18
19    <p style="margin-top: 30px;">
20        <a href="{{ url_for('list_species') }}" style="background: #2c5f2d; color: white; padding: 10px 20px; border-radius: 4px; text-decoration: none;">
21            Voir toutes les espèces →
22        </a>
23    </p>
24{% endblock %}

{% extends 'base.html' %} - hérite de base.html {% block content %} - redéfinit le bloc content

species_list.html - liste des espèces

1{% extends 'base.html' %}
2
3{% block title %}Liste des espèces - Safari Database{% endblock %}
4
5{% block content %}
6    <h2>Toutes les espèces ({{ species | length }})</h2>
7
8    {% if species %}
9        {% for spec in species %}
10            <div style="background: #f9f9f9; padding: 20px; margin: 15px 0; border-radius: 8px; border-left: 4px solid #2c5f2d;">
11                <h3 style="margin-top: 0;">{{ spec.name }}</h3>
12                <p><strong>Nom scientifique :</strong> <em>{{ spec.scientific_name }}</em></p>
13                <p><strong>Population :</strong> {{ spec.population }} individus</p>
14
15                {% if spec.population > 1000 %}
16                    <span style="color: green;">Non menacée</span>
17                {% elif spec.population > 100 %}
18                    <span style="color: orange;">En danger</span>
19                {% else %}
20                    <span style="color: red;">En danger critique</span>
21                {% endif %}
22
23                <p style="margin-top: 15px;">
24                    <a href="{{ url_for('show_species', species_id=spec.id) }}" style="color: #2c5f2d; font-weight: bold;">
25                        Voir les détails →
26                    </a>
27                </p>
28            </div>
29        {% endfor %}
30    {% else %}
31        <p style="color: #666;">Aucune espèce dans la base de données.</p>
32    {% endif %}
33{% endblock %}

species_detail.html - détails d'une espèce

1{% extends 'base.html' %}
2
3{% block title %}{{ species.name }} - Safari Database{% endblock %}
4
5{% block content %}
6    {% if species %}
7        <h2>{{ species.name }}</h2>
8
9        <div style="background: #f9f9f9; padding: 30px; border-radius: 12px; border: 3px solid #2c5f2d;">
10            <table style="width: 100%; border-collapse: collapse;">
11                <tr>
12                    <td style="padding: 10px; font-weight: bold;">Nom commun :</td>
13                    <td style="padding: 10px;">{{ species.name }}</td>
14                </tr>
15                <tr>
16                    <td style="padding: 10px; font-weight: bold;">Nom scientifique :</td>
17                    <td style="padding: 10px;"><em>{{ species.scientific_name }}</em></td>
18                </tr>
19                <tr>
20                    <td style="padding: 10px; font-weight: bold;">Population :</td>
21                    <td style="padding: 10px;">{{ species.population }} individus</td>
22                </tr>
23                <tr>
24                    <td style="padding: 10px; font-weight: bold;">Statut :</td>
25                    <td style="padding: 10px;">
26                        {% if species.population > 1000 %}
27                            <span style="color: green;">Non menacée</span>
28                        {% elif species.population > 100 %}
29                            <span style="color: orange;">En danger</span>
30                        {% else %}
31                            <span style="color: red;">En danger critique</span>
32                        {% endif %}
33                    </td>
34                </tr>
35                <tr>
36                    <td style="padding: 10px; font-weight: bold;">ID système :</td>
37                    <td style="padding: 10px;">{{ species.id }}</td>
38                </tr>
39            </table>
40        </div>
41
42        <p style="margin-top: 30px;">
43            <a href="{{ url_for('list_species') }}" style="color: #2c5f2d;">← Retour à la liste</a> |
44            <a href="{{ url_for('home') }}" style="color: #2c5f2d;">← Page d'accueil</a>
45        </p>
46    {% else %}
47        <h2>Espèce introuvable</h2>
48        <p>L'espèce avec l'ID donné n'existe pas dans la base de données.</p>
49        <p><a href="{{ url_for('list_species') }}">← Retour à la liste</a></p>
50    {% endif %}
51{% endblock %}

Fichiers statiques

Les fichiers CSS, JavaScript, images sont stockés dans le dossier static/.

Structure :

1static/
2├── css/
3│   └── style.css
4├── js/
5│   └── script.js
6└── images/
7    └── logo.png

static/css/style.css

1/* Styles de Safari Database */
2body {
3    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
4    max-width: 900px;
5    margin: 0 auto;
6    padding: 20px;
7    background-color: #f4f1de;
8}
9
10header {
11    background: linear-gradient(135deg, #2c5f2d 0%, #45a049 100%);
12    color: white;
13    padding: 30px;
14    border-radius: 12px;
15    margin-bottom: 20px;
16    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
17}
18
19.species-card {
20    background: white;
21    padding: 20px;
22    margin: 15px 0;
23    border-radius: 8px;
24    border-left: 4px solid #2c5f2d;
25    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
26    transition: transform 0.2s;
27}
28
29.species-card:hover {
30    transform: translateY(-3px);
31    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
32}
33
34.badge {
35    display: inline-block;
36    padding: 4px 12px;
37    border-radius: 4px;
38    font-size: 13px;
39    font-weight: bold;
40}
41
42.badge-safe { background: #4caf50; color: white; }
43.badge-endangered { background: #ff9800; color: white; }
44.badge-critical { background: #f44336; color: white; }

Utiliser des fichiers statiques dans les templates

1<!DOCTYPE html>
2<html>
3<head>
4    <title>Safari Database</title>
5    <!-- Lien vers le CSS -->
6    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
7</head>
8<body>
9    <!-- Logo -->
10    <img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">
11
12    <!-- Contenu de la page -->
13
14    <!-- Lien vers le JS -->
15    <script src="{{ url_for('static', filename='js/script.js') }}"></script>
16</body>
17</html>

url_for('static', filename='...') génère l'URL vers un fichier statique.

Application complète avec templates

app.py

1# app.py
2from flask import Flask, render_template, request, redirect, url_for
3
4app = Flask(__name__)
5
6# Données de test
7species_db = {
8    1: {"id": 1, "name": "Lion", "scientific_name": "Panthera leo", "population": 120},
9    2: {"id": 2, "name": "Éléphant d'Afrique", "scientific_name": "Loxodonta africana", "population": 450},
10    3: {"id": 3, "name": "Guépard", "scientific_name": "Acinonyx jubatus", "population": 7100},
11    4: {"id": 4, "name": "Girafe", "scientific_name": "Giraffa camelopardalis", "population": 850},
12}
13next_id = 5
14
15@app.route('/')
16def home():
17    return render_template('home.html',
18                         species_count=len(species_db))
19
20@app.route('/species')
21def list_species():
22    return render_template('species_list.html',
23                         species=list(species_db.values()))
24
25@app.route('/species/<int:species_id>')
26def show_species(species_id):
27    species = species_db.get(species_id)
28    return render_template('species_detail.html',
29                         species=species)
30
31@app.route('/search')
32def search():
33    query = request.args.get('q', '').lower()
34
35    if query:
36        results = [s for s in species_db.values()
37                  if query in s['name'].lower() or query in s['scientific_name'].lower()]
38    else:
39        results = []
40
41    return render_template('search.html',
42                         query=query,
43                         results=results)
44
45if __name__ == '__main__':
46    print("Démarrage de Safari Database...")
47    app.run(debug=True)

templates/base.html

1<!DOCTYPE html>
2<html lang="fr">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>{% block title %}Safari Database{% endblock %}</title>
7    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8    {% block extra_css %}{% endblock %}
9</head>
10<body>
11    <header>
12        <h1>Safari Database</h1>
13        <p>Système de gestion des données d'espèces africaines</p>
14    </header>
15
16    <nav>
17        <a href="{{ url_for('home') }}">Accueil</a>
18        <a href="{{ url_for('list_species') }}">Liste des espèces</a>
19        <a href="{{ url_for('search') }}">Rechercher</a>
20    </nav>
21
22    <main>
23        {% block content %}{% endblock %}
24    </main>
25
26    <footer>
27        <p>&copy; 2024 Safari Database par Darwin | Propulsé par Flask & Jinja2</p>
28    </footer>
29
30    {% block extra_js %}{% endblock %}
31</body>
32</html>

templates/search.html

1{% extends 'base.html' %}
2
3{% block title %}Recherche - Safari Database{% endblock %}
4
5{% block content %}
6    <h2>Recherche d'espèces</h2>
7
8    <form method="GET" style="margin: 30px 0;">
9        <input type="text"
10               name="q"
11               value="{{ query }}"
12               placeholder="Saisis le nom d'une espèce..."
13               style="padding: 12px; width: 60%; border: 2px solid #2c5f2d; border-radius: 4px; font-size: 16px;">
14        <button type="submit"
15                style="padding: 12px 24px; background: #2c5f2d; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer;">
16            Rechercher
17        </button>
18    </form>
19
20    {% if query %}
21        <h3>Résultats pour : "{{ query }}" ({{ results | length }})</h3>
22
23        {% if results %}
24            {% for species in results %}
25                <div class="species-card">
26                    <h3 style="margin-top: 0;">{{ species.name }}</h3>
27                    <p><em>{{ species.scientific_name }}</em></p>
28                    <p>Population : {{ species.population }} individus</p>
29                    <a href="{{ url_for('show_species', species_id=species.id) }}" style="color: #2c5f2d; font-weight: bold;">
30                        Voir les détails →
31                    </a>
32                </div>
33            {% endfor %}
34        {% else %}
35            <p style="color: #666; text-align: center; padding: 40px;">
36                Aucune espèce trouvée correspondant à la requête "{{ query }}"
37            </p>
38        {% endif %}
39    {% else %}
40        <p style="color: #666; text-align: center; padding: 40px;">
41            Saisis le nom d'une espèce pour lancer la recherche.
42        </p>
43    {% endif %}
44{% endblock %}

Techniques Jinja2 utiles

1. Passer plusieurs variables

1@app.route('/dashboard')
2def dashboard():
3    return render_template('dashboard.html',
4                         title='Tableau de bord',
5                         user='Darwin',
6                         species_count=10,
7                         observations_count=152,
8                         recent_activity=['Observation de lion', "Ajout d'un guépard"])

2. Passer un dictionnaire en **kwargs

1context = {
2    'title': 'Tableau de bord',
3    'user': 'Darwin',
4    'species_count': 10
5}
6
7return render_template('dashboard.html', **context)

3. Variables globales (disponibles dans tous les templates)

1@app.context_processor
2def inject_globals():
3    return {
4        'app_name': 'Safari Database',
5        'version': '1.0.0',
6        'current_year': 2024
7    }
8
9# Maintenant {{ app_name }}, {{ version }}, {{ current_year }} fonctionnent partout !

4. Filtres personnalisés

1@app.template_filter('format_population')
2def format_population(value):
3    if value >= 1000:
4        return f"{value / 1000:.1f}k"
5    return str(value)
6
7# Dans le template :
8# {{ species.population | format_population }}  → "7.1k"

5. Macros (composants réutilisables)

1{# macros.html #}
2{% macro render_species_card(species) %}
3    <div class="species-card">
4        <h3>{{ species.name }}</h3>
5        <p><em>{{ species.scientific_name }}</em></p>
6        <p>Population : {{ species.population }}</p>
7    </div>
8{% endmacro %}
9
10{# Dans d'autres templates #}
11{% from 'macros.html' import render_species_card %}
12
13{% for species in species_list %}
14    {{ render_species_card(species) }}
15{% endfor %}

Résumé

Dans cette leçon tu as appris :

  • Pourquoi les templates valent mieux que le HTML codé en dur
  • La syntaxe Jinja2 ({{ }}, {% %}, {# #})
  • Les variables et les expressions
  • Les structures de contrôle (if, for)
  • Les filtres (|upper, |length, |default)
  • L'héritage de templates (extends, blocks)
  • Les fichiers statiques (CSS, JS, images)
  • url_for() dans les templates
  • L'application complète Safari Database avec templates

Analogie Safari finale : Les templates Jinja2 sont comme des modèles d'ADN génétique - tu as une structure de base (base.html = ADN ancestral), que tu étends chez les descendants (templates enfants = ADN des descendants) avec des caractéristiques uniques (blocs) ! Toutes les espèces (templates) héritent de traits communs (navigation, pied de page), mais ont leur propre singularité (contenu) !

Prochaine leçon : Darwin te montrera Flask-WTF - les formulaires et la validation des données de façon professionnelle !

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Comment rend-on le template « species.html » avec une variable « animals » ?

  2. 2. Qu'est-ce que Jinja2 dans le contexte de Flask ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri horizontal

    À quoi ressemble la syntaxe pour afficher une variable species en Jinja2 ?

  • Tri horizontal

    Comment démarre-t-on une condition if en Jinja2 ?

  • Clique dans l'ordre

    Range une boucle for qui itère sur animals en Jinja2 :

  • Tri vertical

    Range la structure d'un template Jinja2 avec héritage :

  • Éditeur de code

    Écris un template avec une boucle for affichant les noms des animaux dans des éléments <li>.

Articles utiles