We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Change voting error message based on type of missing answer
Submitting a poll form with missing answers will result in an error message: `Please make sure to vote for all rating questions. You can also click on "I can't give feedback" to skip the questions about a single person.`

The second part only makes sense if at least one of the missing answers is to a contributor question. If all of the missing answers are for general questions, this sentence shouldn't be displayed.

--- 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/student/views.py`
Content:
```
1 from collections import OrderedDict
2 
3 from django.conf import settings
4 from django.contrib import messages
5 from django.core.exceptions import PermissionDenied
6 from django.db import transaction
7 from django.db.models import Exists, F, OuterRef, Q
8 from django.http import HttpResponse
9 from django.shortcuts import get_object_or_404, render
10 from django.urls import reverse
11 from django.utils.translation import gettext as _
12 
13 from evap.evaluation.auth import participant_required
14 from evap.evaluation.models import NO_ANSWER, Evaluation, RatingAnswerCounter, Semester, TextAnswer, VoteTimestamp
15 from evap.results.tools import (
16     annotate_distributions_and_grades,
17     get_evaluations_with_course_result_attributes,
18     textanswers_visible_to,
19 )
20 from evap.student.forms import QuestionnaireVotingForm
21 from evap.student.models import TextAnswerWarning
22 from evap.student.tools import answer_field_id
23 
24 SUCCESS_MAGIC_STRING = "vote submitted successfully"
25 
26 
27 @participant_required
28 def index(request):
29     query = (
30         Evaluation.objects.annotate(
31             participates_in=Exists(Evaluation.objects.filter(id=OuterRef("id"), participants=request.user))
32         )
33         .annotate(voted_for=Exists(Evaluation.objects.filter(id=OuterRef("id"), voters=request.user)))
34         .filter(~Q(state=Evaluation.State.NEW), course__evaluations__participants=request.user)
35         .exclude(state=Evaluation.State.NEW)
36         .prefetch_related(
37             "course",
38             "course__semester",
39             "course__grade_documents",
40             "course__type",
41             "course__evaluations",
42             "course__responsibles",
43             "course__degrees",
44         )
45         .distinct()
46     )
47     query = Evaluation.annotate_with_participant_and_voter_counts(query)
48     evaluations = [evaluation for evaluation in query if evaluation.can_be_seen_by(request.user)]
49 
50     inner_evaluation_ids = [
51         inner_evaluation.id for evaluation in evaluations for inner_evaluation in evaluation.course.evaluations.all()
52     ]
53     inner_evaluation_query = Evaluation.objects.filter(pk__in=inner_evaluation_ids)
54     inner_evaluation_query = Evaluation.annotate_with_participant_and_voter_counts(inner_evaluation_query)
55 
56     evaluations_by_id = {evaluation["id"]: evaluation for evaluation in inner_evaluation_query.values()}
57 
58     for evaluation in evaluations:
59         for inner_evaluation in evaluation.course.evaluations.all():
60             inner_evaluation.num_voters = evaluations_by_id[inner_evaluation.id]["num_voters"]
61             inner_evaluation.num_participants = evaluations_by_id[inner_evaluation.id]["num_participants"]
62 
63     annotate_distributions_and_grades(e for e in evaluations if e.state == Evaluation.State.PUBLISHED)
64     evaluations = get_evaluations_with_course_result_attributes(evaluations)
65 
66     # evaluations must be sorted for regrouping them in the template
67     evaluations.sort(key=lambda evaluation: (evaluation.course.name, evaluation.name))
68 
69     semesters = Semester.objects.all()
70     semester_list = [
71         {
72             "semester_name": semester.name,
73             "id": semester.id,
74             "results_are_archived": semester.results_are_archived,
75             "grade_documents_are_deleted": semester.grade_documents_are_deleted,
76             "evaluations": [evaluation for evaluation in evaluations if evaluation.course.semester_id == semester.id],
77         }
78         for semester in semesters
79     ]
80 
81     unfinished_evaluations_query = (
82         Evaluation.objects.filter(
83             participants=request.user,
84             state__in=[
85                 Evaluation.State.PREPARED,
86                 Evaluation.State.EDITOR_APPROVED,
87                 Evaluation.State.APPROVED,
88                 Evaluation.State.IN_EVALUATION,
89             ],
90         )
91         .exclude(voters=request.user)
92         .prefetch_related("course__responsibles", "course__type", "course__semester")
93     )
94 
95     unfinished_evaluations_query = Evaluation.annotate_with_participant_and_voter_counts(unfinished_evaluations_query)
96     unfinished_evaluations = list(unfinished_evaluations_query)
97 
98     # available evaluations come first, ordered by time left for evaluation and the name
99     # evaluations in other (visible) states follow by name
100     def sorter(evaluation):
101         return (
102             evaluation.state != Evaluation.State.IN_EVALUATION,
103             evaluation.vote_end_date if evaluation.state == Evaluation.State.IN_EVALUATION else None,
104             evaluation.full_name,
105         )
106 
107     unfinished_evaluations.sort(key=sorter)
108 
109     template_data = {
110         "semester_list": semester_list,
111         "can_download_grades": request.user.can_download_grades,
112         "unfinished_evaluations": unfinished_evaluations,
113         "evaluation_end_warning_period": settings.EVALUATION_END_WARNING_PERIOD,
114     }
115 
116     return render(request, "student_index.html", template_data)
117 
118 
119 def get_vote_page_form_groups(request, evaluation, preview):
120     contributions_to_vote_on = evaluation.contributions.all()
121     # prevent a user from voting on themselves
122     if not preview:
123         contributions_to_vote_on = contributions_to_vote_on.exclude(contributor=request.user)
124 
125     form_groups = OrderedDict()
126     for contribution in contributions_to_vote_on:
127         questionnaires = contribution.questionnaires.all()
128         if not questionnaires.exists():
129             continue
130         form_groups[contribution] = [
131             QuestionnaireVotingForm(request.POST or None, contribution=contribution, questionnaire=questionnaire)
132             for questionnaire in questionnaires
133         ]
134     return form_groups
135 
136 
137 def render_vote_page(request, evaluation, preview, for_rendering_in_modal=False):
138     form_groups = get_vote_page_form_groups(request, evaluation, preview)
139 
140     assert preview or not all(form.is_valid() for form_group in form_groups.values() for form in form_group)
141 
142     evaluation_form_group = form_groups.pop(evaluation.general_contribution, default=[])
143 
144     contributor_form_groups = [
145         (
146             contribution.contributor,
147             contribution.label,
148             form_group,
149             any(form.errors for form in form_group),
150             textanswers_visible_to(contribution),
151         )
152         for contribution, form_group in form_groups.items()
153     ]
154     evaluation_form_group_top = [
155         questions_form for questions_form in evaluation_form_group if questions_form.questionnaire.is_above_contributors
156     ]
157     evaluation_form_group_bottom = [
158         questions_form for questions_form in evaluation_form_group if questions_form.questionnaire.is_below_contributors
159     ]
160     if not contributor_form_groups:
161         evaluation_form_group_top += evaluation_form_group_bottom
162         evaluation_form_group_bottom = []
163 
164     template_data = {
165         "errors_exist": any(
166             any(form.errors for form in form_group)
167             for form_group in [*(form_groups.values()), evaluation_form_group_top, evaluation_form_group_bottom]
168         ),
169         "evaluation_form_group_top": evaluation_form_group_top,
170         "evaluation_form_group_bottom": evaluation_form_group_bottom,
171         "contributor_form_groups": contributor_form_groups,
172         "evaluation": evaluation,
173         "small_evaluation_size_warning": evaluation.num_participants <= settings.SMALL_COURSE_SIZE,
174         "preview": preview,
175         "success_magic_string": SUCCESS_MAGIC_STRING,
176         "success_redirect_url": reverse("student:index"),
177         "for_rendering_in_modal": for_rendering_in_modal,
178         "general_contribution_textanswers_visible_to": textanswers_visible_to(evaluation.general_contribution),
179         "text_answer_warnings": TextAnswerWarning.objects.all(),
180     }
181     return render(request, "student_vote.html", template_data)
182 
183 
184 @participant_required
185 def vote(request, evaluation_id):
186     # pylint: disable=too-many-nested-blocks,too-many-branches
187     evaluation = get_object_or_404(Evaluation, id=evaluation_id)
188     if not evaluation.can_be_voted_for_by(request.user):
189         raise PermissionDenied
190 
191     form_groups = get_vote_page_form_groups(request, evaluation, preview=False)
192     if not all(form.is_valid() for form_group in form_groups.values() for form in form_group):
193         return render_vote_page(request, evaluation, preview=False)
194 
195     # all forms are valid, begin vote operation
196     with transaction.atomic():
197         # add user to evaluation.voters
198         # not using evaluation.voters.add(request.user) since that fails silently when done twice.
199         evaluation.voters.through.objects.create(userprofile_id=request.user.pk, evaluation_id=evaluation.pk)
200 
201         for contribution, form_group in form_groups.items():
202             for questionnaire_form in form_group:
203                 questionnaire = questionnaire_form.questionnaire
204                 for question in questionnaire.questions.all():
205                     if question.is_heading_question:
206                         continue
207 
208                     identifier = answer_field_id(contribution, questionnaire, question)
209                     value = questionnaire_form.cleaned_data.get(identifier)
210 
211                     if question.is_text_question:
212                         if value:
213                             question.answer_class.objects.create(
214                                 contribution=contribution, question=question, answer=value
215                             )
216                     else:
217                         if value != NO_ANSWER:
218                             answer_counter, __ = question.answer_class.objects.get_or_create(
219                                 contribution=contribution, question=question, answer=value
220                             )
221                             answer_counter.count += 1
222                             answer_counter.save()
223                         if question.allows_additional_textanswers:
224                             textanswer_identifier = answer_field_id(
225                                 contribution, questionnaire, question, additional_textanswer=True
226                             )
227                             textanswer_value = questionnaire_form.cleaned_data.get(textanswer_identifier)
228                             if textanswer_value:
229                                 TextAnswer.objects.create(
230                                     contribution=contribution, question=question, answer=textanswer_value
231                                 )
232 
233         VoteTimestamp.objects.create(evaluation=evaluation)
234 
235         # Update all answer rows to make sure no system columns give away which one was last modified
236         # see https://github.com/e-valuation/EvaP/issues/1384
237         RatingAnswerCounter.objects.filter(contribution__evaluation=evaluation).update(id=F("id"))
238         TextAnswer.objects.filter(contribution__evaluation=evaluation).update(id=F("id"))
239 
240         if not evaluation.can_publish_text_results:
241             # enable text result publishing if first user confirmed that publishing is okay or second user voted
242             if (
243                 request.POST.get("text_results_publish_confirmation_top") == "on"
244                 or request.POST.get("text_results_publish_confirmation_bottom") == "on"
245                 or evaluation.voters.count() >= 2
246             ):
247                 evaluation.can_publish_text_results = True
248                 evaluation.save()
249 
250         evaluation.evaluation_evaluated.send(sender=Evaluation, request=request, semester=evaluation.course.semester)
251 
252     messages.success(request, _("Your vote was recorded."))
253     return HttpResponse(SUCCESS_MAGIC_STRING)
254
```
--- 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/student/views.py b/evap/student/views.py
--- a/evap/student/views.py
+++ b/evap/student/views.py
@@ -161,11 +161,15 @@
         evaluation_form_group_top += evaluation_form_group_bottom
         evaluation_form_group_bottom = []
 
+    contributor_errors_exist = any(form.errors for form_group in form_groups.values() for form in form_group)
+    errors_exist = contributor_errors_exist or any(
+        any(form.errors for form in form_group)
+        for form_group in [evaluation_form_group_top, evaluation_form_group_bottom]
+    )
+
     template_data = {
-        "errors_exist": any(
-            any(form.errors for form in form_group)
-            for form_group in [*(form_groups.values()), evaluation_form_group_top, evaluation_form_group_bottom]
-        ),
+        "contributor_errors_exist": contributor_errors_exist,
+        "errors_exist": errors_exist,
         "evaluation_form_group_top": evaluation_form_group_top,
         "evaluation_form_group_bottom": evaluation_form_group_bottom,
         "contributor_form_groups": contributor_form_groups,

{"golden_diff": "diff --git a/evap/student/views.py b/evap/student/views.py\n--- a/evap/student/views.py\n+++ b/evap/student/views.py\n@@ -161,11 +161,15 @@\n         evaluation_form_group_top += evaluation_form_group_bottom\n         evaluation_form_group_bottom = []\n \n+    contributor_errors_exist = any(form.errors for form_group in form_groups.values() for form in form_group)\n+    errors_exist = contributor_errors_exist or any(\n+        any(form.errors for form in form_group)\n+        for form_group in [evaluation_form_group_top, evaluation_form_group_bottom]\n+    )\n+\n     template_data = {\n-        \"errors_exist\": any(\n-            any(form.errors for form in form_group)\n-            for form_group in [*(form_groups.values()), evaluation_form_group_top, evaluation_form_group_bottom]\n-        ),\n+        \"contributor_errors_exist\": contributor_errors_exist,\n+        \"errors_exist\": errors_exist,\n         \"evaluation_form_group_top\": evaluation_form_group_top,\n         \"evaluation_form_group_bottom\": evaluation_form_group_bottom,\n         \"contributor_form_groups\": contributor_form_groups,\n", "issue": "Change voting error message based on type of missing answer\nSubmitting a poll form with missing answers will result in an error message: `Please make sure to vote for all rating questions. You can also click on \"I can't give feedback\" to skip the questions about a single person.`\r\n\r\nThe second part only makes sense if at least one of the missing answers is to a contributor question. If all of the missing answers are for general questions, this sentence shouldn't be displayed.\n", "before_files": [{"content": "from collections import OrderedDict\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.core.exceptions import PermissionDenied\nfrom django.db import transaction\nfrom django.db.models import Exists, F, OuterRef, Q\nfrom django.http import HttpResponse\nfrom django.shortcuts import get_object_or_404, render\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\n\nfrom evap.evaluation.auth import participant_required\nfrom evap.evaluation.models import NO_ANSWER, Evaluation, RatingAnswerCounter, Semester, TextAnswer, VoteTimestamp\nfrom evap.results.tools import (\n    annotate_distributions_and_grades,\n    get_evaluations_with_course_result_attributes,\n    textanswers_visible_to,\n)\nfrom evap.student.forms import QuestionnaireVotingForm\nfrom evap.student.models import TextAnswerWarning\nfrom evap.student.tools import answer_field_id\n\nSUCCESS_MAGIC_STRING = \"vote submitted successfully\"\n\n\n@participant_required\ndef index(request):\n    query = (\n        Evaluation.objects.annotate(\n            participates_in=Exists(Evaluation.objects.filter(id=OuterRef(\"id\"), participants=request.user))\n        )\n        .annotate(voted_for=Exists(Evaluation.objects.filter(id=OuterRef(\"id\"), voters=request.user)))\n        .filter(~Q(state=Evaluation.State.NEW), course__evaluations__participants=request.user)\n        .exclude(state=Evaluation.State.NEW)\n        .prefetch_related(\n            \"course\",\n            \"course__semester\",\n            \"course__grade_documents\",\n            \"course__type\",\n            \"course__evaluations\",\n            \"course__responsibles\",\n            \"course__degrees\",\n        )\n        .distinct()\n    )\n    query = Evaluation.annotate_with_participant_and_voter_counts(query)\n    evaluations = [evaluation for evaluation in query if evaluation.can_be_seen_by(request.user)]\n\n    inner_evaluation_ids = [\n        inner_evaluation.id for evaluation in evaluations for inner_evaluation in evaluation.course.evaluations.all()\n    ]\n    inner_evaluation_query = Evaluation.objects.filter(pk__in=inner_evaluation_ids)\n    inner_evaluation_query = Evaluation.annotate_with_participant_and_voter_counts(inner_evaluation_query)\n\n    evaluations_by_id = {evaluation[\"id\"]: evaluation for evaluation in inner_evaluation_query.values()}\n\n    for evaluation in evaluations:\n        for inner_evaluation in evaluation.course.evaluations.all():\n            inner_evaluation.num_voters = evaluations_by_id[inner_evaluation.id][\"num_voters\"]\n            inner_evaluation.num_participants = evaluations_by_id[inner_evaluation.id][\"num_participants\"]\n\n    annotate_distributions_and_grades(e for e in evaluations if e.state == Evaluation.State.PUBLISHED)\n    evaluations = get_evaluations_with_course_result_attributes(evaluations)\n\n    # evaluations must be sorted for regrouping them in the template\n    evaluations.sort(key=lambda evaluation: (evaluation.course.name, evaluation.name))\n\n    semesters = Semester.objects.all()\n    semester_list = [\n        {\n            \"semester_name\": semester.name,\n            \"id\": semester.id,\n            \"results_are_archived\": semester.results_are_archived,\n            \"grade_documents_are_deleted\": semester.grade_documents_are_deleted,\n            \"evaluations\": [evaluation for evaluation in evaluations if evaluation.course.semester_id == semester.id],\n        }\n        for semester in semesters\n    ]\n\n    unfinished_evaluations_query = (\n        Evaluation.objects.filter(\n            participants=request.user,\n            state__in=[\n                Evaluation.State.PREPARED,\n                Evaluation.State.EDITOR_APPROVED,\n                Evaluation.State.APPROVED,\n                Evaluation.State.IN_EVALUATION,\n            ],\n        )\n        .exclude(voters=request.user)\n        .prefetch_related(\"course__responsibles\", \"course__type\", \"course__semester\")\n    )\n\n    unfinished_evaluations_query = Evaluation.annotate_with_participant_and_voter_counts(unfinished_evaluations_query)\n    unfinished_evaluations = list(unfinished_evaluations_query)\n\n    # available evaluations come first, ordered by time left for evaluation and the name\n    # evaluations in other (visible) states follow by name\n    def sorter(evaluation):\n        return (\n            evaluation.state != Evaluation.State.IN_EVALUATION,\n            evaluation.vote_end_date if evaluation.state == Evaluation.State.IN_EVALUATION else None,\n            evaluation.full_name,\n        )\n\n    unfinished_evaluations.sort(key=sorter)\n\n    template_data = {\n        \"semester_list\": semester_list,\n        \"can_download_grades\": request.user.can_download_grades,\n        \"unfinished_evaluations\": unfinished_evaluations,\n        \"evaluation_end_warning_period\": settings.EVALUATION_END_WARNING_PERIOD,\n    }\n\n    return render(request, \"student_index.html\", template_data)\n\n\ndef get_vote_page_form_groups(request, evaluation, preview):\n    contributions_to_vote_on = evaluation.contributions.all()\n    # prevent a user from voting on themselves\n    if not preview:\n        contributions_to_vote_on = contributions_to_vote_on.exclude(contributor=request.user)\n\n    form_groups = OrderedDict()\n    for contribution in contributions_to_vote_on:\n        questionnaires = contribution.questionnaires.all()\n        if not questionnaires.exists():\n            continue\n        form_groups[contribution] = [\n            QuestionnaireVotingForm(request.POST or None, contribution=contribution, questionnaire=questionnaire)\n            for questionnaire in questionnaires\n        ]\n    return form_groups\n\n\ndef render_vote_page(request, evaluation, preview, for_rendering_in_modal=False):\n    form_groups = get_vote_page_form_groups(request, evaluation, preview)\n\n    assert preview or not all(form.is_valid() for form_group in form_groups.values() for form in form_group)\n\n    evaluation_form_group = form_groups.pop(evaluation.general_contribution, default=[])\n\n    contributor_form_groups = [\n        (\n            contribution.contributor,\n            contribution.label,\n            form_group,\n            any(form.errors for form in form_group),\n            textanswers_visible_to(contribution),\n        )\n        for contribution, form_group in form_groups.items()\n    ]\n    evaluation_form_group_top = [\n        questions_form for questions_form in evaluation_form_group if questions_form.questionnaire.is_above_contributors\n    ]\n    evaluation_form_group_bottom = [\n        questions_form for questions_form in evaluation_form_group if questions_form.questionnaire.is_below_contributors\n    ]\n    if not contributor_form_groups:\n        evaluation_form_group_top += evaluation_form_group_bottom\n        evaluation_form_group_bottom = []\n\n    template_data = {\n        \"errors_exist\": any(\n            any(form.errors for form in form_group)\n            for form_group in [*(form_groups.values()), evaluation_form_group_top, evaluation_form_group_bottom]\n        ),\n        \"evaluation_form_group_top\": evaluation_form_group_top,\n        \"evaluation_form_group_bottom\": evaluation_form_group_bottom,\n        \"contributor_form_groups\": contributor_form_groups,\n        \"evaluation\": evaluation,\n        \"small_evaluation_size_warning\": evaluation.num_participants <= settings.SMALL_COURSE_SIZE,\n        \"preview\": preview,\n        \"success_magic_string\": SUCCESS_MAGIC_STRING,\n        \"success_redirect_url\": reverse(\"student:index\"),\n        \"for_rendering_in_modal\": for_rendering_in_modal,\n        \"general_contribution_textanswers_visible_to\": textanswers_visible_to(evaluation.general_contribution),\n        \"text_answer_warnings\": TextAnswerWarning.objects.all(),\n    }\n    return render(request, \"student_vote.html\", template_data)\n\n\n@participant_required\ndef vote(request, evaluation_id):\n    # pylint: disable=too-many-nested-blocks,too-many-branches\n    evaluation = get_object_or_404(Evaluation, id=evaluation_id)\n    if not evaluation.can_be_voted_for_by(request.user):\n        raise PermissionDenied\n\n    form_groups = get_vote_page_form_groups(request, evaluation, preview=False)\n    if not all(form.is_valid() for form_group in form_groups.values() for form in form_group):\n        return render_vote_page(request, evaluation, preview=False)\n\n    # all forms are valid, begin vote operation\n    with transaction.atomic():\n        # add user to evaluation.voters\n        # not using evaluation.voters.add(request.user) since that fails silently when done twice.\n        evaluation.voters.through.objects.create(userprofile_id=request.user.pk, evaluation_id=evaluation.pk)\n\n        for contribution, form_group in form_groups.items():\n            for questionnaire_form in form_group:\n                questionnaire = questionnaire_form.questionnaire\n                for question in questionnaire.questions.all():\n                    if question.is_heading_question:\n                        continue\n\n                    identifier = answer_field_id(contribution, questionnaire, question)\n                    value = questionnaire_form.cleaned_data.get(identifier)\n\n                    if question.is_text_question:\n                        if value:\n                            question.answer_class.objects.create(\n                                contribution=contribution, question=question, answer=value\n                            )\n                    else:\n                        if value != NO_ANSWER:\n                            answer_counter, __ = question.answer_class.objects.get_or_create(\n                                contribution=contribution, question=question, answer=value\n                            )\n                            answer_counter.count += 1\n                            answer_counter.save()\n                        if question.allows_additional_textanswers:\n                            textanswer_identifier = answer_field_id(\n                                contribution, questionnaire, question, additional_textanswer=True\n                            )\n                            textanswer_value = questionnaire_form.cleaned_data.get(textanswer_identifier)\n                            if textanswer_value:\n                                TextAnswer.objects.create(\n                                    contribution=contribution, question=question, answer=textanswer_value\n                                )\n\n        VoteTimestamp.objects.create(evaluation=evaluation)\n\n        # Update all answer rows to make sure no system columns give away which one was last modified\n        # see https://github.com/e-valuation/EvaP/issues/1384\n        RatingAnswerCounter.objects.filter(contribution__evaluation=evaluation).update(id=F(\"id\"))\n        TextAnswer.objects.filter(contribution__evaluation=evaluation).update(id=F(\"id\"))\n\n        if not evaluation.can_publish_text_results:\n            # enable text result publishing if first user confirmed that publishing is okay or second user voted\n            if (\n                request.POST.get(\"text_results_publish_confirmation_top\") == \"on\"\n                or request.POST.get(\"text_results_publish_confirmation_bottom\") == \"on\"\n                or evaluation.voters.count() >= 2\n            ):\n                evaluation.can_publish_text_results = True\n                evaluation.save()\n\n        evaluation.evaluation_evaluated.send(sender=Evaluation, request=request, semester=evaluation.course.semester)\n\n    messages.success(request, _(\"Your vote was recorded.\"))\n    return HttpResponse(SUCCESS_MAGIC_STRING)\n", "path": "evap/student/views.py"}], "after_files": [{"content": "from collections import OrderedDict\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.core.exceptions import PermissionDenied\nfrom django.db import transaction\nfrom django.db.models import Exists, F, OuterRef, Q\nfrom django.http import HttpResponse\nfrom django.shortcuts import get_object_or_404, render\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\n\nfrom evap.evaluation.auth import participant_required\nfrom evap.evaluation.models import NO_ANSWER, Evaluation, RatingAnswerCounter, Semester, TextAnswer, VoteTimestamp\nfrom evap.results.tools import (\n    annotate_distributions_and_grades,\n    get_evaluations_with_course_result_attributes,\n    textanswers_visible_to,\n)\nfrom evap.student.forms import QuestionnaireVotingForm\nfrom evap.student.models import TextAnswerWarning\nfrom evap.student.tools import answer_field_id\n\nSUCCESS_MAGIC_STRING = \"vote submitted successfully\"\n\n\n@participant_required\ndef index(request):\n    query = (\n        Evaluation.objects.annotate(\n            participates_in=Exists(Evaluation.objects.filter(id=OuterRef(\"id\"), participants=request.user))\n        )\n        .annotate(voted_for=Exists(Evaluation.objects.filter(id=OuterRef(\"id\"), voters=request.user)))\n        .filter(~Q(state=Evaluation.State.NEW), course__evaluations__participants=request.user)\n        .exclude(state=Evaluation.State.NEW)\n        .prefetch_related(\n            \"course\",\n            \"course__semester\",\n            \"course__grade_documents\",\n            \"course__type\",\n            \"course__evaluations\",\n            \"course__responsibles\",\n            \"course__degrees\",\n        )\n        .distinct()\n    )\n    query = Evaluation.annotate_with_participant_and_voter_counts(query)\n    evaluations = [evaluation for evaluation in query if evaluation.can_be_seen_by(request.user)]\n\n    inner_evaluation_ids = [\n        inner_evaluation.id for evaluation in evaluations for inner_evaluation in evaluation.course.evaluations.all()\n    ]\n    inner_evaluation_query = Evaluation.objects.filter(pk__in=inner_evaluation_ids)\n    inner_evaluation_query = Evaluation.annotate_with_participant_and_voter_counts(inner_evaluation_query)\n\n    evaluations_by_id = {evaluation[\"id\"]: evaluation for evaluation in inner_evaluation_query.values()}\n\n    for evaluation in evaluations:\n        for inner_evaluation in evaluation.course.evaluations.all():\n            inner_evaluation.num_voters = evaluations_by_id[inner_evaluation.id][\"num_voters\"]\n            inner_evaluation.num_participants = evaluations_by_id[inner_evaluation.id][\"num_participants\"]\n\n    annotate_distributions_and_grades(e for e in evaluations if e.state == Evaluation.State.PUBLISHED)\n    evaluations = get_evaluations_with_course_result_attributes(evaluations)\n\n    # evaluations must be sorted for regrouping them in the template\n    evaluations.sort(key=lambda evaluation: (evaluation.course.name, evaluation.name))\n\n    semesters = Semester.objects.all()\n    semester_list = [\n        {\n            \"semester_name\": semester.name,\n            \"id\": semester.id,\n            \"results_are_archived\": semester.results_are_archived,\n            \"grade_documents_are_deleted\": semester.grade_documents_are_deleted,\n            \"evaluations\": [evaluation for evaluation in evaluations if evaluation.course.semester_id == semester.id],\n        }\n        for semester in semesters\n    ]\n\n    unfinished_evaluations_query = (\n        Evaluation.objects.filter(\n            participants=request.user,\n            state__in=[\n                Evaluation.State.PREPARED,\n                Evaluation.State.EDITOR_APPROVED,\n                Evaluation.State.APPROVED,\n                Evaluation.State.IN_EVALUATION,\n            ],\n        )\n        .exclude(voters=request.user)\n        .prefetch_related(\"course__responsibles\", \"course__type\", \"course__semester\")\n    )\n\n    unfinished_evaluations_query = Evaluation.annotate_with_participant_and_voter_counts(unfinished_evaluations_query)\n    unfinished_evaluations = list(unfinished_evaluations_query)\n\n    # available evaluations come first, ordered by time left for evaluation and the name\n    # evaluations in other (visible) states follow by name\n    def sorter(evaluation):\n        return (\n            evaluation.state != Evaluation.State.IN_EVALUATION,\n            evaluation.vote_end_date if evaluation.state == Evaluation.State.IN_EVALUATION else None,\n            evaluation.full_name,\n        )\n\n    unfinished_evaluations.sort(key=sorter)\n\n    template_data = {\n        \"semester_list\": semester_list,\n        \"can_download_grades\": request.user.can_download_grades,\n        \"unfinished_evaluations\": unfinished_evaluations,\n        \"evaluation_end_warning_period\": settings.EVALUATION_END_WARNING_PERIOD,\n    }\n\n    return render(request, \"student_index.html\", template_data)\n\n\ndef get_vote_page_form_groups(request, evaluation, preview):\n    contributions_to_vote_on = evaluation.contributions.all()\n    # prevent a user from voting on themselves\n    if not preview:\n        contributions_to_vote_on = contributions_to_vote_on.exclude(contributor=request.user)\n\n    form_groups = OrderedDict()\n    for contribution in contributions_to_vote_on:\n        questionnaires = contribution.questionnaires.all()\n        if not questionnaires.exists():\n            continue\n        form_groups[contribution] = [\n            QuestionnaireVotingForm(request.POST or None, contribution=contribution, questionnaire=questionnaire)\n            for questionnaire in questionnaires\n        ]\n    return form_groups\n\n\ndef render_vote_page(request, evaluation, preview, for_rendering_in_modal=False):\n    form_groups = get_vote_page_form_groups(request, evaluation, preview)\n\n    assert preview or not all(form.is_valid() for form_group in form_groups.values() for form in form_group)\n\n    evaluation_form_group = form_groups.pop(evaluation.general_contribution, default=[])\n\n    contributor_form_groups = [\n        (\n            contribution.contributor,\n            contribution.label,\n            form_group,\n            any(form.errors for form in form_group),\n            textanswers_visible_to(contribution),\n        )\n        for contribution, form_group in form_groups.items()\n    ]\n    evaluation_form_group_top = [\n        questions_form for questions_form in evaluation_form_group if questions_form.questionnaire.is_above_contributors\n    ]\n    evaluation_form_group_bottom = [\n        questions_form for questions_form in evaluation_form_group if questions_form.questionnaire.is_below_contributors\n    ]\n    if not contributor_form_groups:\n        evaluation_form_group_top += evaluation_form_group_bottom\n        evaluation_form_group_bottom = []\n\n    contributor_errors_exist = any(form.errors for form_group in form_groups.values() for form in form_group)\n    errors_exist = contributor_errors_exist or any(\n        any(form.errors for form in form_group)\n        for form_group in [evaluation_form_group_top, evaluation_form_group_bottom]\n    )\n\n    template_data = {\n        \"contributor_errors_exist\": contributor_errors_exist,\n        \"errors_exist\": errors_exist,\n        \"evaluation_form_group_top\": evaluation_form_group_top,\n        \"evaluation_form_group_bottom\": evaluation_form_group_bottom,\n        \"contributor_form_groups\": contributor_form_groups,\n        \"evaluation\": evaluation,\n        \"small_evaluation_size_warning\": evaluation.num_participants <= settings.SMALL_COURSE_SIZE,\n        \"preview\": preview,\n        \"success_magic_string\": SUCCESS_MAGIC_STRING,\n        \"success_redirect_url\": reverse(\"student:index\"),\n        \"for_rendering_in_modal\": for_rendering_in_modal,\n        \"general_contribution_textanswers_visible_to\": textanswers_visible_to(evaluation.general_contribution),\n        \"text_answer_warnings\": TextAnswerWarning.objects.all(),\n    }\n    return render(request, \"student_vote.html\", template_data)\n\n\n@participant_required\ndef vote(request, evaluation_id):\n    # pylint: disable=too-many-nested-blocks,too-many-branches\n    evaluation = get_object_or_404(Evaluation, id=evaluation_id)\n    if not evaluation.can_be_voted_for_by(request.user):\n        raise PermissionDenied\n\n    form_groups = get_vote_page_form_groups(request, evaluation, preview=False)\n    if not all(form.is_valid() for form_group in form_groups.values() for form in form_group):\n        return render_vote_page(request, evaluation, preview=False)\n\n    # all forms are valid, begin vote operation\n    with transaction.atomic():\n        # add user to evaluation.voters\n        # not using evaluation.voters.add(request.user) since that fails silently when done twice.\n        evaluation.voters.through.objects.create(userprofile_id=request.user.pk, evaluation_id=evaluation.pk)\n\n        for contribution, form_group in form_groups.items():\n            for questionnaire_form in form_group:\n                questionnaire = questionnaire_form.questionnaire\n                for question in questionnaire.questions.all():\n                    if question.is_heading_question:\n                        continue\n\n                    identifier = answer_field_id(contribution, questionnaire, question)\n                    value = questionnaire_form.cleaned_data.get(identifier)\n\n                    if question.is_text_question:\n                        if value:\n                            question.answer_class.objects.create(\n                                contribution=contribution, question=question, answer=value\n                            )\n                    else:\n                        if value != NO_ANSWER:\n                            answer_counter, __ = question.answer_class.objects.get_or_create(\n                                contribution=contribution, question=question, answer=value\n                            )\n                            answer_counter.count += 1\n                            answer_counter.save()\n                        if question.allows_additional_textanswers:\n                            textanswer_identifier = answer_field_id(\n                                contribution, questionnaire, question, additional_textanswer=True\n                            )\n                            textanswer_value = questionnaire_form.cleaned_data.get(textanswer_identifier)\n                            if textanswer_value:\n                                TextAnswer.objects.create(\n                                    contribution=contribution, question=question, answer=textanswer_value\n                                )\n\n        VoteTimestamp.objects.create(evaluation=evaluation)\n\n        # Update all answer rows to make sure no system columns give away which one was last modified\n        # see https://github.com/e-valuation/EvaP/issues/1384\n        RatingAnswerCounter.objects.filter(contribution__evaluation=evaluation).update(id=F(\"id\"))\n        TextAnswer.objects.filter(contribution__evaluation=evaluation).update(id=F(\"id\"))\n\n        if not evaluation.can_publish_text_results:\n            # enable text result publishing if first user confirmed that publishing is okay or second user voted\n            if (\n                request.POST.get(\"text_results_publish_confirmation_top\") == \"on\"\n                or request.POST.get(\"text_results_publish_confirmation_bottom\") == \"on\"\n                or evaluation.voters.count() >= 2\n            ):\n                evaluation.can_publish_text_results = True\n                evaluation.save()\n\n        evaluation.evaluation_evaluated.send(sender=Evaluation, request=request, semester=evaluation.course.semester)\n\n    messages.success(request, _(\"Your vote was recorded.\"))\n    return HttpResponse(SUCCESS_MAGIC_STRING)\n", "path": "evap/student/views.py"}]}