The total LOC delta to migrate Buttondown from Django 4 to Django 5 was +143, -150. (I was incentivized to do so because our search right now is quite slow, and the lowest hanging piece of fruit is to move some of the tsvector generation out of band, and I'd rather do that using the fancy new GeneratedField abstraction than rolling the SQL myself.)

The process was so boring as to almost not warrant a blog post:

  1. Had to remove our usage of DEFAULT_FILE_STORAGE and roll it into the STORAGES dict which we were already using;
  2. Had to bump up django-typescript-routes and django-safedelete to new minor versions (and for the latter create a new migration);
  3. Had to trivially rewrite a few tests that were failing because we were incorrectly passing in some non-existent keyword arguments.

The one wrinkle was some still-unattributable change in Whitenoise / Django behavior; we were using CompressedManifestStaticFilesStorage and some of our vended CSS had, I guess, incorrect manifests? It's still not immediately obvious to me what the root cause was, but it was an easy enough fix.


Django 5's been in production for us for three days. A grand total of zero post-deploy bugs have surfaced.

Lightning bolt
About the author

I'm Justin Duke — a software engineer, writer, and founder. I currently work as the CEO of Buttondown, the best way to start and grow your newsletter, and as a partner at Third South Capital.

Lightning bolt
Greatest hits

Lightning bolt
Elsewhere

Lightning bolt
Don't miss the next essay

Get a monthly roundup of everything I've written: no ads, no nonsense.