Skip to content

Django’s three types of model inheritance

Published on: December 12, 2023    Categories: Django, Python

This is part of a series of posts I’m doing as a sort of Python/Django Advent calendar, offering a small tip or piece of information each day from the first Sunday of Advent through Christmas Eve. See the first post for an introduction.

Inheritance and its discontents

People can, and do, debate whether inheritance in object-oriented programming languages is a thing that ought to exist. There are even debates about what “inheritance” ought to mean, because there are multiple things it could mean (see this post by Hillel Wayne for a good brief explanation of the different potential meanings of “inheritance”).

When Django was first released, over 18 years ago, it supported inheritance in its ORM’s model-class definitions, and the project from which Django had been extracted (a news-oriented content-management system) unfortunately made use of this (for why this was unfortunate, you’ll have to keep reading to the end). Then the Django ORM was completely rewritten and, for a brief time, the rewritten version didn’t support inheritance.

But there was consistent pressure to add the feature back, and it was added back. And Django still supports model class inheritance to this day. In fact, it supports three different flavors of inheritance, only one of which you shouldn’t use. So let’s take a quick look the them. For all the details, of course, refer to the Django documentation on model inheritance.

The good one

The best case for inheritance in Django models is abstract models, which can’t be instantiated or saved on their own, but rather define a useful set of base fields or methods (or both) to be inherited into and reused by other models. Here’s an example:

from django.db import models

class Audited(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

You can write other models that subclass Audited, and they’ll inherit those two fields (created_at will auto-populate with the current timestamp the first time an object is saved; updated_at will auto-populate with the current timestamp every time it’s saved).

This is a really really useful feature, and I recommend using it whenever you find yourself repeating a common set of fields or methods or attributes or any other code across a bunch of models: bundle it up into abstract classes and inherit as needed. Quite a few third-party Django applications are able to add new features to models through inheriting from abstract bases.

The rarely-need-it one

Another option is “proxy” inheritance. This is where you subclass another model, and in your child class’ Meta you declare proxy = True. This one is a bit weird, but the idea is you’re augmenting the parent class’ behavior. A proxy subclass uses the same database table as the parent class, and cannot add or change model fields, but it can add new methods and it can have its own Meta declaration and change some behaviors compared to the parent (for example, you can change the default query ordering).

This is somewhat similar to Ruby’s open classes, or extension methods in C#, except that the new behavior only exists on your subclass, and you have to query through your subclass to get instances with that behavior.

It’s also a feature that you need only rarely, but when you do it’s handy; one example of when you’d need it is when you’re using a model class from a third-party package, so you can’t actually reach in and change its behavior, but you can write a proxy subclass of it and decide how you want it to behave.

The please-don’t-use-it one

Finally, there’s “concrete” or “multi-table” inheritance. By default this is what you get when you subclass an existing model and don’t set proxy = True in the Meta declaration. Multi-table inheritance works like this:

This lets you do things like, say, a base Place model with subclasses Restaurant and Shop and Park and so on.

Which is what that content-management system did. And what I hope never to deal with again, and hope you never will either.

Multi-table inheritance has a lot of issues:

And on and on and on. It’s just a headache and so I strongly recommend against it. If you want a common set of fields reused in a bunch of models, do it with an abstract model class, rather than multi-table inheritance.