Blog · Migrations
How to undo a Django migration
There is no migrate --undo. Name the migration to return to, and
migrate reverses every migration after it. The target is the state to arrive at, not
the migration to remove.
# migrations 0001, 0002, 0003 are applied; reverse 0003 only
manage.py migrate myapp 0002
# reverse every migration in the app
manage.py migrate myapp zero
# see the result first
manage.py migrate myapp 0002 --plan
manage.py showmigrations myapp
The output of showmigrations marks each applied migration with an [X].
After the command above, 0003 shows [ ] and the file stays on disk. To
remove the migration for good, delete the file after the reverse, not before it.
A reverse of AddField drops the column, and the data in that column is
gone. Django asks for no confirmation. Before a reverse on a database with real data,
take a backup. The reverse of RemoveField is dangerous in the other direction. It
restores an empty column, because the old values are not in the migration.
When the reverse is not possible
Django refuses an operation with no reverse and reports
IrreversibleError. A RunPython with no second function is the common
cause. Add the reverse function, or mark the step as a no-operation to permit the reverse.
from django.db import migrations
def forwards(apps, schema_editor):
...
migrations.RunPython(forwards, migrations.RunPython.noop)
noop states that the reverse needs no database work. It does not undo the effect of
forwards. For a data change, write the real reverse function.
--fake, and when it is the wrong tool
--fake writes the migration records and runs no SQL. It is correct when the schema
is already in the wanted state and the records disagree with it.
manage.py migrate myapp 0002 --fake
A common repair is migrate myapp zero --fake, followed by
migrate myapp --fake-initial. The tables stay, and the records restart. Use this after
a squash, or after a manual repair of the schema.
A --fake that hides an error moves the error to the next deploy.
The records then claim a state that the database does not hold. The next real migration fails on a
column that is absent, or on one that is already present. Before a --fake, compare the schema
with the models: run manage.py makemigrations --check --dry-run and read the
table definition in the database.