Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

Guide complet — CLI / Cron / CI / Scénarios réels

Migration Doctor v2 — Audit, Restore, Cleanup, Auto-Fix (Django + MariaDB)

migration_doctor_v2.py  •  manage.py migration_doctor  •  v2

À 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)
Audit safe (read-only) Restore ciblé (réconcilie l’historique) Auto-fix 1050 (table already exists) Churn check (makemigrations infini) Auto-fake unapplied (DB==models) Cleanup (destructif, dev/staging)
La règle d’or :
  • 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)

Cheat-sheet
# 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).

Important : Certaines options sont mutantes (elles modifient l’historique en DB, ou effacent des fichiers). Lis les tags : SAFE MUTATE DANGER

Syntaxe générale

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)

OptionTypeButNotes
--app weglotSAFEVise une seule app.Recommandé pour --restore et toutes les opérations de réparation.
--apps a,b,cSAFEVise plusieurs apps (liste séparée par virgules).Idéal pour audits groupés ou cleanup en dev/staging.
--all-localSAFEInclut toutes les apps “locales”.Filtre django.contrib + site-packages. Parfait en cron.

2) Modes (ce que tu demandes à la commande)

OptionTagCe que ça faitUsage recommandé
--checkSAFEAudit complet : migrations fichiers ↔ DB + tables DB ↔ modèles.Cron quotidien + avant toute réparation.
--planSAFEAffiche le plan que migrate exécuterait (sans appliquer).Comprendre “ce que Django va faire” avant d’agir.
--restoreMUTATERé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.
--cleanupDANGERNettoyage 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-1050MUTATELance 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-checkSAFEDé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-unappliedMUTATECas 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

OptionTagDescriptionQuand l’utiliser
--yes-i-knowMUTATEConfirmation explicite. Obligatoire pour --restore et --cleanup.Empêche le “clic accidentel” en prod.
--forceDANGERIgnore 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).
--storeSAFEStocke le rapport dans l’app doctor si elle existe (DoctorRun, snapshots, findings).Recommandé en cron pour un historique consultable en admin.
--unsafe-fakeDANGERDans --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.

OptionTagDescriptionValeur par défaut / fallback
--backupMUTATEEffectue un dump SQL avant opérations mutantes.Le fichier de sortie est ./backup_<DB>_YYYYmmdd_HHMMSS.sql.
--backup-continue-on-failMUTATEContinue même si mysqldump échoue.Utile en cron/CI quand l’environnement ne permet pas de dumper.
--db mydbSAFENom DB à dumper.Sinon : settings.DATABASES['default']['NAME'].
--db-user rootSAFEUtilisateur DB pour mysqldump.Sinon : settings.DATABASES['default']['USER'].
--db-pass SECRETSAFEMot de passe DB pour mysqldump.Sinon : settings.DATABASES['default']['PASSWORD'].
--db-host 127.0.0.1SAFEHôte DB.Sinon : settings.DATABASES['default']['HOST'].
--db-port 3306SAFEPort DB.Sinon : settings.DATABASES['default']['PORT'].
--mysqldump /usr/bin/mysqldumpSAFEChemin du binaire mysqldump.Par défaut : mysqldump (dans PATH).
Note sécurité : Le script utilise --password=... (visible dans la ligne de commande). En prod, préfère un ~/.my.cnf (option file) ou des mécanismes de secrets plutôt que de poser un mot de passe en clair. (Voir la doc officielle MySQL sur mysqldump / password).

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

Objectif : installer migration_doctor comme commande Django, puis l’exécuter en CLI / cron.

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

URL (copier/coller)
/static/toolbox/migration_doctor_v2.py

Si 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 :

Arborescence Django
<ton_app>/
                management/
                __init__.py
                commands/
                __init__.py
                migration_doctor.py   <-- (ton script ici)
Très important :
  • 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

Check command
# Liste toutes les commandes
                python manage.py help

                # Ou filtre (selon OS)
                python manage.py help | grep migration

4) Premier run (safe)

First 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 --store

5) Cron (Linux) — prêt à copier

Cron (audit)
# 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>&1

6) 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.

Scénario A : Cron audit
# 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 --plan

Scé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.
Scénario B : churn-check
# 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 --check

Scé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.
Pourquoi c’est safe ici ? Le script ne fake que si ok_schema=True (DB==modèles), donc on ne masque pas un schéma manquant. Sinon il “skip” et t’oblige à faire un vrai restore / fix.
Scénario C : autofix-unapplied
# 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 --check

Scé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).
Scénario D : autofix-1050 (full)
# 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 --store

Scénario E — Réconcilier une app (restore) : l’enchaînement exact

Ce mode modifie l’historique DB de l’app. Lis bien : il ne drop pas les tables, mais il réécrit l’historique via migrate app 0 --fake puis migrate --fake-initial.

Tu dois fournir --yes-i-know.
Scénario E : restore (exact)
# 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 --store

Scénario F — Cleanup multi-app (dev/staging) pour repartir propre

Cleanup = destructif sur les fichiers migrations et l’historique. À faire hors production, sur une copie de base si possible.
Scénario F : cleanup (dev/staging)
# 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 --store

Cron / CI — recettes prêtes à copier

Linux — cron quotidien (audit + stockage)

Cron Linux (audit)
# 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>&1

Linux — cron “churn guard” (CI ou nightly)

Cron Linux (churn guard)
# 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>&1

CI (GitHub Actions / GitLab / Jenkins) — concept

But

Empêcher qu’un PR introduise des migrations non déterministes ou “oublie” de commit une migration.

CI concept (ex: stage migrations)
# 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 --check

Windows — Task Scheduler (audit)

Task Scheduler (Windows)
Program/script: C:\Python311\python.exe
                Arguments:      manage.py migration_doctor --all-local --check --store
                Start in:       D:\wamp\www\ideo_lab
Astuce logs : si tu veux des logs “machine-readable”, redirige stdout vers un fichier dédié, et garde le stderr (errors) séparé.

Sé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.).

Backup + restore (propre)
# 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/mysqldump

Focus : --fake vs --fake-initial (pour comprendre les risques)

Résumé clair :
  • --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à).
C’est pour ça que le script utilise --fake-initial par défaut en cleanup/restore, et expose --unsafe-fake explicitement comme dangereux.
Recommandation prod :
  • 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.

Conseil : Quand tu es perdu, fais toujours :
Triptyque anti-panique
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