sut*_*tee 25 python django django-models single-table-inheritance
在Django中是否明确支持单表继承?最后我听说,这个特征仍处于开发和辩论之中.
在此期间我是否可以使用库/黑客来捕获基本行为?我有一个混合不同对象的层次结构.具有Employee类的公司结构的规范示例,雇员类型的子类以及manager_id(parent_id)将是我正在解决的问题的良好近似.
就我而言,我想表达一个想法,即员工可以管理其他员工,同时由不同的员工管理.Manager和Worker没有单独的类,这使得这很难跨表传播.子类将代表员工类型 - 程序员,会计师,销售等,并且独立于谁监督谁(好吧,我想它在某些方面不再是典型的公司).
djv*_*jvg 19
Django 的代理模型为单表继承提供了基础。
但是,需要付出一些努力才能使其发挥作用。
跳到最后以获得可重用的示例。
Martin Fowler将单表继承 (STI) 描述如下:
单表继承将继承结构的所有类的所有字段映射到单个表中。
这正是 Django 的代理模型继承所做的。
请注意,根据2010 年的这篇博客文章,proxy模型自 Django 1.1 以来就已经存在。
“普通”Django 模型是一个具体模型,即它在数据库中有一个专用表。有两种类型的 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
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
然后我们可以Address使用 egAddress(party=person_instance)或初始化一个对象Address(party=organization_instance)。
到现在为止还挺好。
然而,如果我们尝试获取与代理模型对应的对象列表,使用 eg Person.objects.all(),我们会得到所有 Party对象的列表,即Person对象和Organization对象。这是因为代理模型仍然使用来自超类(即Party)的模型管理器。
为了确保Person.objects.all()只返回Person对象,我们需要分配一个单独的模型管理器来过滤查询集Party。要启用此过滤,我们需要一个字段来指示应为对象使用哪个代理模型。
需要明确的是:创建Person对象意味着向Party表中添加一行。也是如此Organization。为了区分这两者,我们需要一列来指示一行是代表 aPerson还是 an 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()
Run Code Online (Sandbox Code Playgroud)
现在返回的查询集Person.objects.all()将只包含Person对象(对于Organization)。
但是,这在与 的ForeignKey关系的情况下不起作用Party,如上Address.party所示,因为Party无论proxy_name字段的值如何,它都将始终返回一个实例(另请参阅docs)。例如,假设我们创建了一个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)
Run Code Online (Sandbox Code Playgroud)
如果字段为 ,现在address.party将实际返回一个Person实例。proxy_namePerson
作为最后一步,我们可以使整个事物可重用:
为了使我们基本的单表继承实现可重用,我们可以使用 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__)
Run Code Online (Sandbox Code Playgroud)
然后我们可以实现我们的继承结构如下:
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)
Run Code Online (Sandbox Code Playgroud)
根据您的需要,可能需要做更多的工作,但我相信这涵盖了一些基础知识。