Django的:预取相关的一个GenericForeignKey的对象(django: prefet

2019-07-31 16:16发布

假设我有一个模型BoxGenericForeignKey指向任何一个Apple实例或Chocolate实例。 AppleChocolate ,反过来,有ForeignKeys到FarmFactory分别。 我想显示的列表Box ES,为此我需要访问FarmFactory 。 如何做到这一点在尽可能少的数据库查询越好?

最小的说明性示例:

class Farm(Model):
    ...

class Apple(Model):
    farm = ForeignKey(Farm)
    ...

class Factory(Model):
    ...

class Chocolate(Model):
    factory = ForeignKey(Factory)
    ...

class Box(Model)
    content_type = ForeignKey(ContentType)
    object_id = PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    ...

    def __unicode__(self):
        if self.content_type == ContentType.objects.get_for_model(Apple):
            apple = self.content_object
            return "Apple {} from Farm {}".format(apple, apple.farm)
        elif self.content_type == ContentType.objects.get_for_model(Chocolate):
            chocolate = self.content_object
            return "Chocolate {} from Factory {}".format(chocolate, chocolate.factory)

下面是我试了几件事情。 在所有这些例子中,N是盒的数目。 查询计数假定ContentType S代表AppleChocolate已经被缓存,所以get_for_model()调用不会打DB。

1)朴素:

print [box for box in Box.objects.all()]

但这1(取盒)+ N(取苹果或巧克力每盒)+ N(取农场每个苹果和工厂每个巧克力)查询。

2) select_related没有帮助在这里,因为Box.content_objectGenericForeignKey

3)1.4的django的, prefetch_related可以取GenericForeignKey秒。

print [box for box in Box.objects.prefetch_related('content_object').all()]

但这1(取盒)+ 2(取所有箱苹果和巧克力)+ N(取农场每个苹果和工厂每个巧克力)查询。

4)显然prefetch_related是不够聪明遵循GenericForeignKeys的ForeignKeys。 如果我尝试:

print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]

它理所当然地抱怨说, Chocolate对象没有一个farm场,反之亦然。

5)我可以这样做:

apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes_with_apples = Box.objects.filter(content_type=apple_ctype).prefetch_related('content_object__farm')
boxes_with_chocolates = Box.objects.filter(content_type=chocolate_ctype).prefetch_related('content_object__factory')

这并不1(取盒)+ 2(取为所有盒苹果和巧克力)+ 2(取农场所有苹果和工厂的所有巧克力)查询。 缺点是,我有合并和排序两个查询集( boxes_with_applesboxes_with_chocolates手动)。 在我的实际应用中,我在一个分页的ModelAdmin显示这些箱子。 这不是明显的如何这个解决方案有整合。 也许我可以写一个自定义分页程序透明地做到这一点缓存?

6)我可以凑齐基于什么这个那个也做O(1)查询。 但我宁愿不具有内部部件(乱_content_object_cache )如果我能避免它。

总结: 打印盒需要访问GenericForeignKey的ForeignKeys。 我怎样才能打印传单N盒在O(1)查询? 是(5)最好我能做到的,或者是有一个简单的解决方案?

加分点: 你会如何重构这个数据库架构做出这样的查询更容易吗?

Answer 1:

您可以手动执行类似prefetch_selected和使用Django的select_related方法,这将使加入数据库查询。

apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes = Box.objects.all()
content_objects = {}
# apples
content_objects[apple_ctype.id] = Apple.objects.select_related(
    'farm').in_bulk(
        [b.object_id for b in boxes if b.content_type == apple_ctype]
    )
# chocolates
content_objects[chocolate_ctype.id] = Chocolate.objects.select_related(
    'factory').in_bulk(
        [b.object_id for b in boxes if b.content_type == chocolate_ctype]
    )

这应该使只有3个查询( get_for_model查询被省略)。 该in_bulk方法返回您格式的字典{ID:模型}。 因此,为了让您的content_object你需要像代码:

content_obj = content_objects[box.content_type_id][box.object_id]

但是我不知道如果这个代码将越快,你的O(5)的解决方案,因为它需要在框的其他迭代查询集并且还生成与查询WHERE id IN (...)声明

但是如果你只用从框模型字段进行排序框,你可以填写content_objects分页后快译通。 但是,你需要通过content_objects__unicode__莫名其妙

你会如何重构这个数据库架构做出这样的查询更容易吗?

我们有相似的结构。 我们店content_objectBox ,但不是object_idcontent_object我们使用ForeignKey(Box)AppleChocolate 。 在Box我们有一个get_object方法返回苹果或巧克力模型。 在这种情况下,我们可以使用select_related ,但我们大部分的使用情况,我们通过CONTENT_TYPE滤盒。 因此,我们有一个像你的第5选择了同样的问题。 但是,我们对Django的1.2启动项目时没有prefetch_selected。

如果你的农场/工厂重新命名为一些常见的名称,如创建者,将prefetch_related工作?

关于您的选项6

我可以说对填充任何_content_object_cache 。 如果你不喜欢对付内部可以填充自定义属性,然后使用

apple = getattr(self, 'my_custop_prop', None)
if apple is None:
    apple = self.content_object


文章来源: django: prefetch related objects of a GenericForeignKey