Migrations
Migrations are Django’s way of handling database schema changes. They allow you to create, modify, and delete database tables and fields without losing data.
🔹 1. Understanding Migrations
Migrations are:
- Auto-generated files that store changes to your models.
- Applied to the database to create/update/delete tables and fields.
- Version-controlled so changes can be tracked over time.
- State-aware: Django keeps both a model state (from migration files) and a DB state (via the
django_migrations
table).
They ensure consistency between the database and Django models.
🔹 2. Key Migration Commands
Command | Description |
---|---|
python manage.py makemigrations |
Generates migration files based on model changes. |
python manage.py migrate |
Applies migrations to the database. |
python manage.py showmigrations |
Displays a list of applied and unapplied migrations. |
python manage.py sqlmigrate <app> <migration_number> |
Shows the raw SQL for a migration. |
python manage.py migrate <app> zero |
Rolls back all migrations for an app. |
python manage.py makemigrations --dry-run --check |
Check if new migrations are needed (CI safe). |
python manage.py migrate --plan |
Preview what will run in order. |
python manage.py migrate --fake-initial |
Mark initial migrations as applied without running them. |
python manage.py makemigrations --merge |
Merge conflicting migration histories. |
🔹 3. How Migrations Work
- Detect model changes →
makemigrations
generates migration files (operations). - Migration files form a DAG → dependencies define order.
- Apply with
migrate
→ runs SQL + records applied migrations indjango_migrations
. - State sync → Django assumes DB matches its recorded state.
👉 Drift (manual DB edits) breaks this. Use --fake
, SeparateDatabaseAndState
, or fresh migrations to realign.
🔹 4. Common Migration Operations
CreateModel
→ new tableAddField
→ new column (withdefault
ornull=True
)RenameField
/RenameModel
→ keeps dataRemoveField
→ drops columnAlterField
→ type/option changes (may warn if risky)RunPython
/RunSQL
→ data or schema logicAddIndex
,AddConstraint
,CreateExtension
(Postgres-specific)
✅ Django usually detects renames vs. add/remove if done in isolation.
🔹 5. Rolling Back Migrations
To a previous migration:
python manage.py migrate app_name 0002
Reset an app:
python manage.py migrate app_name zero
Fake back:
python manage.py migrate app_name 0005 --fake
🔹 6. Handling Migration Issues
Issue | Fix |
---|---|
Missing migrations error | Run makemigrations + migrate |
Table already exists | migrate --fake-initial |
FK constraint fails | Ensure related app migrated first |
Corrupt migration | Roll back → delete bad file → regenerate |
Drift (manual DB change) | Use SeparateDatabaseAndState or --fake |
🔹 7. State-only vs DB-only Migrations
Use SeparateDatabaseAndState
:
migrations.SeparateDatabaseAndState(=[],
database_operations=[
state_operations"customer", "name", "full_name")
migrations.RenameField(
] )
👉 Useful when the DB is already correct, but Django’s state is not (or vice versa).
🔹 8. Large Tables & Zero-Downtime
Add column: always nullable or with default.
Two-step change for non-null:
- Add nullable → backfill → update app code to write both.
- Make column required → drop old.
Indexes:
from django.contrib.postgres.operations import CreateIndexConcurrently class Migration(migrations.Migration): = False atomic = [ operations CreateIndexConcurrently(="order", model_name=models.Index(fields=["created_at"], name="order_created_idx") index ) ]
Set
atomic = False
if long ops (Postgres concurrent indexes can’t run in a transaction).
🔹 9. Migrating Models Between Apps
- Keep same DB table via
db_table
. - Run
makemigrations app_b
. - Apply with
--fake-initial
.
👉 Ensures Django points at the new app, but DB untouched.
🔹 10. Custom Data Migrations
def forwards(apps, schema_editor):
= apps.get_model("shop", "Product")
Product filter(category="").update(category="General")
Product.objects.
def backwards(apps, schema_editor):
= apps.get_model("shop", "Product")
Product ="")
Product.objects.update(category
class Migration(migrations.Migration):
= [migrations.RunPython(forwards, backwards)] operations
⚠️ Always use apps.get_model
(historical models), never direct imports.
🔹 11. Squashing & Merging
Squash long histories:
python manage.py squashmigrations app 0001 0050
Merge conflicts:
python manage.py makemigrations --merge
🔹 12. Best Practices
✅ Do | ❌ Avoid |
---|---|
Commit migration files | Ignoring migrations in VCS |
Use --check --dry-run in CI |
Editing old migrations after release |
Review SQL with sqlmigrate |
Manual DB edits without aligning |
Write reverse functions | RunPython without rollback |
Keep them small & atomic | Huge multi-change migrations |
🔹 13. Quick Cheat Sheet
# Generate migrations
python manage.py makemigrations
# Apply migrations
python manage.py migrate
# Roll back app
python manage.py migrate app_name zero
# Preview SQL
python manage.py sqlmigrate app 0005
# Preview plan
python manage.py migrate --plan