Django Migrations: Managing Database Schema Changes

Django migrations provide version control for database schemas tracking changes to models and automatically generating SQL statements to modify database structure ensuring consistency between code and database. Migrations solve the challenge of evolving database schemas in deployed applications allowing developers to add fields, modify relationships, or restructure data without manual SQL writing or data loss risks. The migration system records every schema change in Python files creating an audit trail of database evolution enabling rollbacks, collaboration across teams, and reproducible database states across development, staging, and production environments. Django automatically detects model changes generating appropriate SQL through database backend abstraction supporting PostgreSQL, MySQL, SQLite, and other databases without database-specific migration code. Understanding migrations is essential for professional Django development as they enable safe database schema evolution, support continuous deployment workflows, facilitate team collaboration on database changes, and maintain data integrity during application updates ensuring smooth application lifecycle management from initial development through long-term maintenance and feature additions.
Migration Basics
Django migrations consist of two primary commands: makemigrations detecting model changes and generating migration files, and migrate applying pending migrations to update database schema. Migration files live in app migrations directories containing Python code describing schema operations like creating tables, adding columns, or modifying constraints. Each migration file includes dependencies specifying order of execution and operations defining actual database changes. Running makemigrations after model modifications creates new migration files numbered sequentially maintaining chronological order. The migrate command applies unapplied migrations executing database operations and recording completed migrations preventing duplicate application. Initial migrations created after defining models establish baseline database schema. Subsequent migrations capture incremental changes enabling evolution without destroying existing data. Understanding migration workflow enables managing database changes systematically, avoiding manual schema modifications, and maintaining reproducible database states across environments ensuring consistency and reliability throughout application lifecycle.
# Migration workflow
# 1. Define or modify model
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
# 2. Create migration file
# python manage.py makemigrations
#
# Output:
# Migrations for 'blog':
# blog/migrations/0001_initial.py
# - Create model Post
# 3. Review generated migration
# blog/migrations/0001_initial.py
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True)),
('title', models.CharField(max_length=200)),
('content', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
]
# 4. Apply migration to database
# python manage.py migrate
#
# Output:
# Operations to perform:
# Apply all migrations: blog
# Running migrations:
# Applying blog.0001_initial... OK
# 5. Add new field to model
# blog/models.py
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey('auth.User', on_delete=models.CASCADE) # NEW
# 6. Create migration for new field
# python manage.py makemigrations
#
# Output:
# Migrations for 'blog':
# blog/migrations/0002_post_author.py
# - Add field author to post
# 7. Apply new migration
# python manage.py migrate
#
# Output:
# Operations to perform:
# Apply all migrations: blog
# Running migrations:
# Applying blog.0002_post_author... OK
# Check migration status
# python manage.py showmigrations
#
# Output:
# blog
# [X] 0001_initial
# [X] 0002_post_author
# View SQL without applying
# python manage.py sqlmigrate blog 0001
#
# Output: Shows actual SQL statementsCommon Migration Operations
Django migrations support various operations modifying database schema including creating and deleting models, adding and removing fields, altering field properties, and managing indexes and constraints. CreateModel operation generates new database tables with specified fields and options. DeleteModel removes tables from the database. AddField adds columns to existing tables providing default values or allowing NULL for existing rows. RemoveField deletes columns handling foreign key cascades and constraints appropriately. AlterField modifies column properties like changing max_length, adding indexes, or updating choices. RenameField and RenameModel operations rename columns and tables preserving data during structural changes. Operations can be combined in single migration files executing multiple schema modifications atomically when database supports transactions. Understanding operations enables implementing complex schema changes, handling data preservation during modifications, and resolving migration conflicts maintaining database integrity while evolving application data structures through controlled, versioned changes.
# Common migration operations
# Generated migration file examples
# 1. Adding a new field
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='post',
name='slug',
field=models.SlugField(default=''),
),
]
# 2. Removing a field
operations = [
migrations.RemoveField(
model_name='post',
name='old_field',
),
]
# 3. Altering a field
operations = [
migrations.AlterField(
model_name='post',
name='title',
field=models.CharField(max_length=300), # Changed from 200
),
]
# 4. Renaming a field
operations = [
migrations.RenameField(
model_name='post',
old_name='author',
new_name='writer',
),
]
# 5. Creating a new model
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(primary_key=True)),
('name', models.CharField(max_length=100)),
('slug', models.SlugField(unique=True)),
],
options={
'ordering': ['name'],
'verbose_name_plural': 'Categories',
},
),
]
# 6. Deleting a model
operations = [
migrations.DeleteModel(name='OldModel'),
]
# 7. Renaming a model
operations = [
migrations.RenameModel(
old_name='Post',
new_name='Article',
),
]
# 8. Adding an index
operations = [
migrations.AddIndex(
model_name='post',
index=models.Index(fields=['created_at', 'published'], name='post_created_pub_idx'),
),
]
# 9. Adding unique constraint
operations = [
migrations.AddConstraint(
model_name='post',
constraint=models.UniqueConstraint(fields=['slug', 'author'], name='unique_post_per_author'),
),
]
# 10. Multiple operations in one migration
operations = [
migrations.AddField(
model_name='post',
name='status',
field=models.CharField(max_length=20, default='draft'),
),
migrations.AddField(
model_name='post',
name='published_at',
field=models.DateTimeField(null=True, blank=True),
),
migrations.AlterField(
model_name='post',
name='title',
field=models.CharField(max_length=250),
),
]Data Migrations
Data migrations modify database content rather than schema enabling data transformations, populating new fields, or migrating between data structures during application evolution. RunPython operation executes custom Python code within migrations accessing Django ORM to query and modify data. These migrations run during deployment updating existing data to match new schema requirements like populating slug fields from titles or migrating data between restructured models. Data migrations must be idempotent allowing safe re-running without causing errors or duplicate data. Forward and reverse functions enable rollback capabilities restoring previous data states if needed. Combining schema and data migrations in appropriate order ensures data consistency during complex refactorings. Understanding data migrations enables safe data transformations during deployment, populating new required fields without manual SQL, and implementing complex data restructuring maintaining referential integrity while evolving application data models through controlled, tested migration processes.
# Data migrations
# Create empty migration
# python manage.py makemigrations --empty blog --name populate_slugs
# blog/migrations/0003_populate_slugs.py
from django.db import migrations
from django.utils.text import slugify
def populate_slugs(apps, schema_editor):
"""Populate slug field from title for existing posts"""
# Get model from migration state (not imported directly)
Post = apps.get_model('blog', 'Post')
for post in Post.objects.all():
if not post.slug:
post.slug = slugify(post.title)
post.save()
def reverse_populate_slugs(apps, schema_editor):
"""Reverse migration - clear slugs"""
Post = apps.get_model('blog', 'Post')
Post.objects.all().update(slug='')
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_post_slug'),
]
operations = [
migrations.RunPython(
populate_slugs,
reverse_code=reverse_populate_slugs
),
]
# Example: Migrate data between models
def migrate_categories(apps, schema_editor):
"""Migrate from old category system to new"""
Post = apps.get_model('blog', 'Post')
Category = apps.get_model('blog', 'Category')
# Create categories from unique post categories
category_names = Post.objects.values_list('old_category', flat=True).distinct()
for name in category_names:
if name:
Category.objects.get_or_create(
name=name,
defaults={'slug': slugify(name)}
)
# Update posts with new category references
for post in Post.objects.all():
if post.old_category:
category = Category.objects.get(name=post.old_category)
post.category = category
post.save()
# Example: Transform data
def split_full_name(apps, schema_editor):
"""Split full_name into first_name and last_name"""
User = apps.get_model('accounts', 'User')
for user in User.objects.all():
if user.full_name:
parts = user.full_name.split(' ', 1)
user.first_name = parts[0]
user.last_name = parts[1] if len(parts) > 1 else ''
user.save()
# Example: Populate computed fields
def calculate_totals(apps, schema_editor):
"""Calculate and store order totals"""
Order = apps.get_model('shop', 'Order')
for order in Order.objects.all():
total = sum(item.price * item.quantity for item in order.items.all())
order.total = total
order.save()
# Apply data migration
# python manage.py migrate blog 0003
# Important: Use apps.get_model() not direct imports
# Direct imports may not match migration stateHandling Migration Conflicts
Migration conflicts occur when multiple developers create migrations simultaneously or when merging branches with divergent migration histories requiring manual resolution. Django detects conflicts showing multiple migration files depending on the same parent migration creating ambiguous migration order. Resolving conflicts involves examining both migrations, merging them into a single migration if they affect different models, or creating a merge migration depending on both conflicting migrations establishing correct dependency order. The makemigrations --merge command automatically creates merge migrations resolving simple conflicts where operations don't interfere. Complex conflicts require manual migration file editing ensuring operations execute in correct order and dependencies accurately reflect migration relationships. Understanding conflict resolution enables collaborative development, managing feature branch migrations, and maintaining linear migration history preventing deployment failures from migration order ambiguities ensuring smooth team collaboration on database schema evolution.
# Migration conflict scenarios
# Scenario: Two developers create migrations simultaneously
# Developer A creates:
# blog/migrations/0003_post_featured.py
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_post_slug'),
]
operations = [
migrations.AddField(
model_name='post',
name='featured',
field=models.BooleanField(default=False),
),
]
# Developer B creates:
# blog/migrations/0003_post_views.py # Same number!
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_post_slug'),
]
operations = [
migrations.AddField(
model_name='post',
name='views',
field=models.IntegerField(default=0),
),
]
# When merging, Django detects conflict:
# python manage.py makemigrations
#
# Output:
# CommandError: Conflicting migrations detected; multiple leaf nodes in the
# migration graph: (0003_post_featured, 0003_post_views in blog).
# To fix them run 'python manage.py makemigrations --merge'
# Create merge migration:
# python manage.py makemigrations --merge
#
# Output:
# Merging blog
# Merging:
# 0003_post_featured
# 0003_post_views
# Created new merge migration blog/0004_merge_20260119_1130.py
# Generated merge migration:
# blog/migrations/0004_merge_20260119_1130.py
class Migration(migrations.Migration):
dependencies = [
('blog', '0003_post_featured'),
('blog', '0003_post_views'),
]
operations = [] # No new operations, just resolves dependency tree
# Manual conflict resolution:
# If migrations conflict (e.g., both modify same field),
# manually edit one migration file:
# Rename one migration to higher number:
# Rename 0003_post_views.py to 0004_post_views.py
# Update its dependencies:
class Migration(migrations.Migration):
dependencies = [
('blog', '0003_post_featured'), # Depend on other migration
]
operations = [
migrations.AddField(
model_name='post',
name='views',
field=models.IntegerField(default=0),
),
]
# Check migration graph:
# python manage.py showmigrations --plan
#
# Output shows execution order:
# [ ] blog.0001_initial
# [ ] blog.0002_post_slug
# [ ] blog.0003_post_featured
# [ ] blog.0004_post_views
# Apply migrations:
# python manage.py migrateRolling Back Migrations
Django supports rolling back migrations to previous states reverting database schema changes when needed for bug fixes, deployment rollbacks, or testing migration sequences. Specifying migration name in migrate command rolls back to that specific migration undoing all subsequent migrations. Migration rollback executes reverse operations defined in migration files requiring reversible operations for successful rollback. Some operations like data deletion cannot be reversed safely requiring careful migration design and testing. The migrate zero command completely rolls back all migrations for an app removing all tables created by that app useful for testing or complete app removal. Understanding rollback capabilities enables safe experimentation with schema changes, recovery from failed migrations, and implementing deployment rollback procedures ensuring database changes can be reversed when deployments need reverting maintaining operational safety and deployment confidence through tested rollback procedures.
# Rolling back migrations
# View current migration status
# python manage.py showmigrations blog
#
# Output:
# blog
# [X] 0001_initial
# [X] 0002_post_slug
# [X] 0003_post_featured
# [X] 0004_post_views
# Rollback to specific migration
# python manage.py migrate blog 0002
#
# Output:
# Operations to perform:
# Target specific migration: 0002_post_slug, from blog
# Running migrations:
# Rendering model states... DONE
# Unapplying blog.0004_post_views... OK
# Unapplying blog.0003_post_featured... OK
# Check status after rollback
# python manage.py showmigrations blog
#
# Output:
# blog
# [X] 0001_initial
# [X] 0002_post_slug
# [ ] 0003_post_featured
# [ ] 0004_post_views
# Rollback all migrations for an app
# python manage.py migrate blog zero
#
# Output:
# Operations to perform:
# Unapply all migrations: blog
# Running migrations:
# Rendering model states... DONE
# Unapplying blog.0002_post_slug... OK
# Unapplying blog.0001_initial... OK
# Re-apply migrations
# python manage.py migrate blog
#
# Output:
# Operations to perform:
# Apply all migrations: blog
# Running migrations:
# Applying blog.0001_initial... OK
# Applying blog.0002_post_slug... OK
# Applying blog.0003_post_featured... OK
# Applying blog.0004_post_views... OK
# Fake migration (mark as applied without running)
# Useful when manually applied SQL or syncing migration state
# python manage.py migrate blog 0003 --fake
#
# Marks 0003 as applied without executing operations
# Fake initial migration (for existing database)
# python manage.py migrate blog --fake-initial
#
# Fakes initial migration if tables already exist
# Irreversible operations require handling
class Migration(migrations.Migration):
dependencies = [
('blog', '0001_initial'),
]
operations = [
# Irreversible operation
migrations.RunSQL(
"UPDATE blog_post SET status='published' WHERE created_at < '2025-01-01';",
reverse_sql=migrations.RunSQL.noop # Cannot reverse data update
),
]
# Testing migrations
# Create test database, apply migrations, then rollback:
# python manage.py migrate --database=test
# python manage.py migrate blog zero --database=testMigration Best Practices
Effective migration management follows established patterns ensuring reliable database evolution and deployment safety. Always review generated migrations before committing verifying operations match intended changes and checking for potential data loss or performance issues. Create descriptive migration names using --name parameter making migration history more understandable when reviewing or troubleshooting. Test migrations on staging databases before production deployment catching issues without affecting live systems. Keep migrations small and focused on single logical changes enabling easier rollback and troubleshooting. Avoid modifying existing migration files after deployment as other developers or servers may have already applied them causing inconsistencies. Use data migrations for populating required fields on existing data preventing NOT NULL constraint failures. Backup databases before applying complex migrations enabling recovery from unexpected issues. Commit migration files to version control ensuring team synchronization and deployment consistency. Run makemigrations regularly during development catching model changes incrementally. These practices ensure reliable database evolution supporting safe deployment and collaborative development maintaining database integrity throughout application lifecycle.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


