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.