diff --git a/generic/views.py b/generic/views.py index 612f3a5e..a956da04 100644 --- a/generic/views.py +++ b/generic/views.py @@ -1902,27 +1902,31 @@ class ExamViews(View, LoginRequiredMixin): @method_decorator(login_required) def exam_list(self, request, all=False, collection=None): + # Build a single queryset and annotate candidate counts to avoid per-item queries if collection is None: if not self.check_user_access(request.user): - # raise PermissionDenied - exam_list = self.Exam.objects.filter( - author__id=request.user.id, exam_mode=True - ).order_by("name") - - - exam_list = exam_list | self.Exam.objects.filter( - markers__id=request.user.id, exam_mode=True - ).order_by("name") - else: + # Use a single queryset with Q to include authored or marked exams exam_list = ( - self.Exam.objects.prefetch_related( - "valid_user_users", "valid_cid_users" + self.Exam.objects.filter( + (Q(author__id=request.user.id) | Q(markers__id=request.user.id)), + exam_mode=True, ) - .filter(exam_mode=True) + .distinct() .order_by("name") ) + else: + exam_list = self.Exam.objects.filter(exam_mode=True).order_by("name") else: - exam_list = self.Exam.objects.filter(exam_mode=True, examcollection__in=[collection]).order_by("name") + exam_list = ( + self.Exam.objects.filter(exam_mode=True, examcollection__in=[collection]) + .order_by("name") + ) + + # Annotate counts for template usage so `.count()` is not called per-object + exam_list = exam_list.annotate( + valid_cid_users_count=Count("valid_cid_users", filter=Q(valid_cid_users__active=True)), + valid_user_users_count=Count("valid_user_users", filter=Q(valid_user_users__is_active=True)), + ).select_related("examcollection").prefetch_related("author") if not all: exam_list = exam_list.filter(archive=False).order_by("name")