Django查询具有不同的ForeignKey

时间:2019-04-05 12:56:32

标签: python django django-queryset

我有两个模型:

class Gallery(models.Model):
    site = models.ForeignKey(Site, related_name='galleries', on_delete=models.CASCADE)
    created = models.DateTimeField(_('created'), auto_now_add=True)

class Photo(models.Model):
    image = ImageField(verbose_name=_('image'), upload_to=upload_path)
    gallery = models.ForeignKey(Gallery, related_name='photos', on_delete=models.CASCADE)
    created = models.DateTimeField(_('created'), auto_now_add=True,)

我想获取最后添加的三张照片。每张照片应分配到不同的画廊。

此解决方案有效,但速度也很慢。我可以使其工作更快吗?

limit = 3
photos = Photo.objects.annotate(
    max_gallery_created=Max('gallery__photos__created'),
).filter(
    gallery__site=site,
    created=F('max_gallery_created'),
).order_by(
    '-max_gallery_created',
)[:limit]

如果我从最近创建的三个画廊中的每一个获得最后一张添加的照片,我也将感到满意。 我尝试过这种方式:

galleries = Gallery.objects.filter(site=site).order_by('-created').values_list('id')[:limit]
photos = Photo.objects.filter(id__in=galleries)

但是它不能防止画廊再次发生。我曾考虑过以某种方式使用distinct(),但我不知道该怎么做。

Django 1.11。

编辑:

我想我应该为期望的结果提供一些例子。

- Gallery1 (created 10.01.2019)
    - PhotoA (created: 15.01.2019 at 12:00)
    - PhotoB (created: 15.01.2019 at 11:00)
    - PhotoC (created: 13.01.2019 at 11:00)
- Gallery2 (created 09.01.2019)
    - PhotoD (created: 13.01.2019 at 10:00)
    - PhotoE (created: 12.01.2019 at 10:00)
    - PhotoF (created: 10.01.2019 at 10:00)
- Gallery3 (created 08.01.2019)
    - PhotoG (created: 14.01.2019 at 11:00)
    - PhotoH (created: 14.01.2019 at 11:00)
    - PhotoI (created: 14.01.2019 at 11:00)
- Gallery4 (created 07.01.2019)
    - PhotoJ (created: 14.01.2019 at 12:00)
    - PhotoK (created: 12.01.2019 at 10:00)
    - PhotoL (created: 12.01.2019 at 10:00)

在这种情况下

我现在得到的是: PhotoA, PhotoB, PhotoJ

我想要得到什么: PhotoA, PhotoJ, PhotoG

如果我得到: PhotoA, PhotoD, PhotoG

2 个答案:

答案 0 :(得分:1)

从Django 1.11开始,您应该可以使用Subquery Expressionsprefetch_related()的组合来做到这一点:

from django.db.models import OuterRef, Subquery, Prefetch

subquery = Subquery(
    Photo.objects.filter(
        gallery_id=OuterRef('gallery_id')
    ).order_by('-created').values_list('id', flat=True)[:3])

galleries = Gallery.objects.order_by('-created').prefetch_related(
    Prefetch('photos',
        queryset=Photo.objects.filter(id__in=subquery).order_by('-created')))

for gallery in galleries:
    for photo in gallery.photos.all(): # should now be the latest 3
        print(photo)

如果您想获取最新照片的属性不同于photos,则可以使用Prefetch classto_attr参数。

答案 1 :(得分:1)

如果使用的是PostgreSQL,则可以使用一个嵌套查询从不同的画廊获取最新的三张照片。应该将任何其他过滤器添加到内部查询中,以提高效率:

photos = (Photo.objects    
    .filter(id__in=Photo.objects.filter(gallery__site=site)
                                .order_by('gallery', '-created')
                                .distinct('gallery').values('id'))
    .order_by('-created')[:3]
)

对于其他后端,您可以使用Bernhard提出的预取子查询,尽管他的解决方案将为您提供按画廊日期排序的所有画廊,每张画廊中都有最新的三张照片。

使用附加过滤器可以按照片日期而不是画廊日期对画廊进行排序和限制,您可以获得与上述相同的结果:最新的3张照片,每个画廊只能容纳一张照片:

prefetch = Prefetch('photos', queryset=Photo.objects.filter(id__in=
    Subquery(Photo.objects.filter(
        gallery_id=OuterRef('gallery_id')
    ).order_by('-created').values_list('id', flat=True)[:1])))


galleries = (Gallery.objects
    .filter(id__in=Gallery.objects
                    .order_by('-photos__created')
                    .distinct()[:3]
            )
    .order_by('-created')
    .prefetch_related(prefetch)
)