In a Django model, what does setting abstract = True in the inner Meta class do, and when would you use it?
answer
- shared columns written once
- reuse at the Python level only
- fields copied into each child table
- no manager, no ForeignKey target
basics
~20 sIt makes the model an abstract base class: Django creates no table and no manager for it, and every concrete subclass inherits its fields, managers and (unless overridden) its Meta in a complete table of its own.
solid answer
~40 sAn abstract base class shares fields and behaviour at the Python level only. With `abstract = True` in `Meta`, Django creates no table for the class, gives it no manager and refuses to instantiate it (`TypeError: Abstract models cannot be instantiated.`). Every concrete subclass receives its own copy of the fields, so each child table carries those columns itself and no JOIN is involved. It suits cross-cutting columns such as `created_at`/`updated_at` or a `branch` foreign key that `Car` and `Van` both need. The price: you cannot query all children through the base or point a `ForeignKey` at it, because nothing exists in the database to query or reference.
code
python · 25 linesfrom django.db import models
class FleetRecord(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
branch = models.ForeignKey(
"fleet.Branch",
on_delete=models.PROTECT,
related_name="%(app_label)s_%(class)s_set",
)
class Meta:
abstract = True
ordering = ["-created_at"]
class Car(FleetRecord):
plate = models.CharField(max_length=12, unique=True)
seats = models.PositiveSmallIntegerField()
class Van(FleetRecord):
plate = models.CharField(max_length=12, unique=True)
cargo_volume_m3 = models.DecimalField(max_digits=5, decimal_places=2)go deeper
Recall the three effects: no table, no manager, fields copied into each child's own table. Have one real use ready, such as created_at and updated_at on every model.
Explain the Meta hand-down, the automatic abstract=False reset, and why a fixed related_name clashes while the %(class)s placeholder does not.
Show when an abstract base is the wrong tool: once anything needs a foreign key to 'any vehicle' or one query over all types, a concrete parent is required.
Frame abstract bases as a code-reuse tool with zero runtime cost, and set a team rule for which cross-cutting columns belong in shared bases versus explicit fields.
## What `abstract = True` changes A Django **model** is normally a Python class that maps to exactly one database table. Setting `abstract = True` inside the model's inner `Meta` class turns it into an **abstract base class**: a template for other models rather than a model in its own right. Django then: - creates **no table** for it — `makemigrations` emits nothing for the base itself; - gives it **no manager**, so `FleetRecord.objects` raises `AttributeError: Manager isn't available; FleetRecord is abstract`; - refuses to **instantiate** it: `FleetRecord()` raises `TypeError: Abstract models cannot be instantiated.`; - copies its **fields** into every concrete subclass, so each child's table carries those columns directly. The result is reuse at the Python level with one self-contained table per concrete child at the database level. Reading a `Car` never joins anything, because the shared columns live in the car table itself. ## What each child receives A concrete subclass of an abstract base inherits: 1. **Fields** — as real columns in its own table. A child may override an inherited field by redeclaring the name, or remove it by setting the name to `None`. This is allowed only for fields that come from an abstract base; a concrete parent's fields cannot be hidden. 2. **Managers** — any manager declared on the base, such as a custom `objects`, is available on each child. 3. **Methods and properties** — ordinary Python inheritance. 4. **`Meta`** — if the child declares no `Meta`, it inherits the base's; to extend it, the child writes `class Meta(FleetRecord.Meta):`. Django sets `abstract=False` on the inherited `Meta`, so children are concrete unless they re-declare `abstract = True` to build an abstract chain. ## What you cannot do with an abstract base | You try to… | What happens | |---|---| | Query it: `FleetRecord.objects.all()` | `AttributeError`: an abstract model has no manager | | Create it: `FleetRecord()` | `TypeError`: abstract models cannot be instantiated | | Point at it: `ForeignKey(FleetRecord, …)` | System check `fields.E300`: the relation targets a model that is not installed or is abstract | | List every vehicle in one `QuerySet` | Not possible through the base — each child is a separate table | If you need any of those, the hierarchy needs a concrete parent — multi-table inheritance — or a single concrete model with a type column. ## The `related_name` trap Because each child gets a *copy* of every field with identical arguments, a `ForeignKey` on the base with a fixed `related_name="records"` would give the target model two reverse accessors named `records` — one from `Car`, one from `Van`. System checks report the clash (`fields.E304`/`fields.E305`). Django's fix, valid in abstract bases only, is two placeholders: - `%(class)s` — replaced by the lowercased child class name; - `%(app_label)s` — replaced by the lowercased app label of the child. If you omit `related_name` altogether, the default reverse name is already the child's own `car_set` / `van_set`, so there is no clash. The same placeholders work in the `name` of an `Index` or constraint declared in the abstract base's `Meta`. ## What the migration shows Running `makemigrations` on the fleet app is the quickest proof of what an abstract base does. The generated migration contains a `CreateModel` for `Car` and one for `Van`, and each lists `created_at`, `updated_at` and `branch` among its own fields. There is no `CreateModel` for `FleetRecord`, and neither operation refers to it. Two consequences follow: - **Changing the base changes every child.** Adding `retired_at` to `FleetRecord` produces an `AddField` per concrete child: one schema change per table, which on large tables means planning several table alterations, not one. - **Children can diverge deliberately.** Because each child owns its columns, `Van` can override `updated_at` or drop it with `updated_at = None` without affecting `Car`. An abstract base is also the usual home for **shared managers and methods**. A manager declared on the base is inherited by each child, so `Car.objects` and `Van.objects` both get it, and a method such as `branch_code()` works on either child's instances because it touches only inherited fields. ## When to reach for it Use an abstract base when several unrelated tables need the **same columns and behaviour** but nothing needs to treat them as **one set**: - audit columns (`created_at`, `updated_at`, `created_by`) on every fleet table; - a shared `branch` foreign key and a helper method that formats the branch code; - a soft-delete flag plus the manager that hides deleted rows. Avoid it when bookings must reference "any vehicle" through one foreign key or a report must page through the whole fleet in one query — those needs require a concrete parent table, which an abstract base by definition does not have.
- Why does a fixed related_name on a ForeignKey in an abstract base break once two children inherit it?Each child gets a copy of the field with the same `related_name`, so the target model would receive two reverse accessors with one name, and system checks report `fields.E304`/`fields.E305`. Use `related_name="%(app_label)s_%(class)s_set"` so each child's name differs, or omit `related_name` so the default `<child>_set` is used.
- Can a child change or remove a field it inherits from an abstract base?Yes. Redeclare the name with a different field or value, or set it to `None` to remove it. This works only for fields inherited from an abstract base; a concrete parent's fields cannot be hidden. Watch for an inherited manager that filters on the field you removed.
saying these in an interview costs you the question
- An abstract base creates a shared table that each child joins to.
- You can list every child type at once through AbstractBase.objects.
- A ForeignKey can point at an abstract model to reach any of its children.
- Children of an abstract base become abstract too unless you reset it by hand.
- A fixed related_name on an abstract base's ForeignKey is fine for any number of children.