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
- Create an empty migration
- Create a forwards operation
- Create a backwards operation
- Tie those operations to the migration
- 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