diff --git a/server/vbv_lernwelt/assignment/admin.py b/server/vbv_lernwelt/assignment/admin.py
index a20e7974..2b0d1a50 100644
--- a/server/vbv_lernwelt/assignment/admin.py
+++ b/server/vbv_lernwelt/assignment/admin.py
@@ -18,3 +18,10 @@ class AssignmentCompletionAdmin(admin.ModelAdmin):
"assignment_user",
"course_session",
]
+ list_filter = [
+ "completion_status",
+ "assignment__assignment_type",
+ "course_session__course",
+ "course_session",
+ ]
+ search_fields = ["assignment_user__email"]
diff --git a/server/vbv_lernwelt/assignment/migrations/0014_assignmentcompletion_evaluation_points_deducted.py b/server/vbv_lernwelt/assignment/migrations/0014_assignmentcompletion_evaluation_points_deducted.py
new file mode 100644
index 00000000..fa92117b
--- /dev/null
+++ b/server/vbv_lernwelt/assignment/migrations/0014_assignmentcompletion_evaluation_points_deducted.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.20 on 2024-05-21 13:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assignment', '0013_assignment_competence_certificate_weight'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='assignmentcompletion',
+ name='evaluation_points_deducted',
+ field=models.FloatField(default=0.0, verbose_name='Punkteabzug'),
+ ),
+ ]
diff --git a/server/vbv_lernwelt/assignment/models.py b/server/vbv_lernwelt/assignment/models.py
index 14b4badb..758e10bf 100644
--- a/server/vbv_lernwelt/assignment/models.py
+++ b/server/vbv_lernwelt/assignment/models.py
@@ -340,10 +340,18 @@ class AssignmentCompletion(models.Model):
related_name="+",
)
evaluation_points = models.FloatField(null=True, blank=True)
+ evaluation_points_deducted = models.FloatField(
+ default=0.0, verbose_name="Punkteabzug"
+ )
evaluation_max_points = models.FloatField(null=True, blank=True)
evaluation_passed = models.BooleanField(null=True, blank=True)
edoniq_extended_time_flag = models.BooleanField(default=False)
+ @property
+ def evaluation_points_final(self):
+ # with Django>=5 it can be replaced with a `GeneratedField`
+ return (self.evaluation_points or 0.0) - self.evaluation_points_deducted
+
assignment_user = models.ForeignKey(User, on_delete=models.CASCADE)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
course_session = models.ForeignKey("course.CourseSession", on_delete=models.CASCADE)
diff --git a/server/vbv_lernwelt/core/management/commands/cypress_reset.py b/server/vbv_lernwelt/core/management/commands/cypress_reset.py
index a1c0ced2..cf8df17f 100644
--- a/server/vbv_lernwelt/core/management/commands/cypress_reset.py
+++ b/server/vbv_lernwelt/core/management/commands/cypress_reset.py
@@ -66,11 +66,16 @@ from vbv_lernwelt.self_evaluation_feedback.models import (
default=None,
help="Provide assignment evaluation scores in the format: 6,6,6,3,3",
)
+@click.option(
+ "--assignment-points-deducted",
+ default=0,
+ help="Provide assignment points deducted",
+)
@click.option(
"--create-edoniq-test-results",
- type=(int, int),
- default=(None, None),
- metavar="USER_POINTS MAX_POINTS",
+ type=(int, int, float),
+ default=(None, None, 0),
+ metavar="USER_POINTS MAX_POINTS POINTS_DEDUCTED",
help="Create edoniq result data for test-student1@example.com with user points and max points",
)
@click.option(
@@ -112,6 +117,7 @@ def command(
create_assignment_completion,
create_assignment_evaluation,
assignment_evaluation_scores,
+ assignment_points_deducted,
create_edoniq_test_results,
create_feedback_responses,
create_course_completion_performance_criteria,
@@ -171,9 +177,10 @@ def command(
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
evaluation_user=User.objects.get(id=TEST_TRAINER1_USER_ID),
input_scores=assignment_evaluation_scores,
+ points_deducted=assignment_points_deducted,
)
- user_points, max_points = create_edoniq_test_results
+ user_points, max_points, points_deducted = create_edoniq_test_results
if user_points is not None and max_points is not None:
print(
f"Create edoniq test results: User Points: {user_points}, Max Points: {max_points}"
@@ -186,6 +193,7 @@ def command(
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
user_points=user_points,
max_points=max_points,
+ evaluation_points_deducted=points_deducted,
)
if create_feedback_responses:
diff --git a/server/vbv_lernwelt/core/utils.py b/server/vbv_lernwelt/core/utils.py
index 083ed57c..2b153233 100644
--- a/server/vbv_lernwelt/core/utils.py
+++ b/server/vbv_lernwelt/core/utils.py
@@ -58,3 +58,10 @@ def pretty_print_json(json_string):
# pylint: disable=broad-except
except Exception:
return json_string
+
+
+def safe_deque_popleft(deq, default=None):
+ try:
+ return deq.popleft()
+ except IndexError:
+ return default
diff --git a/server/vbv_lernwelt/core/views.py b/server/vbv_lernwelt/core/views.py
index c6f9a8cf..191ccc40 100644
--- a/server/vbv_lernwelt/core/views.py
+++ b/server/vbv_lernwelt/core/views.py
@@ -151,6 +151,9 @@ def cypress_reset_view(request):
assignment_evaluation_scores = request.data.get("assignment_evaluation_scores")
if assignment_evaluation_scores:
options["assignment_evaluation_scores"] = assignment_evaluation_scores
+ options["assignment_points_deducted"] = float(
+ request.data.get("assignment_points_deducted") or 0
+ )
options["create_feedback_responses"] = (
request.data.get("create_feedback_responses") == "true"
@@ -159,10 +162,12 @@ def cypress_reset_view(request):
# edoniq test results
edoniq_test_user_points = request.data.get("edoniq_test_user_points")
edoniq_test_max_points = request.data.get("edoniq_test_max_points")
+ edoniq_points_deducted = request.data.get("edoniq_test_points_deducted") or 0
if bool(edoniq_test_user_points and edoniq_test_max_points):
options["create_edoniq_test_results"] = (
int(edoniq_test_user_points),
int(edoniq_test_max_points),
+ float(edoniq_points_deducted),
)
options["create_course_completion_performance_criteria"] = (
diff --git a/server/vbv_lernwelt/course/creators/test_course.py b/server/vbv_lernwelt/course/creators/test_course.py
index b3b4a659..90f58254 100644
--- a/server/vbv_lernwelt/course/creators/test_course.py
+++ b/server/vbv_lernwelt/course/creators/test_course.py
@@ -1,3 +1,4 @@
+from collections import deque
from datetime import datetime
from dateutil.relativedelta import MO, relativedelta, TH, TU, WE
@@ -42,6 +43,7 @@ from vbv_lernwelt.core.constants import (
TEST_TRAINER1_USER_ID,
)
from vbv_lernwelt.core.models import User
+from vbv_lernwelt.core.utils import safe_deque_popleft
from vbv_lernwelt.course.consts import COURSE_TEST_ID
from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import (
@@ -350,20 +352,27 @@ def create_test_assignment_submitted_data(assignment, course_session, user):
def create_test_assignment_evaluation_data(
- assignment, course_session, assignment_user, evaluation_user, input_scores=None
+ assignment,
+ course_session,
+ assignment_user,
+ evaluation_user,
+ input_scores=None,
+ points_deducted=0,
):
if assignment and course_session and assignment_user and evaluation_user:
subtasks = assignment.get_evaluation_tasks()
evaluation_points = 0
+ input_scores_deque = deque(input_scores) if input_scores else deque()
+
for index, evaluation_task in enumerate(subtasks):
task_score = evaluation_task["value"]["max_points"]
- if input_scores[index] < len(input_scores):
+ input_score = safe_deque_popleft(input_scores_deque)
+ if input_score is not None:
task_score = input_scores[index]
-
evaluation_points += task_score
- update_assignment_completion(
+ ac, _ = update_assignment_completion(
assignment_user=assignment_user,
assignment=assignment,
course_session=course_session,
@@ -380,7 +389,7 @@ def create_test_assignment_evaluation_data(
evaluation_user=evaluation_user,
)
- update_assignment_completion(
+ ac, _ = update_assignment_completion(
assignment_user=assignment_user,
assignment=assignment,
course_session=course_session,
@@ -391,16 +400,26 @@ def create_test_assignment_evaluation_data(
evaluation_points=evaluation_points,
)
+ # take the last input score as deduction if there is one left...
+ if points_deducted > 0:
+ ac.evaluation_points_deducted = points_deducted
+ ac.save()
+
def create_edoniq_test_result_data(
- assignment, course_session, assignment_user, user_points=19, max_points=24
+ assignment,
+ course_session,
+ assignment_user,
+ user_points=19,
+ max_points=24,
+ evaluation_points_deducted=0,
):
assignment.assignment.evaluation_tasks.raw_data[0]["value"][
"max_points"
] = max_points
assignment.assignment.save()
if assignment and course_session and assignment_user:
- update_assignment_completion(
+ ac, _ = update_assignment_completion(
assignment_user=assignment_user,
assignment=assignment,
course_session=course_session,
@@ -412,6 +431,10 @@ def create_edoniq_test_result_data(
evaluation_max_points=max_points,
)
+ if evaluation_points_deducted > 0:
+ ac.evaluation_points_deducted = evaluation_points_deducted
+ ac.save()
+
def create_feedback_response_data(
course_session,
diff --git a/server/vbv_lernwelt/templates/admin/index.html b/server/vbv_lernwelt/templates/admin/index.html
index 55e83b58..055f545a 100644
--- a/server/vbv_lernwelt/templates/admin/index.html
+++ b/server/vbv_lernwelt/templates/admin/index.html
@@ -73,6 +73,10 @@
+