Jacob Kaplan-Moss

What is django.contrib?

I wrote this post in 2009, more than 15 years ago. It may be very out of date, partially or totally incorrect. I may even no longer agree with this, or might approach things differently if I wrote this post today. I rarely edit posts after writing them, but if I have there'll be a note at the bottom about what I changed and why. If something in this post is actively harmful or dangerous please get in touch and I'll fix it.

Since it comes up a lot, I thought I’d spend a bit of time writing up my thoughts on what django.contrib really is, and what including a package in it really means.

The following is just my personal opinion – really; that’s why this is posted here instead of over in the official Django documentation. However, most of the core team discussed this topic at length at DjangoCon, so I’m fairly sure there’s consensus over the rough outline.

In a nutshell, django.contrib contains optional, de facto standard implementations of common patterns. That is:

  • Optional: contrib packages should be removable. You should be able to rm -rf django/contrib and Django shouldn’t break.

    Astute readers will note that django.contrib.contenttypes violates this rule. This should be considered a bug, I think.

    Along these lines, django.contrib packages ideally shouldn’t get “special access” to brittle internals – anything a contrib package does ought to be possible in an external app. Again, astute readers will notice that we’ve broken these rules in a few places, and, again, that should be considered a bug.

  • De facto standard: anything in contrib needs to be generally accepted as the Right Way to do something for a large majority of users.

    django.contrib.sessions is one example of this. There are may ways of doing sessions, but the way Django does it – an opaque session key in the cookie, with all the data stored on the backend – is generally accepted as a best practice. Yes, there are other ways of implementing sessions – signed cookies comes to mind – but the approach used by django.contrib.sessions generally works for most users.

  • Common patterns: contrib packages should solve problems encountered frequently by real-world web developers. So I’d argue against including something like django-reversion. It’s incredibly cool, and I use it myself, but tracking of versioned data at such granularity is not needed by a large majority of web sites.

Good contrib packages tackle issues that that are not trivial, are bikeshed prone, and are difficult to get right for the common case. We want to prevent folks from needing to decide among seventeen different session frameworks, for example.

Hence django.contrib.gis: it’s not exactly a common pattern exactly, but if you’re dealing with geographic data, building on existing GIS tools like PostGIS and GDAL is both the common pattern and the best practice. So you could say that django.contrib.gis implements a common pattern for a smaller class of users, I suppose. More importantly, though, it’s a tricky thing to get right, so there’s a great value in having it Just Work.

Of course, there’s a marketing value of having something in contrib. It certainly helps PR to saying that “Django ships with GIS support.” It’s fashionable to look down on PR in the open source world, but it’d be disingenuous to claim that I don’t use the power of contrib as a marketing tool. I certainly do, and I have no qualms about doing so.

However, there’s a danger in bringing something into the core: it stifles future innovation. As soon as we “bless” a contrib package, we drastically reduce impetus to write competing libraries. So, a good contrib package should have general consensus, and should be fairly mature. This is why a schema evolution library won’t be in Django 1.1 despite at least three or four viable alternatives. None of these options are clearly better, and the entire space is evolving fairly quickly. We should wait to “bless” one library until we’re sure we’ve got the right one.

Another downside to contrib inclusion is the coupling of the package’s features to Django’s release cycle. A small package like django-tagging can add features quickly; limiting releases to every six-nine months would retard its spread as users waited for new versions of Django to be able to upgrade.

Including a package in contrib is also a promise of future support. Nothing would be worse than having to remove a contrib package in a future release – we want to add features, not remove ’em! However, adding new contrib packages means more work for committers, so a new contrib package really needs to come along with a new contributor. A group of them, ideally – a contrib package with a bus factor of one isn’t a particularly good idea.