запрос django с отличным ForeignKey - PullRequest
4 голосов
/ 05 апреля 2019

У меня есть две модели:

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)

Но это не мешает повторному появлению галерей.Я думал о том, чтобы как-то использовать Different (), но не знаю, насколько это просто.

Джанго 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 ]

1 голос
/ 05 апреля 2019

Если вы используете 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]
)

С другими бэкэндами вы можете использовать подзапрос предварительной выборки, предложенный Бернхардом, хотя его решение позволит вам отсортировать все галереи по дате галереи с последними тремя фотографиями в каждой.

С помощью дополнительного фильтра, который сортирует и ограничивает галереи по дате фото, а не по дате галереи, вы можете получить тот же результат, что и выше: последние 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)
)
1 голос
/ 05 апреля 2019

Начиная с Django 1.11, вы сможете сделать это с помощью комбинации Subquery выражений и prefetch_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, вы можете использовать to_attr аргумент Prefetch класса .

...