skip to content

In Django, what can a proxy model declared with Meta.proxy = True change about its parent model, and what may it not add?

level: middleimportance: should knowfreq 42%

answer

  1. same table, different class
  2. behaviour, not columns
  3. ordering, methods, default manager
  4. exactly one concrete base

basics

~20 s

A proxy model reuses its parent's table and columns but may change Python-side behaviour: methods, str, default ordering, the default manager, and a separate admin and permissions. It may not add fields and must have exactly one concrete base.

solid answer

~40 s

A proxy is a second Python class over the same table: saving an `ElectricCar` writes the `car` table, and rows created through either class are visible through both. It may change methods and properties, `Meta.ordering` and its default manager, and because it gets its own content type it can be registered in the admin separately and carries its own permissions. It may not declare fields (system check `models.E017`), must inherit from exactly one non-abstract model, and may mix in abstract classes only if they define no fields. Querying the parent still returns parent instances, and without a custom manager the proxy returns every parent row, not a filtered subset. `makemigrations` records the proxy, but no table is created.

code

python · 20 lines
python
from django.db import models


class Car(models.Model):
    plate = models.CharField(max_length=12, unique=True)
    fuel = models.CharField(max_length=10)  # "petrol", "diesel" or "electric"
    range_km = models.PositiveIntegerField(null=True, blank=True)


class ElectricCar(Car):
    class Meta:
        proxy = True
        ordering = ["-range_km"]

    def needs_charge_before(self, trip_km: int) -> bool:
        return self.range_km is not None and self.range_km < trip_km


# ElectricCar.objects.all() returns every car row, petrol and diesel too,
# as ElectricCar instances ordered by range; filtering needs a custom manager.

go deeper

for a junior

Recall that a proxy uses the parent's table and cannot add fields; it exists to change behaviour such as ordering or methods.

for a middle

Explain the base-class rules, why the parent's queries keep returning parent instances, and that a proxy filters nothing without its own manager.

for a senior

Use proxies deliberately for per-kind admin screens and permissions on one table, and know the migration still records them even though no DDL runs.

for a principal

Decide when a proxy per role or kind keeps one table clean versus when it hides a missing domain model that deserves its own table.

## Same table, different class A **proxy model** is a Django model subclass whose `Meta` says `proxy = True`. It maps to its parent's table: no new table, no new columns, no JOIN. Everything you create, update or delete through the proxy lands in the parent's rows, and every existing parent row can be loaded through the proxy. What differs is the **Python class** the rows are loaded into — and therefore the behaviour attached to them. ## What a proxy may change - **Methods and properties** — `needs_charge_before(trip_km)` on `ElectricCar`, a different `__str__`. - **Default ordering** — `Meta.ordering = ["-range_km"]` sorts proxy queries without touching the parent's queries. - **The default manager** — a manager declared on the proxy becomes its default; the parent's managers stay available on it. - **Admin registration** — the proxy is a distinct model, so it can have its own `ModelAdmin`, for example a "Charging queue" screen over the car table. - **Permissions** — a proxy gets its **own content type**, and its default permissions are created against it; it does not inherit the parent's custom `Meta.permissions`. ## What it may not do | Rule | What Django does if you break it | |---|---| | declare a model field | system check `models.E017`: proxy model contains model fields | | inherit from two concrete models | `TypeError`: proxy has more than one non-abstract model base class | | inherit from no concrete model | `TypeError`: proxy has no non-abstract model base class | | mix in an abstract base that has fields | `TypeError`: abstract base class containing model fields not permitted for proxy model | A proxy may inherit from any number of **field-less** abstract classes (useful for adding managers) and from several proxies that share one concrete parent. ## Querying and instances Two behaviours surprise people: 1. **The parent does not change.** `Car.objects.all()` keeps returning `Car` instances. Django has no way to substitute a proxy for its parent globally; the proxy is used only where your code names it. 2. **The proxy does not filter.** Without a custom manager, `ElectricCar.objects.all()` returns **every** row of the car table — petrol and diesel included — just as `ElectricCar` instances. A proxy is a lens, not a subset; making it a subset takes a default manager whose `get_queryset()` filters on a field. `Meta` inheritance for proxies follows the multi-table rule: a proxy inherits the parent's `ordering` and `get_latest_by` if it does not set them, and nothing else. ## Instances, `isinstance` and relations Because a proxy is an ordinary Python subclass, an `ElectricCar` instance passes `isinstance(obj, Car)`, while a `Car` loaded through `Car.objects` is not an `ElectricCar`. To get proxy behaviour for a row you already hold, load it again through the proxy, for example `ElectricCar.objects.get(pk=car.pk)`; no conversion is stored anywhere, since both classes read the same row. A `ForeignKey` may name a proxy as its target. The database column still references the parent's table, because that is the only table there is, but accessing the relation returns proxy instances. Typical uses in a fleet application: - a `ReturnedVan` proxy that the inspection team's admin screen lists, ordered by return time; - a proxy with a report-friendly `__str__` for exports, leaving the main model's text alone; - a proxy per vehicle kind carrying kind-specific methods over one shared table. ## Proxies in migrations `makemigrations` records a proxy as a `CreateModel` operation with `proxy` in its options, so the migration state knows the model exists and later migrations can depend on it. When the migration runs, the schema editor skips proxy models — no DDL is emitted and no table appears. ## Proxy versus `managed = False` A model with `Meta.managed = False` and a hand-set `db_table` could also shadow the car table, but it re-declares every column and must be kept in sync manually. The rule of thumb: - to change only Python behaviour while keeping every field — **proxy**; - to map a database view or a table Django does not own, possibly with fewer columns — **`managed = False`**. In interviews the proxy question usually comes down to one sentence: a proxy changes **how rows behave in Python**, never **which rows exist or what columns they have**. Everything else on this page follows from that.

  • Does ElectricCar.objects.all() return only electric cars when ElectricCar is a proxy of Car?
    No. Without its own manager the proxy inherits the parent's, which returns every row of the car table as `ElectricCar` instances. To restrict it, give the proxy a default manager whose `get_queryset()` filters on `fuel`.
  • How does a proxy differ from a model with Meta.managed = False pointed at the same table?
    A proxy inherits all fields and managers from its parent and stays in sync automatically. An unmanaged model re-declares its columns and `db_table` by hand, so it drifts unless kept in step; it is meant for database views and tables Django does not own.

A proxy is a second pair of reading glasses for the same ledger: it changes how each page is presented and what you can do with it, but it does not remove pages from the drawer or add columns to them.

saying these in an interview costs you the question

  • A proxy model gets its own table that mirrors the parent's rows.
  • A proxy may add a column as long as the column is nullable.
  • Once a proxy exists, querying Car returns ElectricCar objects.
  • A proxy automatically returns only the rows that belong to its kind.
  • A proxy can extend two concrete models to combine their columns.