python Django 中的单表继承
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/241250/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Single Table Inheritance in Django
提问by sutee
Is there explicit support for Single Table Inheritance in Django? Last I heard, the feature was still under development and debate.
Django 中是否明确支持单表继承?最后我听说,该功能仍在开发和辩论中。
Are there libraries/hacks I can use in the meantime to capture the basic behavior? I have a hierarchy that mixes different objects. The canonical example of a corporation structure with an Employee class, subclasses for types of employees, and a manager_id (parent_id) would be a good approximation of the problem I am solving.
是否有我可以同时使用的库/黑客来捕获基本行为?我有一个混合不同对象的层次结构。具有 Employee 类、雇员类型的子类和 manager_id (parent_id) 的公司结构的规范示例将是我正在解决的问题的一个很好的近似。
In my case, I would like to represent the idea that an employee can manage other employees while being managed by a different employee. There are not separate classes for Manager and Worker, which makes this hard to spread across tables. Sub-classes would represent types of employees-programmers, accountants, sales, etc and would be independent of who supervises who (OK, I guess it's no longer a typical corporation in some respect).
就我而言,我想代表这样一种想法,即一名员工可以管理其他员工,同时由不同的员工管理。Manager 和 Worker 没有单独的类,这使得这很难跨表传播。子类将代表员工类型——程序员、会计师、销售人员等,并且独立于谁监督谁(好吧,我想它在某些方面不再是典型的公司)。
采纳答案by montylounge
There are currently two forms of inheritance in Django - MTI (model table inheritance) and ABC (abstract base classes).
目前Django中有两种继承形式——MTI(模型表继承)和ABC(抽象基类)。
I wrote a tutorialon what's going on under the hood.
You can also reference the official docs on model inheritance.
您还可以参考有关模型继承的官方文档。
回答by Bj?rn Lindqvist
I think the OP is asking about Single-Table Inheritance as defined here:
我认为 OP 正在询问此处定义的单表继承:
Relational databases don't support inheritance, so when mapping from objects to databases we have to consider how to represent our nice inheritance structures in relational tables. When mapping to a relational database, we try to minimize the joins that can quickly mount up when processing an inheritance structure in multiple tables. Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
关系型数据库不支持继承,所以当从对象映射到数据库时,我们必须考虑如何在关系表中表示我们良好的继承结构。当映射到关系数据库时,我们尝试最小化在处理多个表中的继承结构时可以快速安装的连接。单表继承将继承结构的所有类的所有字段映射到单个表中。
That is, a single database table for a whole hierarchy of entity classes. Django does not support that kind of inheritance.
也就是说,实体类的整个层次结构的单个数据库表。Django 不支持这种继承。
回答by julx
See my attempt:
看我的尝试:
http://djangosnippets.org/snippets/2408/
http://djangosnippets.org/snippets/2408/
An emulation of "table per hierarchy" a.k.a. "single table inheritance" in Django. The base class must hold all the fields. It's subclasses are not allowed to contain any additional fields and optimally they should be proxies.
在 Django 中模拟“每个层次结构的表”又名“单表继承”。基类必须包含所有字段。它的子类不允许包含任何额外的字段,最好它们应该是代理。
Not exactly "single table inheritance", but close enough for many situations.
不完全是“单表继承”,但对于许多情况来说已经足够接近了。
回答by djvg
Summary
概括
Django's proxy modelsprovide the basis for Single Table Inheritance.
Django 的代理模型为单表继承提供了基础。
However, some effort is required to make it work.
但是,需要付出一些努力才能使其发挥作用。
Skip to the end for a re-usable example.
跳到最后以获得可重用的示例。
Background
背景
Martin Fowlerdescribes Single Table Inheritance (STI) as follows:
Martin Fowler将单表继承 (STI) 描述如下:
Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
单表继承将继承结构的所有类的所有字段映射到单个表中。
This is precisely what Django's proxy model inheritancedoes.
这正是 Django 的代理模型继承所做的。
Note, that, according to this blog post from 2010, proxy
models have been around since Django 1.1. However, only one of the other answers mentions them explicitly.
请注意,根据2010 年的这篇博客文章,proxy
模型自 Django 1.1 以来就已经存在。但是,其他答案中只有一个明确提到了它们。
A "normal" Django model is a concretemodel, i.e. it has a dedicated table in the database. There are two types of Django model that do nothave dedicated database tables, viz. abstractmodels and proxymodels:
“普通”Django 模型是一个具体模型,即它在数据库中有一个专用表。有两种类型的 Django 模型没有专用的数据库表,即。抽象模型和代理模型:
Abstract models act as superclassesfor concrete models. An abstract model can define fields, but it does not have a database table. The fields are only added to the database tables for its concrete subclasses.
Proxy models act as subclassesfor concrete models. A proxy model cannot define new fields. Instead, it operates on the database table associated with its concrete superclass. In other words, a Django concrete model and its proxies all share a single table.
抽象模型充当具体模型的超类。抽象模型可以定义字段,但它没有数据库表。这些字段仅添加到其具体子类的数据库表中。
代理模型充当具体模型的子类。代理模型不能定义新字段。相反,它对与其具体超类相关联的数据库表进行操作。换句话说,Django 具体模型及其代理都共享一个表。
Django's proxy models provide the basis for Single Table Inheritance, viz. they allow different models to share a single table, and they allow us to define proxy-specific behavior on the Python side. However, Django's default object-relational mapping (ORM) does not provide all the behavior that would be expected, so a little customization is required. How much, that depends on your needs.
Django 的代理模型为单表继承提供了基础,即。它们允许不同的模型共享一个表,并且允许我们在 Python 端定义特定于代理的行为。但是,Django 的默认对象关系映射 (ORM) 并没有提供所有预期的行为,因此需要进行一些自定义。多少,看你的需求了。
Let's build a minimal example, step by step, based on the simple data-model in the figure below:
让我们基于下图中的简单数据模型逐步构建一个最小示例:
Step 1: basic "proxy model inheritance"
第一步:基本的“代理模型继承”
Here's the content of models.py
for a basic proxy inheritance implementation:
以下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
and Organization
are two types of parties.
Person
并且Organization
是两种类型的派对。
Only the Party
model has a database table, so allthe fields are defined on this model, including any fields that are specific either to Person
or to Organization
.
只有Party
模型有一个数据库表,所以所有字段都在这个模型上定义,包括特定于Person
或 的任何字段Organization
。
Because Party
, Person
, and Organization
all use the Party
database table, we can define a single ForeignKey
field to Party
, and assign instances of any of the three models to that field, as implied by the inheritance relation in the figure. Note, that, without inheritance, we would need a separate ForeignKey
field for each model.
因为Party
、Person
、Organization
都使用Party
数据库表,所以我们可以为 定义单个ForeignKey
字段Party
,并将三个模型中的任何一个的实例分配给该字段,如图中的继承关系所暗示的。请注意,如果没有继承,我们需要ForeignKey
为每个模型创建一个单独的字段。
For example, suppose we define an Address
model as follows:
例如,假设我们定义一个Address
模型如下:
class Address(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
We can then initialize an Address
object using e.g. Address(party=person_instance)
or Address(party=organization_instance)
.
然后我们可以Address
使用 egAddress(party=person_instance)
或初始化一个对象Address(party=organization_instance)
。
So far, so good.
到现在为止还挺好。
However, if we try to get a list of objects corresponding to a proxy model, using e.g. Person.objects.all()
, we get a list of allParty
objects instead, i.e. both Person
objects and Organization
objects. This is because the proxy models still use the model manager from the superclass (i.e. Party
).
然而,如果我们尝试获取与代理模型对应的对象列表,使用 eg Person.objects.all()
,我们会得到所有Party
对象的列表,即Person
对象和Organization
对象。这是因为代理模型仍然使用来自超类(即Party
)的模型管理器。
Step 2: add proxy model managers
第二步:添加代理模型管理器
To make sure that Person.objects.all()
only returns Person
objects, we need to assign a separate model managerthat filters the Party
queryset. To enable this filtering, we need a field that indicates which proxy model should be used for the object.
为了确保Person.objects.all()
只返回Person
对象,我们需要分配一个单独的模型管理器来过滤查询集Party
。要启用此过滤,我们需要一个字段来指示应为对象使用哪个代理模型。
To be clear: creating a Person
object implies adding a row to the Party
table. The same goes for Organization
. To distinguish between the two, we need a column to indicate if a row represents a Person
or an Organization
. For convenience and clarity, we add a field (i.e. column) called proxy_name
, and use that to store the name of the proxy class.
需要明确的是:创建Person
对象意味着向Party
表中添加一行。也是如此Organization
。为了区分这两者,我们需要一列来指示一行是代表 aPerson
还是 an Organization
。为了方便和清晰,我们添加了一个名为 的字段(即列)proxy_name
,并使用它来存储代理类的名称。
So, enter the ProxyManager
model manager and the proxy_name
field:
因此,输入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()
Now the queryset returned by Person.objects.all()
will only contain Person
objects (and the same for Organization
).
现在,由 返回的查询集Person.objects.all()
将只包含Person
对象(对于 也是如此Organization
)。
However, this does not work in the case of a ForeignKey
relation to Party
, as in Address.party
above, because that will always return a Party
instance, regardless of the value of the proxy_name
field (also see docs). For example, suppose we create an address = Address(party=person_instance)
, then address.party
will return a Party
instance, instead of a Person
instance.
但是,这在与 的ForeignKey
关系的情况下不起作用Party
,如上Address.party
所示,因为Party
无论proxy_name
字段的值如何(另请参阅docs),它都将始终返回一个实例。例如,假设我们创建了一个address = Address(party=person_instance)
,然后address.party
将返回一个Party
实例,而不是一个Person
实例。
Step 3: extend the Party
constructor
第 3 步:扩展Party
构造函数
One way to deal with the related-field issue is to extend the Party.__new__
method, so it returns an instance of the class specified in the 'proxy_name' field. The end result looks like this:
处理相关字段问题的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)
Now address.party
will actually return a Person
instance if the proxy_name
field is Person
.
如果字段为 ,现在address.party
将实际返回一个Person
实例。proxy_name
Person
As a last step, we can make the whole thing re-usable:
作为最后一步,我们可以使整个事物可重用:
Step 4: make it re-usable
第 4 步:使其可重复使用
To make our rudimentary Single-Table Inheritance implementation re-usable, we can use Django's abstract inheritance:
为了使我们基本的单表继承实现可重用,我们可以使用 Django 的抽象继承:
inheritance/models.py
:
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__)
Then we can implement our inheritance structure as follows:
然后我们可以实现我们的继承结构如下:
parties/models.py
:
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)
More work may be required, depending on your needs, but I believe this covers some of the basics.
根据您的需要,可能需要做更多的工作,但我相信这涵盖了一些基础知识。
回答by Michael Bylstra
this might be of use: https://github.com/craigds/django-typed-modelsIt looks to be somewhat of an implementation of Single Table Inheritance but it has the limitation that subclasses can't have any extra fields.
这可能有用:https: //github.com/craigds/django-typed-models它看起来有点像单表继承的实现,但它的限制是子类不能有任何额外的字段。
there is also a fork that addresses the problem of not being able to create extra fields: https://github.com/KrzysiekJ/django-typed-models
还有一个 fork 解决了无法创建额外字段的问题:https: //github.com/KrzysiekJ/django-typed-models
update: I believe the fork may have been merged back in
更新:我相信叉子可能已经合并回来了
here is a recent discussion on the django developer mailing list about STI: https://groups.google.com/forum/#!msg/django-developers/-UOM8HNUnxg/6k34kopzerEJ
这是最近关于 STI 的 django 开发人员邮件列表的讨论:https://groups.google.com/forum/#!msg/django-developers/-UOM8HNUnxg/ 6k34kopzerEJ
回答by James
I think you can do something akin to this.
我认为你可以做一些类似的事情。
I have to implement a solution for this problem myself, and here was how I solved it:
我必须自己为这个问题实现一个解决方案,这是我解决它的方法:
class Citrus(models.Model)
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
class TangeloManager(models.Manager)
def get_query_set(self):
return super(TangeloManager, self).get_query_set().filter(type='Tangelo')
class Tangelo(models.Model)
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
objects = TangeloManager()
class Meta:
# 'appname' below is going to vary with the name of your app
db_table = u'appname_citrus'
This may have some locking issues... I'm not really sure how django handles that off the top of my head. Also, I didn't really test the above code, it's strictly for entertainment purposes, to hopefully put you on the right track.
这可能有一些锁定问题......我不太确定 django 如何处理这个问题。另外,我并没有真正测试上面的代码,它只是出于娱乐目的,希望能让你走上正轨。