| name | django-migrations |
| description | Safe, reversible Django migrations for my-api-project. Use when adding a column, table, FK, index, or constraint. Covers makemigrations, data migrations, non-destructive adds, and FK index requirements. |
Django migrations — my-api-project
Config
manage.py — entry point
apps/<app>/migrations/ — one directory per app
apps/<app>/migrations/0001_initial.py — initial migration
Procedure
python manage.py makemigrations <app_name>
python manage.py migrate --check
python manage.py migrate
python manage.py migrate <app_name> <previous_number>
python manage.py migrate <app_name>
pytest -q -m database
Templates
Add nullable column
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("<app>", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="resource",
name="priority",
field=models.IntegerField(null=True, blank=True),
),
]
Add NOT NULL column with default (safe on populated tables)
operations = [
migrations.AddField(
model_name="resource",
name="status",
field=models.CharField(max_length=32, default="draft"),
),
]
Add NOT NULL column without default (use a data migration first)
from django.db import migrations, models
def backfill_score(apps, schema_editor):
Resource = apps.get_model("<app>", "Resource")
Resource.objects.filter(score__isnull=True).update(score=0)
class Migration(migrations.Migration):
dependencies = [("<app>", "0002_...")]
operations = [
migrations.AddField(
model_name="resource",
name="score",
field=models.IntegerField(null=True, blank=True),
),
migrations.RunPython(backfill_score, migrations.RunPython.noop),
]
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("<app>", "0003_add_score_nullable")]
operations = [
migrations.AlterField(
model_name="resource",
name="score",
field=models.IntegerField(),
),
]
Add FK + index
operations = [
migrations.AddField(
model_name="answer",
name="idea",
field=models.ForeignKey(
to="ideas.idea",
on_delete=models.CASCADE,
related_name="answers",
db_index=True,
),
),
]
Composite index
operations = [
migrations.AddIndex(
model_name="resource",
index=models.Index(
fields=["created_by", "is_deleted"],
name="ix_resources_user_active",
),
),
]
Data migration (separate from schema)
from django.db import migrations
def set_display_name(apps, schema_editor):
Resource = apps.get_model("<app>", "Resource")
for resource in Resource.objects.filter(display_name__isnull=True):
resource.display_name = resource.name
resource.save(update_fields=["display_name"])
class Migration(migrations.Migration):
dependencies = [("<app>", "0004_...")]
operations = [
migrations.RunPython(set_display_name, migrations.RunPython.noop),
]
Hard rules
- Always reversible: every migration must be reversible. If
RunPython is used, provide a reverse function or migrations.RunPython.noop.
- Never destructive on populated tables: adding NOT NULL → provide a default or split into (a) add nullable, (b) backfill, (c) set NOT NULL.
- Every new FK gets an index:
db_index=True on the field or explicit AddIndex.
- Every new column used in WHERE/ORDER BY gets an index.
- Data migrations are separate: don't mix schema changes and data backfills in one migration.
- Don't edit applied migrations: write a new one.
- Test forward and backward locally before committing.
- Don't import models inside RunPython: use
apps.get_model().
Don't do
- Don't use
migrations.RunSQL for anything AlterField/AddField covers.
- Don't disable FK checks.
- Don't drop a NOT NULL constraint on a column that's referenced by an index expression.
- Don't commit migrations without testing
migrate and migrate <app> <previous>.