In Django, how do you choose between abstract base, multi-table and proxy model inheritance for a car-rental fleet's vehicle types?
answer
- what must exist in the database
- different columns or only behaviour
- does anything reference 'any vehicle'
- JOIN per query versus separate tables
basics
~20 sChoose by what the database must hold: an abstract base gives each vehicle type its own complete table, multi-table inheritance keeps a queryable Vehicle table joined one-to-one to each child, and a proxy changes behaviour on one existing table without new columns.
solid answer
~40 sTwo questions decide it: do the types need different columns, and must anything treat "any vehicle" as one set? If types differ in columns but nothing references a generic vehicle, an abstract `VehicleBase` gives `Car`, `Van` and `Motorbike` their own complete tables with no JOINs. If a `Booking` needs one `ForeignKey(Vehicle)` and the fleet is listed as a whole, multi-table inheritance gives a real `vehicle` table with each child linked by an implicit `vehicle_ptr` one-to-one key, at the cost of a JOIN on every child read and two rows per save. If the difference is behaviour only (ordering, methods, a default manager, a separate admin), a proxy (`Meta.proxy = True`) reuses the table. A common alternative to a deep multi-table tree is one concrete `Vehicle` with a `kind` field plus proxies.
code
python · 27 linesfrom django.db import models
class Audited(models.Model): # abstract: columns copied, no table
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class Vehicle(Audited): # concrete parent: bookings can point here
plate = models.CharField(max_length=12, unique=True)
daily_rate = models.DecimalField(max_digits=7, decimal_places=2)
class Van(Vehicle): # multi-table child: van table + vehicle_ptr_id
cargo_volume_m3 = models.DecimalField(max_digits=5, decimal_places=2)
class ReturnedVan(Van): # proxy: same van table, different behaviour
class Meta:
proxy = True
ordering = ["-created_at"]
class Booking(models.Model):
vehicle = models.ForeignKey(Vehicle, on_delete=models.PROTECT)go deeper
Know that there are three styles and the one-line storage shape of each: no base table, a parent table plus child tables, or the same table.
Answer with the two deciding questions, different columns and a generic reference, and name the JOIN that multi-table inheritance adds to every child read.
Bring the regret stories: bulk_create refusing multi-table children, no automatic downcast, and when a single concrete model with a kind field beats a multi-table tree.
Weigh schema purity against runtime cost and team clarity, and decide who approves a new concrete parent before a hierarchy grows levels that every query must join.
## Three styles, three storage shapes Django supports three ways for one model class to inherit from another. They look almost identical in Python — `class Car(Vehicle):` — and differ entirely in what they create in the database: | Style | How you declare it | Tables | Query the base? | Base as `ForeignKey` target? | Cost per child read | |---|---|---|---|---|---| | **Abstract base class** | `Meta.abstract = True` on the base | one per concrete child | no — the base has no manager | no | none: one table | | **Multi-table inheritance** | subclass a concrete model | parent table + one per child | yes — returns parent instances | yes | an `INNER JOIN` to the parent | | **Proxy model** | `Meta.proxy = True` on the child | none new: the parent's table | yes | yes | none: same table | Django has **no** built-in single-table mode with a discriminator column; if you want one table for several kinds you model it yourself with a concrete model and a type field. ## The two questions that decide 1. **Do the subtypes need different columns?** A van has `cargo_volume_m3`, a car has `seats`, a motorbike has `engine_cc`. If yes, a proxy is out: a proxy may not declare fields. 2. **Must anything treat all vehicles as one set?** A `Booking` with a single `ForeignKey` to "whatever was rented", a fleet list paginated across types, a unique `plate` across the whole fleet. If yes, you need a concrete table that holds every vehicle, which an abstract base does not create. ## Applying it to the fleet - **Types differ, nothing generic references them** → abstract base. `VehicleBase` holds `plate`, `daily_rate` and `branch`; `Car`, `Van` and `Motorbike` each get a self-contained table. Reads are single-table, but "all vehicles at branch 12" means three queries, and a booking needs three nullable foreign keys or a generic relation. - **Types differ and bookings must point at any vehicle** → multi-table inheritance. `Vehicle` is concrete; `Car(Vehicle)` gets a `car` table whose primary key `vehicle_ptr_id` is also a foreign key to `vehicle.id`. `Booking.vehicle = ForeignKey(Vehicle, …)` works for every type. - **Same columns, different behaviour** → proxy. `ElectricCar(Car)` with `proxy = True` can sort by range, add `needs_charge_before()`, and get its own admin screen, all on the `car` table. ## Costs that show up later Multi-table inheritance is the style most often regretted, because its costs are invisible in the class definition: - every query that loads `Van` objects joins `van` to `vehicle`; deep trees join once per level; - `Vehicle.objects.all()` returns plain `Vehicle` objects — reaching van data needs `vehicle.van` (another query) or `select_related("van")`; - each save writes one row per level, and `bulk_create()` refuses multi-table children; - adding a relation from a subclass back to the parent needs an explicit `related_name`, because the implicit link already claims the default name. Abstract bases cost nothing at runtime but push "all vehicles" logic into application code. Proxies cost nothing but cannot add data. ## Questions to ask before committing Before choosing, walk through the queries and writes the fleet actually runs: 1. **Which screen lists several types together?** If the branch dashboard pages through cars and vans in one table, an abstract base forces a merge in Python; multi-table inheritance or one concrete table serve it in one query. 2. **Which code reads one type deeply?** A van-loading tool that filters on `cargo_volume_m3` pays a JOIN per query under multi-table inheritance and none under an abstract base. 3. **How are vehicles imported?** Large bulk imports argue against multi-table inheritance, because `bulk_create()` refuses multi-table children. 4. **How often do types gain columns?** Each new type under an abstract base is a new table and new listing code; under one concrete table it is more nullable columns. When the answers conflict, the reference and listing needs usually win, because a missing concrete parent is the hardest thing to add later. ## A common hybrid A frequent production shape combines them: an abstract mixin for audit columns, **one concrete `Vehicle`** with a `kind` choice field and the type-specific columns made nullable (guarded by conditional check constraints), and a **proxy per kind** carrying behaviour and, through a custom default manager, the filter on `kind`. That keeps one table, one foreign key target and no JOINs, and trades it for nullable columns. Choose multi-table inheritance when the subtype columns are many, mostly non-null and genuinely different — and when the JOIN is acceptable on every subtype query.
- Bookings must reference any vehicle, but you want to avoid multi-table JOINs; what else works?Use one concrete `Vehicle` model with a `kind` choice field and nullable type-specific columns, guarded by check constraints conditioned on `kind`, plus a proxy per kind for behaviour. That is one table and one `ForeignKey` target with no JOIN; the trade is nullable columns and per-kind validation in code and constraints.
- Can one Django hierarchy mix the three styles?Yes. An abstract mixin can feed a concrete parent, a multi-table child can extend that parent, and a proxy can sit on the child. The rules still apply per class: a proxy needs exactly one concrete base and no fields, and an abstract base supplies columns only to concrete descendants.
saying these in an interview costs you the question
- An abstract base lets you query every vehicle type through one manager.
- A proxy model is the right choice when a subtype needs extra columns.
- Multi-table inheritance is free because Django caches the parent row.
- Querying Vehicle returns Car and Van objects automatically where rows match.
- Django has a built-in single-table inheritance mode with a discriminator column.