Proxy model patterns for polymorphic behavior without new tables. Use when: adding model-level behavior variants, custom managers per type, admin registrations per subtype.
proxy = True in Meta — otherwise Django creates a new tabledb_table — never set a different oneModelAdmin registrationfrom apps.core.models import TimestampedModel
class Firmware(TimestampedModel):
name = models.CharField(max_length=255)
firmware_type = models.CharField(
max_length=20,
choices=[
("official", "Official"),
("engineering", "Engineering"),
("modified", "Modified"),
],
)
class Meta:
db_table = "firmwares_firmware"
class OfficialFirmwareManager(models.Manager["OfficialFirmware"]):
def get_queryset(self) -> models.QuerySet["OfficialFirmware"]:
return super().get_queryset().filter(firmware_type="official")
class OfficialFirmware(Firmware):
objects = OfficialFirmwareManager()
class Meta:
proxy = True
verbose_name = "Official Firmware"
verbose_name_plural = "Official Firmwares"
def save(self, *args: Any, **kwargs: Any) -> None:
self.firmware_type = "official"
super().save(*args, **kwargs)
from django.contrib import admin
@admin.register(OfficialFirmware)
class OfficialFirmwareAdmin(admin.ModelAdmin["OfficialFirmware"]):
list_display = ("name", "version", "created_at")
list_filter = ("brand",)
def get_queryset(self, request: HttpRequest) -> QuerySet[OfficialFirmware]:
return super().get_queryset(request).select_related("brand")
class SecurityEvent(TimestampedModel):
event_type = models.CharField(max_length=50)
severity = models.CharField(max_length=20)
ip_address = models.GenericIPAddressField()
class Meta:
db_table = "security_securityevent"
class CriticalSecurityEvent(SecurityEvent):
"""Proxy for critical events — adds notification behavior."""
class Meta:
proxy = True
def notify_admins(self) -> None:
"""Send alert for critical security events."""
# Business logic here or delegate to services.py
...
class PublishedPostManager(models.Manager["PublishedPost"]):
def get_queryset(self) -> models.QuerySet["PublishedPost"]:
return super().get_queryset().filter(status="published")
class PublishedPost(BlogPost):
objects = PublishedPostManager()
class Meta:
proxy = True
verbose_name = "Published Post"
proxy = True — creates a multi-table inheritance join& .\.venv\Scripts\python.exe -m ruff check . --fix
& .\.venv\Scripts\python.exe -m ruff format .
& .\.venv\Scripts\python.exe manage.py check --settings=app.settings_dev