A static checker can catch one dangerous kind of Django migration drift without starting Django at all: a model field that is declared in models.py but has never been added by a migration. The approach, described by FROWNINGdev in a DEV Community post, replays migration files into a field set and compares that set with the model declarations. It is useful when a cold CI runner or a broken virtual environment cannot run Django’s own commands. It does not replace Django’s migration machinery, and it does not check every migration behavior.
Why Django’s own check is the reference point
Django’s built-in answer to migration drift is python manage.py makemigrations --check. The command compares the current model state with the migrations on disk and exits with a non-zero status when model changes have no matching migration, which makes it a natural step in CI. The catch, as the author points out, is that it only works if the project’s dependencies are installed and its settings module imports cleanly. If either fails, the command never reaches the comparison.
As an Amazon Associate I earn from qualifying purchases.
The environments where that check cannot run
The motivating case is an environment where ordinary Django command execution cannot start. Two common examples:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →- Cold CI runners that execute a lint job before dependencies are installed, or that run it in a lightweight container with no database or secrets available to settings.
- Broken virtual environments where a dependency has drifted, a settings module raises at import time, or a required environment variable is missing on a developer machine.
In both cases the check you actually want, whether a model field was ever migrated, is blocked by unrelated failures. A checker that reads source files sidesteps those failures because it never executes the project’s code.
#1 Best Overall
How the static approach works
The technique rests on two representations of the same schema, both obtained without importing Django:
- Parse each migration file and replay its operations in order, such as field additions, removals and renames, into a reconstructed field set per model.
- Parse the model declarations in
models.pyto collect the fields each model declares. - Diff the two sets. Any field declared in the models but absent from the replayed migration state is reported as drift.
Because both sides are read from source text, the checker needs only the files in the repository. The article does not list the parser or other libraries it depends on, so the exact install footprint of the tool is not established by the source.
Rank #2
What the checker detects, and what it does not
The article’s scope is narrow and should be read that way:
- Detected: a field declared in a model but never added by any migration. This is the direction that causes runtime failures once code expects a column that the database does not have.
- Not established: the reverse direction, where a migration adds or keeps a field that the model no longer declares. The source does not say whether this is covered.
- Not claimed: full schema equivalence. Replaying operations into a field set does not verify database constraints, index definitions, data migrations, or the SQL that Django would generate for a given backend.
- Not claimed: that the checker validates every migration’s behavior. It answers a question about declared fields, not about whether a migration runs correctly.
Treat it as an early warning in environments where Django’s check cannot run, not as a substitute for makemigrations --check in environments where it can.
Comparing the two approaches
| Aspect | makemigrations –check | Static replay and model diff (as described) |
|---|---|---|
| Environment prerequisites | Installed dependencies and a settings module that imports cleanly | Repository files; no Django import, per the article |
| Drift direction detected | Model changes with no matching migration, as Django computes them | Fields declared in models but never migrated |
| Reverse direction (migrated but removed from models) | Covered by Django’s comparison | Not stated in the source |
| Database-level or backend-specific differences | Outside the check’s comparison of model state | Not claimed by the article |
| Typical use | Developer machines and CI jobs with a working environment | Cold runners and broken environments where the Django command cannot start |
The reported timing, and how to read it
The author reports that model graphs from Zulip, Saleor, Wagtail, django CMS, and Mezzanine parse together in about 20 ms on a laptop. Three qualifications apply. The figure is the author’s own report, not an independently verified benchmark. The indexed text does not specify the hardware or the measurement method. And the post’s publication year was not visible in the text available for this summary, so the figure should be read as a single author-reported observation rather than a current measurement for any given version of those projects.
The figure is still useful as a signal that a source-only pass is cheap enough to run on every commit, which is the practical argument for placing it early in a pipeline.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Deciding which check to run
- If
makemigrations --checkruns reliably in your CI, keep it. It covers the comparison that Django itself performs. - If your lint stage runs before dependencies are installed, or a developer environment cannot import settings, a static declared-but-unmigrated check can still catch the most common failure at low cost.
- Do not read a passing static check as proof that the migration history is complete or correct. It only establishes the one direction it covers.
The original post by FROWNINGdev is at I built a Django linter that never imports Django on DEV Community. Installation steps, repository details and the supported Django versions were not available in the text used for this summary, so check the post directly before adopting the tool.
Recommended Free Tools
Quick Recap
Best Value
The Bottom Line
“”
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




