Migrating to a Parent Model with Multi-table Inheritance in Django

Adding a Parent model in #Django is tricky business.

I just added a “Notes” section to this website, and in so doing, did a fair amount of refactoring. Notes are different from Posts in that a Note doesn't have a title and is only short content. But otherwise they are very similar. Additionally, I wanted to show a single feed on the home page of both types.

At first I tried adding the Notes model and an abstract model for both Notes and Posts to inherit their common pieces from, but getting a combined feed for the home page was a bear. I did get it working, but the code was a mess.

My next shot was using multi-table inheritance. Have a proper model, “FeedItem”, with the shared properties and then I could use the built in View classes to do a feed of FeedItems, testing for and passing the child model for templating. The only problem here was Django’s makemigrations command really choked on adding a multi-table inheritance parent model. To make it work took a stepped migration process.

First I made a manual OneToOne relationship between FeedItem and Note/Post, a key point being that in multi-table inheritance, the parent model is called model_ptr and the database column is model_ptr_id. In this first iteration, the relationship was nullable and was not the primary key. And FeedItem was an empty class, so I didn't have to worry about field name collisions. I ran makemigrations and migrate and made sure everything was working.

Next I made an empty migration and used the migrations.RunPython to write functions that created FeedItem records for each Post and Note in the database, attaching them through the OneToOne relationship. I ran makemigrations and migrate and made sure everything was working.

Once every Note and Post had a FeedItem, I changed the relationship to be the primary key. This sets up the relationship to move from OneToOne to multi-table inheritance, because, behind the scenes, this is the multi-table inheritance relationship. I ran makemigrations and migrate and made sure everything was working.

At this point, removing the relationship and adding the FeedItem as a class being inherited shouldn't trigger any migrations. I ran makemigrations to make sure Django didn't see any database changes.

Next I prefixed all the fields that needed to go to the parent model with old_, ran makemigrations, migrate, and made sure everything was working.

I added the fields to the parent model and ran makemigrations. In the migration I added a step to follow the field creation to copy the old_ values to the new fields. I ran makemigrations and migrate and made sure everything was working.

Finally I removed the old fields from the child models and did a final makemigrations and migrate run.

In reality, it took several failed attempts to finally to do it in small enough steps for everything to work. It was a big ol‘ pain, but it did work.

I'm finding that a lot of my work on this new site is requiring tricky refactors in Django. Hopefully it's just my unfamiliarity with the platform and doing a poor job architecting from the beginning, but it does feel a little difficult to make application changes. I'm often starting new apps, deleting them, having to make database changes, undoing database changes, and so on just to figure out how to implement new features.