Как вычислить значения дочерних таблиц с помощью 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.

  1. context['avgs'] = Professor.objects.all().evaluation_set.all().annotate(avg_satisfactions=Avg('satisfaction'))
  2. context['avgs'] = Professor.objects.prefetch_related().all().annotate(avg_satisfactions=Avg('satisfaction'))
  3. 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
Вернуться на верх