From 587f354e52a48a6c7cf3bd77f72c1d345d0ce3b4 Mon Sep 17 00:00:00 2001
From: Christian Cueni
Date: Mon, 22 Jan 2024 15:38:37 +0100
Subject: [PATCH 1/5] Handle invalid recipient email addresses
---
server/vbv_lernwelt/notify/services.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/server/vbv_lernwelt/notify/services.py b/server/vbv_lernwelt/notify/services.py
index bf792511..84102609 100644
--- a/server/vbv_lernwelt/notify/services.py
+++ b/server/vbv_lernwelt/notify/services.py
@@ -3,6 +3,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import structlog
+from django.core.exceptions import ValidationError
+from django.core.validators import validate_email
from django.db.models import Model
from notifications.signals import notify
@@ -281,6 +283,13 @@ class NotificationService:
template_data=template_data,
)
emailed = False
+
+ try:
+ validate_email(recipient.email)
+ except ValidationError:
+ log.info("Recipient email is invalid")
+ return f"{notification_identifier}_invalid_email"
+
try:
notification = NotificationService._find_duplicate_notification(
recipient=recipient,
From da3b1f03af5ac50000e0dbe3b717e84a1a0b278d Mon Sep 17 00:00:00 2001
From: Christian Cueni
Date: Mon, 22 Jan 2024 11:24:57 +0100
Subject: [PATCH 2/5] Add new Lehrgang
---
server/vbv_lernwelt/course/consts.py | 1 +
.../creators/versicherungsvermittlerin.py | 3 ++
.../commands/create_default_courses.py | 33 +++++++++++++++++++
.../commands/create_vermittler_pruefung.py | 23 +++++++++++++
.../learnpath/create_vv_new_learning_path.py | 20 +++++++++++
5 files changed, 80 insertions(+)
create mode 100644 server/vbv_lernwelt/course/management/commands/create_vermittler_pruefung.py
diff --git a/server/vbv_lernwelt/course/consts.py b/server/vbv_lernwelt/course/consts.py
index 5b3b30ab..39fc79c9 100644
--- a/server/vbv_lernwelt/course/consts.py
+++ b/server/vbv_lernwelt/course/consts.py
@@ -8,3 +8,4 @@ COURSE_UK_IT = -8
COURSE_UK_TRAINING_IT = -9
COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID = -10
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11
+COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID = -12
diff --git a/server/vbv_lernwelt/course/creators/versicherungsvermittlerin.py b/server/vbv_lernwelt/course/creators/versicherungsvermittlerin.py
index 48fa0bc6..2ad76a87 100644
--- a/server/vbv_lernwelt/course/creators/versicherungsvermittlerin.py
+++ b/server/vbv_lernwelt/course/creators/versicherungsvermittlerin.py
@@ -1,5 +1,6 @@
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
from vbv_lernwelt.course.factories import CoursePageFactory
+from vbv_lernwelt.course.models import CircleContactType
from vbv_lernwelt.course.utils import get_wagtail_default_site
@@ -20,6 +21,8 @@ def create_versicherungsvermittlerin_with_categories(
id=course_id,
title=title,
category_name="Handlungsfeld",
+ enable_circle_documents=False,
+ circle_contact_type=CircleContactType.LEARNING_MENTOR.value,
)
CourseCategory.objects.get_or_create(course=course, title="Allgemein", general=True)
diff --git a/server/vbv_lernwelt/course/management/commands/create_default_courses.py b/server/vbv_lernwelt/course/management/commands/create_default_courses.py
index ac931c2d..af21c29e 100644
--- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py
+++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py
@@ -59,6 +59,7 @@ from vbv_lernwelt.course.consts import (
COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID,
+ COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID,
)
from vbv_lernwelt.course.creators.test_course import (
create_edoniq_test_assignment,
@@ -95,6 +96,7 @@ from vbv_lernwelt.importer.services import (
from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
create_vv_new_learning_path,
+ create_vv_pruefung_learning_path,
)
from vbv_lernwelt.learnpath.models import (
Circle,
@@ -153,6 +155,9 @@ def command(course):
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID, language="it"
)
+ if COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID in course:
+ create_versicherungsvermittlerin_pruefung_course()
+
if COURSE_UK in course:
create_course_uk_de()
create_course_uk_de_course_sessions()
@@ -276,6 +281,34 @@ def create_versicherungsvermittlerin_course(
)
+def create_versicherungsvermittlerin_pruefung_course(
+ course_id=COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID, language="de"
+):
+ names = {
+ "de": "Versicherungsvermittler/-in VBV Prüfung",
+ "fr": "Intermédiaire d’assurance AFA Examen",
+ "it": "Intermediario/a assicurativo/a AFA Esame",
+ }
+ # Versicherungsvermittler/in mit neuen Circles
+ course = create_versicherungsvermittlerin_with_categories(
+ course_id=course_id,
+ title=names[language],
+ )
+
+ # assignments create assignments parent page
+ _assignment_list_page = AssignmentListPageFactory(
+ parent=course.coursepage,
+ )
+
+ create_vv_new_competence_profile(course_id=course_id)
+ create_default_media_library(course_id=course_id)
+ create_vv_reflection(course_id=course_id)
+
+ CourseSession.objects.create(course_id=course_id, title=names[language])
+
+ create_vv_pruefung_learning_path(course_id=course_id)
+
+
def create_course_uk_de(course_id=COURSE_UK, lang="de"):
names = {
"de": "Überbetriebliche Kurse",
diff --git a/server/vbv_lernwelt/course/management/commands/create_vermittler_pruefung.py b/server/vbv_lernwelt/course/management/commands/create_vermittler_pruefung.py
new file mode 100644
index 00000000..c227d1b0
--- /dev/null
+++ b/server/vbv_lernwelt/course/management/commands/create_vermittler_pruefung.py
@@ -0,0 +1,23 @@
+import djclick as click
+
+from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID
+from vbv_lernwelt.course.management.commands.create_default_courses import (
+ create_versicherungsvermittlerin_pruefung_course,
+)
+from vbv_lernwelt.course.models import Course
+
+ADMIN_EMAILS = ["info@iterativ.ch", "admin"]
+
+
+@click.command()
+def command():
+ print(
+ "Creating Vermittler Prüfung course",
+ COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID,
+ )
+
+ if Course.objects.filter(id=COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID).exists():
+ print("Course already exists, skipping")
+ return
+
+ create_versicherungsvermittlerin_pruefung_course()
diff --git a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py
index 915b2239..29eee41e 100644
--- a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py
+++ b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py
@@ -83,6 +83,26 @@ def create_vv_new_learning_path(
Page.objects.update(owner=user)
+def create_vv_pruefung_learning_path(
+ course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, user=None
+):
+ if user is None:
+ user = User.objects.get(username="info@iterativ.ch")
+
+ course_page = CoursePage.objects.get(course_id=course_id)
+ lp = LearningPathFactory(
+ title="Lernpfad",
+ parent=course_page,
+ )
+
+ TopicFactory(title="Prüfung", parent=lp)
+ create_circle_pruefungsvorbereitung(lp)
+ create_circle_pruefung(lp)
+
+ # all pages belong to 'admin' by default
+ Page.objects.update(owner=user)
+
+
def create_circle_basis(lp, title="Basis"):
circle = CircleFactory(
title=title,
From 58213bcb90f115be4621ddc38ba095e123f6126a Mon Sep 17 00:00:00 2001
From: Christian Cueni
Date: Wed, 24 Jan 2024 09:58:34 +0100
Subject: [PATCH 3/5] Add lehrgang on startup
---
compose/django/docker_start.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/compose/django/docker_start.sh b/compose/django/docker_start.sh
index 8da059c4..4e30ebbe 100644
--- a/compose/django/docker_start.sh
+++ b/compose/django/docker_start.sh
@@ -21,5 +21,8 @@ fi
# sed -i "s|command=/usr/local/bin/supercronic /app/supercronic_crontab|command=/usr/local/bin/supercronic /app/supercronic_crontab -sentry-dsn '$IT_SENTRY_DSN'|" /app/supervisord.conf
#fi
+# Create Prüfungslehrgang
+python /app/manage.py create_vermittler_pruefung
+
# Set the command to run supervisord
/home/django/.local/bin/supervisord -c /app/supervisord.conf
From 978136fba046af95d6548d68c7b4e42063715fec Mon Sep 17 00:00:00 2001
From: Christian Cueni
Date: Wed, 24 Jan 2024 15:17:59 +0100
Subject: [PATCH 4/5] Don't create date if none is present
---
.../assignmentEvaluationPage/EvaluationContainer.vue | 4 +++-
.../assignmentEvaluationPage/EvaluationIntro.vue | 10 ++++++++--
cypress/e2e/assignment/assignmentTrainer.cy.js | 5 ++++-
3 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue
index 04af049c..b04fdaef 100644
--- a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue
+++ b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue
@@ -65,7 +65,9 @@ const assignmentDetail = computed(() => {
});
const dueDate = computed(() =>
- dayjs(assignmentDetail.value?.evaluation_deadline?.start)
+ assignmentDetail.value?.evaluation_deadline?.start
+ ? dayjs(assignmentDetail.value?.evaluation_deadline?.start)
+ : undefined
);
const inEvaluationTask = computed(
diff --git a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue
index 931fb4ba..445e2245 100644
--- a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue
+++ b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue
@@ -11,7 +11,7 @@ const props = defineProps<{
assignmentUser: CourseSessionUser;
assignment: Assignment;
assignmentCompletion: AssignmentCompletion;
- dueDate?: Dayjs;
+ dueDate?: Dayjs | undefined;
}>();
const emit = defineEmits(["startEvaluation"]);
@@ -101,7 +101,13 @@ async function startEvaluation() {
- {{ $t(text.evaluationInstruction) }}
+ {{
+ $t(text.evaluationInstruction, {
+ name: `${
+ props.assignmentUser.first_name + " " + props.assignmentUser.last_name
+ }`,
+ })
+ }}
diff --git a/cypress/e2e/assignment/assignmentTrainer.cy.js b/cypress/e2e/assignment/assignmentTrainer.cy.js
index 5902ff3d..905d05c4 100644
--- a/cypress/e2e/assignment/assignmentTrainer.cy.js
+++ b/cypress/e2e/assignment/assignmentTrainer.cy.js
@@ -218,7 +218,10 @@ describe("assignmentTrainer.cy.js", () => {
cy.get('[data-cy="title"]').should("contain", "Feedback");
cy.get('[data-cy="evaluation-duedate]"').should("not.exist");
- cy.get('[data-cy="instruction"]').should("contain", "Intro für Feedback");
+ cy.get('[data-cy="instruction"]').should(
+ "contain",
+ "Bitte unterstütze Test Student1 und gib Feedback zum Auftrag."
+ );
cy.get('[data-cy="start-evaluation"]').click();
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5");
From ccbeb9725fd3262ea8caa2b120fde11fb57c4459 Mon Sep 17 00:00:00 2001
From: Christian Cueni
Date: Mon, 29 Jan 2024 13:14:49 +0100
Subject: [PATCH 5/5] Use different verbs for different types of assignments
---
server/vbv_lernwelt/notify/services.py | 42 ++++-
...ers.py => test_assigment_notifications.py} | 171 +++++++++++++++++-
2 files changed, 202 insertions(+), 11 deletions(-)
rename server/vbv_lernwelt/notify/tests/{test_assigment_reminders.py => test_assigment_notifications.py} (61%)
diff --git a/server/vbv_lernwelt/notify/services.py b/server/vbv_lernwelt/notify/services.py
index 84102609..aadde5aa 100644
--- a/server/vbv_lernwelt/notify/services.py
+++ b/server/vbv_lernwelt/notify/services.py
@@ -40,11 +40,22 @@ class NotificationService:
def send_assignment_submitted_notification(
cls, recipient: User, sender: User, assignment_completion: AssignmentCompletion
):
- texts = {
- "de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» abgegeben.",
- "fr": "%(sender)s a soumis l'étude de cas dirigée «%(assignment_title)s».",
- "it": "%(sender)s ha consegnato il caso di studio guidato «%(assignment_title)s».",
- }
+ if (
+ assignment_completion.assignment.assignment_type
+ == AssignmentType.PRAXIS_ASSIGNMENT.value
+ ):
+ texts = {
+ "de": "%(sender)s hat den Praxisauftrag «%(assignment_title)s» abgegeben.",
+ "fr": "%(sender)s a soumis la mission pratique «%(assignment_title)s».",
+ "it": "%(sender)s ha consegnato l'incarico pratico «%(assignment_title)s».",
+ }
+ # this was the default case before the praxis assignment was introduced
+ else:
+ texts = {
+ "de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» abgegeben.",
+ "fr": "%(sender)s a soumis l'étude de cas dirigée «%(assignment_title)s».",
+ "it": "%(sender)s ha consegnato il caso di studio guidato «%(assignment_title)s».",
+ }
verb = texts.get(recipient.language, "de") % {
"sender": sender.get_full_name(),
"assignment_title": assignment_completion.assignment.title,
@@ -70,11 +81,22 @@ class NotificationService:
assignment_completion: AssignmentCompletion,
target_url: str,
):
- texts = {
- "de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» bewertet.",
- "fr": "%(sender)s a évalué l'étude de cas dirigée «%(assignment_title)s».",
- "it": "%(sender)s ha valutato il caso di studio guidato «%(assignment_title)s».",
- }
+ if (
+ assignment_completion.assignment.assignment_type
+ == AssignmentType.PRAXIS_ASSIGNMENT.value
+ ):
+ texts = {
+ "de": "%(sender)s hat den Praxisauftrag «%(assignment_title)s» bewertet.",
+ "fr": "%(sender)s a évalué la mission pratique «%(assignment_title)s».",
+ "it": "%(sender)s ha valutato l'incarico pratico «%(assignment_title)s».",
+ }
+ # this was the default case before the praxis assignment was introduced
+ else:
+ texts = {
+ "de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» bewertet.",
+ "fr": "%(sender)s a évalué l'étude de cas dirigée «%(assignment_title)s».",
+ "it": "%(sender)s ha valutato il caso di studio guidato «%(assignment_title)s».",
+ }
verb = texts.get(recipient.language, "de") % {
"sender": sender.get_full_name(),
"assignment_title": assignment_completion.assignment.title,
diff --git a/server/vbv_lernwelt/notify/tests/test_assigment_reminders.py b/server/vbv_lernwelt/notify/tests/test_assigment_notifications.py
similarity index 61%
rename from server/vbv_lernwelt/notify/tests/test_assigment_reminders.py
rename to server/vbv_lernwelt/notify/tests/test_assigment_notifications.py
index 7c912559..d83fb977 100644
--- a/server/vbv_lernwelt/notify/tests/test_assigment_reminders.py
+++ b/server/vbv_lernwelt/notify/tests/test_assigment_notifications.py
@@ -6,7 +6,13 @@ from django.test import TestCase
from django.utils import timezone
from freezegun import freeze_time
-from vbv_lernwelt.assignment.models import AssignmentType
+from vbv_lernwelt.assignment.models import (
+ Assignment,
+ AssignmentCompletion,
+ AssignmentCompletionStatus,
+ AssignmentType,
+)
+from vbv_lernwelt.core.admin import User
from vbv_lernwelt.core.constants import TEST_COURSE_SESSION_BERN_ID
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.course.creators.test_course import create_test_course
@@ -24,6 +30,7 @@ from vbv_lernwelt.notify.email.reminders.assigment import (
send_assignment_reminder_notifications,
)
from vbv_lernwelt.notify.models import Notification
+from vbv_lernwelt.notify.services import NotificationService
EXPECTED_MEMBER_VERB = "Erinnerung: Bald ist ein Abgabetermin"
EXPECTED_EXPERT_VERB = "Erinnerung: Bald ist ein Bewertungstermin"
@@ -40,6 +47,7 @@ ASSIGNMENT_TYPE_LEARNING_CONTENT_LOOKUP: Dict[AssignmentType, str] = {
AssignmentType.PREP_ASSIGNMENT: "test-lehrgang-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto",
AssignmentType.REFLECTION: "test-lehrgang-lp-circle-fahrzeug-lc-reflexion",
AssignmentType.CASEWORK: "test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice",
+ AssignmentType.PRAXIS_ASSIGNMENT: "test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm",
}
@@ -284,3 +292,164 @@ class TestAssignmentCourseRemindersTest(TestCase):
with self.assertRaises(Notification.DoesNotExist):
Notification.objects.get(recipient__username=RECIPIENT_TRAINER)
+
+
+class TestAssignmentCourseUpdateTest(TestCase):
+ def setUp(self):
+ create_default_users()
+ create_test_course(with_sessions=True)
+
+ CourseSessionAssignment.objects.all().delete()
+ Notification.objects.all().delete()
+
+ self.student = User.objects.get(email=RECIPIENT_STUDENTS[0])
+ self.trainer = User.objects.get(email=RECIPIENT_TRAINER)
+
+ @freeze_time("2023-01-01")
+ def test_notification_title_casework_for_experts(self):
+ # GIVEN
+ casework = create_assignment(
+ assignment_type=AssignmentType.CASEWORK,
+ submission_deadline=timezone.make_aware(datetime(2022, 12, 12)),
+ evaluation_deadline=timezone.make_aware(datetime(2023, 1, 2)),
+ )
+ assignment = Assignment.objects.get(
+ slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
+ )
+
+ ac = AssignmentCompletion.objects.create(
+ completion_status=AssignmentCompletionStatus.SUBMITTED.value,
+ assignment_user=self.student,
+ assignment=assignment,
+ evaluation_passed=True,
+ course_session=casework.course_session,
+ completion_data={},
+ evaluation_max_points=10,
+ evaluation_points=10,
+ evaluation_user=self.trainer,
+ )
+
+ # WHEN
+ NotificationService.send_assignment_submitted_notification(
+ recipient=self.trainer,
+ sender=ac.assignment_user,
+ assignment_completion=ac,
+ )
+
+ # THEN
+ self.assertEqual(1, len(Notification.objects.all()))
+
+ notification = Notification.objects.get(recipient__username=RECIPIENT_TRAINER)
+ self.assertEqual("USER_INTERACTION", notification.notification_category)
+ self.assertIn("hat die geleitete Fallarbeit", notification.verb)
+
+ def test_notification_title_praxis_assignment_for_experts(self):
+ # GIVEN.
+ casework = create_assignment(
+ assignment_type=AssignmentType.PRAXIS_ASSIGNMENT,
+ )
+ assignment = Assignment.objects.get(
+ slug="test-lehrgang-assignment-mein-kundenstamm"
+ )
+
+ ac = AssignmentCompletion.objects.create(
+ completion_status=AssignmentCompletionStatus.SUBMITTED.value,
+ assignment_user=self.student,
+ assignment=assignment,
+ evaluation_passed=True,
+ course_session=casework.course_session,
+ completion_data={},
+ evaluation_max_points=10,
+ evaluation_points=10,
+ evaluation_user=self.trainer,
+ )
+
+ # WHEN
+ NotificationService.send_assignment_submitted_notification(
+ recipient=self.trainer,
+ sender=ac.assignment_user,
+ assignment_completion=ac,
+ )
+
+ # THEN
+ self.assertEqual(1, len(Notification.objects.all()))
+
+ notification = Notification.objects.get(recipient__username=RECIPIENT_TRAINER)
+ self.assertEqual("USER_INTERACTION", notification.notification_category)
+ self.assertIn("hat den Praxisauftrag", notification.verb)
+
+ @freeze_time("2023-01-01")
+ def test_notification_title_casework_for_student(self):
+ # GIVEN
+ casework = create_assignment(
+ assignment_type=AssignmentType.CASEWORK,
+ submission_deadline=timezone.make_aware(datetime(2022, 12, 12)),
+ evaluation_deadline=timezone.make_aware(datetime(2023, 1, 2)),
+ )
+ assignment = Assignment.objects.get(
+ slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
+ )
+
+ ac = AssignmentCompletion.objects.create(
+ completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
+ assignment_user=self.student,
+ assignment=assignment,
+ evaluation_passed=True,
+ course_session=casework.course_session,
+ completion_data={},
+ evaluation_max_points=10,
+ evaluation_points=10,
+ evaluation_user=self.trainer,
+ )
+
+ # WHEN
+ NotificationService.send_assignment_evaluated_notification(
+ recipient=ac.assignment_user,
+ sender=self.trainer,
+ assignment_completion=ac,
+ target_url="/some/url",
+ )
+
+ # THEN
+ self.assertEqual(1, len(Notification.objects.all()))
+
+ notification = Notification.objects.get(recipient__username=self.student.email)
+ self.assertEqual("USER_INTERACTION", notification.notification_category)
+ self.assertIn("hat die geleitete Fallarbeit", notification.verb)
+
+ @freeze_time("2023-01-01")
+ def test_notification_title_praxis_assignment_for_student(self):
+ # GIVEN
+ casework = create_assignment(
+ assignment_type=AssignmentType.PRAXIS_ASSIGNMENT,
+ )
+ assignment = Assignment.objects.get(
+ slug="test-lehrgang-assignment-mein-kundenstamm"
+ )
+
+ ac = AssignmentCompletion.objects.create(
+ completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
+ assignment_user=self.student,
+ assignment=assignment,
+ evaluation_passed=True,
+ course_session=casework.course_session,
+ completion_data={},
+ evaluation_max_points=10,
+ evaluation_points=10,
+ evaluation_user=self.trainer,
+ )
+
+ # WHEN
+ NotificationService.send_assignment_evaluated_notification(
+ recipient=ac.assignment_user,
+ sender=self.trainer,
+ assignment_completion=ac,
+ target_url="/some/url",
+ )
+
+ # THEN
+ self.assertEqual(1, len(Notification.objects.all()))
+
+ notification = Notification.objects.get(recipient__username=self.student.email)
+ self.assertEqual("USER_INTERACTION", notification.notification_category)
+ self.assertIn("hat den Praxisauftrag", notification.verb)