DjangosMany to Manyrelationships allow you to have a many-to-many relationship without anintermediate model this is great for when you dont need anything except foreign keys on the through table, and gives you an easy API to work with to get related objects. But what about when business needs change, and now youneed additional fields on the intermediate model and you need to make the change without any downtime or loss of data?
Lets say weve got the following models:
tags =models.ManyToManyField(Tag, db_table=course_tag, related_name=courses, blank=True)
There is a table in my database calledcourse_tag, but I dont have aCourseTagmodel, so I cant add additional fields to it. Creating this model doesnt actually require any changes to your database, but you need to let your Django app know that the model exists, so that future modifying migrations work as expected (and so that you can queryCourseTagobjects directly).
Django has a util calledinspectdbthat comes in handy here, since we want our first migration to change nothing about the database. I ran this and copied the intermediate model into mymodels.pywithout changing anything about it other than removingmanaged = Falsefromclass Meta that I want it to bemanagedis the whole point.
I also went ahead and changed the line that saidtags = models.ManyToManyField(Tag, db_table=course_tag, related_name=courses, blank=True)to readtags = models.ManyToManyField(Tag, through=CourseTag). This also doesnt change anything about your database, but does impact the APIs that are available to you within the ORM.
At this point, runpython manage.py makemigrations.
If you run this migration normally, youll get an error that says something likedjango.db.utils.ProgrammingError: relation course_tag already exists. You have a couple of options to deal with this:
option appended. This will not change anything about your database, but will let Django know that the migration has been run so that any future
operations will run successfully. The downside here, at least in my case, was that our automatic build failed, because the test suite runs migration files. Tests can of course be run with the
option, in which case this likely wont be an issue for you, but if it is, move on to option 2. (If you ever need to re-create your database from scratch, youll run into problems with this option also, unless you migrate in stages and fake this migration every time)
Alter your initial migration to include the information from the newly generated one. I went with this option so my build would pass. You have to be a little careful about order here to make sure things that rely on something else arent called prior to their existence. Your tests (assuming youre not running them with
, because youve chosen this option), will let you know if youve gone awry here. For me, I changed the following things about my initial migration:
using the correct through relationship
At this point, youre finished with the transition, and you can add fields to your intermediate model the way you would usually add new fields I ran another migration that added the new fields withnull=True, pushed the code to make sure all fields were being populated, and then had a final clean-up migration to removenull=Truefrom the fields that didnt need it. All the rows in your original database table will automatically be available in the newCourseTagmodel, since they use the same database table.
Keep in mind that there are some differences to the Django API depending on what time of many to many field youre using, so you may need to update some of your database queries for related objects using the through table correctly namely that you can no longer run a query on theTagtable directly referencingcourses. A query that used to read:
From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.