100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

Migrations in Django

How Django's migration system tracks model changes over time and applies them safely to the database schema.

Models & ORMIntermediate8 min readJul 10, 2026
Analogies

What Migrations Do

Migrations are Python files that describe incremental changes to your database schema — adding a table, adding a column, altering a field's type — generated by running python manage.py makemigrations after changing models.py. Each migration file lists dependencies on prior migrations, forming a directed graph that Django's migration executor walks in order when you run python manage.py migrate. Migrations are database-agnostic: the same migration file can generate PostgreSQL, MySQL, or SQLite DDL depending on your configured backend.

🏏

Cricket analogy: A chain of migrations dependent on prior ones is like a Test match's over-by-over scorecard — over 24 can only be recorded after over 23 is complete, just as migration 0005 depends on 0004 being applied first.

makemigrations, migrate, and the Migration State

makemigrations only writes migration files to disk based on differences it detects between your models and the last known migration state — it does not touch the database. migrate is the command that actually applies (or reverses) those files against a real database, and Django tracks which migrations have run per app in the django_migrations table. Running python manage.py sqlmigrate app_name 0003 prints the raw SQL a migration will execute without running it, which is useful for reviewing risky schema changes like column drops before they hit production.

🏏

Cricket analogy: makemigrations writing a plan without touching the database is like a captain announcing a bowling change in the huddle — the decision is recorded, but no ball has been bowled (no SQL executed) until migrate runs it.

bash
# After changing models.py
python manage.py makemigrations blog

# Inspect the SQL before running it against a real database
python manage.py sqlmigrate blog 0004

# Apply all pending migrations
python manage.py migrate

# Roll back to a specific earlier migration
python manage.py migrate blog 0002

Editing a migration file after it has already been applied and shared with teammates (or deployed) is dangerous — Django tracks migrations by name and file state, so out-of-sync migration histories across environments can cause 'InconsistentMigrationHistory' errors or silently diverging schemas.

Data Migrations and Backward Compatibility

Beyond schema changes, migrations can also transform data using RunPython, letting you write a forward function (and optionally a reverse function) that operates on historical model state accessed via apps.get_model() rather than importing the model directly — this matters because the model as it exists today may have fields that didn't exist when an old migration ran. A common safe pattern for adding a required field to a table with existing rows is a three-step migration: first add the field as nullable, then run a data migration to backfill values, then a final migration to make the field non-nullable.

🏏

Cricket analogy: Using apps.get_model() instead of importing the live model in a data migration is like consulting the exact rulebook edition in force during a 2005 match instead of today's DRS-updated rules — you must reason about the schema as it existed at that point in history.

  • makemigrations generates migration files from model changes but does not touch the database.
  • migrate applies (or reverses) pending migration files against the actual database and records progress in django_migrations.
  • sqlmigrate prints the raw SQL a migration will run, useful for reviewing risky changes before deployment.
  • Migrations form a dependency graph; Django applies them in the order their dependencies require.
  • RunPython enables data migrations, using apps.get_model() to access historical model state rather than the live model.
  • Adding a required field to a table with existing data safely follows a three-step pattern: nullable, backfill, then non-nullable.
  • Editing already-applied and shared migration files risks InconsistentMigrationHistory errors across environments.

Practice what you learned

Was this page helpful?

Topics covered

#Python#DjangoStudyNotes#WebDevelopment#MigrationsInDjango#Migrations#Django#Makemigrations#Migrate#StudyNotes#SkillVeris