makemigrations, migrate, and the dependency graph
coreintermediatemakemigrations compares current models against migration history and WRITES a new migration file describing the difference — it never touches the database. migrate READS migration files and actually APPLIES (or unapplies) them against the real database. Each migration file lists dependencies (which other migrations must run first) and operations (what to actually do) — Django builds an in-memory graph from every app's dependencies to compute one consistent apply order. Two developers each adding a migration on the same base, in parallel branches, produces a CONFLICT — makemigrations --merge resolves it by creating one migration depending on both.
Think of it as
makemigrations is the "diff" step — it looks at your models.py files and figures out what changed since the last migration, writing that difference down as a new Python file, entirely offline, no database connection needed. migrate is the "apply" step — it reads whatever migration files exist and actually runs their operations against a real database, tracking which ones have already been applied in its own table (django_migrations) so re-running migrate is a no-op for anything already done. The dependency graph exists because migrations rarely stand alone — a migration in one app might need another app's migration to have already run (a ForeignKey to a model in a different app), and Django computes ONE consistent, valid order across every app's migrations by treating dependencies as edges in a graph, not just a simple per-app sequence.
What we're doing: Resolve a migration conflict after two branches each independently added a migration on top of the same base migration.
- 1
- The error names exactly the conflict: TWO different 0003 migrations both exist, both claiming 0002 as their dependency — the graph has two "leaf" branches instead of one.
- 4
- --merge writes a new migration whose dependencies list includes BOTH conflicting 0003 migrations, giving the graph a single, unambiguous path forward again.
Why this works: This conflict is an entirely normal consequence of two developers working in parallel branches, each running makemigrations against the same starting point — Django cannot silently guess which of the two "goes first" (they might even touch unrelated fields), so it stops and asks explicitly, and --merge is the documented, safe way to reconcile them into one valid history.
Deleting one branch's migration file to "resolve" a conflict, instead of merging
Wrong
Better
What you see: The deleted migration's actual schema change (e.g. the discount field) never gets applied to any database going forward, even though the model field it was meant to create might still exist in models.py — a silent, hard-to-trace drift between the model definitions and the migration history.
Why: Deleting a migration file does not remove whatever model changes it was meant to apply — if the corresponding model field is still present in models.py, a future makemigrations run may generate a confusing, unrelated-looking migration trying to "add" a field that was actually already intended to exist; --merge is the tool built specifically to reconcile two real, both-needed migrations into one consistent history, not to discard one arbitrarily.
- models.py — current model definitions
- leads to makemigrations
- makemigrations — diffs against migration history — no DB touched
- leads to 0003_add_rating.py
- 0003_add_rating.py — dependencies + operations
- leads to migrate
- migrate — reads the file, applies it
- leads to database schema
- database schema
makemigrations vs migrate
Together
Remember: makemigrations writes a migration file from a models.py diff (no database touched); migrate reads migration files and actually applies them. A migration's dependencies (which can cross app boundaries) plus every app's combined graph determine one consistent apply order. A conflict (two migrations claiming the same dependency) is resolved with makemigrations --merge, not by deleting a file.
See also: forward reverse and irreversible operations · data migrations fake and squashing · the expand and contract technique

