Django аннотирует набор запросов по полю из m2m через модель

У меня есть модели BottleType и OrganisationBottleType. И я хочу аннотировать набор запросов BottleType по полям «is_accepted» и «точки» из OrganisationBottleType.

Модель OrganizationBottleType:

class OrganisationBottleType(models.Model):
    organisation = models.ForeignKey(
        'Organisation',
        related_name='organisation_bottle_types',
        on_delete=models.CASCADE,
    )
    bottle_type = models.ForeignKey(
        'machines.BottleType',
        related_name='organisation_bottle_types',
        on_delete=models.CASCADE,
    )

    is_accepted = models.BooleanField(...)
    points = models.FloatField(...)

Предположим, у меня есть набор запросов Organization_id и BottleType, поэтому для каждого объекта из набора запросов нужно найти OrganisationBottleType, отфильтрованный по Bottle_type и организации, и аннотировать поля «is_accepted» и «points».

(При обнаружении отфильтрованного OrganisationBottleType qs может просто взять из него первый объект, поскольку предполагается, что пара полей: организация - тип_бутылки уникальна)

Моя идея состоит в том, чтобы использовать подзапрос в аннотации, но я не могу сделать это правильно.

Буду признателен за совет!


person Timofey Katalnikov    schedule 04.08.2020    source источник


Ответы (1)


Если я вас правильно понял, для каждого типа бутылки вы хотите:

  • найти его точки
  • проверьте, принято ли.

Есть два пути решения вашей проблемы:

  1. Получить набор запросов OrganisationBottleType и сопоставить объекты OrganisationBottleType с соответствующими объектами в BottleType в python с помощью объектов .prefetch_related() и Prefetch()
  2. Аннотировать набор запросов BottleType соответствующими объектами из OrganisationBottleType с помощью .annotate() и Subquery()

Оба варианта описаны ниже:

<сильный>1. Используйте .prefetch_related с объектом Prefetch

Данный:

  • queryset - BottleType набор запросов
  • organisation_id - id нужной организации

Решение:

queryset = queryset.prefetch_related(
    Prefetch(
        'organisation_bottle_types', 
        queryset= OrganisationBottleType.objects.filter(organisation_id=organisation_id)
    )
)

После этого вы можете получить желаемые данные таким образом:

for bottle_type in queryset:
    if bottle_type.organisation_bottle_types.all():
        related_object = bottle_type.organisation_bottle_types.all()[0]
        is_accepted = related_object.is_accepted
        points = related_object.points
    else:
        is_accepted = False
        points = None

<сильный>2. Используйте SubQuery

Данный:

  • queryset - BottleType набор запросов
  • organisation_id - id нужной организации

Решение:

 organisation_bottle_types = OrganisationBottleType.objects.filter(organisation_id=organisation_id, bottle_type=OuterRef('id'))
queryset = queryset.annotate(
    is_accepted=Subquery(organisation_bottle_types.values('is_accepted')[:1])
).annotate(
    points=Subquery(organisation_bottle_types.values('points')[:1], output_field=BooleanField())
)

После того, как вы можете сделать:

for bottle_type in queryset:
    is_accepted = bottle_type.is_accepted
    points = bottle_type.points

Резюме:

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

Первый вариант лучше, когда вам нужно сопоставить весь объект, а не только несколько полей (например, points и is_accepted из вашего вопроса)

person Kochetov Alexander    schedule 13.08.2020