Django查询没有外键的反向关系

时间:2014-02-16 00:06:36

标签: django django-models django-queryset

我目前正在开发一个Django库来管理多个分辨率的图像(django-multires),我无法优化保留关系查询。在我解释这个问题之前,让我试着解释一下我想要实现的目标。

背景

这个想法是存储图像的多个分辨率,但是根据图像路径而不是外键来保持对原始图像的引用。我认为一个例子会更有意义:

# the goal is to store multiple resolutions for 'image' field
class FooModel(models.Model):
    image = MultiresImageField(...)

# MultiresImage.source will be the identical to FooModel.image
# so MultiresImage.source will act sort of like a foreign key
class MultiresImage(models.Model):
    source = models.ImageField(...)
    ...

使用此方法而不是使用外键(或通用外键)链接到源图像允许向源模型添加多个MultiresImageField

class FooModel(models.Model):
    image  = MultiresImageField(...)
    image2 = MultiresImageField(...)

现在假设您需要为源模型中的图像字段获取所有不同的分辨率:

foo = FooModel(...)
foo.image.get_all_multires_images()
# which behind the scenes will do something similar to
return MultiresImage.objects.filter(source=foo.image.name)

这很有效,直到你需要查询多个FooModel实例,在这种情况下,对于每个模型实例,我被迫进行数据库查找以获得该模型的所有分辨率:

sources = FooModel.objects.filter(...)
for source in sources:
    # this incurs a db query
    foo.image.get_all_multires_images()

通常我会使用prefetch_related来进行性能优化,但是在这里我不能,因为我的多重模型没有源模型的外键,因此源模型上不存在反向关系。 / p>

问题

所以我的问题是如何优化上述查询?

目前的一些想法

  • 由于我正在制作自定义模型字段,我可以使用contribute_to_class手动向源模型添加反向关系,但我无法弄清楚如何?
  • 另一个问题是在Django>=1.7中使用Prefetch API,但我也无法弄清楚如何做到这一点。

1 个答案:

答案 0 :(得分:0)

最简单但可能不是最优雅的解决方案是编写一种辅助类:

def prefetch_related_images(image_queryset):
    multires_images = MultiresImage.objects.filter(source__in=[image.name for image in image_queryset ])
    for image in image_queryset:
        image.multires_images = []
        for multires_image in multires_images:
            if multires_image.source == image.name:
                image.multires_images.append(multires_image)

而且,一个更优雅的解决方案可能与您对contrib_to_class的想法一致。你为什么不尝试像通用关系这样的东西:

class MultiresImage(models.Model):
    source = models.ImageField(...)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    image_target= GenericForeignKey('content_type', 'object_id')

然后像那样修改contrib_to_class:

def contribute_to_class(self, cls, name):
    """
    Attached necessary garbage collection signals.
    """
    super(MultiresImageField, self).contribute_to_class(cls, name)

    setattr(cls,'%_source',  GenericRelation(MultiresImage)

然后处理通过信号管理关系的细节(就像你已经做的那样)。