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 queried
ElectricCar
model but query is issued to theCar
model with the specifiedid
- 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.