Django Model Inheritance: Why You Don’t Need It

dboost.me
2 min readJun 8, 2022

--

Class inheritance became a common concept for almost any object oriented language. Django framework relies on that and allows us to store data in a database via subclassing django.db.models.Model and specifying required columns and types. But what will happen when we decide to inherit from the overriden model?

class Car(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
manufacturer = models.TextField()
max_speed = models.IntegerField()


class ElectricCar(Car):
capacity = models.IntegerField()

We want to figure out:

  • how is it stored?
  • how it is queried?
  • what the implications are?

Migrations

When we generate migrations we are going to see next:

migrations.CreateModel(
name='Car',
fields=[
('id', models.UUIDField(
default=uuid.uuid4,
primary_key=True,
serialize=False
)),
('manufacturer', models.TextField()),
('max_speed', models.IntegerField()),
],
),
migrations.CreateModel(
name='ElectricCar',
fields=[
('car_ptr', models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to='hello_app.car'
)),
('capacity', models.IntegerField()),
],
bases=('hello_app.car',),
),

Subclassed ElectricCar model is going to have a capacity field and a reference to the Car model, which is also going to serve as a primary key. So both models are going to have the same primary key:

def test_both_models_have_the_same_id(self):
car_id = uuid.uuid4()

car_qs = Car.objects.filter(id=car_id)
ecar_qs = ElectricCar.objects.filter(id=car_id)

self.assertIsNone(car_qs.first())
self.assertIsNone(ecar_qs.first())

ElectricCar(
id=car_id,
manufacturer="Tesla",
max_speed=100,
capacity=10
).save()

self.assertIsNotNone(car_qs.first())
self.assertIsNotNone(ecar_qs.first())

Also note that nothing from the Car model is added to the new inherited model table.

SQL queries

Let’s try to decompose the simplest Django ORM query:

car_id = uuid.uuid4()
ElectricCar(
id=car_id,
manufacturer="Tesla",
max_speed=100,
capacity=10
).save()
es = ElectricCar.objects.filter(id=car_id)
print(es.query)

The SQL transformation looks as follows:

SELECT 
car.id,
car.manufacturer,
car.max_speed,
electriccar.car_ptr_id,
electriccar.capacity
FROM electriccar INNER JOIN car ON (
electriccar.car_ptr_id = car.id
) WHERE electriccar.car_ptr_id = 'fdae2d44b7364f7f871ff9b5a85c9dd0'

Couple of useful things here:

  • we queriedElectricCar model but query is issued to the Car model with the specified id
  • join was performed

Implications

Each child model will have a reference to its parent model, during querying phase Django ORM is going to perform joins to fetch all the related data. For each inherited model we are going to have a pointer and perform a join and in unrealistic scenario of inheritance from 10 models we are going to perform 10 joins for a simple get query.

UPD as noted in the comments: Django model inheritance is fine when you use abstract=True for shared models — tables won’t be generated for them, only for models inheriting from them.

--

--

Responses (1)