Reverting migrations in Django

4 minute read

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.

Putting Django in reverse gear

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!

buy me a coffee logo