Reverting migrations in Django
When developing Django applications it’s sometimes necessary to revert (i.e. undo) a migration, especially if something didn’t quite work as planned. If something does go awry with a migration, it’s handy to know how to back out changes and start from an earlier known good state.

The inspiration for the explanation in this post comes from the StackOverflow question “How to revert the last migration?”. Since I need to do this every so often at work, it’s handy to have written up the explanation myself even if it’s only for my own future reference. You never know: it might also be helpful for someone else!
Finding out where we are
Knowing what our current state is helps us to work out how to proceed. In
this case, the Django command we’re looking for is showmigrations
; this
command shows us the migrations for the app we’re developing and the status
of each of the migrations: i.e. if they’ve been applied to the database or
not. For instance, running the showmigrations
command on one of our
Django apps at work gives the following output:
$ ./manage.py showmigrations icedata --settings=config.settings.dev
icedata
[X] 0001_initial
[X] 0002_add_file_size_attr
[X] 0003_add_user_table
[X] 0004_add_data_product_download_table
[X] 0005_rename_name_to_full_name
[X] 0006_add_email_unique_error_message
[X] 0007_add_email_validation_key
[X] 0008_add_registration_datetime
[X] 0009_allow_access_token
[X] 0010_allow_email_verification_key
[X] 0011_add_is_subscribed_to_newsletter
[X] 0012_add_accepts_terms_and_conditions
[X] 0013_add_client_version_table
[X] 0014_add_service_worker_version_table
[X] 0015_add_user_agent_table
[X] 0016_add_client_connection_attr_table
[X] 0017_use_textfield_for_useragent_model
[X] 0018_add_map_region
[X] 0019_add_registered_data_product
A cross within the square brackets [X]
means that the migration has been
applied. If the migration hasn’t been applied, the square brackets will be
empty: [ ]
. In the case above, we see that all migrations have been
applied.
The showmigrations
command is very much like many other Django management
commands: it is run via manage.py
and it takes an app label as an argument
as well as several optional keyword arguments.
In the example shown here, the app is called icedata
and, because we’re in
the development environment, we specify the development settings explicitly
with the value config.settings.dev
. For those who are interested: this
project uses the layout pattern recommended in Two scoops of
Django, hence the
settings are specified under the config
directory/namespace.
Rolling back migrations
Rolling back a single migration (or a set of migrations) is as simple as specifying the name of the migration before the migration(s) one wants to roll back. Another way to put this is that we specify the last applied migration we want to have. To reduce typing a bit, it’s even possible to just use the migration’s number to specify which migration we want to use as the last applied migration. Let’s see this in action.
Reverting a single migration
To roll back the last migration shown in the example above, we specify
0018_add_map_region
in a migrate
command:
$ ./manage.py migrate icedata 0018_add_map_region --settings=config.settings.dev
Operations to perform:
Target specific migration: 0018_add_map_region, from icedata
Running migrations:
Rendering model states... DONE
Unapplying icedata.0019_add_registered_data_product... OK
If we now run the showmigrations
command again, we see that the migration
0019_add_registered_data_product
is still known to Django, however is no
longer applied to the database (the Django
documentation refers to this process as
unapplying migrations):
$ ./manage.py showmigrations icedata --settings=config.settings.dev
icedata
[X] 0001_initial
[X] 0002_add_file_size_attr
[X] 0003_add_user_table
[X] 0004_add_data_product_download_table
[X] 0005_rename_name_to_full_name
[X] 0006_add_email_unique_error_message
[X] 0007_add_email_validation_key
[X] 0008_add_registration_datetime
[X] 0009_allow_access_token
[X] 0010_allow_email_verification_key
[X] 0011_add_is_subscribed_to_newsletter
[X] 0012_add_accepts_terms_and_conditions
[X] 0013_add_client_version_table
[X] 0014_add_service_worker_version_table
[X] 0015_add_user_agent_table
[X] 0016_add_client_connection_attr_table
[X] 0017_use_textfield_for_useragent_model
[X] 0018_add_map_region
[ ] 0019_add_registered_data_product
Reverting multiple migrations
If we had wanted to roll back migrations 16–19 (inclusive), we would have run this command:
$ ./manage.py migrate icedata 0015_add_user_agent_table --settings=config.settings.dev
which could equivalently have been written as:
$ ./manage.py migrate icedata 0015 --settings=config.settings.dev
Reverting all migrations
Should one wish to revert all migrations in an app, use:
$ ./manage.py migrate icedata zero --settings=config.settings.dev
With the name zero
having a special meaning to Django in this context and
meaning the migration “before” the first migration; this therefore removes
all migrations.
Checking the migration plan
It’s even possible to show a list of what actions would take place without
actually running them by passing the --plan
keyword argument to migrate
.
This is similar to the dryrun
option of many other command line utilities.
In other words, if we wanted to check what is going to happen before rolling back migrations 16–19 above, we would run this command first:
$ ./manage.py migrate --plan icedata 0015_add_user_agent_table --settings=config.settings.dev
and get output similar to this:
Planned operations:
icedata.0019_add_registered_data_product
Undo Create model RegisteredDataProduct
icedata.0018_add_map_region
Undo Create model MapRegion
icedata.0017_use_textfield_for_useragent_model
Undo Alter field user_agent on useragent
icedata.0016_add_client_connection_attr_table
Undo Create model ClientConnectionAttribute
Undo Create constraint unique_client_connection_attr_set on model clientconnectionattribute
Moving forward again after reverting a migration
Now that our unwanted migrations have been reverted, we can continue on our
way. This might be to fix the issues found in the reverted migration and
then running the corrected migration to continue with the current direction
of development, or one might even decide to remove the migration completely.
Note that removing the migration file will cause the unchecked migration to
be removed from the showmigrations
list, because Django now has no way of
knowing that the migration ever existed at all.
Conclusion
And that’s it! To revert a migration, just migrate to the migration before the one you want to revert and Bob’s your auntie’s live-in lover.
Support
If you liked this post and want to see more like this, please buy me a coffee!
