I've searched around stack overflow for an answer to this (probably simple) question, but most of the solutions I see seem overly complicated and hard to understand.
我已经在堆栈溢出中搜索了这个(可能是简单的)问题的答案,但我看到的大多数解决方案看起来过于复杂且难以理解。
I have a model "Post" which is an abstract base class. Models "Announcement" and "Event" inherit from Post.
我有一个模型“Post”,它是一个抽象的基类。模特“公告”和“事件”继承自Post。
Right now I'm keeping related lists of Events and Announcements in other models. For instance, I have "removed_events" and "removed_announcements" fields in another model.
现在我在其他模型中保留相关的事件和公告列表。例如,我在另一个模型中有“removed_events”和“removed_announcements”字段。
However, in my project, "removed_events" and "removed_announcements" are treated exactly the same way. There is no need to disambiguate between a "removed event" and a "removed announcement." In other words, a field keeping track of "removed_posts" would be sufficient.
但是,在我的项目中,“removed_events”和“removed_announcements”的处理方式完全相同。没有必要消除“删除的事件”和“删除的公告”之间的歧义。换句话说,跟踪“removed_posts”的字段就足够了。
I don't know how to (or perhaps can't) create a field "removed_posts," since Post is abstract. However, right now I feel like I'm repeating myself in the code (and having to do a lot of clutter-some checks to figure out whether the post I'm looking at is an event or an announcement and add it to the appropriate removed field).
我不知道如何(或许不能)创建一个字段“removed_posts”,因为Post是抽象的。但是,现在我觉得我在代码中重复自己(并且不得不做一些混乱 - 一些检查以确定我正在看的帖子是一个事件还是一个公告并将其添加到相应的删除字段)。
What is the best option here? I could make Posts non-abstract, but Post objects themselves should never be created, and I don't think I can enforce this on a non-abstract object.
这里最好的选择是什么?我可以使Posts非抽象,但Post对象本身永远不应该创建,我不认为我可以在非抽象对象上强制执行。
My understanding of databases is weak, but I'm under the impression that making Post non-abstract would complicate the database due to joins. Is this a big deal?
我对数据库的理解很薄弱,但我的印象是,由于连接,使Post非抽象会使数据库复杂化。这是一个大问题吗?
Finally, there are other fields in other models where I'd like to condense things that amount to an event_list and an announcement_list into a post_list, but those fields do need to be disambiguated. I could filter the post_list based on post type, but the call to filter() would be slower than being able to directly access the event and announcement lists separately, wouldn't it? Any suggestions here?
最后,在其他模型中还有其他字段,我想将event_list和announcement_list中的内容压缩到post_list中,但这些字段确实需要消除歧义。我可以根据帖子类型过滤post_list,但是对filter()的调用比单独直接访问事件和公告列表要慢,不是吗?这里有什么建议?
Thanks a ton for reading through this.
非常感谢您阅读本文。
2 个解决方案
#1
11
There are two kinds of model subclassing in Django - Abstract Base Classes; and Multi-Table inheritance.
Django中有两种模型子类 - 抽象基类;和多表继承。
Abstract Base Classes aren't ever used by themselves, and do not have a database table or any form of identification. They are simply a way of shortening code, by grouping sets of common fields in code, not in the database.
抽象基类本身并不使用,也没有数据库表或任何形式的标识。它们只是缩短代码的一种方法,它通过在代码中而不是在数据库中对公共字段组进行分组。
For example:
例如:
class Address(models.Model):
street = ...
city = ...
class Meta:
abstract = True
class Employee(Address):
name = ...
class Employer(Address):
employees = ...
company_name = ...
This is a contrived example, but as you can see, an Employee
isn't an Address
, and neither is an Employer
. They just both contain fields relating to an address. There are only two tables in this example; Employee
, and Employer
- and both of them contain all the fields of Address. An employer address can not be compared to an employee address at the database level - an address doesn't have a key of its own.
这是一个人为的例子,但正如您所看到的,员工不是地址,也不是雇主。它们都包含与地址相关的字段。这个例子中只有两个表;员工和雇主 - 两者都包含地址的所有字段。雇主地址无法与数据库级别的员工地址进行比较 - 地址没有自己的密钥。
Now, with multi-table inheritance, (remove the abstract=True from Address), Address does have a table all to itself. This will result in 3 distinct tables; Address
, Employer
, and Employee
. Both Employer and Employee will have a unique foreign key (OneToOneField) back to Address.
现在,通过多表继承,(从地址中删除abstract = True),Address确实拥有一个表格。这将产生3个不同的表格;地址,雇主和员工。雇主和员工都将拥有一个唯一的外键(OneToOneField)回到地址。
You can now refer to an Address without worrying about what type of address it is.
您现在可以引用地址而无需担心它是什么类型的地址。
for address in Address.objects.all():
try:
print address.employer
except Employer.DoesNotExist: # must have been an employee
print address.employee
Each address will have its own primary key, which means it can be saved in a fourth table on its own:
每个地址都有自己的主键,这意味着它可以单独保存在第四个表中:
class FakeAddresses(models.Model):
address = models.ForeignKey(Address)
note = ...
Multi-table Inheritance is what you're after, if you need to work with objects of type Post
without worrying about what type of Post it is. There will be an overhead of a join if accessing any of the Post fields from the subclass; but the overhead will be minimal. It is a unique index join, which should be incredibly quick.
如果您需要使用Post类型的对象而不必担心Post的类型,那么多表继承就是您所追求的。如果从子类访问任何Post字段,将会有连接开销;但开销很小。它是一个独特的索引连接,应该非常快。
Just make sure, that if you need access to the Post
, that you use select_related
on the queryset.
只需确保,如果您需要访问Post,则在查询集上使用select_related。
Events.objects.select_related(depth=1)
That will avoid additional queries to fetch the parent data, but will result in the join occurring. So only use select related if you need the Post.
这将避免额外的查询来获取父数据,但会导致连接发生。因此,如果您需要Post,请仅使用select相关。
Two final notes; if a Post can be both an Announcement AND an Event, then you need to do the traditional thing, and link to Post via a ForeignKey. No subclassing will work in this case.
两个最后的笔记;如果帖子既可以是公告也可以是活动,那么你需要做传统的事情,并通过ForeignKey链接到Post。在这种情况下,没有子类化。
The last thing is that if the joins are performance critical between the parent and the children, you should use abstract inheritance; and use Generic Relations to refer to the abstract Posts from a table that is much less performance critical.
最后一点是,如果连接在父级和子级之间具有性能关键,则应该使用抽象继承;并使用通用关系来引用性能要低得多的表中的抽象帖子。
Generic Relations essentially store data like this:
通用关系本质上存储如下数据:
class GenericRelation(models.Model):
model = ...
model_key = ...
DeletedPosts(models.Model):
post = models.ForeignKey(GenericRelation)
That will be a lot more complicated to join in SQL (django helps you with that), but it will also be less performant than a simple OneToOne join. You should only need to go down this route if the OneToOne joins are severely harming performance of your application which is probably unlikely.
加入SQL会更复杂(django可以帮助你),但它的性能也不如简单的OneToOne连接。如果OneToOne加入会严重损害应用程序的性能,那么您应该只需沿着这条路走下去,这可能不太可能。
#2
2
Generic relationships and foreign keys are your friend in your path to succeed. Define an intermediate model where one side is generic, then the other side will get a related list of polymorphic models. It's just a little more complicated than a standard m2m join model, in that the generic side has two columns, one to ContentType (actually a FK) and the other to the PK of the actual linked model instance. You can also restrict the models to be linked with using standard FK parameters. You'll get used with it quickly.
通用关系和外键是您成功之路上的朋友。定义一个中间模型,其中一方是通用的,然后另一方将获得相关的多态模型列表。它比标准m2m连接模型稍微复杂一点,因为通用端有两列,一列是ContentType(实际上是FK),另一列是实际链接模型实例的PK。您还可以使用标准FK参数限制要链接的模型。你会很快习惯它。
(now that I get an actual keyboard to write with, here there is the example:)
(现在我得到一个真正的键盘来写,这里有例子:)
class Post(models.Model):
class Meta: abstract = True
CONCRETE_CLASSES = ('announcement', 'event',)
removed_from = generic.GenericRelation('OwnerRemovedPost',
content_type_field='content_type',
object_id_field='post_id',
)
class Announcement(Post): pass
class Event(Post): pass
class Owner(models.Model):
# non-polymorphic m2m
added_events = models.ManyToManyField(Event, null=True)
# polymorphic m2m-like property
def removed_posts(self):
# can't use ManyToManyField with through.
# can't return a QuerySet b/c it would be a union.
return [i.post for i in self.removed_post_items.all()]
def removed_events(self):
# using Post's GenericRelation
return Event.objects.filter(removed_from__owner=self)
class OwnerRemovedPost(models.Model):
content_type = models.ForeignKey(ContentType,
limit_choices_to={'name__in': Post.CONCRETE_CLASSES},
)
post_id = models.PositiveIntegerField()
post = generic.GenericForeignKey('content_type', 'post_id')
owner = models.ForeignKey(Owner, related_name='removed_post_items')
class Meta:
unique_together = (('content_type', 'post_id'),) # to fake FK constraint
You can't filter into the related collection like a classic many-to-many, but with the proper methods in Owner
, and using the concrete classes' managers smartly, you get everywhere you want.
你不能像经典的多对多一样过滤到相关的集合,但是在Owner中使用适当的方法,并且巧妙地使用具体类的管理器,你可以随心所欲。
#1
11
There are two kinds of model subclassing in Django - Abstract Base Classes; and Multi-Table inheritance.
Django中有两种模型子类 - 抽象基类;和多表继承。
Abstract Base Classes aren't ever used by themselves, and do not have a database table or any form of identification. They are simply a way of shortening code, by grouping sets of common fields in code, not in the database.
抽象基类本身并不使用,也没有数据库表或任何形式的标识。它们只是缩短代码的一种方法,它通过在代码中而不是在数据库中对公共字段组进行分组。
For example:
例如:
class Address(models.Model):
street = ...
city = ...
class Meta:
abstract = True
class Employee(Address):
name = ...
class Employer(Address):
employees = ...
company_name = ...
This is a contrived example, but as you can see, an Employee
isn't an Address
, and neither is an Employer
. They just both contain fields relating to an address. There are only two tables in this example; Employee
, and Employer
- and both of them contain all the fields of Address. An employer address can not be compared to an employee address at the database level - an address doesn't have a key of its own.
这是一个人为的例子,但正如您所看到的,员工不是地址,也不是雇主。它们都包含与地址相关的字段。这个例子中只有两个表;员工和雇主 - 两者都包含地址的所有字段。雇主地址无法与数据库级别的员工地址进行比较 - 地址没有自己的密钥。
Now, with multi-table inheritance, (remove the abstract=True from Address), Address does have a table all to itself. This will result in 3 distinct tables; Address
, Employer
, and Employee
. Both Employer and Employee will have a unique foreign key (OneToOneField) back to Address.
现在,通过多表继承,(从地址中删除abstract = True),Address确实拥有一个表格。这将产生3个不同的表格;地址,雇主和员工。雇主和员工都将拥有一个唯一的外键(OneToOneField)回到地址。
You can now refer to an Address without worrying about what type of address it is.
您现在可以引用地址而无需担心它是什么类型的地址。
for address in Address.objects.all():
try:
print address.employer
except Employer.DoesNotExist: # must have been an employee
print address.employee
Each address will have its own primary key, which means it can be saved in a fourth table on its own:
每个地址都有自己的主键,这意味着它可以单独保存在第四个表中:
class FakeAddresses(models.Model):
address = models.ForeignKey(Address)
note = ...
Multi-table Inheritance is what you're after, if you need to work with objects of type Post
without worrying about what type of Post it is. There will be an overhead of a join if accessing any of the Post fields from the subclass; but the overhead will be minimal. It is a unique index join, which should be incredibly quick.
如果您需要使用Post类型的对象而不必担心Post的类型,那么多表继承就是您所追求的。如果从子类访问任何Post字段,将会有连接开销;但开销很小。它是一个独特的索引连接,应该非常快。
Just make sure, that if you need access to the Post
, that you use select_related
on the queryset.
只需确保,如果您需要访问Post,则在查询集上使用select_related。
Events.objects.select_related(depth=1)
That will avoid additional queries to fetch the parent data, but will result in the join occurring. So only use select related if you need the Post.
这将避免额外的查询来获取父数据,但会导致连接发生。因此,如果您需要Post,请仅使用select相关。
Two final notes; if a Post can be both an Announcement AND an Event, then you need to do the traditional thing, and link to Post via a ForeignKey. No subclassing will work in this case.
两个最后的笔记;如果帖子既可以是公告也可以是活动,那么你需要做传统的事情,并通过ForeignKey链接到Post。在这种情况下,没有子类化。
The last thing is that if the joins are performance critical between the parent and the children, you should use abstract inheritance; and use Generic Relations to refer to the abstract Posts from a table that is much less performance critical.
最后一点是,如果连接在父级和子级之间具有性能关键,则应该使用抽象继承;并使用通用关系来引用性能要低得多的表中的抽象帖子。
Generic Relations essentially store data like this:
通用关系本质上存储如下数据:
class GenericRelation(models.Model):
model = ...
model_key = ...
DeletedPosts(models.Model):
post = models.ForeignKey(GenericRelation)
That will be a lot more complicated to join in SQL (django helps you with that), but it will also be less performant than a simple OneToOne join. You should only need to go down this route if the OneToOne joins are severely harming performance of your application which is probably unlikely.
加入SQL会更复杂(django可以帮助你),但它的性能也不如简单的OneToOne连接。如果OneToOne加入会严重损害应用程序的性能,那么您应该只需沿着这条路走下去,这可能不太可能。
#2
2
Generic relationships and foreign keys are your friend in your path to succeed. Define an intermediate model where one side is generic, then the other side will get a related list of polymorphic models. It's just a little more complicated than a standard m2m join model, in that the generic side has two columns, one to ContentType (actually a FK) and the other to the PK of the actual linked model instance. You can also restrict the models to be linked with using standard FK parameters. You'll get used with it quickly.
通用关系和外键是您成功之路上的朋友。定义一个中间模型,其中一方是通用的,然后另一方将获得相关的多态模型列表。它比标准m2m连接模型稍微复杂一点,因为通用端有两列,一列是ContentType(实际上是FK),另一列是实际链接模型实例的PK。您还可以使用标准FK参数限制要链接的模型。你会很快习惯它。
(now that I get an actual keyboard to write with, here there is the example:)
(现在我得到一个真正的键盘来写,这里有例子:)
class Post(models.Model):
class Meta: abstract = True
CONCRETE_CLASSES = ('announcement', 'event',)
removed_from = generic.GenericRelation('OwnerRemovedPost',
content_type_field='content_type',
object_id_field='post_id',
)
class Announcement(Post): pass
class Event(Post): pass
class Owner(models.Model):
# non-polymorphic m2m
added_events = models.ManyToManyField(Event, null=True)
# polymorphic m2m-like property
def removed_posts(self):
# can't use ManyToManyField with through.
# can't return a QuerySet b/c it would be a union.
return [i.post for i in self.removed_post_items.all()]
def removed_events(self):
# using Post's GenericRelation
return Event.objects.filter(removed_from__owner=self)
class OwnerRemovedPost(models.Model):
content_type = models.ForeignKey(ContentType,
limit_choices_to={'name__in': Post.CONCRETE_CLASSES},
)
post_id = models.PositiveIntegerField()
post = generic.GenericForeignKey('content_type', 'post_id')
owner = models.ForeignKey(Owner, related_name='removed_post_items')
class Meta:
unique_together = (('content_type', 'post_id'),) # to fake FK constraint
You can't filter into the related collection like a classic many-to-many, but with the proper methods in Owner
, and using the concrete classes' managers smartly, you get everywhere you want.
你不能像经典的多对多一样过滤到相关的集合,但是在Owner中使用适当的方法,并且巧妙地使用具体类的管理器,你可以随心所欲。