概括
Django 的代理模型为单表继承提供了基础。
但是,需要一些努力才能使其发挥作用。
跳到最后一个可重用的例子。
背景
Martin Fowler对单表继承 (STI) 的描述如下:
单表继承将继承结构的所有类的所有字段映射到一个表中。
这正是 Django 的代理模型继承所做的。
请注意,根据2010 年的这篇博文,proxy
模型自 Django 1.1 以来就已经存在。
一个“正常”的 Django 模型是一个具体的模型,即它在数据库中有一个专用的表。有两种类型的 Django 模型没有专用的数据库表,即。抽象模型和代理模型:
Django 的代理模型为单表继承提供了基础,即。它们允许不同的模型共享一个表,并且它们允许我们在 Python 端定义代理特定的行为。但是,Django 的默认对象关系映射 (ORM) 并没有提供所有预期的行为,因此需要进行一些定制。多少,这取决于你的需求。
让我们根据下图中的简单数据模型逐步构建一个最小示例:

第一步:基本的“代理模型继承”
以下models.py
是基本代理继承实现的内容:
from django.db import models
class Party(models.Model):
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
class Person(Party):
class Meta:
proxy = True
class Organization(Party):
class Meta:
proxy = True
Person
Organization
是两种类型的聚会。
只有Party
模型有一个数据库表,所以所有字段都在这个模型上定义,包括特定于Person
或 的任何字段Organization
。
因为Party
、Person
和Organization
都使用Party
数据库表,所以我们可以为 定义一个ForeignKey
字段Party
,并将三个模型中的任何一个的实例分配给该字段,如图中的继承关系所示。请注意,如果没有继承,我们将需要ForeignKey
为每个模型一个单独的字段。
例如,假设我们定义一个Address
模型如下:
class Address(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
Address
然后我们可以使用 egAddress(party=person_instance)
或初始化一个对象Address(party=organization_instance)
。
到现在为止还挺好。
但是,如果我们尝试获取与代理模型相对应的对象列表,使用 eg Person.objects.all()
,我们将获得所有 Party
对象的列表,即Person
对象和Organization
对象。这是因为代理模型仍然使用超类(即Party
)的模型管理器。
第 2 步:添加代理模型管理器
为了确保Person.objects.all()
只返回Person
对象,我们需要分配一个单独的模型管理器来过滤查询Party
集。要启用此过滤,我们需要一个字段来指示应该为对象使用哪个代理模型。
需要明确的是:创建一个Person
对象意味着向Party
表中添加一行。也是如此Organization
。为了区分这两者,我们需要一个列来指示一行是代表 aPerson
还是代表 a Organization
。为了方便和清晰,我们添加了一个名为 的字段(即列)proxy_name
,并使用它来存储代理类的名称。
因此,输入ProxyManager
模型管理器和proxy_name
字段:
from django.db import models
class ProxyManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(proxy_name=self.model.__name__)
class Party(models.Model):
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
class Person(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Organization(Party):
class Meta:
proxy = True
objects = ProxyManager()
现在返回的查询集Person.objects.all()
将只包含Person
对象(对于 也是如此Organization
)。
ForeignKey
但是,这在与 的关系的情况下不起作用Party
,如上所述,因为无论字段的值如何,Address.party
它总是会返回一个Party
实例(另请参阅docs)。例如,假设我们创建一个,那么将返回一个实例,而不是一个实例。proxy_name
address = Address(party=person_instance)
address.party
Party
Person
第三步:扩展Party
构造函数
处理相关字段问题的一种方法是扩展该Party.__new__
方法,因此它返回“proxy_name”字段中指定的类的实例。最终结果如下所示:
class Party(models.Model):
PROXY_FIELD_NAME = 'proxy_name'
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
party_class = cls
try:
# get proxy name, either from kwargs or from args
proxy_name = kwargs.get(cls.PROXY_FIELD_NAME)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(cls.PROXY_FIELD_NAME))
proxy_name = args[proxy_name_field_index]
# get proxy class, by name, from current module
party_class = getattr(sys.modules[__name__], proxy_name)
finally:
return super().__new__(party_class)
如果字段为.Nowaddress.party
将实际返回一个Person
实例。proxy_name
Person
作为最后一步,我们可以使整个东西可重用:
第 4 步:使其可重复使用
为了使我们基本的单表继承实现可重用,我们可以使用 Django 的抽象继承:
inheritance/models.py
:
import sys
from django.db import models
class ProxySuper(models.Model):
class Meta:
abstract = True
proxy_name = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
""" create an instance corresponding to the proxy_name """
proxy_class = cls
try:
field_name = ProxySuper._meta.get_fields()[0].name
proxy_name = kwargs.get(field_name)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(field_name))
proxy_name = args[proxy_name_field_index]
proxy_class = getattr(sys.modules[cls.__module__], proxy_name)
finally:
return super().__new__(proxy_class)
class ProxyManager(models.Manager):
def get_queryset(self):
""" only include objects in queryset matching current proxy class """
return super().get_queryset().filter(proxy_name=self.model.__name__)
然后我们可以实现我们的继承结构如下:
parties/models.py
:
from django.db import models
from inheritance.models import ProxySuper, ProxyManager
class Party(ProxySuper):
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
class Person(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Organization(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Placement(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
根据您的需要,可能需要做更多的工作,但我相信这涵盖了一些基础知识。