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_migrationstable).
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 →
makemigrationsgenerates 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 (withdefaultornull=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 0002Reset an app:
python manage.py migrate app_name zeroFake 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=[
migrations.RenameField("customer", "name", "full_name")
]
)👉 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): atomic = False operations = [ CreateIndexConcurrently( model_name="order", index=models.Index(fields=["created_at"], name="order_created_idx") ) ]Set
atomic = Falseif 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):
Product = apps.get_model("shop", "Product")
Product.objects.filter(category="").update(category="General")
def backwards(apps, schema_editor):
Product = apps.get_model("shop", "Product")
Product.objects.update(category="")
class Migration(migrations.Migration):
operations = [migrations.RunPython(forwards, backwards)]⚠️ Always use apps.get_model (historical models), never direct imports.
🔹 11. Squashing & Merging
Squash long histories:
python manage.py squashmigrations app 0001 0050Merge 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