抽象基类的Django模型字段

时间:2011-04-13 21:17:50

标签: django class abstract base django-queryset

我在堆栈溢出周围搜索了这个(可能是简单的)问题的答案,但我看到的大多数解决方案看起来过于复杂且难以理解。

我有一个模型“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()的调用比单独直接访问事件和公告列表要慢,不是吗?这里有什么建议吗?

非常感谢您阅读本文。

2 个答案:

答案 0 :(得分:13)

Django中有两种模型子类 - Abstract Base Classes;和多表继承。

抽象基类本身并不使用,也没有数据库表或任何形式的标识。它们只是缩短代码的一种方法,可以将代码中的公共字段分组,而不是在数据库中。

例如:

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

    class Meta:
        abstract = True


class Employee(Address):
    name = ...

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

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

现在,通过多表继承,(从地址中删除abstract = True),地址确实拥有一个自己的表。这将产生3个不同的表格; AddressEmployerEmployee。雇主和员工都将拥有一个唯一的外键(OneToOneField)回到地址。

您现在可以参考地址而不必担心它的地址类型。

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,则在查询集上使用select_related

Events.objects.select_related(depth=1)

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

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

最后一点是,如果连接在父级和子级之间具有性能关键,则应使用抽象继承;并使用Generic Relations来从表格中引用抽象帖子,这些表格的性能要低得多。

Generic Relations基本上存储如下数据:

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


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

加入SQL会更复杂(django可以帮助你),但它的性能也不如简单的OneToOne连接。如果OneToOne加入会严重损害应用程序的性能,那么您应该只需沿着这条路走下去,这可能不太可能。

答案 1 :(得分: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中的正确方法,并且巧妙地使用具体类的管理器,你可以随心所欲。