Merge branch 'develop' into feature/VBV-529-prototyp-persoenliches-profil

This commit is contained in:
Reto Aebersold 2024-02-05 09:19:14 +01:00
commit ab31695dab
11 changed files with 309 additions and 15 deletions

View File

@ -65,7 +65,9 @@ const assignmentDetail = computed(() => {
}); });
const dueDate = 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( const inEvaluationTask = computed(

View File

@ -11,7 +11,7 @@ const props = defineProps<{
assignmentUser: CourseSessionUser; assignmentUser: CourseSessionUser;
assignment: Assignment; assignment: Assignment;
assignmentCompletion: AssignmentCompletion; assignmentCompletion: AssignmentCompletion;
dueDate?: Dayjs; dueDate?: Dayjs | undefined;
}>(); }>();
const emit = defineEmits(["startEvaluation"]); const emit = defineEmits(["startEvaluation"]);
@ -101,7 +101,13 @@ async function startEvaluation() {
</p> </p>
<p class="my-4" data-cy="instruction"> <p class="my-4" data-cy="instruction">
{{ $t(text.evaluationInstruction) }} {{
$t(text.evaluationInstruction, {
name: `${
props.assignmentUser.first_name + " " + props.assignmentUser.last_name
}`,
})
}}
</p> </p>
<p v-if="props.assignment.assignment_type === 'CASEWORK'" class="my-4"> <p v-if="props.assignment.assignment_type === 'CASEWORK'" class="my-4">

View File

@ -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 # 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 #fi
# Create Prüfungslehrgang
python /app/manage.py create_vermittler_pruefung
# Set the command to run supervisord # Set the command to run supervisord
/home/django/.local/bin/supervisord -c /app/supervisord.conf /home/django/.local/bin/supervisord -c /app/supervisord.conf

View File

@ -218,7 +218,10 @@ describe("assignmentTrainer.cy.js", () => {
cy.get('[data-cy="title"]').should("contain", "Feedback"); cy.get('[data-cy="title"]').should("contain", "Feedback");
cy.get('[data-cy="evaluation-duedate]"').should("not.exist"); 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="start-evaluation"]').click();
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5"); cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5");

View File

@ -8,3 +8,4 @@ COURSE_UK_IT = -8
COURSE_UK_TRAINING_IT = -9 COURSE_UK_TRAINING_IT = -9
COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID = -10 COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID = -10
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11 COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID = -12

View File

@ -1,5 +1,6 @@
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
from vbv_lernwelt.course.factories import CoursePageFactory from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import CircleContactType
from vbv_lernwelt.course.utils import get_wagtail_default_site from vbv_lernwelt.course.utils import get_wagtail_default_site
@ -20,6 +21,8 @@ def create_versicherungsvermittlerin_with_categories(
id=course_id, id=course_id,
title=title, title=title,
category_name="Handlungsfeld", 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) CourseCategory.objects.get_or_create(course=course, title="Allgemein", general=True)

View File

@ -59,6 +59,7 @@ from vbv_lernwelt.course.consts import (
COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID, COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_ID, COURSE_VERSICHERUNGSVERMITTLERIN_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID, COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID,
) )
from vbv_lernwelt.course.creators.test_course import ( from vbv_lernwelt.course.creators.test_course import (
create_edoniq_test_assignment, 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.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.create_vv_new_learning_path import ( from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
create_vv_new_learning_path, create_vv_new_learning_path,
create_vv_pruefung_learning_path,
) )
from vbv_lernwelt.learnpath.models import ( from vbv_lernwelt.learnpath.models import (
Circle, Circle,
@ -153,6 +155,9 @@ def command(course):
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID, language="it" course_id=COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID, language="it"
) )
if COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID in course:
create_versicherungsvermittlerin_pruefung_course()
if COURSE_UK in course: if COURSE_UK in course:
create_course_uk_de() create_course_uk_de()
create_course_uk_de_course_sessions() 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 dassurance 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"): def create_course_uk_de(course_id=COURSE_UK, lang="de"):
names = { names = {
"de": "Überbetriebliche Kurse", "de": "Überbetriebliche Kurse",

View File

@ -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()

View File

@ -83,6 +83,26 @@ def create_vv_new_learning_path(
Page.objects.update(owner=user) 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"): def create_circle_basis(lp, title="Basis"):
circle = CircleFactory( circle = CircleFactory(
title=title, title=title,

View File

@ -3,6 +3,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import structlog import structlog
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.db.models import Model from django.db.models import Model
from notifications.signals import notify from notifications.signals import notify
@ -38,11 +40,22 @@ class NotificationService:
def send_assignment_submitted_notification( def send_assignment_submitted_notification(
cls, recipient: User, sender: User, assignment_completion: AssignmentCompletion cls, recipient: User, sender: User, assignment_completion: AssignmentCompletion
): ):
texts = { if (
"de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» abgegeben.", assignment_completion.assignment.assignment_type
"fr": "%(sender)s a soumis l'étude de cas dirigée «%(assignment_title)s».", == AssignmentType.PRAXIS_ASSIGNMENT.value
"it": "%(sender)s ha consegnato il caso di studio guidato «%(assignment_title)s».", ):
} 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") % { verb = texts.get(recipient.language, "de") % {
"sender": sender.get_full_name(), "sender": sender.get_full_name(),
"assignment_title": assignment_completion.assignment.title, "assignment_title": assignment_completion.assignment.title,
@ -68,11 +81,22 @@ class NotificationService:
assignment_completion: AssignmentCompletion, assignment_completion: AssignmentCompletion,
target_url: str, target_url: str,
): ):
texts = { if (
"de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» bewertet.", assignment_completion.assignment.assignment_type
"fr": "%(sender)s a évalué l'étude de cas dirigée «%(assignment_title)s».", == AssignmentType.PRAXIS_ASSIGNMENT.value
"it": "%(sender)s ha valutato il caso di studio guidato «%(assignment_title)s».", ):
} 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") % { verb = texts.get(recipient.language, "de") % {
"sender": sender.get_full_name(), "sender": sender.get_full_name(),
"assignment_title": assignment_completion.assignment.title, "assignment_title": assignment_completion.assignment.title,
@ -281,6 +305,13 @@ class NotificationService:
template_data=template_data, template_data=template_data,
) )
emailed = False emailed = False
try:
validate_email(recipient.email)
except ValidationError:
log.info("Recipient email is invalid")
return f"{notification_identifier}_invalid_email"
try: try:
notification = NotificationService._find_duplicate_notification( notification = NotificationService._find_duplicate_notification(
recipient=recipient, recipient=recipient,

View File

@ -6,7 +6,13 @@ from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from freezegun import freeze_time 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.constants import TEST_COURSE_SESSION_BERN_ID
from vbv_lernwelt.core.create_default_users import create_default_users from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.course.creators.test_course import create_test_course 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, send_assignment_reminder_notifications,
) )
from vbv_lernwelt.notify.models import Notification from vbv_lernwelt.notify.models import Notification
from vbv_lernwelt.notify.services import NotificationService
EXPECTED_MEMBER_VERB = "Erinnerung: Bald ist ein Abgabetermin" EXPECTED_MEMBER_VERB = "Erinnerung: Bald ist ein Abgabetermin"
EXPECTED_EXPERT_VERB = "Erinnerung: Bald ist ein Bewertungstermin" 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.PREP_ASSIGNMENT: "test-lehrgang-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto",
AssignmentType.REFLECTION: "test-lehrgang-lp-circle-fahrzeug-lc-reflexion", AssignmentType.REFLECTION: "test-lehrgang-lp-circle-fahrzeug-lc-reflexion",
AssignmentType.CASEWORK: "test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice", 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): with self.assertRaises(Notification.DoesNotExist):
Notification.objects.get(recipient__username=RECIPIENT_TRAINER) 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)