16

我已经在堆栈溢出中搜索了这个(可能很简单)问题的答案,但是我看到的大多数解决方案似乎都过于复杂且难以理解。

我有一个模型“Post”,它是一个抽象基类。模型“公告”和“事件”继承自 Post。

现在我在其他模型中保留了事件和公告的相关列表。例如,我在另一个模型中有“removed_events”和“removed_announcements”字段。

但是,在我的项目中,“removed_events”和“removed_announcements”的处理方式完全相同。无需区分“已删除事件”和“已删除公告”。换句话说,跟踪“removed_posts”的字段就足够了。

我不知道如何(或者可能不能)创建一个字段“removed_posts”,因为 Post 是抽象的。然而,现在我觉得我在代码中重复自己(并且不得不做很多杂乱的检查以确定我正在查看的帖子是事件还是公告并将其添加到适当的删除字段)。

这里最好的选择是什么?我可以使 Posts 非抽象,但 Post 对象本身不应该被创建,而且我认为我不能对非抽象对象强制执行此操作。

我对数据库的理解很薄弱,但我的印象是使 Post 非抽象会使数据库由于连接而复杂化。这有什么大不了的吗?

最后,在其他模型中还有其他字段,我想将相当于 event_list 和announcement_list 的内容压缩为 post_list,但这些字段确实需要消除歧义。我可以根据帖子类型过滤 post_list,但是对 filter() 的调用会比能够分别直接访问事件和公告列表要慢,不是吗?这里有什么建议吗?

非常感谢您阅读本文。

4

2 回答 2

17

Django 中有两种模型子类化——抽象基类;和多表继承。

抽象基类从不单独使用,也没有数据库表或任何形式的标识。它们只是一种缩短代码的方法,通过在代码中而不是在数据库中对公共字段集进行分组。

例如:

class Address(models.Model):
    street = ...
    city = ...

    class Meta:
        abstract = True


class Employee(Address):
    name = ...

class Employer(Address):
    employees = ...
    company_name = ...

这是一个人为的例子,但正如您所见, anEmployee不是 an Address, an 也不是Employer。它们都包含与地址相关的字段。此示例中只有两个表;Employee, 和Employer- 并且它们都包含地址的所有字段。无法在数据库级别将雇主地址与员工地址进行比较 - 地址没有自己的密钥。

现在,通过多表继承(从 Address 中删除 abstract=True),Address确实有一个自己的表。这将产生 3 个不同的表;Address, Employer, 和Employee. Employer 和 Employee 都将有一个唯一的外键 (OneToOneField) 返回到 Address。

您现在可以引用地址,而不必担心它是什么类型的地址。

for address in Address.objects.all():
    try:
        print address.employer
    except Employer.DoesNotExist: # must have been an employee
        print address.employee

每个地址都有自己的主键,这意味着它可以单独保存在第四个表中:

class FakeAddresses(models.Model):
    address = models.ForeignKey(Address)
    note = ...

多表继承是您所追求的,如果您需要使用类型的对象Post而不担心它是什么类型的 Post。如果从子类访问任何 Post 字段,将会产生连接开销;但开销将是最小的。这是一个唯一的索引连接,应该非常快。

只要确保,如果您需要访问您在查询集上Post使用select_related的 , 。

Events.objects.select_related(depth=1)

这将避免额外的查询来获取父数据,但会导致连接发生。因此,如果您需要 Post,请仅使用 select related。

两个最后的笔记;如果帖子既可以是公告也可以是事件,那么您需要做传统的事情,并通过 ForeignKey 链接到 Post。在这种情况下,没有子类化将起作用。

最后一件事是,如果连接对父子之间的性能至关重要,则应该使用抽象继承;并使用通用关系来引用表中的抽象帖子,这对性能的要求要低得多。

Generic Relations 本质上是这样存储数据的:

class GenericRelation(models.Model):
    model = ...
    model_key = ...


DeletedPosts(models.Model):
    post = models.ForeignKey(GenericRelation)

在 SQL 中加入要复杂得多(django 可以帮助你),但它的性能也比简单的 OneToOne 连接要低。如果 OneToOne 连接严重损害了您的应用程序的性能,您应该只需要沿着这条路线走,这可能不太可能。

于 2011-04-13T22:57:36.010 回答
2

通用关系和外键是您成功之路上的朋友。定义一个中间模型,其中一侧是通用的,然后另一侧将获得多态模型的相关列表。它只是比标准的 m2m 连接模型稍微复杂一点,因为通用端有两列,一列到 ContentType(实际上是 FK),另一列到实际链接模型实例的 PK。您还可以使用标准 FK 参数限制要链接的模型。你会很快适应它。

(现在我得到了一个真正的键盘来写,这里有一个例子:)

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

您不能像经典的多对多那样过滤到相关集合中,但是通过 中的正确方法Owner,并巧妙地使用具体类的管理器,您可以随心所欲。

于 2011-04-13T21:47:40.843 回答