Загрузите экземпляры иностранных моделей в 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')
)