Загрузите экземпляры иностранных моделей в queryset.values()

У меня есть набор запросов, который группирует и агрегирует некоторые данные:

EmployeeAssessment.objects.all().annotate(month=TruncMonth('assessment_date')).values(
    'month', 'assessed_employee', 'subject'
).annotate(average_score=Sum('scores__score') / Count('scores__score', distinct=True))

И из-за .values() я получаю assessed_employee и subject как ID, а не как экземпляры модели

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

Моя проблема заключается в том, что мне нужно получить два вложенных объекта: "assessed_employee" и "subject". Оба они являются ForeignKeys

Я использую сериализатор DRF следующим образом:

class EmployeeAverageScoreSerializer(Serializer):
    month = DateField()
    assessed_employee = EmployeeSerializer()  # <- ModelSerializer
    subject = AssessmentSubjectSerializer()  # <- ModelSerializer
    average_score = FloatField()

Хорошо, если у меня будет несколько подзапросов или что-то в этом роде, но мне нужен один набор запросов, который все еще может быть постраничным.

Как я могу загрузить модели по ID после применения ".values" или какой другой способ я могу использовать для получения этих данных без потери экземпляров модели в результирующем наборе запросов?

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

Решением является использование Subquery для запроса агрегированных значений. Сначала я использую distinct() для получения значений, одинаковых для всех агрегированных групп, а затем я использую Subquery для обогащения этих строк агрегированными данными:

average_score_subquery = (
    Employee.objects.all()
    # ... here is some filtering we need
    .annotate(month=TruncMonth('assessment_date'))
    .filter(  # First of all, filter a subquery by our row values
        assessed_employee=OuterRef('assessed_employee'),
        subject=OuterRef('subject'),
        month=OuterRef('month'),
    )  # Then use values() to group them by distinct values
    .values('assessed_employee', 'subject', 'month')
    .annotate(  # Finally, add aggregated data to the queryset
        average_score=Cast(Sum('scores__score'), output_field=FloatField())
        / Count('pk', distinct=True)
    )
    .values('average_score')  # Subquery must return no more than 1 column, so we use values() again to narrow the subquery to our aggregated column
)
return (
    Employee.objects.all()
    # ... here is some filtering, THE SAME FILTERING AS ABOVE
    .filter(assessed_employee=employee)
    .annotate(month=TruncMonth('assessment_date')) # I can't reuse annotation from subquery used to get aggregated data as subquery must return 1 column, so I do this again as I need it too
    .distinct('subject', 'assessed_employee', 'month')  # Distinct values
    .annotate(average_score=Subquery(average_score_subquery.values('average_score')[:1]))  # add the aggregated column from the subquery
    .order_by('-month')
)
Вернуться на верх