Semaine 11
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,180 @@
|
||||
# Exercice 3.3 - Jinja2 multi-pages
|
||||
|
||||
## Navigation + templates dynamiques (sans base de données)
|
||||
|
||||
---
|
||||
|
||||
## Objectifs pédagogiques
|
||||
|
||||
* structurer une application Flask avec plusieurs routes ;
|
||||
* générer plusieurs pages HTML avec Jinja2 ;
|
||||
* utiliser des templates réutilisables ;
|
||||
* exploiter des données simulées côté serveur (Python) ;
|
||||
* manipuler boucles et conditions dans Jinja2 ;
|
||||
* construire une navigation entre pages ;
|
||||
* séparer clairement logique serveur et affichage.
|
||||
|
||||
---
|
||||
|
||||
# Contexte
|
||||
|
||||
Vous développez une mini application de type **tableau de bord de gestion de parc informatique**.
|
||||
|
||||
Les données sont **simulées en mémoire (liste Python)**.
|
||||
|
||||
L’application doit permettre :
|
||||
|
||||
* d’afficher une liste globale des équipements ;
|
||||
* de voir uniquement les équipements actifs ;
|
||||
* de consulter une page de détail par équipement ;
|
||||
* de naviguer entre les pages via des liens HTML.
|
||||
|
||||
---
|
||||
|
||||
# Structure proposée
|
||||
|
||||
```
|
||||
app.py
|
||||
/templates
|
||||
base.html
|
||||
index.html
|
||||
actifs.html
|
||||
detail.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Travail demandé
|
||||
|
||||
---
|
||||
|
||||
## Étape 1 - Initialiser l’application Flask
|
||||
|
||||
Créer une application Flask fonctionnelle.
|
||||
L’application doit pouvoir afficher une page d’accueil.
|
||||
|
||||
---
|
||||
|
||||
## Étape 2 - Créer les données simulées
|
||||
|
||||
Définir côté serveur une liste Python représentant des équipements.
|
||||
Chaque équipement contient :
|
||||
|
||||
* un identifiant ;
|
||||
* un nom ;
|
||||
* un statut (actif / inactif) ;
|
||||
* une catégorie (serveur, poste, réseau, etc.).
|
||||
|
||||
Ces données sont **fixes et non modifiables**.
|
||||
|
||||
---
|
||||
|
||||
## Étape 3 - Créer une structure de templates
|
||||
|
||||
Mettre en place :
|
||||
|
||||
* un template principal servant de base commune ;
|
||||
* une structure avec bloc de contenu réutilisable ;
|
||||
* un menu de navigation commun à toutes les pages.
|
||||
|
||||
Le menu doit permettre de naviguer entre les pages principales.
|
||||
|
||||
---
|
||||
|
||||
## Étape 4 - Page d’accueil (liste complète)
|
||||
|
||||
Créer une route et une page affichant :
|
||||
|
||||
* la liste complète des équipements ;
|
||||
* toutes leurs informations.
|
||||
|
||||
Dans le template :
|
||||
|
||||
* utiliser une boucle Jinja2 ;
|
||||
* afficher chaque équipement dans un bloc structuré ;
|
||||
* distinguer visuellement les équipements actifs et inactifs avec une condition.
|
||||
|
||||
---
|
||||
|
||||
## Étape 5 - Page “équipements actifs”
|
||||
|
||||
Créer une page dédiée affichant uniquement :
|
||||
* les équipements actifs.
|
||||
|
||||
La logique de filtrage doit être réalisée côté serveur (Python), pas dans le template.
|
||||
|
||||
Dans le template :
|
||||
* afficher uniquement les éléments fournis par la vue ;
|
||||
* utiliser une boucle simple sans logique complexe.
|
||||
|
||||
---
|
||||
|
||||
## Étape 6 - Page de détail d’un équipement
|
||||
|
||||
Créer une route dynamique permettant d’afficher :
|
||||
* un équipement spécifique selon son identifiant.
|
||||
|
||||
La page doit afficher :
|
||||
* toutes les informations de l’équipement ;
|
||||
* un message d’erreur si l’identifiant n’existe pas.
|
||||
|
||||
Dans le template :
|
||||
* utiliser des variables Jinja2 ;
|
||||
* utiliser une condition pour gérer le cas “non trouvé”.
|
||||
|
||||
---
|
||||
|
||||
## Étape 7 - Navigation entre pages
|
||||
|
||||
Dans le template principal :
|
||||
* ajouter un menu de navigation commun ;
|
||||
* permettre d’accéder à toutes les pages ;
|
||||
* assurer une cohérence visuelle sur toutes les vues.
|
||||
|
||||
---
|
||||
|
||||
## Étape 8 - Mise en forme Jinja2 obligatoire
|
||||
|
||||
Dans les templates, vous devez utiliser :
|
||||
* des boucles `{% for %}` ;
|
||||
* des conditions `{% if %}` ;
|
||||
* des variables `{{ }}` ;
|
||||
* au moins un bloc réutilisable (`block`) ;
|
||||
* une logique de filtrage côté Python (obligatoire).
|
||||
|
||||
---
|
||||
|
||||
## Étape 9
|
||||
|
||||
Ajouter :
|
||||
* une page “statistiques” affichant :
|
||||
|
||||
* nombre total d’équipements ;
|
||||
* nombre d’actifs ;
|
||||
* nombre d’inactifs ;
|
||||
* un message conditionnel si la liste est vide dans une page.
|
||||
|
||||
---
|
||||
|
||||
# Livrables attendus
|
||||
|
||||
* application Flask fonctionnelle ;
|
||||
* plusieurs routes Python ;
|
||||
* 3 à 4 templates Jinja2 ;
|
||||
* aucune base de données ;
|
||||
* aucune méthode POST ;
|
||||
* aucune modification de données.
|
||||
|
||||
---
|
||||
|
||||
# Compétences évaluées
|
||||
|
||||
| Compétence | Niveau attendu |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| Flask routing | Plusieurs routes fonctionnelles |
|
||||
| Jinja2 | Boucles + conditions maîtrisées |
|
||||
| Architecture templates | base.html + héritage |
|
||||
| Séparation logique/vue | filtrage côté Python |
|
||||
| Navigation web | cohérence UX simple |
|
||||
| Lecture de données | affichage structuré |
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Exo 3.3 "Templates dynamiques" - BOICHÉ Gauvain
|
||||
|
||||
Je déclare avec fait usage de l'IA :
|
||||
|
||||
# Raison 1
|
||||
|
||||
Pour de "l'autocomplétion" avec le plan gratuit de Github Copilot (modèle inconnu) pour générer la base <!DOCTYPE> html parce que ça me casse un peu les noix de refaire ça. C'est un genre d'aamorçage qui devrait exister de base dans les IDE avec un algo à l'ancienne ultra-léger...
|
||||
|
||||
## Pourquoi cet usage
|
||||
|
||||
Parce que ça me gonfle de refaire ça pour la Xième fois parce que je ne l'ai pas retenu tellement l'HTML me gonfle.
|
||||
|
||||
## Ce que j'en retiens
|
||||
|
||||
Je dois trouver une extension qui le fait pour moi.
|
||||
|
||||
# Raison 2
|
||||
|
||||
Comprendre pourquoi mes pages Jinja2 ne "marchent pas".
|
||||
|
||||
## Pourquoi cet usage
|
||||
|
||||
Refaisant un document <!DOCTYPE> pour chaque page, je n'ai pas su comprendre par les docs accessibles (qui donnaient juste les blocs avec % et variables Python) qu'il fallait les peupler UNIQUEMENT des éléments étendant la "base". Que l'HTML s'oubliait sur ces parties-là. Je prenais les blocs en le mettant dans des balises <body> etc sans succès.
|
||||
|
||||
## Ce que j'en ai retenu
|
||||
|
||||
Les patrons Jinja2 héritent de TOUT, et on ne rajotue que ce qu'il y a besoin. y compris les bases DOCTYPE.
|
||||
|
||||
# Raison 3
|
||||
|
||||
## Pourquoi cet usage
|
||||
|
||||
Je cliquais sur "actives" et "details" mais je restait avec le contenu de "index". Je demande une revue de code à MiniMax M3, qui me diagnostique trois possibilités :
|
||||
|
||||
| Symptom | Likely cause | Remedy |
|
||||
|---------|--------------|--------|
|
||||
|Same content on every page | Browser cache serving the old HTML | Hard-reload: Ctrl + F5 (or Cmd + Shift + R on macOS) |
|
||||
URL changes but body identical | A single route handling all paths | Verify each @app.route is distinct |
|
||||
Template not overridden | Missing {% block content %} in child | Ensure children declare the block |
|
||||
|
||||
J'essaye, et rien.
|
||||
|
||||
## Ce que j'en ai retenu
|
||||
|
||||
Que je suis un imbécile. Je demande à l'IA de m'aider, et rien n'y a fait. Jusqu'à ce que je vois ma connerie :
|
||||
|
||||
```
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html', username=USERNAME, equipments=EQUIPMENTS)
|
||||
|
||||
@app.route('/actives')
|
||||
def actives():
|
||||
return render_template('index.html', username=USERNAME, equipments=EQUIPMENTS)
|
||||
|
||||
@app.route('/details')
|
||||
def details():
|
||||
return render_template('index.html', username=USERNAME, equipments=EQUIPMENTS)
|
||||
```
|
||||
|
||||
Trois fois "index.html". Bon. Résultat, je corrige et ça marche. L'IA ne m'a servi à RIEN ici, sauf ceci : des fois, il faut aller faire un tour, boire de l'eau, revenir et relire.
|
||||
@@ -0,0 +1,54 @@
|
||||
from flask import Flask, render_template, abort
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
EQUIPMENTS = [
|
||||
{"id": 1, "hostname": "srv-web-main-01", "type": "server", "active": True},
|
||||
{"id": 2, "hostname": "sw-access-01", "type": "switch", "active": True},
|
||||
{"id": 3, "hostname": "srv-api-01", "type": "server", "active": True},
|
||||
{"id": 4, "hostname": "waf-front-01", "type": "waf", "active": True},
|
||||
{"id": 5, "hostname": "individual-alice-01", "type": "instance", "active": True},
|
||||
{"id": 6, "hostname": "sw-access-02", "type": "switch", "active": False},
|
||||
{"id": 7, "hostname": "individual-bob-01", "type": "instance", "active": False},
|
||||
{"id": 8, "hostname": "srv-web-replica-01", "type": "server", "active": True},
|
||||
]
|
||||
|
||||
USERNAME = "administratort"
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html', username=USERNAME, equipments=EQUIPMENTS)
|
||||
|
||||
@app.route('/actives')
|
||||
def actives():
|
||||
ACTIVE_EQUIPMENTS = [e for e in EQUIPMENTS if e["active"]]
|
||||
return render_template('actives.html', username=USERNAME, equipments=ACTIVE_EQUIPMENTS)
|
||||
|
||||
@app.route('/list')
|
||||
def details():
|
||||
return render_template('list.html', username=USERNAME, equipments=EQUIPMENTS)
|
||||
|
||||
@app.route("/details/<int:equipment_id>")
|
||||
def equipment_detail(equipment_id):
|
||||
equipment = next((e for e in EQUIPMENTS if e["id"] == equipment_id), None)
|
||||
return render_template(
|
||||
"details.html",
|
||||
username=USERNAME,
|
||||
equipment=equipment,
|
||||
)
|
||||
|
||||
@app.route("/stats")
|
||||
def stats():
|
||||
total = len(EQUIPMENTS)
|
||||
active = sum(1 for e in EQUIPMENTS if e["active"])
|
||||
inactive = total - active
|
||||
return render_template(
|
||||
"stats.html",
|
||||
username=USERNAME,
|
||||
total=total,
|
||||
active=active,
|
||||
inactive=inactive,
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='127.0.0.1', port=5000, debug=True)
|
||||
@@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Active devices - Network Inventory{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Active devices</h1>
|
||||
<p>Signed-in as <strong>{{ username }}</strong>.</p>
|
||||
|
||||
<ul>
|
||||
{% for equipment in equipments %}
|
||||
<li>{{ equipment.hostname }}</li>
|
||||
{% else %}
|
||||
<li><em>No active device.</em></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}NETWORK INVENTORY{% endblock %}</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 2rem; }
|
||||
header, footer { background: #f4f4f4; padding: 1rem; }
|
||||
nav a { margin-right: 1rem; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #ccc; padding: 0.5rem; text-align: left; }
|
||||
th { background: #eee; }
|
||||
.up { color: green; font-weight: bold; }
|
||||
.down { color: red; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<strong>- MENU -</strong>
|
||||
<a href="{{ url_for('index') }}">Home</a>
|
||||
<a href="{{ url_for('actives') }}">Active devices</a>
|
||||
<a href="{{ url_for('details') }}">Details</a>
|
||||
<a href="{{ url_for('stats') }}">Stats</a>
|
||||
</nav>
|
||||
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer>Gauvain B. - Flask Jinja2 app</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Equipment - Network Inventory{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>Signed-in as <strong>{{ username }}</strong>.</p>
|
||||
|
||||
{% if equipment %}
|
||||
<h1>{{ equipment.hostname }}</h1>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<td>{{ equipment.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Hostname</th>
|
||||
<td>{{ equipment.hostname }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<td>{{ equipment.type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th>
|
||||
<td>
|
||||
{% if equipment.active %}
|
||||
<span class="up">UP</span>
|
||||
{% else %}
|
||||
<span class="down">DOWN</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h1>There's no device with this ID</h1>
|
||||
<p>The requested equipment (ID {{ equipment_id }}) does not exist.</p>
|
||||
{% endif %}
|
||||
|
||||
<p><a href="{{ url_for('details') }}">← Back to all devices</a></p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Home - Network Inventory{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Welcome, {{ username }}.</h1>
|
||||
|
||||
{% if username == "administratort" %}
|
||||
<p>You signed-in with ADMIN rights.</p>
|
||||
{% else %}
|
||||
<p>You signed-in with USER rights.</p>
|
||||
{% endif %}
|
||||
|
||||
<h2>Devices list:</h2>
|
||||
<ul>
|
||||
{% for equipment in equipments %}
|
||||
<li>{{ equipment.hostname }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Details - Network Inventory{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Device details</h1>
|
||||
<p>Signed-in as <strong>{{ username }}</strong>.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Hostname</th>
|
||||
<th>Type</th>
|
||||
<th>State</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for equipment in equipments %}
|
||||
<tr>
|
||||
<td>{{ equipment.id }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('equipment_detail', equipment_id=equipment.id) }}">
|
||||
{{ equipment.hostname }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ equipment.type }}</td>
|
||||
<td>
|
||||
{% if equipment.active %}
|
||||
<span class="up">UP</span>
|
||||
{% else %}
|
||||
<span class="down">DOWN</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,35 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Statistics - Network Inventory{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Inventory statistics</h1>
|
||||
<p>Signed-in as <strong>{{ username }}</strong>.</p>
|
||||
|
||||
{% if total > 0 %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Metric</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total equipments</td>
|
||||
<td>{{ total }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Active</td>
|
||||
<td><span class="up">{{ active }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Inactive</td>
|
||||
<td><span class="down">{{ inactive }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p><em>There's no equipment in the inventory.</em></p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user