Sometimes we can make a mistake when creating a migration. Don’t worry! This is no cause to panic. There are two approaches we can take when a migration needs to be removed:
Roll it back.
Perform a new migration.
Let's look at each approach.
Rollback an Unwanted Migration
First, let’s create our unwanted migration. We want to add a like_new
field to the Listing
model, but “Oops!” we added it to the Band
model instead and made and ran our migrations.
In models.py, add:
class Band(models.Model):
…
like_new = models.fields.BooleanField(default=False)
Then run in the terminal:
(env) ~/projects/django-web-app/merchex
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0006_band_like_new.py
- Add field like_new to band
(env) ~/projects/django-web-app/merchex (master)
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listings, sessions
Running migrations:
Applying listings.0006_band_like_new... OK
We now have an unwanted migration. In this scenario, the mistake has only been made on our own machine. It has not been shared with any other developers, e.g., uploaded to GitHub and pulled by anyone, or run elsewhere, such as on our production database.
In this case, we can just roll back the migration locally and delete it.
To do this, first list all of the migrations using python manage.py showmigrations
.
(env) ~/projects/django-web-app/merchex
→ python manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
[X] 0003_logentry_add_action_flag_choices
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
[X] 0010_alter_group_name_max_length
[X] 0011_update_proxy_permissions
[X] 0012_alter_user_first_name_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
listings
[X] 0001_initial
[X] 0002_listing
[X] 0003_auto_20210329_2350
[X] 0004_auto_20210524_2030
[X] 0005_listing_band ← This is the previous migration
[X] 0006_band_like_new ← This is our unwanted migration
sessions
[X] 0001_initial
Next, identify the migration that needs to be rolled back, in our case, 0006_band_like_new
. Then, grab the name of the previous migration to this one, 0005_listing_band
. You will also need to note the app name, which here is listings
.
Now to roll back the migration, use the python manage.py migrate
command while specifying the app name and the previous migration. This will roll back all of the subsequent migrations to the one we targeted.
(env) ~/projects/django-web-app/merchex
→ python manage.py migrate listings 0005_listing_band
Operations to perform:
Target specific migration: 0005_listing_band, from listings
Running migrations:
Rendering model states... DONE
Unapplying listings.0006_band_like_new... OK
We can see that the listings.0006_band_like_new
migration has been unapplied. If we show the migrations again, we can see that the migration no longer has an X
next to it, indicating that it has not been run.
→ python manage.py showmigrations listings # list only the migrations for the listings app
listings
[X] 0001_initial
[X] 0002_listing
[X] 0003_auto_20210329_2350
[X] 0004_auto_20210524_2030
[X] 0005_listing_band
[ ] 0006_band_like_new
Now that the migration has been rolled back locally, we are free to delete it. But we’re going to use this migration to demonstrate another strategy for reverting changes, so let’s leave it for now.
(env) ~/projects/django-web-app/merchex
→ rm listings/migrations/0006_band_like_new.py # we can do this but let’s not for now!
Revert Changes With a New Migration
The approach laid out in the previous section is only valid if the unwanted migration has not been shared elsewhere. If it has, you should not do this!
If the unwanted changes have been applied on another machine, you will have to create a new migration to revert the changes.
To do that, just change your models.py file until it is in the desired format.
class Band(models.Model):
…
# like_new = models.fields.BooleanField(default=False) <-- REMOVE THIS LINE
Then make and run your migrations.
(env) ~/projects/django-web-app/merchex
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0007_remove_band_like_new.py
- Remove field like_new from band
(env) ~/projects/django-web-app/merchex
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listings, sessions
Running migrations:
Applying listings.0007_remove_band_like_new... OK
The database is now returned to the state it was before. So if anyone has run the unwanted migration on their machine, it will revert once they run this new one!
If you want to go over any of the previous strategies for reverting migrations, follow the steps in the screencast.
Now You Try! Create and Rollback a Migration
It’s your turn! Add an unwanted field to the Listing
model, then make and run the migration.
Now you need to revert the changes. It’s up to you which strategy to use, but ensure that once you are done, the database is in the same state as when you started.
Merge Migrations to Overcome Conflicts
The final migration pitfall we are going to tackle is migration conflicts. This scenario is uncommon if you are working alone on a project on a single Git branch but will crop up frequently when working on projects with other developers or using different branches to add features to your project.
In this scenario, we are adding two fields - hometown
and record_compan
y - to Band
.
Let’s say two different developers have been working on these features. The first has added the hometown field on a feature branch FEATURE-add-hometown-to-band
. They then make the changes to the models.py
file and create the relevant migrations.
(env) ~/projects/django-web-app/merchex (master)
→ git checkout -b FEATURE-add-hometown-to-band
Switched to a new branch 'FEATURE-add-hometown-to-band'
(env) ~/projects/django-web-app/merchex (FEATURE-add-hometown-to-band)
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0008_band_hometown.py
- Add field hometown to band
(env) ~/projects/django-web-app/merchex (FEATURE-add-hometown-to-band)
→ git add listings/
(env) ~/projects/django-web-app/merchex (FEATURE-add-hometown-to-band)
→ git commit -m 'added hometown field to band'
[FEATURE-add-hometown-to-band c577b20] added hometown field to band
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_hometown.py
At the same time, another developer has added the field record_company
to Band
on their own feature branch.
(env) ~/projects/django-web-app/merchex (master)
→ git checkout -b FEATURE-add-record-company-to-band
Switched to a new branch 'FEATURE-add-record-company-to-band'
(env) ~/projects/django-web-app/merchex (FEATURE-add-record-company-to-band)
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0006_band_record_company.py
- Add field record_company to band
(env) ~/projects/django-web-app/merchex (FEATURE-add-record-company-to-band)
→ git add listings/
(env) ~/projects/django-web-app/merchex (FEATURE-add-record-company-to-band)
→ git commit -m 'added record_company field to Band'
[FEATURE-add-record-company-to-band 406ccdf] added record_company field to Band
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_record_company.py
Next, both of these feature branches get merged into the master
branch.
→ git merge FEATURE-add-record-company-to-band
Updating eef83c9..406ccdf
Fast-forward
merchex/listings/migrations/0006_band_record_company.py | 18 ++++++++++++++++++
merchex/listings/models.py | 1 +
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_record_company.py
→ git merge FEATURE-add-hometown-to-band
Updating eef83c9..c577b20
Fast-forward
merchex/listings/migrations/0006_band_hometown.py | 18 ++++++++++++++++++
merchex/listings/models.py | 1 +
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_hometown.py
But now, if we inspect our migrations for the listings
app, we can see that we now have two migrations prefixed with 0006
.
(env) ~/projects/django-web-app/merchex (master)
→ pm showmigrations listings
listings
[X] 0001_initial
[X] 0002_listing
[X] 0003_auto_20210329_2350
[X] 0004_auto_20210524_2030
[X] 0005_listing_band
[ ] 0006_band_hometown
[ ] 0006_band_record_company
And what’s more, if we try to migrate them, we get an error message.
(env) ~/projects/django-web-app/merchex (master)
→ python manage.py migrate
CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph: (0006_band_record_company, 0006_band_hometown in listings).
To fix them run 'python manage.py makemigrations --merge'
As both of these migrations were created without knowledge of the other one, Django has no idea which migration to run first.
Luckily, we can merge these migrations to get them to run smoothly using the --merge
flag with makemigrations
.
→ python manage.py makemigrations --merge
Merging listings
Branch 0006_band_hometown
- Add field hometown to band
Branch 0006_band_record_company
- Add field record_company to band
Merging will only work if the operations printed above do not conflict with each other (working on different fields or models)
Do you want to merge these migration branches? [y/N] y
Created new merge migration ~/projects/django-web-app/merchex/listings/migrations/0007_merge_20210701_1911.py
Now all we need to do is migrate!
(env) ~/projects/django-web-app/merchex (master)
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listings, sessions
Running migrations:
Applying listings.0006_band_record_company... OK
Applying listings.0006_band_hometown... OK
Applying listings.0007_merge_20210701_1911... OK
Let’s Recap!
There are two major strategies for reverting changes from migrations:
1: Roll back the migration locally and delete it, and
2: Delete the migration with a new migration
If the unwanted changes haven’t been shared with other users of machines, you can roll back the migration locally and then delete it.
If the changes have been shared, it is better to create a new migration, which reverts the changes of the unwanted one.
Sometimes, when working on a project with other developers, you will come across conflicting migrations. If these are affecting different fields or models, you can merge them together; otherwise, delete them and create new migrations instead.
In Part 3, we’ve filled out our models with more fields, including linking our models together in a one-to-many relationship. We’ve also looked at some migration strategies that will help when altering fields on models. We’ve been able to set values for those fields in new objects we create in the Django Admin, as well as existing ones. Now we need to build a CRUD interface in our frontend - one that our users can use - and that’s what we’ll do in Part 4. But first, a Quiz!