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 @@
+
@@ -84,6 +88,10 @@
+