À quoi ça sert (vraiment) ?
Migration Doctor v2 est une commande Django conçue pour être cron-friendly : elle identifie, explique et (si tu le demandes explicitement) corrige les désynchronisations classiques entre :
- Fichiers de migrations (dossier app/migrations/)
- Historique en base dans django_migrations
- Schéma réel MariaDB (tables existantes) vs modèles Django (tables attendues)
- En prod : commence toujours par --check ou --plan.
- Ne “fake” que si la DB est déjà bonne (sinon tu caches un vrai problème).
- Les opérations potentiellement destructrices exigent --yes-i-know.
Cheat-sheet ultra rapide (les 6 commandes qui couvrent 90% des cas)
# 1) Audit global (cron) : read-only
python manage.py migration_doctor --all-local --check --store
# 2) Audit ciblé : comprendre une app qui "boucle" ou casse souvent
python manage.py migration_doctor --app weglot --check
# 3) Voir ce que Django prévoit (utile avant tout migrate)
python manage.py migration_doctor --app weglot --plan
# 4) "DB est bonne mais l'historique est en retard" (1060/duplicate column)
python manage.py migration_doctor --app weglot --autofix-unapplied --store
# 5) "table already exists" (MySQL 1050) sans savoir l'app responsable
python manage.py migration_doctor --autofix-1050 --backup --db-user root --db-pass "SECRET" --backup-continue-on-fail
# 6) Réconcilier une app (historique cassé / out-of-sync) : explicite et assumé
python manage.py migration_doctor --app weglot --restore --yes-i-know --backup --db-user root --db-pass "SECRET"Ce que la commande fait et ne fait pas
✅ Elle fait
- Comparer fichiers migrations ↔ historique DB
- Comparer tables attendues (modèles) ↔ tables en DB
- Générer un plan propre (restore) sans dropper les tables
- Automatiser la résolution du MySQL 1050 sur migrate
- Détecter le “makemigrations infini” (non-déterminisme)
❌ Elle ne fait pas
- Réparer magiquement un modèle incorrect (FK cassée, types incohérents, etc.)
- Réécrire ton historique sans confirmation (d’où --yes-i-know)
- Dropper les tables en prod (le script n’appelle pas DROP TABLE)
- Remplacer un vrai process de review des migrations (c’est un “doctor”, pas un dev)
Référence CLI — toutes les options (documentées)
Toutes les options ci-dessous proviennent directement du script migration_doctor_v2.py. La commande Django s’appelle migration_doctor (le fichier Python dans management/commands/ doit s’appeler migration_doctor.py).
Syntaxe générale
# Mode "app-ciblé" (la plupart des actions)
python manage.py migration_doctor --app <app_label> [OPTIONS]
# Mode "multi-apps"
python manage.py migration_doctor --apps a,b,c [OPTIONS]
# Mode "auto" sur toutes les apps locales (hors site-packages, hors django.contrib)
python manage.py migration_doctor --all-local [OPTIONS]
# Mode "autofix-1050" : NE REQUIERT PAS de sélection d'app
python manage.py migration_doctor --autofix-1050 [OPTIONS]1) Sélection des apps (périmètre)
| Option | Type | But | Notes |
|---|---|---|---|
| --app weglot | SAFE | Vise une seule app. | Recommandé pour --restore et toutes les opérations de réparation. |
| --apps a,b,c | SAFE | Vise plusieurs apps (liste séparée par virgules). | Idéal pour audits groupés ou cleanup en dev/staging. |
| --all-local | SAFE | Inclut toutes les apps “locales”. | Filtre django.contrib + site-packages. Parfait en cron. |
2) Modes (ce que tu demandes à la commande)
| Option | Tag | Ce que ça fait | Usage recommandé |
|---|---|---|---|
| --check | SAFE | Audit complet : migrations fichiers ↔ DB + tables DB ↔ modèles. | Cron quotidien + avant toute réparation. |
| --plan | SAFE | Affiche le plan que migrate exécuterait (sans appliquer). | Comprendre “ce que Django va faire” avant d’agir. |
| --restore | MUTATE | Réconcilie une app : régénère ses migrations, remet l’historique à zéro (fake), puis réapplique en --fake-initial. | Quand l’historique est cassé (out-of-sync) ou que 1050 revient en boucle sur une app. |
| --cleanup | DANGER | Nettoyage total : supprime les fichiers de migrations et purge django_migrations pour les apps visées, puis makemigrations et migrate. | Dev/staging pour repartir propre. Évite en prod. |
| --autofix-1050 | MUTATE | Lance migrate. Si MySQL/MariaDB renvoie une erreur 1050 (table already exists), déduit la/les table(s) impliquée(s), mappe à l’app, auto-restore l’app, puis relance migrate. | Quand python manage.py migrate casse sans que tu saches quelle app est la source. |
| --churn-check | SAFE | Détecte le non-déterminisme : exécute makemigrations <app> --dry-run --check deux fois, et compare les outputs. | Quand makemigrations régénère des migrations “fantômes” à chaque run. |
| --autofix-unapplied | MUTATE | Cas fréquent : DB déjà conforme, mais historique en retard. Le script fake chaque migration non appliquée (migrate app mig --fake) uniquement si schéma OK. | Quand tu as des erreurs type “duplicate column” alors que le champ est déjà en DB. |
3) Sécurité / confort / stockage
| Option | Tag | Description | Quand l’utiliser |
|---|---|---|---|
| --yes-i-know | MUTATE | Confirmation explicite. Obligatoire pour --restore et --cleanup. | Empêche le “clic accidentel” en prod. |
| --force | DANGER | Ignore l’audit pré-check dans --cleanup (autorise un cleanup même si des écarts existent). | Uniquement si tu sais exactement ce que tu fais (souvent staging). |
| --store | SAFE | Stocke le rapport dans l’app doctor si elle existe (DoctorRun, snapshots, findings). | Recommandé en cron pour un historique consultable en admin. |
| --unsafe-fake | DANGER | Dans --cleanup, au lieu de migrate --fake-initial (recommandé), fait un migrate --fake global. | Évite autant que possible. Peut masquer de vrais problèmes. |
4) Backup (mysqldump) — options de connexion
Le backup n’est exécuté que si tu passes --backup. Le script construit une commande mysqldump en utilisant soit les flags, soit les valeurs de settings.DATABASES.
| Option | Tag | Description | Valeur par défaut / fallback |
|---|---|---|---|
| --backup | MUTATE | Effectue un dump SQL avant opérations mutantes. | Le fichier de sortie est ./backup_<DB>_YYYYmmdd_HHMMSS.sql. |
| --backup-continue-on-fail | MUTATE | Continue même si mysqldump échoue. | Utile en cron/CI quand l’environnement ne permet pas de dumper. |
| --db mydb | SAFE | Nom DB à dumper. | Sinon : settings.DATABASES['default']['NAME']. |
| --db-user root | SAFE | Utilisateur DB pour mysqldump. | Sinon : settings.DATABASES['default']['USER']. |
| --db-pass SECRET | SAFE | Mot de passe DB pour mysqldump. | Sinon : settings.DATABASES['default']['PASSWORD']. |
| --db-host 127.0.0.1 | SAFE | Hôte DB. | Sinon : settings.DATABASES['default']['HOST']. |
| --db-port 3306 | SAFE | Port DB. | Sinon : settings.DATABASES['default']['PORT']. |
| --mysqldump /usr/bin/mysqldump | SAFE | Chemin du binaire mysqldump. | Par défaut : mysqldump (dans PATH). |
5) Codes de sortie et comportements “cron-friendly”
Ce que tu peux exploiter en CI
- --check : termine par CHECK terminé (ne raise pas).
- --churn-check : si churn détecté, la commande termine en erreur (raise CommandError), donc CI fail.
- --restore/--cleanup : bloqués sans --yes-i-know.
- --autofix-1050 : tente de “guérir” puis relance migrate global.
Installation & Download
1) Télécharger le script
Fichier : migration_doctor_v2.py (à renommer en migration_doctor.py dans ton projet Django pour correspondre au nom de la commande).
Download direct
Script à télécharger : ⬇️ Télécharger migration_doctor_v2.py
/static/toolbox/migration_doctor_v2.pySi tu veux une URL absolue (externe), utilise : https://www.ideo-lab.com/static/toolbox/migration_doctor_v2.py
2) Emplacement dans le projet Django (obligatoire)
Pour que Django détecte la commande, le fichier doit être dans :
<ton_app>/
management/
__init__.py
commands/
__init__.py
migration_doctor.py <-- (ton script ici)- Le fichier doit s’appeler migration_doctor.py (sinon la commande ne s’appelle pas migration_doctor).
- management/ et commands/ doivent contenir un __init__.py.
- L’app qui porte la commande doit être dans INSTALLED_APPS.
3) Vérifier que la commande est visible
# Liste toutes les commandes
python manage.py help
# Ou filtre (selon OS)
python manage.py help | grep migration4) Premier run (safe)
# Safe (read-only)
python manage.py migration_doctor --all-local --check
# Variante : stocker le rapport si l’app doctor existe
python manage.py migration_doctor --all-local --check --store5) Cron (Linux) — prêt à copier
# Tous les jours 03:15
15 3 * * * cd /var/www/ideo_lab && /opt/venv/bin/python manage.py migration_doctor --all-local --check --store >> /var/log/ideo_lab/migration_doctor.log 2>&16) Permissions / environnement
Checklist
- Le cron utilise le bon venv (python du virtualenv).
- Le cron a les variables d’environnement nécessaires (DJANGO_SETTINGS_MODULE si besoin).
- Si --backup : mysqldump doit être dans le PATH (ou passer --mysqldump).
Modes & enchaînements — “quoi lancer, dans quel ordre”
L’objectif ici est de donner des enchaînements complets et reproductibles (exactement ce qu’il faut pour tes futurs utilisateurs). Les scénarios ci-dessous sont alignés sur les comportements réels du script.
Scénario A — Cron quotidien (audit complet, zéro risque)
But
Détecter tôt les écarts et les apps “à risque”, sans rien modifier.
# Audit toutes apps locales + stockage du rapport
python manage.py migration_doctor --all-local --check --store
# Optionnel : voir aussi le plan d'une app problématique
python manage.py migration_doctor --app weglot --planScénario B — “makemigrations infini” (migrations fantômes)
Symptômes
- Tu lances makemigrations et il te (re)génère des migrations à chaque fois, même sans changement réel.
- La DB est stable mais Django n’arrive pas à stabiliser l’auto-detector.
# 1) Détecter le churn (non-déterminisme)
python manage.py migration_doctor --app weglot --churn-check
# 2) Si churn: corriger le modèle (causes fréquentes)
# - permissions/indexes/constraints générés via set()/dict() (ordre instable)
# - Meta.permissions / Meta.indexes / Meta.constraints dynamiques
# - naming d'index/constraint dépendant de valeurs variables
#
# 3) Re-valider que churn est terminé
python manage.py migration_doctor --app weglot --churn-check
# 4) Puis audit normal
python manage.py migration_doctor --app weglot --checkScénario C — Schéma OK mais historique en retard (autofix-unapplied)
Symptômes
- migrate plante sur des erreurs type “duplicate column”, “table already exists”, etc.
- Mais quand tu regardes la DB, tout semble déjà présent.
- Souvent : une migration a été appliquée manuellement, ou un restore partiel a cassé l’historique.
# 1) Audit ciblé pour confirmer le diagnostic
python manage.py migration_doctor --app weglot --check
# 2) Autofix : marque les migrations comme appliquées (fake) si schéma OK
python manage.py migration_doctor --app weglot --autofix-unapplied --store
# 3) Re-vérification
python manage.py migration_doctor --app weglot --checkScénario D — MySQL 1050 “table already exists” pendant migrate global (autofix-1050)
Symptômes
- python manage.py migrate casse avec une 1050.
- Tu ne sais pas forcément quelle app est responsable (ça peut être trans-app).
# Le script lance migrate. Si 1050 survient, il extrait la table, mappe l'app, puis auto-restore cette app:
python manage.py migration_doctor --autofix-1050 --backup --db-user root --db-pass "SECRET" --backup-continue-on-fail
# Ensuite, re-run un audit global (recommandé)
python manage.py migration_doctor --all-local --check --storeScénario E — Réconcilier une app (restore) : l’enchaînement exact
Tu dois fournir --yes-i-know.
# 0) Audit (toujours)
python manage.py migration_doctor --app weglot --check --store
# 1) Restore
python manage.py migration_doctor --app weglot --restore --yes-i-know --backup --db-user root --db-pass "SECRET"
# 2) Plan post-restore
python manage.py migration_doctor --app weglot --plan
# 3) Audit final
python manage.py migration_doctor --app weglot --check --storeScénario F — Cleanup multi-app (dev/staging) pour repartir propre
# 1) Audit préalable (la commande le fait aussi, mais ici tu lis tranquillement)
python manage.py migration_doctor --apps accounts,agenda,weglot --check
# 2) Cleanup (obligatoire: --yes-i-know). Optionnel: --backup
python manage.py migration_doctor --apps accounts,agenda,weglot --cleanup --yes-i-know --backup --db-user root --db-pass "SECRET"
# 3) Audit final
python manage.py migration_doctor --apps accounts,agenda,weglot --check --storeCron / CI — recettes prêtes à copier
Linux — cron quotidien (audit + stockage)
# Tous les jours 03:15
15 3 * * * cd /var/www/ideo_lab && /opt/venv/bin/python manage.py migration_doctor --all-local --check --store >> /var/log/ideo_lab/migration_doctor.log 2>&1Linux — cron “churn guard” (CI ou nightly)
# Tous les jours 03:30 : échoue si churn / non-déterminisme
30 3 * * * cd /var/www/ideo_lab && /opt/venv/bin/python manage.py migration_doctor --all-local --churn-check >> /var/log/ideo_lab/migration_churn.log 2>&1CI (GitHub Actions / GitLab / Jenkins) — concept
But
Empêcher qu’un PR introduise des migrations non déterministes ou “oublie” de commit une migration.
# 1) Détecter churn / migrations non commitées
python manage.py migration_doctor --all-local --churn-check
# 2) Audit read-only (optionnel) - utile pour logs
python manage.py migration_doctor --all-local --checkWindows — Task Scheduler (audit)
Program/script: C:\Python311\python.exe
Arguments: manage.py migration_doctor --all-local --check --store
Start in: D:\wamp\www\ideo_labSécurité / Backup — ce qu’il faut savoir
Pourquoi le backup est un “must” sur restore/cleanup
Ce que le script modifie
- --restore : réécrit l’historique de l’app dans django_migrations.
- --cleanup : supprime des fichiers de migrations + purge django_migrations pour les apps visées.
Ça ne droppe pas tes tables, mais ça peut casser le futur si tu te trompes de périmètre.
Exemples backup : 3 stratégies “propre”
Stratégie 1 — db creds en flags (simple)
Rapide, mais le mot de passe peut apparaître en clair dans la ligne de commande / logs.
Stratégie 2 — option file (~/.my.cnf)
Recommandé en serveur : pas de password dans les logs, mysqldump lit le secret dans un fichier protégé.
Stratégie 3 — secrets manager
Idéal en CI/Prod (Vault, env vars chiffrées, etc.).
# Exemple "simple" avec flags (pratique mais attention aux logs)
python manage.py migration_doctor --app weglot --restore --yes-i-know --backup --db-user root --db-pass "SECRET"
# Exemple "propre" : utiliser un mysqldump sans password explicite (via ~/.my.cnf)
python manage.py migration_doctor --app weglot --restore --yes-i-know --backup --db-user root --mysqldump /usr/bin/mysqldumpFocus : --fake vs --fake-initial (pour comprendre les risques)
- --fake : marque comme appliqué, sans exécuter le SQL. Tu peux facilement te retrouver avec un historique qui “ment”.
- --fake-initial : cas plus “safe” pour une initial migration quand la DB pré-existe (Django skip l'initial si les tables existent déjà).
- Pas de --cleanup en prod (ou alors uniquement sur une base clonée).
- Utilise --restore sur une app unique, avec backup, et re-audit ensuite.
- Si tu as des migrations non déterministes : corrige le modèle, ne cherche pas à “forcer” la DB.
Dépannage — erreurs fréquentes et réponses “correctes”
Erreur 1050 — table already exists
Réponse immédiate
Si tu ne sais pas quelle app est en cause : --autofix-1050 (avec --backup). Si tu connais l’app : --restore ciblé.
Erreur 1060 — Duplicate column / champ déjà existant
Souvent, la DB est bonne
Cas typique de “history behind”. Lance d’abord --check. Si schéma OK et migrations non appliquées : --autofix-unapplied.
Erreur 1146 — Table doesn’t exist
Deux possibilités
- La table doit exister : migration réellement non appliquée (faire un vrai migrate).
- La table ne doit plus exister : modèle obsolète / table orpheline (réviser le modèle).
Le mode --check te montre clairement absent_in_db et orphans_in_db.
“Aucune migration détectée”, mais Django en crée tout le temps
Churn check
Lance --churn-check : s’il détecte du non-déterminisme, corrige les causes (ordre instable). Ensuite seulement, relance un audit et, si besoin, un restore.
python manage.py migration_doctor --app <app> --check
python manage.py migration_doctor --app <app> --plan
python manage.py migration_doctor --app <app> --churn-check