Аннотирование набора запросов Django с помощью 'related_name' через отношения "многие-ко-многим" (NO 'Count', 'Avg', 'Max', ...)

В моем коде есть модели, похожие на следующие:

class CompanyProject(models.Model):
    """ This class holds project related information read in from the 'project'
        custom 'manage.py' command.
    """

    project_number = models.IntegerField(blank=False, null=False, unique=True)
    project_worktype = models.CharField(blank=True, max_length=255, null=True)
    created_at = models.DateTimeField(auto_now_add=True, blank=False, null=False)
    updated_at = models.DateTimeField(auto_now=True, blank=False, null=False)
    last_seen = models.DateField(blank=False, null=False)


    def get_project_subtypes(self):
        subtypes = self.project_subtype.all()
        return [ subtype.project_subtype for subtype in subtypes ]

    class Meta:
        ordering = ['project_number']

class CompanySubType(models.Model):
    class CompanySubTypeChoices(models.TextChoices):
        G1A = '1A', _('1A')
        G1B = '1B', _('1B')
        G2A = '2A', _('2A')
        G2B = '2B', _('2B')
        G3A = '3A', _('3A')
        G3B = '3B', _('3B')

    company_project = models.ManyToManyField(CompanyProject, related_name='project_subtype')
    project_subtype = models.CharField(blank=False, choices=CompanySubTypeChoices.choices, max_length=2, null=False)


class ListEntry(models.Model):

    list_project = models.OneToOneField(CompanyProject, on_delete=models.CASCADE, related_name='list_project')
    list_reviewer = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='+')

Я хотел бы вернуть набор аннотированных ListEntry объектов, аннотированных списком ВСЕХ подтипов проекта, идентифицированных с ListEntry проектом.

В конечном итоге, мне нужно иметь возможность передать эти аннотированные данные в сериализатор Django REST Framework, но сначала мне нужно заставить аннотацию работать так, как я этого хочу.

Моя проблема в том, что я могу аннотировать просто отлично, делая что-то вроде этого:

list_entry_qs = ListEntry.objects.prefetch_related('list_project', 'list_reviewer').annotate(subtypes=F('list_pmatt__project_subtype__project_subtype')).all()

и он работает просто отлично. Проблема заключается в том, что набор запросов, возвращаемый этой командой, дублирует объект list_entry, если он имеет более одной аннотации subtype.

Например, если проект с номером 1234 имеет два подтипа проекта, '1A' и '3A', я получу два list_entry объекта: один с subtype аннотацией '1A' и отдельный list_entry объект для того же проекта с subtype аннотацией '3A'.

Я хотел бы получить ОДИН объект, возвращенный для проекта 1234, который имеет subtype аннотацию '1A', '2A', который в конечном итоге будет сериализован в JSON объект.

Должен быть простой способ сделать это?

OK, Потратив буквально 8 часов на это, я наконец нашел, как это сделать:

from django.contrib.postgres.aggregates.general import ArrayAgg

q = ListEntry.objects.prefetch_related('list_pmatt', 'list_reviewer', 'list_pmatt__project_subtype').annotate(my_subtype=ArrayAgg('list_pmatt__project_subtype__project_subtype'))

>>> q[2].my_subtype
['1A', '3A']
Вернуться на верх