102

In Django's migrations code, there's a squashmigrations command which: "Squashes the migrations for app_label up to and including migration_name down into fewer migrations, if possible."

So, if you want to squash, say, the first 5 migrations, this will help.

What's the best way to squash starting with a particular migration_name?

In a project I'm currently working on, we've added 5-10 new migration files as we've added new features. We'll deploy the whole project at once and it looks like running these individually will take too long. I'd like to squash all the migrations for this project into a single migration and test the time to run that.

Doug Harris
  • 3,169
  • 5
  • 29
  • 31
  • 1
    Update on this - after squashing and testing, it took far too long. A big part of this was because for each column I was adding, MySQL would copy the whole table, add the column, and then rename the table. I used `sqlmigrate` to look at the SQL that would run and combined four separate ALTER TABLE statements into one with four ADD COLUMN sections and ran this using `migrations.RunSQL` with its `state_operations` argument to keep the migrations state logic happy. – Doug Harris Jun 30 '17 at 13:44

4 Answers4

181
python manage.py squashmigrations <appname> <squashfrom> <squashto>

python manage.py help squashmigrations

https://docs.djangoproject.com/en/dev/topics/migrations/#migration-squashing

This will give you more granular control over which migrations to squash, and let you keep a cleaner commit history. Deleting + recreating all migrations may cause other issues such as circular dependencies depending on how models are constructed.

A---
  • 2,373
  • 2
  • 19
  • 25
  • 3
    Good answer -- _if_ running Django 1.9 or newer. Sadly, this project is on 1.8 – Doug Harris Oct 14 '16 at 13:45
  • 3
    This gets the accepted answer because it uses functionality native to modern versions of Django. Those still on versions of Django prior to 1.9 should see Dan's answer. – Doug Harris Oct 14 '16 at 14:41
  • 31
    To save yourself having to read all the long migration file names. You can use the first 4 digits e.g ```manage.py squashmigrations myapp 0011 0015``` to squash migrations 0011_auto_someintshere to 0015_auto_someintshere – unlockme Aug 21 '18 at 16:03
31

You can just delete the migration files and run makemigrations again. If you have a dev deployment that uses these, you should migrate back to the one before the first one you delete.

Also, it's probably a good idea to commit your code first, in case something goes wrong.

Also:

The slight complication with this is that if there's custom RunPython code, it won't be included in the new migration created by makemigrations

Community
  • 1
  • 1
Dan
  • 12,409
  • 3
  • 50
  • 87
  • So sensible and obvious. I'll do it in a branch as well :-) – Doug Harris Oct 13 '16 at 19:17
  • 15
    The slight complication with this is that if there's custom `RunPython` code, it won't be included in the new migration created by `makemigrations` – Doug Harris Oct 13 '16 at 20:20
  • 1
    If you delete selected migrations (say all migrations after `__init__`), then run `makemigrations` and see a message like `You have 1 unapplied migration(s)`, you can run `./manage.py migrate --fake` to 'fake' the migration (though other team members will get the full migration). – inostia Aug 15 '17 at 21:44
  • I've always wondered about this. I constantly struggle with having so many migrations, yet [Django recommends it explicitly](https://docs.djangoproject.com/en/3.1/topics/migrations/#squashing-migrations). I suppose it depends on the scenario, eg, `RunPython`, your dev teammates, etc. Thanks for pointing out the obvious amidst the muddled. – nicorellius Dec 20 '20 at 20:17
  • This should be not a good idea and not the best practice. It ONLY work if the project is working by only one developer, not by a team and there is NO any other pending changes. And it could be used in the last resort if you have tried all other options and those do not work. So, there are at least two cases, this will be failed, beside a case of some migrations has RunPython code: 1) In team working, there are usually many new features(so new migration files are there) which are pending on staging/testing server 2) The Meta class in a Model has the attribute managed = False – Dat TT Mar 30 '23 at 13:35
6

Squash migrations command was introduced in Django 1.9

If you are using Django 1.8 you need to

pymen
  • 5,737
  • 44
  • 35
4

I created django-squash https://pypi.org/project/django-squash/ as a way to not have to deal with migrations on a per-app level or worse a per-app-specific-migration level, and handle it on a per-project level. The idea is to hopefully integrate it inside core Django at some point.

Basic idea:

  • You have a product, nothing open source that other people enhance, but yours, your teams, you deal with it.
  • After each release you want to squash all migrations you did in the past release and start a new because your product has evolved from what it was last release and your data model as well.
  • You squash, it looks to see if you've squashed previously, if you have, it will deleted any VERY old migrations that have no business in your codebase anymore. Finally, create a new snapshot of your migrations, and keep what migrations you had around.
  • You will do this every release/when ever you feel your tests are taking too long running all your migrations.

Example:

/app1/migrations/__init__.py
/app1/migrations/0001_initial.py
/app1/migrations/0002_created_user_model.py
/app1/migrations/0003_added_username.py
/app1/migrations/0004_added_password.py
/app1/migrations/0005_last_name.py

You've applied them all.

But every time you run your tests, every single one of those steps need to run, taking valuable time. So we squash. The new directory will look like this:

/app1/migrations/__init__.py
/app1/migrations/0001_initial.py
/app1/migrations/0002_created_user_model.py
/app1/migrations/0003_added_username.py
/app1/migrations/0004_added_password.py
/app1/migrations/0005_last_name.py
/app1/migrations/0006_squash.py

inside 0006_squash.py you will find a replaces = [..] with the names of migrations 1-5. You will also find a Migration.operations = [..] with everything you would expect if you deleted all your migrations and did a ./manage.py makemigrations + any RunSQL/RunPython with elidable=False. If you deploy to an environment that is missing any of migrations 1-5 it will apply it from source and not use 0006 AT ALL. (this is standard Django migrations)

Some time passes, now your migrations look like this:

/app1/migrations/__init__.py
/app1/migrations/0001_initial.py
/app1/migrations/0002_created_user_model.py
/app1/migrations/0003_added_username.py
/app1/migrations/0004_added_password.py
/app1/migrations/0005_last_name.py
/app1/migrations/0006_squash.py
/app1/migrations/0007_change_username_to_100_char.py
/app1/migrations/0008_added_dob.py

You squash again. This time the following will happen. Anything inside the replaces = [..] will be deleted. 0006_squash.py will be modified to have replaces be an empty list. Lastly the squash will be recreated with the new changes. All told, will look like this:

/app1/migrations/0006_squash.py
/app1/migrations/0007_change_username_to_100_char.py
/app1/migrations/0008_added_dob.py
/app1/migrations/0009_squash.py

Starting the cycle once again.

Javier Buzzi
  • 6,296
  • 36
  • 50