diff --git a/deployment/localsettings.template.py b/deployment/localsettings.template.py
index a55f0ca5d..71e27dd76 100644
--- a/deployment/localsettings.template.py
+++ b/deployment/localsettings.template.py
@@ -53,3 +53,5 @@
"de": mark_safe("Deine Teilnahme am Evaluationsprojekt wird helfen. Evaluiere also jetzt!"),
"en": mark_safe("Your participation in the evaluation helps, so evaluate now!"),
}
+# Questionnaires automatically added to exam evaluations
+EXAM_QUESTIONNAIRE_IDS = [111]
diff --git a/evap/development/fixtures/test_data.json b/evap/development/fixtures/test_data.json
index 0203e18e6..6af5c5b40 100644
--- a/evap/development/fixtures/test_data.json
+++ b/evap/development/fixtures/test_data.json
@@ -656,6 +656,24 @@
"is_locked": false
}
},
+{
+ "model": "evaluation.questionnaire",
+ "pk": 111,
+ "fields": {
+ "type": 10,
+ "name_de": "Klausur",
+ "name_en": "Exam",
+ "description_de": "",
+ "description_en": "",
+ "public_name_de": "Klausur",
+ "public_name_en": "Exam",
+ "teaser_de": "",
+ "teaser_en": "",
+ "order": 62,
+ "visibility": 1,
+ "is_locked": false
+ }
+},
{
"model": "evaluation.program",
"pk": 1,
@@ -21784,6 +21802,18 @@
"type": 10
}
},
+{
+ "model": "evaluation.question",
+ "pk": 478,
+ "fields": {
+ "order": 1,
+ "questionnaire": 111,
+ "text_de": "Wie fandest du die Klausur?",
+ "text_en": "How did you like the exam?",
+ "allows_additional_textanswers": true,
+ "type": 6
+ }
+},
{
"model": "evaluation.ratinganswercounter",
"pk": "0009be0e-4a00-4f89-82b7-9733ff0fe35f",
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
index 9db4e1bbd..8e95bf588 100644
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -4,7 +4,7 @@
from collections import defaultdict
from collections.abc import Collection, Container, Iterable, Sequence
from dataclasses import dataclass
-from datetime import date, datetime, timedelta
+from datetime import date, datetime, time, timedelta
from enum import Enum, auto
from functools import partial
from numbers import Real
@@ -470,6 +470,35 @@ class State(models.IntegerChoices):
verbose_name=_("wait for grade upload before publishing"), default=True
)
+ @property
+ def has_exam_evaluation(self):
+ return self.course.evaluations.filter(name_de="Klausur", name_en="Exam").exists()
+
+ @property
+ def earliest_possible_exam_date(self):
+ return self.vote_start_datetime.date() + timedelta(days=1)
+
+ @transaction.atomic
+ def create_exam_evaluation(self, exam_date: date):
+ self.weight = 9
+ self.vote_end_date = exam_date - timedelta(days=1)
+ self.save()
+ exam_evaluation = Evaluation(
+ course=self.course,
+ name_de="Klausur",
+ name_en="Exam",
+ weight=1,
+ is_rewarded=False,
+ vote_start_datetime=datetime.combine(exam_date + timedelta(days=1), time(8, 0)),
+ vote_end_date=exam_date + timedelta(days=3),
+ )
+ exam_evaluation.save()
+
+ exam_evaluation.participants.set(self.participants.all())
+ for contribution in self.contributions.exclude(contributor=None):
+ exam_evaluation.contributions.create(contributor=contribution.contributor)
+ exam_evaluation.general_contribution.questionnaires.set(settings.EXAM_QUESTIONNAIRE_IDS)
+
class TextAnswerReviewState(Enum):
NO_TEXTANSWERS = auto()
NO_REVIEW_NEEDED = auto()
diff --git a/evap/evaluation/tests/tools.py b/evap/evaluation/tests/tools.py
index 7594d6770..64c116bf7 100644
--- a/evap/evaluation/tests/tools.py
+++ b/evap/evaluation/tests/tools.py
@@ -188,11 +188,12 @@ def create_evaluation_with_responsible_and_editor():
}
-def make_manager():
+def make_manager(**kwargs):
return baker.make(
UserProfile,
email="manager@institution.example.com",
groups=[Group.objects.get(name="Manager")],
+ **kwargs,
)
@@ -263,6 +264,7 @@ def assert_no_database_modifications(*args, **kwargs):
query["sql"].startswith('INSERT INTO "testing_cache_sessions"')
or query["sql"].startswith('UPDATE "testing_cache_sessions"')
or query["sql"].startswith('DELETE FROM "testing_cache_sessions"')
+ or query["sql"].startswith('UPDATE "evaluation_userprofile" SET "last_login" = ')
):
# These queries are caused by interacting with the test-app (self.app.get()), since that opens a session.
# That's not what we want to test for here
diff --git a/evap/settings.py b/evap/settings.py
index 5a7cd7b61..77c5cd764 100644
--- a/evap/settings.py
+++ b/evap/settings.py
@@ -107,6 +107,8 @@
# Amount of hours in which participant will be warned
EVALUATION_END_WARNING_PERIOD = 5
+# Questionnaires automatically added to exam evaluations
+EXAM_QUESTIONNAIRE_IDS: list[int] = []
### Installation specific settings
diff --git a/evap/staff/templates/staff_semester_view.html b/evap/staff/templates/staff_semester_view.html
index 1805f97b9..40c34cfd9 100644
--- a/evap/staff/templates/staff_semester_view.html
+++ b/evap/staff/templates/staff_semester_view.html
@@ -374,6 +374,13 @@
{{ semester.name }}
+ {% for evaluation in evaluations %}
+ {# separate forms for each modal since we want separate date-selection inputs because each exam_creation_modal needs its own exam date input field. #}
+
+ {% endfor %}
+
@@ -466,6 +473,7 @@
{{ semester.name }}
{% endif %}
+
@@ -546,7 +554,7 @@
{{ semester.name }}
href="{% url 'staff:course_copy' course.id %}"
title="{% translate 'Copy course' %}">
-
+
{% endif %}
{% if course.can_be_deleted_by_manager %}
diff --git a/evap/staff/templates/staff_semester_view_evaluation.html b/evap/staff/templates/staff_semester_view_evaluation.html
index b15686244..9d069bd5f 100644
--- a/evap/staff/templates/staff_semester_view_evaluation.html
+++ b/evap/staff/templates/staff_semester_view_evaluation.html
@@ -180,6 +180,25 @@
+ {% if not evaluation.has_exam_evaluation %}
+
+ {% translate 'Create exam evaluation' %}
+ {% translate 'Create exam evaluation' %}
+
+ {% blocktranslate trimmed %}
+ Create an exam evaluation based on this evaluation. This will copy all the participants and contributors from the original evaluation. It will set the weight of the original evaluation to 9 and its end date will be set to the day before the exam.
+ {% endblocktranslate %}
+
+