Migrations
he Version Control System for Databases
In Django’s architecture, Migrations function exactly like version control (such as Git) but are built specifically for your database schemas. Instead of requiring you to write manual, risk-prone SQL statements to alter tables when your code changes, Django tracks changes to your models and generates incremental execution scripts automatically.
This ensures your database layout stays perfectly in sync with your Python models across all environments—from local development setups to multi-node production clusters.
The Two-Step Database Lifecycle Workflow
Modifying a database structure is split into two distinct steps to decouple identifying changes from executing them.
[Edit models.py] ──► python manage.py makemigrations ──► Generates Migration File (.py)
│
[Updated Database] ◄── python manage.py migrate ◄────────────────┘
1. Compilation Stage (makemigrations)
The makemigrations command scans your active models.py files, cross-references them against your existing migration files, and calculates the structural difference (the delta). It then writes a new Python script containing the instructions to apply those changes.
python manage.py makemigrations
Note: This stage is completely non-destructive. Running
makemigrationsonly builds new configuration scripts inside your app’smigrations/folder; it does not touch your live database tables.
2. Execution Stage (migrate)
The migrate command reads any pending migration scripts and executes them against your database. It checks a specialized internal table named django_migrations to see which scripts have already run, ensuring it only applies new, unexecuted migrations.
python manage.py migrate
Anatomy of a Migration File
Migration files are written in pure Python, not raw SQL. This abstraction allows the same migration script to run seamlessly across completely different database systems (such as switching from a local SQLite setup to production PostgreSQL).
Here is a look inside a typical auto-generated migration file:
# positions_board/migrations/0002_add_salary_field.py
from django.db import migrations, models
class Migration(migrations.Migration):
# Pointer linking to the exact file that must execute directly before this script
dependencies = [
('positions_board', '0001_initial'),
]
# The exact atomic actions the database engine must execute
operations = [
migrations.AddField(
model_name='enterpriserequisition',
name='allotted_budget',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12),
),
]
Schema Migrations vs. Data Migrations
Django handles two fundamentally different types of migrations:
Schema Migrations (Auto-generated): These alter the structural layout of your database—such as creating tables, removing columns, or updating indexes. Django manages these entirely on its own.
Data Migrations (Manually written): These alter the actual records stored inside those structures—such as populating configuration constants, combining two text columns into one, or pre-filling data categories before launching an application.
How to Build a Data Migration
To write a data migration, you generate an empty migration shell file and add your data manipulation logic using Django's ORM:
# Step 1: Generate an empty, uniquely named migration shell file python manage.py makemigrations --empty positions_board --name populate_initial_tiers Open the generated file and use the RunPython action to safely loop through and save records inside the database: Python # positions_board/migrations/0003_populate_initial_tiers.py from django.db import migrations def initialize_default_categories(apps, schema_editor): # CRITICAL: Retrieve the model configuration safely through historical app states # instead of importing models directly. This avoids code-breaking dependency loops. JobCategory = apps.get_model('positions_board', 'JobCategory') # Run the transaction update loop JobCategory.objects.create(name="Engineering", code_prefix="ENG") JobCategory.objects.create(name="Product Management", code_prefix="PROD") class Migration(migrations.Migration): dependencies = [ ('positions_board', '0002_add_salary_field'), ] operations = [ # Executes your custom python data loop safely inside the migration sequence migrations.RunPython(initialize_default_categories), ]
Advanced Migration Management: Rollbacks & Fixing Conflicts
1. Rolling Back Migrations
If you apply a migration that causes issues, you can roll back your database state by passing the target application name followed by the number of the migration file you want to revert to.
[Image diagram showing Django migration rollback mechanism moving the database schema backwards sequentially to a targeted migration number state]
# Roll back the database schema to the state it was in at file '0001_initial'
python manage.py migrate positions_board 0001Django reads the differences between your current state and target state, calculates the inversion rules, and cleanly rolls back any newer tables or columns.
2. Reverting All Migrations
To completely wipe out all managed tables associated with an application without dropping your entire database, roll back to a baseline value of zero:
python manage.py migrate positions_board zeroUsing Fake Migrations (--fake)
The --fake parameter updates Django’s migration tracking ledger table (django_migrations) without actually executing any SQL statements on your database hardware.
python manage.py migrate --fake
When should you use Fake Migrations?
Recovering from Manual Database Alterations: If a database administrator manually adds a column directly inside production SQL tools to fix an immediate crisis, Django's local migration script will fail when deployed, throwing an "OperationalError: column already exists" crash. Running
--fakeinstructs Django to mark that migration script as "completed" in its tracking ledger, matching reality without crashing.Integrating Legacy Databases: When importing an existing pre-built database into a new Django project, you generate an initial migration blueprint representing the current layout. You then run
migrate --fakeso Django registers the tables as tracked without trying to create tables that already exist.