Как вычислить значения дочерних таблиц с помощью ForeignKey из родительской таблицы
Моя цель - получить средние значения величин, хранящихся в дочерних таблицах, в зависимости от каждой родительской таблицы. В моем случае я хочу получить средние значения удовлетворенности, хранящиеся в модели Evaluation (дочерней) в зависимости от каждой модели Professor (родительской).
models.py
class Professor(models.Model):
college = models.CharField(max_length=50, choices=COLLEGE_CHOICES)
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Evaluation(models.Model):
name = models.ForeignKey(Professor, on_delete=models.CASCADE, related_name='evaluation_names', null=True)
satisfaction = models.IntegerField(choices=SATISFACTION_CHOICES)
def __str__(self):
return self.comment
views.py
class ProfessorDetail(generic.DetailView):
model = Professor
context_object_name = "professor_detail"
template_name = "professors/professor_detail.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['avgs'] = Professor.objects.all().evaluation_set.all().annotate(avg_satisfactions=Avg('satisfaction'))
return context
professors/professor_detail.html
{% for evaluation in avgs %}
<p>{{ evaluation.avg_satisfactions }}</p>
{% endfor %}
Я попробовал следующие коды для views.py.
context['avgs'] = Professor.objects.all().evaluation_set.all().annotate(avg_satisfactions=Avg('satisfaction'))
context['avgs'] = Professor.objects.prefetch_related().all().annotate(avg_satisfactions=Avg('satisfaction'))
context['avgs'] = Professor.objects.all().prefetch_related(Prefetch('evaluation_set', queryset=Evaluation.objects.all().annotate(avg_satisfactions=Avg('satisfaction'))))
Но все они не работают.
Для аннотации среднего удовлетворения вам необходимо сделать
Professor.objects.annotate(avg_satisfaction=Avg("evaluation_names__satisfaction"))
"evaluation_names" - имя связанной таблицы, а "satisfaction" - столбец для вычисления среднего значения.
Я также хочу прояснить использование метода prefetch_related, поскольку вы пытались использовать его для решения своей проблемы.
Метод prefetch_related используется для уменьшения количества запросов при итерации по набору запросов.
Представьте, что вы хотите отобразить все оценки для каждого профессора. Вы бы обратились к current_professor.evaluation.all()
for current_professor in Professor.objects.all()
.
Это создаст sql-запрос, эквивалентный Evaluation.objects.filter(name=current_professor)
для каждого профессора, что приведет к n+1 запросу к БД (с n - количеством профессоров).
Prefetch_related здесь, чтобы избежать этой проблемы, Professor.objects.prefetch_related(Prefetch("evaluation_names"))
будет префетчить и кэшировать соответствующие запросы, так что доступ к current_professor.evaluation.all()
не делает никаких дополнительных запросов.
Если вы хотите использовать только средние данные соответствующего профессора на детальной странице, вы можете отфильтровать следующим образом:
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
prof_list = Professor.objects.annotate(avg_satisfaction=Avg("evaluation_names__satisfaction"))
context['avg'] = prof_list.get(pk=self.kwargs['pk']).avg_satisfaction
return context