• 12 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 7/20/22

Overcome Common Migration Pitfalls

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!

Example of certificate of achievement
Example of certificate of achievement