Django - получение объектов по последней дате для каждой группы - PersonPhoto

Моя БД содержит изображения паспортов разных людей. Что-то вроде:

class Person(models.Model):
    pass

class PersonPhoto(models.Model):
    date_captured = models.DateField()
    person = models.ForeignKey(Person, null=False)

Я хочу извлечь для каждого человека все изображения с последней даты, когда он был сфотографирован. Так, если у человека A есть фотографии от 5, 5, 9, 11, 11 августа, а у человека B есть изображения от 7, 9, 13, 13, 19, 19 августа, то я хочу извлечь оба изображения от 11 августа для человека A, и оба изображения от 19 августа для человека B.

В настоящее время я делаю это следующим образом:

specific_dates_queryset = Q()
for photo in PersonPhoto.objects.all().annotate(max_date=Max('date_captured')).values('person_id'):
    specific_dates_queryset |= Q(person_id=photo["person_id"], date_captured=photo["max_date"])


for photo in PersonPhoto.objects.filter(specific_dates_queryset).order_by("person_id"):
    print(f"image for person {photo.person_id}, of date {photo.date_captured}")

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

Есть ли более простое решение, которое делает все внутри БД и позволяет избежать избыточных запросов и получения данных?

Вы можете получить все связанные фотографии человека и отфильтровать их на основе последней даты, указанной на фотографии, следующим образом:

from django.db.models import F, Max, Prefetch

person_qs = Person.objects.annotate(
    latest_photo_date=Max('personphoto__date_captured')
).prefetch_related(
    Prefetch(
        'personphoto_set',
        queryset=PersonPhoto.objects.annotate(
            person_latest_photo_captured=Max('person__personphoto__date_captured')
        ).filter(
            date_captured=F('person_latest_photo_captured')
        ),
        to_attr='latest_photos',
    )
)

Все последние экземпляры PersonPhoto будут доступны в виде списка в атрибуте latest_photos экземпляра Person, так что вы можете получить к ним доступ следующим образом:

for person in person_qs:
    print(f'Latest images for {person.name} taken on {person.latest_photo_date}:')
    for photo in person.latest_photos:
        print(f'Photo ID: {photo.id} - Captured at: {photo.date_captured}')
    print()

Выход:

Latest images for B taken on 2021-08-19:
Photo ID: 10 - Captured at: 2021-08-19
Photo ID: 11 - Captured at: 2021-08-19

Latest images for A taken on 2021-08-11:
Photo ID: 5 - Captured at: 2021-08-11
Photo ID: 4 - Captured at: 2021-08-11

Всего будет сделано два запроса, один для списка персон, а другой для получения всех отфильтрованных связанных фотографий каждой персоны.

Простой способ сделать это в одном запросе - аннотировать каждую фотографию последней датой для связанного с ней человека, а затем отфильтровать по аннотации. Это должно вернуть все желаемые PersonPhoto в наборе запросов

from django.db.models import Max, F

PersonPhoto.objects.annotate(
   latest=Max('person__personphoto__date_captured')
).filter(
    date_captured=F('latest')
)

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

Вернуться на верх