Cours Python · Module 5: Flask et applications web
Templates Jinja2 - interfaces dynamiques
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 ?
- Tu crées des fichiers HTML dans le dossier
templates/ - Tu utilises
{{ variable }}pour afficher des données - Tu utilises
{% if %},{% for %}pour la logique - 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.pngFlask 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 casselength- longueur d'une liste/chaînedefault(value)- valeur par défaut si Noneround(n)- arrondir à n décimalestruncate(n)- tronquer le textejoin(separator)- joindre une listesafe- 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 :
- Cherche le fichier
templates/home.html - Rend le template avec les variables passées
- 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>© 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.pngstatic/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>© 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. Comment rend-on le template « species.html » avec une variable « animals » ?
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>.