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.
Author

Benedict Thekkel

🔹 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.

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_name> <migration_number> Shows the raw SQL that will be executed for a migration.
python manage.py migrate <app_name> zero Rolls back all migrations for an app.
python manage.py makemigrations --dry-run Shows what migrations will be created without making changes.
python manage.py migrate --fake-initial Marks initial migrations as applied without actually running them.

🔹 3. How Migrations Work

Step 1: Create a Model

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)

Run:

python manage.py makemigrations

This creates a migration file, e.g., 0001_initial.py, inside migrations/.


Step 2: Apply the Migration

python manage.py migrate

Django creates the corresponding SQL table.

To see the SQL executed:

python manage.py sqlmigrate <app_name> 0001

🔹 4. Common Migration Operations

🛠 Adding a New Field

Add a category field to the Product model:

category = models.CharField(max_length=100, default="General")

Run:

python manage.py makemigrations
python manage.py migrate

✅ The field is added without losing existing data.


🛠 Renaming a Field

Change name to title:

class Product(models.Model):
    title = models.CharField(max_length=255)  # Renamed

Django automatically detects this change and creates a migration.


🛠 Removing a Field

Delete the category field:

class Product(models.Model):
    title = models.CharField(max_length=255)

Run:

python manage.py makemigrations
python manage.py migrate

✅ Django removes the column from the database.


🛠 Changing Field Type

Change price from DecimalField to IntegerField:

price = models.IntegerField()

Django will warn if data conversion might cause data loss.


🛠 Creating a New Model

Add an Order model:

class Order(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    order_date = models.DateTimeField(auto_now_add=True)

Run:

python manage.py makemigrations
python manage.py migrate

✅ Django creates a new table in the database.


🔹 5. Rolling Back Migrations

Undo the Last Migration

python manage.py migrate <app_name> <previous_migration_number>

🔹 Example: If the latest migration is 0003_auto, roll back to 0002:

python manage.py migrate product 0002

Undo All Migrations for an App

python manage.py migrate <app_name> zero

🔹 Example:

python manage.py migrate product zero

This removes all tables for product but does not delete migration files.


🔹 6. Handling Migration Issues

🛑 Missing Migrations Error

If Django detects model changes but no migrations were created, run:

python manage.py makemigrations
python manage.py migrate

🛑 “Table already exists” Error

This happens when Django tries to create a table that already exists.

🔹 Fix: Fake the initial migration:

python manage.py migrate <app_name> --fake-initial

🛑 Foreign Key Constraint Error

This happens if a related model does not exist.

🔹 Fix: Ensure the related model has been migrated first:

python manage.py migrate <related_app>

🛑 Reverse a Corrupt Migration

If a migration was applied incorrectly:

python manage.py migrate <app_name> <previous_migration>

🔹 Example:

python manage.py migrate product 0001

Then delete the incorrect migration file and re-run:

python manage.py makemigrations
python manage.py migrate

🔹 7. Deleting Migrations and Starting Fresh

⚠️ This is destructive and removes all migration history.

Step 1: Delete Migration Files

find . -path "*/migrations/*.py" -not -name "__init__.py" -delete

Step 2: Reset the Database

python manage.py flush

This deletes all database records but keeps tables.

Step 3: Recreate Migrations

python manage.py makemigrations
python manage.py migrate

✅ Now your project has a clean migration history.


🔹 8. Migrating Models to Another App (Without Data Loss)

If you move a model from app_a to app_b, follow these steps:

Step 1: Add db_table in the New Model

class MyModel(models.Model):
    class Meta:
        db_table = "app_a_mymodel"

Step 2: Fake the Migration

Run:

python manage.py makemigrations app_b
python manage.py migrate app_b --fake-initial

The database remains unchanged, but Django recognizes the model in the new app.


🔹 9. Best Practices

Always Commit Migration Files – Never ignore migrations in version control.
Use --dry-run Before Applying Changes – Run: bash python manage.py makemigrations --dry-runKeep Migrations Linear – Don’t edit old migration files unless necessary.
Use --fake-initial When Moving Models – Prevents Django from recreating tables.
Backup Before Major Changes – Always use: bash python manage.py dumpdata > backup.json


✅ 10. Basic Custom Migration (Add a Data Change)

Let’s say you want to insert default categories into your Category model.

Step 1: Create a blank migration

python manage.py makemigrations --empty your_app_name

This generates something like:

your_app/migrations/0002_auto_add_default_categories.py

Step 2: Edit the migration file

# your_app/migrations/0002_auto_add_default_categories.py

from django.db import migrations

def add_default_categories(apps, schema_editor):
    Category = apps.get_model('your_app', 'Category')
    Category.objects.bulk_create([
        Category(name="Food"),
        Category(name="Transport"),
        Category(name="Utilities"),
    ])

class Migration(migrations.Migration):

    dependencies = [
        ('your_app', '0001_initial'),  # Update based on your actual last migration
    ]

    operations = [
        migrations.RunPython(add_default_categories),
    ]

✅ 11. Add Reverse Function (for Rollbacks)

For full reversibility:

def remove_default_categories(apps, schema_editor):
    Category = apps.get_model('your_app', 'Category')
    Category.objects.filter(name__in=["Food", "Transport", "Utilities"]).delete()

operations = [
    migrations.RunPython(add_default_categories, reverse_code=remove_default_categories),
]

If no reverse logic needed, you can use migrations.RunPython(add_default_categories, reverse_code=migrations.RunPython.noop)


✅ 12. Custom Schema Changes (Manual SQL or Conditional Alter)

For raw SQL:

migrations.RunSQL("ALTER TABLE my_table ADD COLUMN custom_field INT DEFAULT 0")

For conditional logic:

def alter_schema_if_condition(apps, schema_editor):
    if schema_editor.connection.vendor == 'postgresql':
        schema_editor.execute("ALTER TABLE ...")

✅ 13. Custom Migration for Complex Logic (e.g., data transformation)

You can use the apps.get_model() trick to avoid direct model import:

def upgrade_data_format(apps, schema_editor):
    Transaction = apps.get_model('your_app', 'Transaction')
    for txn in Transaction.objects.all():
        txn.description = txn.description.upper()
        txn.save()

⚠️ Avoid importing real models from your app in migrations — always use apps.get_model() to get historical versions of models.


✅ Migration Lifecycle Recap

# Create an empty migration
python manage.py makemigrations --empty your_app

# Apply it
python manage.py migrate

# View history
python manage.py showmigrations

# Fake apply (if you already did it manually)
python manage.py migrate your_app_name --fake

🎯 Summary of Key Migration Commands

Command Action
makemigrations Create migration files
migrate Apply migrations to the database
showmigrations List applied and unapplied migrations
sqlmigrate <app> <num> Show SQL for a migration
migrate <app> zero Roll back all migrations for an app
migrate --fake-initial Mark migrations as applied without running them
flush Reset database (delete all data)

🧪 Best Practices

Tip Why It Helps
Use apps.get_model() Avoid importing future-incompatible models
Add reverse migrations Enables rollback
Keep business logic out Keep migrations lean, call helpers if needed
Document purpose in name Use --name when generating migration
Avoid .save() in loops Use bulk_create() or update() where possible

Back to top