Repository: e-valuation/EvaP

Issue:
Participant count incorrect on results index
E.g. for "Lehrerworkshop bei der Endrunde des Bundeswettbewerb Informatik (Sommersemester 2017)" the overview shows "0/0", or for MINT-Camps it says "1/1" or "3/3" participants - the correct numbers are 12, 24 and 22.

![capture](https://user-images.githubusercontent.com/2188983/43835155-2096edf6-9b11-11e8-98ea-b6f8e62231fa.PNG)



Discussion:
The mentioned MINT-Camps aren't private courses, so this is a general issue.
When caching the results, `participants` and `voters` are counted, which is correct when publishing for the first time, but not when recalculating the cache later on. Some users might have already been deleted so `num_participants` and `num_voters` have to be used instead.
Oh, true. I thought they were private, as other Schülerklub evaluations sometimes are - but you're right: the common theme is the set of external participants.

Resolving patch:
diff --git a/evap/results/views.py b/evap/results/views.py
--- a/evap/results/views.py
+++ b/evap/results/views.py
@@ -51,8 +51,9 @@ def warm_up_template_cache(courses):
 
 def get_courses_with_prefetched_data(courses):
     if isinstance(courses, QuerySet):
+        participant_counts = courses.annotate(num_participants=Count("participants")).values_list("num_participants", flat=True)
+        voter_counts = courses.annotate(num_voters=Count("voters")).values_list("num_voters", flat=True)
         courses = (courses
-            .annotate(num_participants=Count("participants", distinct=True), num_voters=Count("voters", distinct=True))
             .select_related("type")
             .prefetch_related(
                 "degrees",
@@ -60,7 +61,10 @@ def get_courses_with_prefetched_data(courses):
                 Prefetch("contributions", queryset=Contribution.objects.filter(responsible=True).select_related("contributor"), to_attr="responsible_contributions")
             )
         )
-        for course in courses:
+        for course, participant_count, voter_count in zip(courses, participant_counts, voter_counts):
+            if course._participant_count is None:
+                course.num_participants = participant_count
+                course.num_voters = voter_count
             course.responsible_contributors = [contribution.contributor for contribution in course.responsible_contributions]
     for course in courses:
         if not course.is_single_result:


Test patch:
diff --git a/evap/results/tests/test_views.py b/evap/results/tests/test_views.py
--- a/evap/results/tests/test_views.py
+++ b/evap/results/tests/test_views.py
@@ -1,8 +1,10 @@
 from django.contrib.auth.models import Group
+from django.test.testcases import TestCase
 from model_mommy import mommy
 
 from evap.evaluation.models import Semester, UserProfile, Course, Contribution, Questionnaire, Degree, Question, RatingAnswerCounter
 from evap.evaluation.tests.tools import ViewTest, WebTest
+from evap.results.views import get_courses_with_prefetched_data
 
 import random
 
@@ -16,6 +18,25 @@ def setUpTestData(cls):
         mommy.make(UserProfile, username='manager', email="manager@institution.example.com")
 
 
+class TestGetCoursesWithPrefetchedData(TestCase):
+    def test_returns_correct_participant_count(self):
+        """ Regression test for #1248 """
+        participants = mommy.make(UserProfile, _quantity=2)
+        course = mommy.make(Course,
+            state='published', _participant_count=2, _voter_count=2,
+            participants=participants, voters=participants
+        )
+        participants[0].delete()
+        course = Course.objects.get(pk=course.pk)
+
+        courses = get_courses_with_prefetched_data([course])
+        self.assertEqual(courses[0].num_participants, 2)
+        self.assertEqual(courses[0].num_voters, 2)
+        courses = get_courses_with_prefetched_data(Course.objects.filter(pk=course.pk))
+        self.assertEqual(courses[0].num_participants, 2)
+        self.assertEqual(courses[0].num_voters, 2)
+
+
 class TestResultsViewContributionWarning(WebTest):
     @classmethod
     def setUpTestData(cls):