Database Migrations

Introduction

Django migrations are version-controlled Python files that describe schema changes—new tables, columns, indexes, and data transforms. They replace ad-hoc SQL scripts in team workflows. This chapter covers the full makemigrations / migrate cycle, inspecting migration history, fixtures, data migrations, and collaboration rules after you defined models in Models and ORM Basics and QuerySets and Relationships.

Prerequisites

How Migrations Fit In

text
models.py change
    → makemigrations (generates Python migration file)
    → migrate (applies to database)
    → django_migrations table records applied state

Django tracks applied migrations in the django_migrations table. Each app has a migrations/ folder with numbered files like 0001_initial.py, 0002_post_author.py.

Core Commands

CommandPurpose
python manage.py makemigrationsCreate migration files from model changes
python manage.py makemigrations blogLimit to one app
python manage.py migrateApply all pending migrations
python manage.py migrate blog 0001Migrate app to a specific state
python manage.py showmigrationsList applied [X] vs pending [ ]
python manage.py sqlmigrate blog 0001Print SQL without running it
python manage.py migrate --planPreview migration order

Typical workflow

bash
# Edit blog/models.py — add a field
python manage.py makemigrations blog
python manage.py migrate
python manage.py showmigrations blog

Example output after adding summary to Post:

text
Migrations for 'blog':
  blog/migrations/0003_post_summary.py
    + Add field summary to post

Reading a Migration File

blog/migrations/0003_post_summary.py (generated):

Code explanation:

  • dependencies — must run after listed migrations
  • operations — ordered list of schema changes
  • File name includes sequence number—never reuse numbers in Git

Inspect SQL:

bash
python manage.py sqlmigrate blog 0003

Schema Change Examples

Add nullable field (safe on live DB)

python
summary = models.CharField(max_length=300, blank=True, default="")

Run makemigrations / migrate. Existing rows get default="".

Rename field

Prefer explicit migration:

python
# models.py: rename body → content (example)

Django may ask:

text
Did you rename post.body to post.content? [y/N]

Answer y to preserve data instead of drop+add.

Remove field

Delete from models.py, makemigrations, migrate. Data in that column is lost—back up production first.

Add non-null field to populated table

Provide default= or use a multi-step migration:

  1. Add field null=True
  2. Data migration to fill values
  3. Alter field null=False

Squashing and Conflicts (Teams)

When two branches add 0003_*.py, Git merge conflicts occur. Resolution:

  1. Keep both logical changes in one new migration, or
  2. One developer rebases and regenerates makemigrations

Rules:

  • Commit migration files to Git with model changes
  • Do not edit applied migrations on shared branches
  • Do not delete migration files that others already applied

For long histories, python manage.py squashmigrations blog 0001 0010 combines migrations (optional maintenance).

Roll Back

bash
python manage.py migrate blog 0002

Reverts blog to migration 0002 state (runs reverse operations when defined). Not all operations are fully reversible—test on staging.

Warning

Production Rollbacks

Prefer forward-fix migrations (new migration correcting data) over downgrade on live databases with traffic.

Initial Data: Fixtures

Fixtures serialize model rows to JSON/YAML for repeatable seeds.

blog/fixtures/posts.json:

Load:

bash
python manage.py loaddata posts

Dump existing data:

bash
python manage.py dumpdata blog.Post --indent 2 > blog/fixtures/posts.json

Use fixtures for demos and tests; large production seeds often use management commands instead.

Data Migrations (RunPython)

Transform rows without changing model fields:

blog/migrations/0004_populate_slugs.py:

Code explanation:

  • Use apps.get_model() — not direct from blog.models import Post (historical model state)
  • backwards optional for rollback
  • Keep data migrations small and idempotent when possible

Fake Migrations (Advanced)

When DB already matches models (legacy import):

bash
python manage.py migrate blog 0001 --fake

Marks migration applied without running SQL—dangerous if schema actually differs. Use only when you know the DB state.

Zero-Downtime Awareness (Preview)

Production deploy order often:

  1. Deploy code compatible with old and new schema
  2. Run migrations
  3. Deploy code that requires new schema

Additive changes (new nullable column) are safer than destructive ones (drop column) under load. Full patterns appear in Production Deployment.

Compare with Flask-Migrate

DjangoFlask-Migrate (Alembic)
Built-in makemigrationsflask db migrate
migrateflask db upgrade
Per-app migrations/Single versions/ folder

See Flask migrations.

Common Mistakes

MistakeSymptomFix
Model change, no migrationOperationalError: no such columnmakemigrations + migrate
Migration not in GitWorks locally, fails on CICommit migrations/*.py
Edit old migration after deployDrift, integrity errorsNew forward migration only
loaddata duplicate PKIntegrityErrorFlush test DB or use fresh PKs
Wrong DB in productionMigrated SQLite, not MySQLCheck DJANGO_SETTINGS_MODULE

Post-Chapter Checklist

  • You run makemigrations after every model change
  • You use showmigrations and sqlmigrate for debugging
  • Migration files are committed to version control
  • You understand fixtures and RunPython data migrations
  • You know team rules: do not rewrite applied migrations

FAQ## FAQ

Delete all migrations and start over?

Only on disposable dev DBs: delete migrations/ except __init__.py, delete db.sqlite3, makemigrations, migrate. Never on shared/production history.

migrate without makemigrations?

migrate only applies existing files—it does not detect model diffs.

Multiple databases?

migrate --database=replica — requires DATABASE_ROUTERS in settings.

Migration asks for one-off default?

Pick a sensible default for existing rows or cancel and add default= in model first.

Empty migration?

python manage.py makemigrations --empty blog for custom RunPython / RunSQL.