We've created a large Django application, and we want to squash migrations. However, the squashed migrations have circular dependencies between the apps in our application. How can we break those circular dependencies without breaking Django's migration squashing?
I've created a small sample project to reproduce the problem. The project has two apps:
To manually resolve a, break out one of the ForeignKeys in the circular dependency loop into a separate migration, and move the dependency on the other app with it. If you’re unsure, see how makemigrations deals with the problem when asked to create brand new migrations from your models. In a future release of Django, squashmigrations will be updated to attempt to resolve these errors itself.
$ ./manage.py migrate
django.db.utils.ProgrammingError: column "bacon_id" of relation "fruit_cranberry" already exists
This seems like a lot of work, but it's the best solution I've found so far. I've posted the squashed migrations in the master branch. Before running
squashmigrations, we replace the foreign key
Bacon with an integer field. Override the field name so it
_id suffix of a foreign key. This will break the dependency without losing data.
# TODO: switch back to the foreign key. # bacon = models.ForeignKey('meat.Bacon', null=True) bacon = models.IntegerField(db_column='bacon_id', null=True)
makemigrations and rename the migration to show that it is starting
the squash process:
fruit/0100_unlink_appsconverts the foreign key to an integer field
squashmigrations fruit 0100 and rename the migration to make it easier
to follow the sequence:
fruit/0101_squashedcombines all the migrations from 1 to 100.
Comment out the dependency from
isn't really needed, and it creates a circular dependency. With more complicated
migration histories, the foreign keys to other apps might not get optimized out.
Search the file for all the app names listed in the dependencies to see if there
are any foreign keys left. If so, manually replace them with the integer fields.
Usually, this means replacing a
AlterModel(...IntegerField...) with a
The next commit contains all these changes for demonstration purposes. It wouldn't make sense to push it without the following commit, though, because the apps are still unlinked.
Switch back to the foreign key from
Bacon, and run
makemigrations one last time. Rename the migration to show that it is
finishing the squash process:
fruit/0102_relink_appsconverts the integer field back to a foreign key
Remove the dependency from
and add a dependency from
The original dependency just won't work. Take the dependencies that were
commented out in
fruit/0101_squashed and add them to
That will ensure the links get created in the right order.
Run the test suite to show that the squashed migration works properly. If you
can, test against something other than SQLite, because it doesn't catch some
foreign key problems. Back up the development or production database and run
migrate to see that the unlinking and relinking of the apps doesn't break
Take a nap.
The convert_squash branch shows what could happen in the future once all
installations have migrated past the squash point. Delete all the migrations
from 1 to 100, because they've been replaced by 101. Delete the
showmigrations to check for any broken
dependencies, and replace them with
If you are unlucky enough to have a many-to-many relationship between two apps, it gets really ugly. I had to use the
SeparateDatabaseAndState operation to disconnect the two apps without having to write a data migration. The trick is to replace the many-to-many relationship with a temporary child model using the same table and field names, then tell Django to just update its state without touching the database schema. To see an example, look at my unlink, squashed, and relink migrations.