Custom Django Python Migration

Sometimes I find myself wanting to add some model objects along with a feature. You can make a custom migration to handle that!

Overview of what we will do

  1. Create an empty migration
  2. Create a forwards operation
  3. Create a backwards operation
  4. Tie those operations to the migration
  5. Apply, un-apply, and re-apply the migration

Whole file contents

For a tl;dr, make an empty migration and populate it like this:

# Generated by Django 5.1.1 on 2024-10-03 12:59

from django.db import migrations


def forwards(apps, schema_editor):
    MyModel = apps.get_model('my_app', 'MyModel')
    MyModel.objects.create(
        field_name1="name", field_name2="describe"
    )


def backwards(apps, schema_editor):
    MyModel = apps.get_model('my_app', 'MyModel')
    MyModel.objects.get(
        field_name1="name", field_name2="describe"
    ).delete()


class Migration(migrations.Migration):

    dependencies = [
        ("app_name", "0001_initial"),  # could be more here
    ]

    operations = [
        migrations.RunPython(forwards, backwards),
    ]

Create an empty migration

python manage.py makemigrations <app_name> --empty

I tend to add a descriptive name as well:

python manage.py makemigrations <app_name> \
    --name <description> \
    --empty

The empty file should look something like this:

# Generated by Django 5.1.1 on 2024-10-03 12:59

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ("app_name", "0001_initial"),  # could be more here
    ]

    operations = []

Create the change you want to see

Next, I'm going to add a top-level function that runs the code I seek to run:

def forwards(apps, schema_editor):
    MyModel = apps.get_model('my_app', 'MyModel')
    MyModel.objects.create(
        field_name1="name", field_name2="describe"
    )

Note the part where we set the model with apps.get_model. This ensures the migration uses the correct version of the model at the time the migration is applied even if the model changes later.

Undo the change you made for migrating back in time

Then, we'll write the equivalent of an "undo" operation in another top-level function:

def backwards(apps, schema_editor):
    MyModel = apps.get_model('my_app', 'MyModel')
    MyModel.objects.get(
        field_name1="name", field_name2="describe"
    ).delete()

Tie these operations to the migration

Now we need to tell Django that we're looking to run Python code for this migration:

class Migration(migrations.Migration):

    dependencies = [...]

    operations = [
        migrations.RunPython(forwards, backwards),
    ]

Apply, un-apply, and re-apply the migration

Assuming, like our example, that the migration we've been working on is 0002:

# Apply the migration
python manage.py migrate <app_name> 0002

# Check for the expected change

# Un-apply the migration
python manage.py migrate <app_name> 0001

# Check for reversal of change

# Re-apply the migration
python manage.py migrate <app_name 0002

Get Notified of New Posts

Sign up for the newsletter and I'll send you an email when there's a new post.