liveBook · Manning

RunPython

As explained in Django in Action

RunPython is a Django migration operation that executes supplied Python functions when migrating forward or backward.

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

How Django in Action covers it

How deep it goes
Explains a complete custom migration flow, including dependencies, historical models, forward functions, and reversible or irreversible operations.
Examples
Uses Promoter records to replace first_name and last_name with full_name and common_name.
What it assumes
Assumes familiarity with ordinary field migrations while introducing the custom data-translation step.
What you can do after
Apply it via Django migrations to transform existing rows during forward and, when supported, backward schema changes.
How it connects
Connects Django migrations with model fields, makemigrations, migrate, historical models, and database side effects.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Django in Action. The section links point to the parts of the book it draws on.