总结
Django 的proxy models 提供了单表继承的基础。
但是,需要付出一些努力才能使其发挥作用。
跳到最后查看可重复使用的示例。
背景
Martin Fowler 对单表继承 (STI) 的描述如下:
单表继承将继承结构的所有类的所有字段映射到一个表中。
这正是 Django 的 proxy model inheritance 所做的。
请注意,根据blog post from 2010,proxy 模型自 Django 1.1 以来就已经存在。
“正常”的 Django 模型是一个具体的模型,即它在数据库中有一个专用表。
有两种类型的 Django 模型没有有专用的数据库表,即。 抽象模型和代理模型:
Django 的代理模型为单表继承提供了基础,即。它们允许不同的模型共享一个表,并且它们允许我们在 Python 端定义代理特定的行为。但是,Django 的默认对象关系映射 (ORM) 并没有提供所有预期的行为,因此需要进行一些定制。多少,这取决于您的需求。
让我们根据下图中的简单数据模型逐步构建一个最小示例:
第 1 步:基本的“代理模型继承”
这是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 的任何字段。 p>
因为Party、Person 和Organization 都使用Party 数据库表,我们可以为ForeignKey 定义一个ForeignKey 字段,并将三个模型中的任何一个分配给该字段字段,如图中的继承关系所暗示的那样。请注意,如果没有继承,我们需要为每个模型创建一个单独的 ForeignKey 字段。
例如,假设我们定义一个Address模型如下:
class Address(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
然后我们可以使用例如初始化Address 对象。 Address(party=person_instance) 或 Address(party=organization_instance)。
到目前为止,一切都很好。
但是,如果我们尝试获取与代理模型对应的对象列表,使用例如Person.objects.all(),我们得到所有 Party 对象的列表,即Person 对象和Organization 对象。这是因为代理模型仍然使用超类中的模型管理器(即Party)。
第 2 步:添加代理模型管理器
为了确保Person.objects.all() 只返回Person 对象,我们需要分配一个单独的model manager 来过滤Party 查询集。要启用此过滤,我们需要一个字段来指示对象应该使用哪个代理模型。
明确一点:创建Person 对象意味着向Party 表中添加一行。 Organization 也是如此。为了区分这两者,我们需要一个列来指示一行是代表Person 还是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,因为无论proxy_name 的值如何,这将始终返回一个Party 实例字段(另见docs)。例如,假设我们创建了一个address = Address(party=person_instance),那么address.party 将返回一个Party 实例,而不是Person 实例。
第 3 步:扩展 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)
如果proxy_name 字段是Person,现在address.party 实际上会返回一个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)
根据您的需要,可能需要做更多的工作,但我相信这涵盖了一些基础知识。