RunPython is a Django migration operation for changing existing database rows with Python while a migration runs. Supply one function for the forward move and another for the backward move, so schema changes and data changes can travel together. section 10.2 section 10.6
It is especially useful when changing fields would otherwise strand old data. A safe pattern is: add replacement fields, run a custom data migration that copies or reshapes values, then remove the original fields. Tiny database moving van included. section 10.2 section 10.6
Create and use a custom data migration as follows:
- Add the new model fields and generate the normal schema migration. section 10.2
- Create an empty migration stub with makemigrations --empty, then place the data-copying functions in it. section 10.2 section 10.6
- Make the custom migration depend on the migration that added the fields, then include RunPython in its operations. section 10.2
- Fetch models through apps.get_model rather than importing the current model class, because the current class may be newer than the migration’s historical state. section 10.2
- Provide a reverse function when reversal is possible; otherwise leave it empty, knowing Django raises an exception if a backward migration is attempted. section 10.6
Copying promoter names
After adding full_name and common_name to Promoter, a forward function obtains the historical Promoter model, loops through its rows, builds full_name from first_name and last_name, sets common_name, and saves each row. A reverse function is supplied for the backward direction. section 10.2
Listing 10.7 A migration script that calls Python to transform data during migration
# RiffMates/promoters/migrations/0004_data_fullname.py
from django.db import migrations
def de_westernize_names(apps, schema_editor): #1
# Can't import Promoter Model, the current version may be newer than
# the one in this migration. Use historical version instead.
Promoter = apps.get_model("promoters", "Promoter") #2
for promoter in Promoter.objects.all(): #3
promoter.full_name = f"{promoter.first_name} {promoter.last_name}"
promoter.common_name = promoter.first_name
promoter.save()
def re_westernize_names(apps, schema_editor): #4
Promoter = apps.get_model("promoters", "Promoter")
for promoter in Promoter.objects.all():
promoter.first_name = promoter.common_name
length = len(promoter.first_name)
promoter.last_name = promoter.full_name[length + 1:]
promoter.save()
class Migration(migrations.Migration):
dependencies = [
("promoters", "0003_promoter_common_name_promoter_full_name"),
]
operations = [
migrations.RunPython( #5
de_westernize_names,
re_westernize_names
),
]
This migration listing shows the two functions, historical-model lookup, row iteration, and value updates that RunPython executes. section 10.2
