Rerun a Django data migration

DjangoDjango Migrations

Django Problem Overview


How would I rerun a data migration on Django 1.8+? If relevant, my migration is numbered 0011_my_data_migration.py and is the latest migration.

Django Solutions


Solution 1 - Django

Fake back to the migration before the one you want to rerun.

./manage.py migrate --fake yourapp 0010_my_previous_data_migration

Then rerun the migration.

./manage.py migrate yourapp 0011_my_data_migration

Then you can fake back to the most recent migration that you have run. In your case, you said that 0011 was the latest, so you can skip this stage.

./manage.py migrate --fake yourapp 0014_my_latest_data_migration

Note that depending on the state of your database and the contents of the migrations, rerunning a migration like this might cause errors. Note the warning in the docs about the --fake option:

> This is intended for advanced users to manipulate the current migration state directly if they’re manually applying changes; be warned that using --fake runs the risk of putting the migration state table into a state where manual recovery will be needed to make migrations run correctly.

Solution 2 - Django

Alasdair's answer gives a disclaimer about this, but faking a migration back to the previous one is only safe if your migration is idempotent, which means you can run it multiple times without side effects like duplicate data. Most people don't write their migrations this way, but it's good practice.

You have two options to make this process safe:

  1. Make your data migrations idempotent. This means that any created data is either reused (like with the Model.objects.get_or_create() method) or deleted and recreated. Reused is the better option, as deleting and recreating will change database indexes and sequences.
  2. Make reverse data migrations. You can do this by passing 2 functions to migrations.RunPython(). For example, if you have migrations.RunPython(add_countries), you would change that to migrations.RunPython(add_countries, remove_countries) and delete any relevant countries in the second function.

If you choose option #2 then you would run:

./manage.py migrate yourapp 0010_my_previous_data_migration
./manage.py migrate yourapp 0011_my_data_migration

If you wanted to make that a one liner so that you can use it over and over:

./manage.py migrate yourapp 0010_my_previous_data_migration && ./manage.py migrate yourapp 0011_my_data_migration

Solution 3 - Django

Based on the accepted answer, here's a script for reapplying a given migration.

#! /bin/bash
# This script re-applies a given migration.
app_name="$1"
migration_index="$2"

prev_migration_index="$(echo "$migration_index" | sed 's/^0*//' | awk '{ print $1 - 1 }' | xargs printf "%04d")"
last_migration_index="$(django-admin showmigrations --plan | grep -oP "\.\K\d{4}" | sort | tail -n 1)"

# fake-migrate to the migration prior to the one we want to reapply
django-admin migrate --fake "$app_name" "$prev_migration_index"
# reapply the migration
django-admin migrate "$app_name" "$migration_index"
# fake-migrate to the last migration
django-admin migrate --fake "$app_name" "$last_migration_index"

Usage:

$ bash reapply_migration.sh <app_name> <migration_to_reapply_index>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondavidhwangView Question on Stackoverflow
Solution 1 - DjangoAlasdairView Answer on Stackoverflow
Solution 2 - DjangoJordanView Answer on Stackoverflow
Solution 3 - DjangoPaweł RubinView Answer on Stackoverflow