A Django proxy model is a subclass that maps to the same database table as its parent model but changes how that model behaves in Python — you create one by adding proxy = True to the model's class Meta. It adds no table, no columns and no extra rows; it only overrides behaviour such as the default manager, Meta.ordering, the string representation and custom methods.
Because a proxy shares the parent's storage, every create, query, update and delete on the proxy reads and writes the parent's table — so the two classes stay perfectly in sync while exposing different default behaviour to different parts of your app.
Key takeaways
- A proxy model uses the same table as its parent — set
proxy = Trueinclass Meta. - You cannot add database fields on a proxy; you can only change behaviour (managers,
Meta.ordering, methods,verbose_name). - Use a proxy when you want a different default manager or ordering over the same rows — e.g. a managers-only view of an
Employeetable. - Proxies are ideal for giving a model a second, pre-filtered Django admin screen without duplicating data.
- Choose between abstract base classes, multi-table inheritance and proxy models by whether you need new columns, a new table, or just new behaviour.
- Proxy changes are migration-light: adding
proxy = Truerecords a state-only migration, not a schema change.
What is a Django proxy model, and how is it different from a normal subclass?
A regular Django model subclass triggers multi-table inheritance — Django silently creates a new table for the child, linked to the parent by an automatic OneToOneField. A proxy model is deliberately different: by setting proxy = True, you tell Django "do not create a table; reuse the parent's." The proxy and the parent become two Python classes over one set of rows.
Querying ManagerEmployee.objects.all() and Employee.objects.all() both hit the same employee table. The only difference is the behaviour each class layers on top — a custom default manager, ordering, admin presentation or helper methods.
# models.py
from django.db import models
class Employee(models.Model):
"""The single concrete model — the only real database table."""
DEVELOPER = "D"
MANAGER = "M"
EMPLOYEE_TYPES = [
(DEVELOPER, "Developer"),
(MANAGER, "Manager"),
]
name = models.CharField(max_length=120)
type = models.CharField(max_length=1, choices=EMPLOYEE_TYPES, default=DEVELOPER)
def __str__(self):
return self.name
class ManagerEmployeeManager(models.Manager):
"""Default manager for the proxy: scope every query to managers."""
def get_queryset(self):
return super().get_queryset().filter(type=Employee.MANAGER)
def create(self, **kwargs):
kwargs.setdefault("type", Employee.MANAGER)
return super().create(**kwargs)
class ManagerEmployee(Employee):
"""Proxy model: same 'employee' table, manager-only behaviour."""
objects = ManagerEmployeeManager()
class Meta:
proxy = True
ordering = ["name"]
verbose_name = "Manager"
verbose_name_plural = "Managers"
def promote(self):
# You can add methods freely on a proxy — fields you cannot.
self.type = Employee.MANAGER
self.save(update_fields=["type"])In the example above, ManagerEmployee reuses the employee table but ships its own default manager, Meta.ordering and a promote() helper. Creating a ManagerEmployee writes a row to the employee table with type='M'; querying the proxy returns only manager rows, while Employee.objects.all() still returns everyone.
Note the hard limit: a proxy model cannot declare new model fields. Add one and Django raises an error like Proxy model 'ManagerEmployee' contains model fields at startup. Proxies are for behaviour only — managers, Meta options such as ordering and verbose_name, the __str__ method and any custom methods or properties. If you need an extra column, reach for a different inheritance style (see the comparison below).
When should you use a Django proxy model?
Reach for a proxy model when the data is identical but the behaviour should differ. Common cases:
- A pre-filtered default view of one table — e.g.
ManagerEmployeealways returnstype='M'rows. - A second Django admin screen for the same table with different
list_display, filters or permissions. - A different default ordering for the same rows in one part of the app.
- Domain-specific methods or properties layered onto shared data without touching the original model.
If you instead need extra columns, a separate physical table, or a true "is-a" relationship that carries its own data, a proxy is the wrong tool — use multi-table inheritance or an abstract base class.
How do you create a proxy model and a separate admin screen?
The recipe is four steps: subclass the concrete model, attach a manager that encodes the behaviour you want, set proxy = True in Meta, then register the proxy in admin.py to get an independent admin screen.
# admin.py
from django.contrib import admin
from .models import Employee, ManagerEmployee
@admin.register(Employee)
class EmployeeAdmin(admin.ModelAdmin):
list_display = ("name", "type")
list_filter = ("type",)
@admin.register(ManagerEmployee)
class ManagerEmployeeAdmin(admin.ModelAdmin):
"""A second admin screen backed by the SAME table, pre-filtered to managers."""
list_display = ("name",)
def get_queryset(self, request):
# Uses the proxy's default manager, so only type='M' rows are listed.
return super().get_queryset(request)Registering the proxy gives you a second, fully independent admin screen — "Managers" — backed by the same table as "Employees" but pre-filtered by the proxy's default manager and shown with its own list_display and ordering. No data is duplicated.
For deeper manager patterns, read our guides on Django model managers and properties and how to add a custom manager in Django. If you genuinely need to add or change fields at runtime — a very different problem from a proxy — see dynamically adding new fields to models from the admin. Proxies also pair well with other Django building blocks such as GenericForeignKey and custom management commands.
Proxy vs abstract base class vs multi-table inheritance
Django offers three styles of model inheritance. Picking the right one comes down to a single question: do you need a new table, new columns, or just new behaviour? This table is the quickest way to decide.
| Capability | Abstract base class | Multi-table inheritance | Proxy model |
|---|---|---|---|
| Creates its own DB table? | No — only the child models get tables | Yes — a child table plus the parent table | No — reuses the parent's table |
| Shares storage with the parent? | N/A (the parent has no table) | Yes, via an automatic OneToOneField |
Yes — same table and same rows |
| Can add new fields? | Yes | Yes | No |
| Can override methods, managers and ordering? | Yes | Yes | Yes |
| Typical use case | Share common fields across many models | Model an "is-a" that needs extra data | Change Python behaviour over shared data |
In short: an abstract base class is a reusable field/method mixin (no table of its own); multi-table inheritance is a real "is-a" with its own columns; a proxy model is pure behaviour over the parent's existing rows.
Refactoring a tangled Django models layer or unsure which inheritance style fits? Our Django development services team has shipped 50+ projects on Django since 2014 and can help you model it cleanly.
Frequently Asked Questions
Can a Django proxy model add new fields?
No. A proxy model maps to the same database table as its parent, so it cannot declare new model fields — Django raises an error such as "Proxy model contains model fields" at startup. A proxy can only change behaviour: managers, Meta.ordering, verbose_name, the str method and custom methods or properties. If you need an extra column, use multi-table inheritance or an abstract base class instead.
Does a proxy model create a new database table?
No. Setting proxy = True tells Django to reuse the parent model's table. No new table, columns or rows are created, and the generated migration only records model state — it makes no schema change. The proxy and the parent operate over exactly the same rows.
What is the difference between a proxy model and multi-table inheritance?
Multi-table inheritance creates a new table for the child, linked to the parent by an automatic OneToOneField, and lets you add fields. A proxy model creates no table and cannot add fields — it only overrides Python behaviour on the parent's existing table. Use multi-table inheritance when you need new data, and a proxy when you only need new behaviour.
Can a proxy model have its own manager and ordering?
Yes. Defining a manager on a proxy makes it that proxy's default manager, and you can set Meta.ordering along with verbose_name, verbose_name_plural and permissions. This is the main reason proxies exist — a different default queryset, ordering or admin presentation for the same underlying data.
Can you register a proxy model separately in the Django admin?
Yes. Because a proxy is a distinct Python class, you can register it with admin.site.register() or the @admin.register decorator to get a second admin screen — for example a "Managers" view backed by the same table as "Employees" but pre-filtered by the proxy's default manager and shown with different list_display columns.
Do proxy models require a migration in Django 5.x?
Yes, but it is lightweight. Adding proxy = True or a new proxy class generates a migration that records the model in Django's migration state without altering the database schema. Run python manage.py makemigrations and migrate as usual; on Django 5.2 the operation applies almost instantly because no tables change.