skip to content

A Django fitness tracker adds avg_heart_rate to Workout and production then fails with a missing column; how do makemigrations --check, migrate --check, showmigrations and sqlmigrate catch or diagnose it?

level: seniorimportance: should knowfreq 48%

answer

  1. file missing or never applied
  2. a non-zero exit in CI
  3. implies a dry run
  4. unapplied migrations as a gate
  5. read the SQL before running it

basics

~20 s

A missing column means the migration was never written or never applied. makemigrations --check fails CI when a model change lacks a migration; migrate --check fails when unapplied migrations exist; showmigrations and sqlmigrate show which migration is missing and what SQL it runs.

solid answer

~40 s

There are two causes. Either nobody ran `makemigrations`, so no file adds the column, or the file exists but `migrate` did not run against production. `makemigrations --check` catches the first in CI: it implies `--dry-run`, writes nothing and exits with status 1 when models have changes without migrations. `migrate --check` catches the second as a gate: it applies nothing and exits non-zero when the plan is not empty. To diagnose a live incident, `showmigrations workouts` shows whether `0004_workout_avg_heart_rate` is `[ ]` or `[X]`; an `[X]` with a missing column means it was faked. `migrate --plan` lists what would run, and `sqlmigrate workouts 0004` prints the exact SQL so you can check it before applying.

code

bash · 10 lines
bash
# CI: fail the build if a model change has no migration file
python manage.py makemigrations --check --dry-run

# Release gate: fail if the target database still has unapplied migrations
python manage.py migrate --check

# Incident diagnosis
python manage.py showmigrations workouts
python manage.py migrate --plan
python manage.py sqlmigrate workouts 0004

go deeper

for a junior

Know that a missing column usually means a migration was not written or not applied, and that makemigrations --check catches the first in CI.

for a middle

Explain what --check does on each command, its exit statuses, and how showmigrations and sqlmigrate help diagnose.

for a senior

Build the gate: makemigrations --check on every change, migrate --check before traffic, and a clear procedure for faked or skipped migrations.

for a principal

Decide how migration safety is enforced across teams, which checks block a release, and who owns applying schema changes.

## Two different failures with the same symptom A view querying `Workout` fails with a database error saying `avg_heart_rate` does not exist. In Django that has two usual causes: | Cause | Where it breaks | What catches it | |---|---|---| | The model has the field, but no migration adds it | the file was never written or never committed | `makemigrations --check` in CI | | The migration file exists but was not applied to this database | the release did not run `migrate`, or ran it against another database | `migrate --check` or `showmigrations` | | The migration is recorded as applied, yet the column is missing | someone ran `migrate --fake` | `showmigrations` shows `[X]`, `sqlmigrate` shows what was skipped | Tests do not reliably catch the first cause. The test runner builds its database by running migrations, so the column is missing there too, but only tests that actually query `Workout` fail, and they fail with a database error that looks like a bug in the code under test rather than a missing file. ## makemigrations --check in CI `makemigrations --check`: - implies `--dry-run`, so it **never writes files**; - exits with status **1** when any model change has no migration, and 0 otherwise; - prints the operations it would have written, which tells the developer what is missing. Run it with the same settings module and `INSTALLED_APPS` as production, since an app missing from the CI settings is simply not checked. It needs no migrated database: it does try to connect to check migration history consistency, but if that connection fails it only emits a warning and continues. ```bash python manage.py makemigrations --check --dry-run ``` `--dry-run` is redundant there but harmless and makes the intent obvious to readers of the pipeline. ## migrate --check as a gate `migrate --check` loads the plan for the target database and exits non-zero if **any** migration is unapplied, without applying anything. Two common uses: 1. After the release step, to prove the database is fully migrated before traffic reaches the new code. 2. In a health or readiness check for a worker that must not start against an old schema. Combined with `--plan`, it also prints what is pending before exiting non-zero. ## Diagnosing a live incident 1. **`showmigrations workouts`** lists each migration with `[X]` (applied) or `[ ]` (not applied). If `0004_workout_avg_heart_rate` is missing from the list entirely, the file never reached the deployed code. 2. **`migrate --plan`** shows every operation that would run, in order, including migrations from other apps that the target depends on. 3. **`sqlmigrate workouts 0004`** prints the SQL for this backend without running it. Review it for locks and table rewrites before running it on a large table; `--backwards` prints the reverse SQL. 4. If step 1 shows `[X]` but the column is missing, the migration was faked: fake back to `0003`, then apply for real. ## What the check does not cover `makemigrations --check` answers one question: do the migration files describe the current models? It passes in several situations that still hurt: - **Apps missing from the CI settings.** An app absent from `INSTALLED_APPS` in the settings CI uses is never examined. - **Wrong but present migrations.** A rename recorded as `RemoveField` plus `AddField` matches the models perfectly and still drops data. - **Missing data work.** A new field that needs values backfilled produces no model difference once its `AddField` exists. - **Version skew.** A different Django version in CI and on laptops can change the diff, as the 6.0 `BigAutoField` default did. It does fail on one extra case for free: when two branches each added a migration to the same app, the command stops with *Conflicting migrations detected* before diffing, and the non-zero exit fails the build. ## Making it hard to happen again - Run `makemigrations --check` on every pull request; it takes seconds and fails before review ends. - Review generated files like code: a misread rename, an unexpected `AlterField` on every id after an upgrade, or a missing dependency shows up there. - Gate traffic on `migrate --check` succeeding against the production database. - Keep one command, in one place, responsible for applying migrations, so no environment depends on someone remembering. Where and how `migrate` runs in the release pipeline is a deployment decision, but these four commands are how a Django developer proves the code and the schema agree.

  • makemigrations --check passes in CI, yet production still lacks the column; what does that tell you?
    The migration file exists and matches the models, so the problem is applying it: `migrate` did not run, ran against another database alias, or the migration was faked. `showmigrations workouts` against production separates these: `[ ]` means not applied, `[X]` with a missing column means faked.
  • Why read sqlmigrate output before applying a migration to a large table?
    The operation name hides what the database does. An `AlterField` might be a metadata change, a full table rewrite, or `-- (no-op)`; an index creation might lock writes. `sqlmigrate` prints the exact statements for the production backend, so you can judge the lock and duration before running it.

saying these in an interview costs you the question

  • makemigrations --check writes the missing migration and then fails the build.
  • The test suite always fails clearly when a migration file is missing.
  • migrate --check applies pending migrations and reports success.
  • showmigrations [X] guarantees the column exists in the database.
  • sqlmigrate runs the migration's SQL inside a transaction to test it.