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
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
[ ]. In the case above, we see that all migrations have been
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
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
$ ./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
$ ./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
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
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.
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.
If you liked this post and want to see more like this, please buy me a coffee!