06 — Gestionnaire de tâches
Niveau requis : 3 pour la v1, 5 pour la v2 / Difficulté : 3/5 / Durée estimée : 2 h (v1) + 2 h (v2)
Objectif
Une todo-list en ligne de commande : ajouter une tâche, lister les tâches (avec leur état fait / à faire), marquer une tâche comme faite, supprimer une tâche. Menu en boucle jusqu'à q.
1. Ajouter 2. Lister 3. Terminer 4. Supprimer q. Quitter
> 2
[ ] 1. Acheter du cafe
[x] 2. Finir la lecon 03-2
[ ] 3. Réviser les dicts
Deux versions :
- v1 (niveau 3) — tout en mémoire : la liste disparaît quand le programme se ferme. C'est frustrant, et c'est voulu : cette frustration est la meilleure motivation pour le niveau 5.
- v2 (niveau 5) — persistance dans un fichier JSON : les tâches survivent d'un lancement à l'autre. Le programme devient un vrai outil.
Notions utilisées
- Liste de dictionnaires (
[{"title": ..., "done": False}, ...]) — niveau 3 - Parcours avec
enumerate()pour numéroter — niveau 3 - Mutabilité : modifier un dict DANS la liste — niveau 3 (notion difficile, la leçon le dit)
while+ menu — niveau 2- v2 :
json.load()/json.dump(), ouverture de fichiers avecwith,try/except FileNotFoundError— niveau 5 - v2 : fonctions pour découper (recommandé si niveau 4 acquis) — niveau 4
Cahier des charges
Commun :
- Fichier
tasks.py(v1),tasks_v2.py(v2). - Une tâche = un dict
{"title": str, "done": bool}. Pas deux listes parallèles (une pour les titres, une pour les états) — c'est le piège central du projet. - Le listing affiche
[x]ou[ ], le numéro (à partir de 1) et le titre. - « Terminer » et « Supprimer » demandent le numéro affiché par le listing.
- Numéro invalide (hors liste, non numérique) : message clair, retour au menu, pas de traceback.
- Liste vide : « Aucune tache. » au lieu d'un listing vide muet.
Spécifique v2 :
- Les tâches sont chargées depuis
tasks.jsonau démarrage et sauvegardées après CHAQUE modification (pas seulement à la sortie — un crash ne doit rien perdre). - Premier lancement (fichier absent) : le programme démarre avec une liste vide, sans erreur.
tasks.jsonest lisible :json.dump(tasks, f, indent=2, ensure_ascii=False).
Étapes de construction
v1 — niveau 3
- Pars d'une liste de tâches codée en dur (3 dicts) et écris UNIQUEMENT le listing avec
enumerate(tasks, start=1)et l'affichage[x]/[ ]. Lance : l'affichage doit être exactement celui de l'exemple. - Ajoute le menu en boucle
whileavec le choix2(lister) etq(quitter). Les autres choix affichent « pas encore implemente ». - Implémente « Ajouter » :
input()du titre,tasks.append({"title": title, "done": False}). Ajoute, liste, vérifie. - Implémente « Terminer » : demande le numéro, convertis en indice (
index = number - 1), passetasks[index]["done"]àTrue. Vérifie qu'aucune copie n'est nécessaire : tu modifies le dict en place, c'est la mutabilité au travail. - Implémente « Supprimer » avec
tasks.pop(index). Ajoute la validation du numéro (les deux commandes en ont besoin : mauvais numéro → message, retour au menu). Vide la liste en dur initiale : le programme est complet. Commit v1.
v2 — niveau 5
- Copie en
tasks_v2.py. Écrissave_tasks(tasks)(ouvretasks.jsonen écriture avecwith,json.dump) et appelle-la après chaque ajout/terminaison/suppression. Lance, ajoute une tâche, ouvretasks.jsondans VS Code : tes données sont là, lisibles. - Écris
load_tasks():json.loaddans untry/except FileNotFoundErrorqui retourne[]si le fichier n'existe pas. Appelle-la au démarrage. - Test de bout en bout : ajoute deux tâches, quitte, relance, liste — elles sont là. Supprime
tasks.json, relance — liste vide, pas d'erreur. Commit v2.
Extensions possibles
- Ajouter une date de création à chaque tâche (
datetime.date.today(), converti enstrpour le JSON). - Filtrer le listing : tout / à faire / terminées.
- Ajouter une priorité (1–3) et trier le listing par priorité.
- v2 : gérer un fichier JSON corrompu (
json.JSONDecodeError) : message, sauvegarde du fichier cassé sous un autre nom, redémarrage à vide — c'est ce que ferait un vrai outil.
Erreurs fréquentes sur ce projet
- Décalage d'indice : l'utilisateur voit « 1 », la liste commence à 0. Oublier le
- 1termine ou supprime la MAUVAISE tâche — pire qu'un crash, car silencieux. Teste explicitement : terminer la tâche 1 doit cocher la première affichée. IndexError: list index out of range: numéro hors liste non validé. La validation c'est : c'est un entier ET1 <= number <= len(tasks).- Deux listes parallèles (
titlesetdones) au lieu d'une liste de dicts : ça marche… jusqu'à la première suppression qui désynchronise tout. Le cahier des charges l'interdit pour cette raison. - v2 : sauvegarder seulement à la sortie sur
q: unCtrl+Cperd tout le travail de la session. D'où la règle « sauvegarde après chaque modification ». - v2 :
TypeError: Object of type date is not JSON serializable(si tu fais l'extension 1) : JSON ne connaît que str/int/float/bool/list/dict/None. Convertis enstravant de sauvegarder.
Critères de réussite
- v1 : ajouter, lister, terminer, supprimer fonctionnent, y compris avec des numéros invalides.
- v1 : terminer la tâche N coche exactement la tâche affichée N.
- v2 : quitter puis relancer restitue exactement les tâches.
- v2 : premier lancement sans
tasks.json: aucun traceback. - v2 :
tasks.jsonest lisible et contient les accents corrects. - Je peux expliquer pourquoi une liste de dicts bat deux listes parallèles.
Commit conseillé
git add projects/mini-projects/06-gestionnaire-de-taches/
git commit -m "project: add task manager"
# ... puis, apres la v2 :
git commit -m "project: add task manager with JSON persistence"
Puis entrée de journal + git push. Si tu ne veux pas versionner tes vraies tâches, ajoute tasks.json au .gitignore — décision à noter dans le journal.