iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
A static checker can catch one specific and risky kind of Django migration drift without importing Django, loading settings, or running a manage.py command. The author’s write-up describes this as a way to check models against migrations on a cold CI runner or in a broken virtual environment, where Django’s own check cannot start. The technique covers one direction only: a model field that is declared in models.py but has not been migrated yet.
The problem: Django’s check needs a working Django
The standard way to find migration drift in a Django project is makemigrations --check. It compares the current model definitions with the migration history and exits with an error if the models describe changes that no migration yet records. The author of the write-up treats it as the right answer for this problem, with one practical condition: it needs the project’s dependencies installed and its settings to import cleanly.
That condition fails in exactly the places where drift checks are most useful. A fresh CI runner may not have the full dependency set cached. A virtual environment may have a package that no longer installs, or a settings module that reads an environment variable the job does not provide. In those cases the check cannot start, so it cannot report anything.
How the static approach works
The alternative in the write-up does not execute the project. It reads files as text and builds two representations that can be compared.
#1 Best Overall
Step 1: Read the migration files
The checker reads each app’s migration modules and walks their operations in order. Operations that add a field contribute that field to a running set for the relevant model.
Step 2: Replay the operations into a field set
Replaying the operations produces the field set the migrations would leave behind. This is a reconstruction from declarations, not the database schema and not the state Django builds at runtime.
Rank #2
Step 3: Parse the model declarations
The checker reads each model declared in models.py and collects the field names it declares. Because this is source inspection, no model class is instantiated and no app registry is populated.
Recommended Free Tools
Step 4: Compare the two sets
Any field present in the model declarations but absent from the replayed set is reported as declared but never migrated. According to the author, replaying the migration files and diffing against models.py was enough to catch this direction of drift without requiring the normal environment.
What it detects, and what it does not
The claim is deliberately narrow. The write-up describes catching the dangerous direction of drift: code that expects a column the migrations will never create. That is the failure that breaks a deploy at query time, which is why it was chosen as the first target.
The write-up does not establish that the checker detects other kinds of mismatch. Nothing in the available description shows that it validates every migration operation, handles every form of field removal or rename, or confirms that a migration produces the same schema Django would build. Treat it as a guard for one failure mode, not as a replacement for Django’s migration machinery or for running migrations against a real database.
Where it fits
- Cold CI runners where installing the full project dependency set is slow or unavailable and the job only needs to fail fast on a missing migration.
- Broken virtual environments where
manage.pycannot start, so Django’s own check produces no output. - Pre-check stages that run before the heavier test or migration jobs, so an unmigrated field is caught early with a clear message.
When the full environment works, makemigrations --check remains the more complete tool, because it uses Django’s own model state rather than a reconstruction.
Comparing the two approaches
| Aspect | makemigrations --check |
Static replay and diff |
|---|---|---|
| Environment prerequisites | Installed dependencies and settings that import cleanly | Source files only; no Django import, no settings load |
| Drift direction covered | Django’s own comparison of model state against migrations, as Django defines it | Fields declared in models but not migrated, as described by the author |
| Source of truth for model state | Django’s runtime model state | Reconstruction from migration operations and parsed declarations |
| Framework-aware validation | Provided by Django itself | Not established in the write-up |
| Reported speed | Not stated in the write-up | Author reports about 20 ms to parse model graphs for Zulip, Saleor, Wagtail, django CMS, and Mezzanine together, on a laptop |
The timing figure comes from the author alone. The write-up does not describe the hardware, the measurement method, or the version of each project used, and no independent benchmark has been published for it. The article’s publication date is also not shown in the available text, so the figure should be read as a report about that version of the checker, not a current measurement.
Best Value
Applying the idea in your own project
If you want the same guard, the logic is simple enough to reproduce: collect field additions from migrations, replay them per model, parse field declarations from models.py, and fail when a declared field has no matching addition. Keep the scope the same as the author’s. A failing result means a field is missing from the migrations. A passing result does not prove the migrations match the models in every other respect.
The write-up’s own repository, installation steps, supported versions, and test methodology are not described in the material available to this article, so this article does not specify them.
The original write-up is by FROWNINGdev on DEV Community: I built a Django linter that never imports Django — DEV Community.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →The Bottom Line
Use the static check as an early, environment-independent guard for unmigrated fields, and keep makemigrations --check as the authoritative test wherever Django can run.
Quick Recap
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.

