Django migrations

Prerequisites

Django Migrations

Migrations are Django's way of propagating changes made to your models (defined in models.py) into your database schema. They allow you to:

  • Create database schemas from your models.
  • Modify existing schemas when your models change.
  • Share changes between team members or deployments.

Migrations are stored as files inside the migrations directory of each Django app and contain Python code to apply (or undo) specific changes to the database.


The Migration Workflow

  1. Make Changes to Models
    Update or define new models in models.py.

  2. Generate Migration File
    Run the makemigrations command to create a migration file based on the changes made to your models:

    python manage.py makemigrations
    

  3. Apply Migrations
    Apply the migration file to the database using:

    python manage.py migrate
    

  4. Inspect Migration Status
    Check the status of migrations with:

    python manage.py showmigrations
    


Example: Step-by-Step Migration Process

1. Creating an Initial Migration

Suppose we have a simple app called blog. In models.py:

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published_date = models.DateTimeField()

Run the following command to create an initial migration:

python manage.py makemigrations

This generates a migration file in blog/migrations/, typically named something like 0001_initial.py.

The migration file might look like:

# Generated by Django 5.x on YYYY-MM-DD HH:MM

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, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=200)),
                ('content', models.TextField()),
                ('published_date', models.DateTimeField()),
            ],
        ),
    ]

Apply the migration:

python manage.py migrate

This creates the Post table in the database.


2. Adding a New Field

Now, let’s add an author field to the Post model:

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published_date = models.DateTimeField()
    author = models.CharField(max_length=100)  # New field

Run makemigrations again:

python manage.py makemigrations

A new migration file will be generated, e.g., 0002_add_author_field.py:

# Generated by Django 5.x on YYYY-MM-DD HH:MM

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('blog', '0001_initial'),
    ]

    operations = [
        migrations.AddField(
            model_name='post',
            name='author',
            field=models.CharField(default='', max_length=100),
        ),
    ]

Apply the migration:

python manage.py migrate


3. Removing a Field

If we decide to remove the author field:

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published_date = models.DateTimeField()
    # author field removed

Run makemigrations:

python manage.py makemigrations

This generates a new migration file, e.g., 0003_remove_author_field.py:

# Generated by Django 5.x on YYYY-MM-DD HH:MM

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('blog', '0002_add_author_field'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='post',
            name='author',
        ),
    ]

Apply the migration:

python manage.py migrate


Advanced Topics

1. Customizing Migration Operations

You can define custom SQL or Python logic in migrations using RunSQL or RunPython.

For example, adding default data:

from django.db import migrations

def add_default_posts(apps, schema_editor):
    Post = apps.get_model('blog', 'Post')
    Post.objects.create(title='Hello World', content='Welcome to the blog!', published_date='2024-01-01')

class Migration(migrations.Migration):

    dependencies = [
        ('blog', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(add_default_posts),
    ]

2. Rolling Back Migrations

You can reverse migrations using:

python manage.py migrate app_name migration_name
Example:
python manage.py migrate blog 0001
This will reverse all migrations applied after 0001.

3. Squashing Migrations

As projects grow, the number of migrations can become unwieldy. Squashing combines multiple migrations into one:

python manage.py squashmigrations blog 0001 0003


Common Issues and Solutions

1. Migration Conflicts

When multiple developers create migrations independently, conflicts can occur. Resolve them by editing dependencies or merging operations manually.

2. Missing Migrations

If Django detects model changes not reflected in migrations, ensure to run makemigrations and apply them.

3. Data Loss

Be cautious with destructive operations like RemoveField. Test migrations in a staging environment before applying to production.


Best Practices

  • Use descriptive names for fields and migrations.
  • Test migrations locally or in staging before applying to production.
  • Avoid editing migration files directly unless necessary.
  • Keep migration files under version control.

References