We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `evap/results/views.py`
Content:
```
1 from collections import defaultdict
2 from statistics import median
3 
4 from django.conf import settings
5 from django.db.models import QuerySet, Prefetch, Count
6 from django.core.cache import caches
7 from django.core.cache.utils import make_template_fragment_key
8 from django.core.exceptions import PermissionDenied
9 from django.shortcuts import get_object_or_404, render
10 from django.template.loader import get_template
11 from django.contrib.auth.decorators import login_required
12 from django.utils import translation
13 
14 from evap.evaluation.models import Semester, Degree, Contribution, Course, CourseType
15 from evap.evaluation.auth import internal_required
16 from evap.results.tools import collect_results, calculate_average_distribution, distribution_to_grade, \
17     TextAnswer, TextResult, HeadingResult, get_single_result_rating_result
18 
19 
20 def get_course_result_template_fragment_cache_key(course_id, language, can_user_see_results_page):
21     return make_template_fragment_key('course_result_template_fragment', [course_id, language, can_user_see_results_page])
22 
23 
24 def delete_template_cache(course):
25     assert course.state != 'published'
26     caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'en', True))
27     caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'en', False))
28     caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'de', True))
29     caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'de', False))
30 
31 
32 def warm_up_template_cache(courses):
33     courses = get_courses_with_prefetched_data(courses)
34     current_language = translation.get_language()
35     try:
36         for course in courses:
37             assert course.state == 'published'
38             translation.activate('en')
39             get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=True))
40             get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=False))
41             translation.activate('de')
42             get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=True))
43             get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=False))
44             assert get_course_result_template_fragment_cache_key(course.id, 'en', True) in caches['results']
45             assert get_course_result_template_fragment_cache_key(course.id, 'en', False) in caches['results']
46             assert get_course_result_template_fragment_cache_key(course.id, 'de', True) in caches['results']
47             assert get_course_result_template_fragment_cache_key(course.id, 'de', False) in caches['results']
48     finally:
49         translation.activate(current_language)  # reset to previously set language to prevent unwanted side effects
50 
51 
52 def get_courses_with_prefetched_data(courses):
53     if isinstance(courses, QuerySet):
54         courses = (courses
55             .annotate(num_participants=Count("participants", distinct=True), num_voters=Count("voters", distinct=True))
56             .select_related("type")
57             .prefetch_related(
58                 "degrees",
59                 "semester",
60                 Prefetch("contributions", queryset=Contribution.objects.filter(responsible=True).select_related("contributor"), to_attr="responsible_contributions")
61             )
62         )
63         for course in courses:
64             course.responsible_contributors = [contribution.contributor for contribution in course.responsible_contributions]
65     for course in courses:
66         if not course.is_single_result:
67             course.distribution = calculate_average_distribution(course)
68             course.avg_grade = distribution_to_grade(course.distribution)
69         else:
70             course.single_result_rating_result = get_single_result_rating_result(course)
71     return courses
72 
73 
74 @internal_required
75 def index(request):
76     semesters = Semester.get_all_with_published_unarchived_results()
77     courses = Course.objects.filter(semester__in=semesters, state='published')
78     courses = [course for course in courses if course.can_user_see_course(request.user)]
79 
80     if request.user.is_reviewer:
81         additional_courses = Course.objects.filter(semester__in=semesters, state__in=['in_evaluation', 'evaluated', 'reviewed'])
82         courses += get_courses_with_prefetched_data(additional_courses)
83 
84     course_pks = [course.pk for course in courses]
85     degrees = Degree.objects.filter(courses__pk__in=course_pks).distinct()
86     course_types = CourseType.objects.filter(courses__pk__in=course_pks).distinct()
87     template_data = dict(
88         courses=courses,
89         degrees=degrees,
90         course_types=sorted(course_types, key=lambda course_type: course_type.name),
91         semesters=semesters,
92     )
93     return render(request, "results_index.html", template_data)
94 
95 
96 @login_required
97 def course_detail(request, semester_id, course_id):
98     semester = get_object_or_404(Semester, id=semester_id)
99     course = get_object_or_404(semester.course_set, id=course_id, semester=semester)
100 
101     if not course.can_user_see_results_page(request.user):
102         raise PermissionDenied
103 
104     course_result = collect_results(course)
105 
106     if request.user.is_reviewer:
107         public_view = request.GET.get('public_view') != 'false'  # if parameter is not given, show public view.
108     else:
109         public_view = request.GET.get('public_view') == 'true'  # if parameter is not given, show own view.
110 
111     # redirect to non-public view if there is none because the results have not been published
112     if not course.can_publish_rating_results:
113         public_view = False
114 
115     represented_users = list(request.user.represented_users.all()) + [request.user]
116 
117     # remove text answers if the user may not see them
118     for questionnaire_result in course_result.questionnaire_results:
119         for question_result in questionnaire_result.question_results:
120             if isinstance(question_result, TextResult):
121                 question_result.answers = [answer for answer in question_result.answers if user_can_see_text_answer(request.user, represented_users, answer, public_view)]
122         # remove empty TextResults
123         questionnaire_result.question_results = [result for result in questionnaire_result.question_results if not isinstance(result, TextResult) or len(result.answers) > 0]
124 
125     # filter empty headings
126     for questionnaire_result in course_result.questionnaire_results:
127         filtered_question_results = []
128         for index, question_result in enumerate(questionnaire_result.question_results):
129             # filter out if there are no more questions or the next question is also a heading question
130             if isinstance(question_result, HeadingResult):
131                 if index == len(questionnaire_result.question_results) - 1 or isinstance(questionnaire_result.question_results[index + 1], HeadingResult):
132                     continue
133             filtered_question_results.append(question_result)
134         questionnaire_result.question_results = filtered_question_results
135 
136     # remove empty questionnaire_results and contribution_results
137     for contribution_result in course_result.contribution_results:
138         contribution_result.questionnaire_results = [questionnaire_result for questionnaire_result in contribution_result.questionnaire_results if questionnaire_result.question_results]
139     course_result.contribution_results = [contribution_result for contribution_result in course_result.contribution_results if contribution_result.questionnaire_results]
140 
141     add_warnings(course, course_result)
142 
143     # split course_result into different lists
144     course_questionnaire_results_top = []
145     course_questionnaire_results_bottom = []
146     contributor_contribution_results = []
147     for contribution_result in course_result.contribution_results:
148         if contribution_result.contributor is None:
149             for questionnaire_result in contribution_result.questionnaire_results:
150                 if questionnaire_result.questionnaire.is_below_contributors:
151                     course_questionnaire_results_bottom.append(questionnaire_result)
152                 else:
153                     course_questionnaire_results_top.append(questionnaire_result)
154         else:
155             contributor_contribution_results.append(contribution_result)
156 
157     if not contributor_contribution_results:
158         course_questionnaire_results_top += course_questionnaire_results_bottom
159         course_questionnaire_results_bottom = []
160 
161     course.distribution = calculate_average_distribution(course)
162     course.avg_grade = distribution_to_grade(course.distribution)
163 
164     template_data = dict(
165             course=course,
166             course_questionnaire_results_top=course_questionnaire_results_top,
167             course_questionnaire_results_bottom=course_questionnaire_results_bottom,
168             contributor_contribution_results=contributor_contribution_results,
169             reviewer=request.user.is_reviewer,
170             contributor=course.is_user_contributor_or_delegate(request.user),
171             can_download_grades=request.user.can_download_grades,
172             public_view=public_view)
173     return render(request, "results_course_detail.html", template_data)
174 
175 
176 def add_warnings(course, course_result):
177     if not course.can_publish_rating_results:
178         return
179 
180     # calculate the median values of how many people answered a questionnaire across all contributions
181     questionnaire_max_answers = defaultdict(list)
182     for questionnaire_result in course_result.questionnaire_results:
183         max_answers = max((question_result.count_sum for question_result in questionnaire_result.question_results if question_result.question.is_rating_question), default=0)
184         questionnaire_max_answers[questionnaire_result.questionnaire].append(max_answers)
185 
186     questionnaire_warning_thresholds = {}
187     for questionnaire, max_answers_list in questionnaire_max_answers.items():
188         questionnaire_warning_thresholds[questionnaire] = max(settings.RESULTS_WARNING_PERCENTAGE * median(max_answers_list), settings.RESULTS_WARNING_COUNT)
189 
190     for questionnaire_result in course_result.questionnaire_results:
191         rating_results = [question_result for question_result in questionnaire_result.question_results if question_result.question.is_rating_question]
192         max_answers = max((rating_result.count_sum for rating_result in rating_results), default=0)
193         questionnaire_result.warning = 0 < max_answers < questionnaire_warning_thresholds[questionnaire_result.questionnaire]
194 
195         for rating_result in rating_results:
196             rating_result.warning = questionnaire_result.warning or rating_result.has_answers and rating_result.count_sum < questionnaire_warning_thresholds[questionnaire_result.questionnaire]
197 
198 
199 def user_can_see_text_answer(user, represented_users, text_answer, public_view=False):
200     assert text_answer.state in [TextAnswer.PRIVATE, TextAnswer.PUBLISHED]
201 
202     if public_view:
203         return False
204     if user.is_reviewer:
205         return True
206 
207     contributor = text_answer.contribution.contributor
208 
209     if text_answer.is_private:
210         return contributor == user
211 
212     if text_answer.is_published:
213         if text_answer.contribution.responsible:
214             return contributor == user or user in contributor.delegates.all()
215 
216         if contributor in represented_users:
217             return True
218         if text_answer.contribution.course.contributions.filter(
219                 contributor__in=represented_users, comment_visibility=Contribution.ALL_COMMENTS).exists():
220             return True
221         if text_answer.contribution.is_general and text_answer.contribution.course.contributions.filter(
222                 contributor__in=represented_users, comment_visibility=Contribution.COURSE_COMMENTS).exists():
223             return True
224 
225     return False
226
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 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 @@
                 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:

{"golden_diff": "diff --git a/evap/results/views.py b/evap/results/views.py\n--- a/evap/results/views.py\n+++ b/evap/results/views.py\n@@ -51,8 +51,9 @@\n \n def get_courses_with_prefetched_data(courses):\n     if isinstance(courses, QuerySet):\n+        participant_counts = courses.annotate(num_participants=Count(\"participants\")).values_list(\"num_participants\", flat=True)\n+        voter_counts = courses.annotate(num_voters=Count(\"voters\")).values_list(\"num_voters\", flat=True)\n         courses = (courses\n-            .annotate(num_participants=Count(\"participants\", distinct=True), num_voters=Count(\"voters\", distinct=True))\n             .select_related(\"type\")\n             .prefetch_related(\n                 \"degrees\",\n@@ -60,7 +61,10 @@\n                 Prefetch(\"contributions\", queryset=Contribution.objects.filter(responsible=True).select_related(\"contributor\"), to_attr=\"responsible_contributions\")\n             )\n         )\n-        for course in courses:\n+        for course, participant_count, voter_count in zip(courses, participant_counts, voter_counts):\n+            if course._participant_count is None:\n+                course.num_participants = participant_count\n+                course.num_voters = voter_count\n             course.responsible_contributors = [contribution.contributor for contribution in course.responsible_contributions]\n     for course in courses:\n         if not course.is_single_result:\n", "issue": "Participant count incorrect on results index\nE.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.\r\n\r\n![capture](https://user-images.githubusercontent.com/2188983/43835155-2096edf6-9b11-11e8-98ea-b6f8e62231fa.PNG)\r\n\n", "before_files": [{"content": "from collections import defaultdict\nfrom statistics import median\n\nfrom django.conf import settings\nfrom django.db.models import QuerySet, Prefetch, Count\nfrom django.core.cache import caches\nfrom django.core.cache.utils import make_template_fragment_key\nfrom django.core.exceptions import PermissionDenied\nfrom django.shortcuts import get_object_or_404, render\nfrom django.template.loader import get_template\nfrom django.contrib.auth.decorators import login_required\nfrom django.utils import translation\n\nfrom evap.evaluation.models import Semester, Degree, Contribution, Course, CourseType\nfrom evap.evaluation.auth import internal_required\nfrom evap.results.tools import collect_results, calculate_average_distribution, distribution_to_grade, \\\n    TextAnswer, TextResult, HeadingResult, get_single_result_rating_result\n\n\ndef get_course_result_template_fragment_cache_key(course_id, language, can_user_see_results_page):\n    return make_template_fragment_key('course_result_template_fragment', [course_id, language, can_user_see_results_page])\n\n\ndef delete_template_cache(course):\n    assert course.state != 'published'\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'en', True))\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'en', False))\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'de', True))\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'de', False))\n\n\ndef warm_up_template_cache(courses):\n    courses = get_courses_with_prefetched_data(courses)\n    current_language = translation.get_language()\n    try:\n        for course in courses:\n            assert course.state == 'published'\n            translation.activate('en')\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=True))\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=False))\n            translation.activate('de')\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=True))\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=False))\n            assert get_course_result_template_fragment_cache_key(course.id, 'en', True) in caches['results']\n            assert get_course_result_template_fragment_cache_key(course.id, 'en', False) in caches['results']\n            assert get_course_result_template_fragment_cache_key(course.id, 'de', True) in caches['results']\n            assert get_course_result_template_fragment_cache_key(course.id, 'de', False) in caches['results']\n    finally:\n        translation.activate(current_language)  # reset to previously set language to prevent unwanted side effects\n\n\ndef get_courses_with_prefetched_data(courses):\n    if isinstance(courses, QuerySet):\n        courses = (courses\n            .annotate(num_participants=Count(\"participants\", distinct=True), num_voters=Count(\"voters\", distinct=True))\n            .select_related(\"type\")\n            .prefetch_related(\n                \"degrees\",\n                \"semester\",\n                Prefetch(\"contributions\", queryset=Contribution.objects.filter(responsible=True).select_related(\"contributor\"), to_attr=\"responsible_contributions\")\n            )\n        )\n        for course in courses:\n            course.responsible_contributors = [contribution.contributor for contribution in course.responsible_contributions]\n    for course in courses:\n        if not course.is_single_result:\n            course.distribution = calculate_average_distribution(course)\n            course.avg_grade = distribution_to_grade(course.distribution)\n        else:\n            course.single_result_rating_result = get_single_result_rating_result(course)\n    return courses\n\n\n@internal_required\ndef index(request):\n    semesters = Semester.get_all_with_published_unarchived_results()\n    courses = Course.objects.filter(semester__in=semesters, state='published')\n    courses = [course for course in courses if course.can_user_see_course(request.user)]\n\n    if request.user.is_reviewer:\n        additional_courses = Course.objects.filter(semester__in=semesters, state__in=['in_evaluation', 'evaluated', 'reviewed'])\n        courses += get_courses_with_prefetched_data(additional_courses)\n\n    course_pks = [course.pk for course in courses]\n    degrees = Degree.objects.filter(courses__pk__in=course_pks).distinct()\n    course_types = CourseType.objects.filter(courses__pk__in=course_pks).distinct()\n    template_data = dict(\n        courses=courses,\n        degrees=degrees,\n        course_types=sorted(course_types, key=lambda course_type: course_type.name),\n        semesters=semesters,\n    )\n    return render(request, \"results_index.html\", template_data)\n\n\n@login_required\ndef course_detail(request, semester_id, course_id):\n    semester = get_object_or_404(Semester, id=semester_id)\n    course = get_object_or_404(semester.course_set, id=course_id, semester=semester)\n\n    if not course.can_user_see_results_page(request.user):\n        raise PermissionDenied\n\n    course_result = collect_results(course)\n\n    if request.user.is_reviewer:\n        public_view = request.GET.get('public_view') != 'false'  # if parameter is not given, show public view.\n    else:\n        public_view = request.GET.get('public_view') == 'true'  # if parameter is not given, show own view.\n\n    # redirect to non-public view if there is none because the results have not been published\n    if not course.can_publish_rating_results:\n        public_view = False\n\n    represented_users = list(request.user.represented_users.all()) + [request.user]\n\n    # remove text answers if the user may not see them\n    for questionnaire_result in course_result.questionnaire_results:\n        for question_result in questionnaire_result.question_results:\n            if isinstance(question_result, TextResult):\n                question_result.answers = [answer for answer in question_result.answers if user_can_see_text_answer(request.user, represented_users, answer, public_view)]\n        # remove empty TextResults\n        questionnaire_result.question_results = [result for result in questionnaire_result.question_results if not isinstance(result, TextResult) or len(result.answers) > 0]\n\n    # filter empty headings\n    for questionnaire_result in course_result.questionnaire_results:\n        filtered_question_results = []\n        for index, question_result in enumerate(questionnaire_result.question_results):\n            # filter out if there are no more questions or the next question is also a heading question\n            if isinstance(question_result, HeadingResult):\n                if index == len(questionnaire_result.question_results) - 1 or isinstance(questionnaire_result.question_results[index + 1], HeadingResult):\n                    continue\n            filtered_question_results.append(question_result)\n        questionnaire_result.question_results = filtered_question_results\n\n    # remove empty questionnaire_results and contribution_results\n    for contribution_result in course_result.contribution_results:\n        contribution_result.questionnaire_results = [questionnaire_result for questionnaire_result in contribution_result.questionnaire_results if questionnaire_result.question_results]\n    course_result.contribution_results = [contribution_result for contribution_result in course_result.contribution_results if contribution_result.questionnaire_results]\n\n    add_warnings(course, course_result)\n\n    # split course_result into different lists\n    course_questionnaire_results_top = []\n    course_questionnaire_results_bottom = []\n    contributor_contribution_results = []\n    for contribution_result in course_result.contribution_results:\n        if contribution_result.contributor is None:\n            for questionnaire_result in contribution_result.questionnaire_results:\n                if questionnaire_result.questionnaire.is_below_contributors:\n                    course_questionnaire_results_bottom.append(questionnaire_result)\n                else:\n                    course_questionnaire_results_top.append(questionnaire_result)\n        else:\n            contributor_contribution_results.append(contribution_result)\n\n    if not contributor_contribution_results:\n        course_questionnaire_results_top += course_questionnaire_results_bottom\n        course_questionnaire_results_bottom = []\n\n    course.distribution = calculate_average_distribution(course)\n    course.avg_grade = distribution_to_grade(course.distribution)\n\n    template_data = dict(\n            course=course,\n            course_questionnaire_results_top=course_questionnaire_results_top,\n            course_questionnaire_results_bottom=course_questionnaire_results_bottom,\n            contributor_contribution_results=contributor_contribution_results,\n            reviewer=request.user.is_reviewer,\n            contributor=course.is_user_contributor_or_delegate(request.user),\n            can_download_grades=request.user.can_download_grades,\n            public_view=public_view)\n    return render(request, \"results_course_detail.html\", template_data)\n\n\ndef add_warnings(course, course_result):\n    if not course.can_publish_rating_results:\n        return\n\n    # calculate the median values of how many people answered a questionnaire across all contributions\n    questionnaire_max_answers = defaultdict(list)\n    for questionnaire_result in course_result.questionnaire_results:\n        max_answers = max((question_result.count_sum for question_result in questionnaire_result.question_results if question_result.question.is_rating_question), default=0)\n        questionnaire_max_answers[questionnaire_result.questionnaire].append(max_answers)\n\n    questionnaire_warning_thresholds = {}\n    for questionnaire, max_answers_list in questionnaire_max_answers.items():\n        questionnaire_warning_thresholds[questionnaire] = max(settings.RESULTS_WARNING_PERCENTAGE * median(max_answers_list), settings.RESULTS_WARNING_COUNT)\n\n    for questionnaire_result in course_result.questionnaire_results:\n        rating_results = [question_result for question_result in questionnaire_result.question_results if question_result.question.is_rating_question]\n        max_answers = max((rating_result.count_sum for rating_result in rating_results), default=0)\n        questionnaire_result.warning = 0 < max_answers < questionnaire_warning_thresholds[questionnaire_result.questionnaire]\n\n        for rating_result in rating_results:\n            rating_result.warning = questionnaire_result.warning or rating_result.has_answers and rating_result.count_sum < questionnaire_warning_thresholds[questionnaire_result.questionnaire]\n\n\ndef user_can_see_text_answer(user, represented_users, text_answer, public_view=False):\n    assert text_answer.state in [TextAnswer.PRIVATE, TextAnswer.PUBLISHED]\n\n    if public_view:\n        return False\n    if user.is_reviewer:\n        return True\n\n    contributor = text_answer.contribution.contributor\n\n    if text_answer.is_private:\n        return contributor == user\n\n    if text_answer.is_published:\n        if text_answer.contribution.responsible:\n            return contributor == user or user in contributor.delegates.all()\n\n        if contributor in represented_users:\n            return True\n        if text_answer.contribution.course.contributions.filter(\n                contributor__in=represented_users, comment_visibility=Contribution.ALL_COMMENTS).exists():\n            return True\n        if text_answer.contribution.is_general and text_answer.contribution.course.contributions.filter(\n                contributor__in=represented_users, comment_visibility=Contribution.COURSE_COMMENTS).exists():\n            return True\n\n    return False\n", "path": "evap/results/views.py"}], "after_files": [{"content": "from collections import defaultdict\nfrom statistics import median\n\nfrom django.conf import settings\nfrom django.db.models import QuerySet, Prefetch, Count\nfrom django.core.cache import caches\nfrom django.core.cache.utils import make_template_fragment_key\nfrom django.core.exceptions import PermissionDenied\nfrom django.shortcuts import get_object_or_404, render\nfrom django.template.loader import get_template\nfrom django.contrib.auth.decorators import login_required\nfrom django.utils import translation\n\nfrom evap.evaluation.models import Semester, Degree, Contribution, Course, CourseType\nfrom evap.evaluation.auth import internal_required\nfrom evap.results.tools import collect_results, calculate_average_distribution, distribution_to_grade, \\\n    TextAnswer, TextResult, HeadingResult, get_single_result_rating_result\n\n\ndef get_course_result_template_fragment_cache_key(course_id, language, can_user_see_results_page):\n    return make_template_fragment_key('course_result_template_fragment', [course_id, language, can_user_see_results_page])\n\n\ndef delete_template_cache(course):\n    assert course.state != 'published'\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'en', True))\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'en', False))\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'de', True))\n    caches['results'].delete(get_course_result_template_fragment_cache_key(course.id, 'de', False))\n\n\ndef warm_up_template_cache(courses):\n    courses = get_courses_with_prefetched_data(courses)\n    current_language = translation.get_language()\n    try:\n        for course in courses:\n            assert course.state == 'published'\n            translation.activate('en')\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=True))\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=False))\n            translation.activate('de')\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=True))\n            get_template('results_index_course.html').render(dict(course=course, can_user_see_results_page=False))\n            assert get_course_result_template_fragment_cache_key(course.id, 'en', True) in caches['results']\n            assert get_course_result_template_fragment_cache_key(course.id, 'en', False) in caches['results']\n            assert get_course_result_template_fragment_cache_key(course.id, 'de', True) in caches['results']\n            assert get_course_result_template_fragment_cache_key(course.id, 'de', False) in caches['results']\n    finally:\n        translation.activate(current_language)  # reset to previously set language to prevent unwanted side effects\n\n\ndef get_courses_with_prefetched_data(courses):\n    if isinstance(courses, QuerySet):\n        participant_counts = courses.annotate(num_participants=Count(\"participants\")).values_list(\"num_participants\", flat=True)\n        voter_counts = courses.annotate(num_voters=Count(\"voters\")).values_list(\"num_voters\", flat=True)\n        courses = (courses\n            .select_related(\"type\")\n            .prefetch_related(\n                \"degrees\",\n                \"semester\",\n                Prefetch(\"contributions\", queryset=Contribution.objects.filter(responsible=True).select_related(\"contributor\"), to_attr=\"responsible_contributions\")\n            )\n        )\n        for course, participant_count, voter_count in zip(courses, participant_counts, voter_counts):\n            if course._participant_count is None:\n                course.num_participants = participant_count\n                course.num_voters = voter_count\n            course.responsible_contributors = [contribution.contributor for contribution in course.responsible_contributions]\n    for course in courses:\n        if not course.is_single_result:\n            course.distribution = calculate_average_distribution(course)\n            course.avg_grade = distribution_to_grade(course.distribution)\n        else:\n            course.single_result_rating_result = get_single_result_rating_result(course)\n    return courses\n\n\n@internal_required\ndef index(request):\n    semesters = Semester.get_all_with_published_unarchived_results()\n    courses = Course.objects.filter(semester__in=semesters, state='published')\n    courses = [course for course in courses if course.can_user_see_course(request.user)]\n\n    if request.user.is_reviewer:\n        additional_courses = Course.objects.filter(semester__in=semesters, state__in=['in_evaluation', 'evaluated', 'reviewed'])\n        courses += get_courses_with_prefetched_data(additional_courses)\n\n    course_pks = [course.pk for course in courses]\n    degrees = Degree.objects.filter(courses__pk__in=course_pks).distinct()\n    course_types = CourseType.objects.filter(courses__pk__in=course_pks).distinct()\n    template_data = dict(\n        courses=courses,\n        degrees=degrees,\n        course_types=sorted(course_types, key=lambda course_type: course_type.name),\n        semesters=semesters,\n    )\n    return render(request, \"results_index.html\", template_data)\n\n\n@login_required\ndef course_detail(request, semester_id, course_id):\n    semester = get_object_or_404(Semester, id=semester_id)\n    course = get_object_or_404(semester.course_set, id=course_id, semester=semester)\n\n    if not course.can_user_see_results_page(request.user):\n        raise PermissionDenied\n\n    course_result = collect_results(course)\n\n    if request.user.is_reviewer:\n        public_view = request.GET.get('public_view') != 'false'  # if parameter is not given, show public view.\n    else:\n        public_view = request.GET.get('public_view') == 'true'  # if parameter is not given, show own view.\n\n    # redirect to non-public view if there is none because the results have not been published\n    if not course.can_publish_rating_results:\n        public_view = False\n\n    represented_users = list(request.user.represented_users.all()) + [request.user]\n\n    # remove text answers if the user may not see them\n    for questionnaire_result in course_result.questionnaire_results:\n        for question_result in questionnaire_result.question_results:\n            if isinstance(question_result, TextResult):\n                question_result.answers = [answer for answer in question_result.answers if user_can_see_text_answer(request.user, represented_users, answer, public_view)]\n        # remove empty TextResults\n        questionnaire_result.question_results = [result for result in questionnaire_result.question_results if not isinstance(result, TextResult) or len(result.answers) > 0]\n\n    # filter empty headings\n    for questionnaire_result in course_result.questionnaire_results:\n        filtered_question_results = []\n        for index, question_result in enumerate(questionnaire_result.question_results):\n            # filter out if there are no more questions or the next question is also a heading question\n            if isinstance(question_result, HeadingResult):\n                if index == len(questionnaire_result.question_results) - 1 or isinstance(questionnaire_result.question_results[index + 1], HeadingResult):\n                    continue\n            filtered_question_results.append(question_result)\n        questionnaire_result.question_results = filtered_question_results\n\n    # remove empty questionnaire_results and contribution_results\n    for contribution_result in course_result.contribution_results:\n        contribution_result.questionnaire_results = [questionnaire_result for questionnaire_result in contribution_result.questionnaire_results if questionnaire_result.question_results]\n    course_result.contribution_results = [contribution_result for contribution_result in course_result.contribution_results if contribution_result.questionnaire_results]\n\n    add_warnings(course, course_result)\n\n    # split course_result into different lists\n    course_questionnaire_results_top = []\n    course_questionnaire_results_bottom = []\n    contributor_contribution_results = []\n    for contribution_result in course_result.contribution_results:\n        if contribution_result.contributor is None:\n            for questionnaire_result in contribution_result.questionnaire_results:\n                if questionnaire_result.questionnaire.is_below_contributors:\n                    course_questionnaire_results_bottom.append(questionnaire_result)\n                else:\n                    course_questionnaire_results_top.append(questionnaire_result)\n        else:\n            contributor_contribution_results.append(contribution_result)\n\n    if not contributor_contribution_results:\n        course_questionnaire_results_top += course_questionnaire_results_bottom\n        course_questionnaire_results_bottom = []\n\n    course.distribution = calculate_average_distribution(course)\n    course.avg_grade = distribution_to_grade(course.distribution)\n\n    template_data = dict(\n            course=course,\n            course_questionnaire_results_top=course_questionnaire_results_top,\n            course_questionnaire_results_bottom=course_questionnaire_results_bottom,\n            contributor_contribution_results=contributor_contribution_results,\n            reviewer=request.user.is_reviewer,\n            contributor=course.is_user_contributor_or_delegate(request.user),\n            can_download_grades=request.user.can_download_grades,\n            public_view=public_view)\n    return render(request, \"results_course_detail.html\", template_data)\n\n\ndef add_warnings(course, course_result):\n    if not course.can_publish_rating_results:\n        return\n\n    # calculate the median values of how many people answered a questionnaire across all contributions\n    questionnaire_max_answers = defaultdict(list)\n    for questionnaire_result in course_result.questionnaire_results:\n        max_answers = max((question_result.count_sum for question_result in questionnaire_result.question_results if question_result.question.is_rating_question), default=0)\n        questionnaire_max_answers[questionnaire_result.questionnaire].append(max_answers)\n\n    questionnaire_warning_thresholds = {}\n    for questionnaire, max_answers_list in questionnaire_max_answers.items():\n        questionnaire_warning_thresholds[questionnaire] = max(settings.RESULTS_WARNING_PERCENTAGE * median(max_answers_list), settings.RESULTS_WARNING_COUNT)\n\n    for questionnaire_result in course_result.questionnaire_results:\n        rating_results = [question_result for question_result in questionnaire_result.question_results if question_result.question.is_rating_question]\n        max_answers = max((rating_result.count_sum for rating_result in rating_results), default=0)\n        questionnaire_result.warning = 0 < max_answers < questionnaire_warning_thresholds[questionnaire_result.questionnaire]\n\n        for rating_result in rating_results:\n            rating_result.warning = questionnaire_result.warning or rating_result.has_answers and rating_result.count_sum < questionnaire_warning_thresholds[questionnaire_result.questionnaire]\n\n\ndef user_can_see_text_answer(user, represented_users, text_answer, public_view=False):\n    assert text_answer.state in [TextAnswer.PRIVATE, TextAnswer.PUBLISHED]\n\n    if public_view:\n        return False\n    if user.is_reviewer:\n        return True\n\n    contributor = text_answer.contribution.contributor\n\n    if text_answer.is_private:\n        return contributor == user\n\n    if text_answer.is_published:\n        if text_answer.contribution.responsible:\n            return contributor == user or user in contributor.delegates.all()\n\n        if contributor in represented_users:\n            return True\n        if text_answer.contribution.course.contributions.filter(\n                contributor__in=represented_users, comment_visibility=Contribution.ALL_COMMENTS).exists():\n            return True\n        if text_answer.contribution.is_general and text_answer.contribution.course.contributions.filter(\n                contributor__in=represented_users, comment_visibility=Contribution.COURSE_COMMENTS).exists():\n            return True\n\n    return False\n", "path": "evap/results/views.py"}]}