Monday, April 22, 2019

In Django you cannot add or remove through= on M2M fields

Using native migrations in Django has been a very positive experience. Now and then however, some thing appear difficult or impossible. In this post, I will demonstrate how migrations can be used to move from an automatic ManyToMany relation in Django to an explicit relation. Lets start by creating a small app with a ManyToMany relation. Suppose we want some way to order the list of Bar instances in the relation. In setting up the ManyToMany relation, Django has created a table for us, named through_foo_bars. We can see what it looks like in python using the inspectdb command
$ ./manage.py inspectdb through_foo_bars
We can make explicit the automatic relation that Django has created in the form of an extra table, by adding the model to our application. Note the ordering we specified in the Meta definition. We told Django to use our new model for the relation by specifying it as the through argument in the ManyToManyField. Django is happy to create a migration for us, incorporating the change.
$ ./manage.py makemigrations
The problem starts when we try to execute the migrations
$ ./manage.py migrate
...
ValueError: Cannot alter field through.Foo.bars into through.Foo.bars - they are not compatible types (you cannot alter to or from M2M fields, or add or remove through= on M2M fields)
So according to Django this kind of migration is not possible. Let's analyse what the problem is. The below migration tries to create a new model called ThroughFooBars and then change the ManyToManyField on Foo. The problem is ofcourse that no new model needs to be created because the table allready exists. In this case we DO want to change the migration state, altering the relation, but we do NOT want to change the database schema. We are lucky that django offers such migrations by providing us with migrations.SeparateDatabaseAndState. We will first modify the state to make the relation explicit, after which we can do the modifications to the schema to add the ordering. In the migration below, you can see we first modify the state, making the relation explicit in the state only, without modifying the database schema. Next we add the order field and the ordering Meta definition. We can check if the migration was succesful with inspectdb:
$ ./manage.py inspectdb through_foo_bars
Seems to look good to me?

2 comments:

  1. Both migrations file are equal

    ReplyDelete
    Replies
    1. I fixed it. github wont let you embed a different revision of a file than the latest :\

      Delete