diff --git a/server/vbv_lernwelt/edoniq_test/tests/test_import.py b/server/vbv_lernwelt/edoniq_test/tests/test_import.py index f6ffa336..69b4e528 100644 --- a/server/vbv_lernwelt/edoniq_test/tests/test_import.py +++ b/server/vbv_lernwelt/edoniq_test/tests/test_import.py @@ -3,11 +3,23 @@ from django.utils import timezone from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.core.create_default_users import create_default_users +from vbv_lernwelt.core.models import User from vbv_lernwelt.course.creators.test_course import create_test_course -from vbv_lernwelt.course.models import CourseSession -from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse, CourseSessionAssignment -from vbv_lernwelt.importer.services import create_or_update_course_session -from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse, LearningContentAssignment +from vbv_lernwelt.course.models import CourseSession, CourseSessionUser +from vbv_lernwelt.course_session.models import ( + CourseSessionAssignment, + CourseSessionAttendanceCourse, +) +from vbv_lernwelt.duedate.models import DueDate +from vbv_lernwelt.importer.services import ( + create_or_update_course_session, + create_or_update_trainer, +) +from vbv_lernwelt.learnpath.models import ( + Circle, + LearningContentAssignment, + LearningContentAttendanceCourse, +) TEST_SESSION = { "ID": "AG 2023 A", @@ -45,37 +57,104 @@ TEST_SESSION = { TEST_CIRCLES = ["Fahrzeug"] LP_DATA = { - "Fahrzeug": - { - "de": { - "title": "Fahrzeug", - "slug": "fahrzeug", - "presence_course": "fahrzeug-lc-präsenzkurs-fahrzeug", - "assignments": [ - "fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice", - "fahrzeug-lc-fahrzeug-mein-erstes-auto", - ] - }, + "Fahrzeug": { + "de": { + "title": "Fahrzeug", + "slug": "fahrzeug", + "presence_course": "fahrzeug-lc-präsenzkurs-fahrzeug", + "assignments": [ + "fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice", + "fahrzeug-lc-fahrzeug-mein-erstes-auto", + ], }, + }, +} + +TEST_TRAINER = { + "Name": "Muster", + "Vorname": "Hans", + "Email": "Hans.muster@eiger-versicherung.ch", + "Sprache": "de", + "Generation": "AG 2023", + "Klasse": "A", + "Circles": "Fahrzeug", + "Anrede": "Herr", + "Tel. Geschäft": "031 321 32 32", + "Tel. Mobil": "079 321 32 32", + "Strasse": "Thunstrasse 1", + "PLZ": "3014", + "Wohnort": "Bern", } -class EdoniqSessionTrainerImportTestCase(TestCase): +class EdoniqSessionImportTestCase(TestCase): def setUp(self) -> None: create_default_users() self.course = create_test_course(with_sessions=False) def test_session_import(self): - create_or_update_course_session(self.course, TEST_SESSION, "de", circle_keys=TEST_CIRCLES) + create_or_update_course_session( + self.course, TEST_SESSION, "de", circle_keys=TEST_CIRCLES + ) cs = CourseSession.objects.get(import_id="AG 2023 A") self.assertEqual(cs.course, self.course) for circle_name in TEST_CIRCLES: - self._check_attendance(circle_name, cs) - self._check_assignments(circle_name, cs) + self._check_attendance(circle_name, cs, TEST_SESSION) + self._check_assignments(circle_name, cs, TEST_SESSION) - def _check_attendance(self, circle_name: str, cs: CourseSession): + self.assertEqual(4, DueDate.objects.count()) + + def test_update_session_import(self): + create_or_update_course_session( + self.course, TEST_SESSION, "de", circle_keys=TEST_CIRCLES + ) + cs = CourseSession.objects.get(import_id="AG 2023 A") + + trainer1 = User.objects.get(email="test-trainer1@example.com") + csu = CourseSessionUser.objects.create( + course_session=cs, + user=trainer1, + role=CourseSessionUser.Role.EXPERT, + ) + csu.expert.add(Circle.objects.get(slug="test-lehrgang-lp-circle-fahrzeug")) + + UPDATE_SESSION = { + "ID": "AG 2023 A", + "Generation": "2023", + "Region": "Aargau", + "Sprache": "de", + "Klasse": "A", + "Fahrzeug Start": "09.11.2023, 10:15", + "Fahrzeug Ende": "09.11.2023, 16:00", + "Fahrzeug Raum": "E35", + "Fahrzeug Standort": "HKV Aarau2", + "Fahrzeug Adresse": "Bahnhofstrasse 460, 5001, Aarau2", + } + + create_or_update_course_session( + self.course, UPDATE_SESSION, "de", circle_keys=TEST_CIRCLES + ) + + for circle_name in TEST_CIRCLES: + self._check_attendance( + circle_name, + cs, + UPDATE_SESSION, + trainer_name=f"{trainer1.first_name} {trainer1.last_name}", + ) + self._check_assignments(circle_name, cs, UPDATE_SESSION) + + self.assertEqual(4, DueDate.objects.count()) + + def _check_attendance( + self, + circle_name: str, + cs: CourseSession, + session_data: dict, + trainer_name: str = "", + ): attendance = LearningContentAttendanceCourse.objects.filter( slug=f"{self.course.slug}-lp-circle-{LP_DATA[circle_name]['de']['presence_course']}", ) @@ -86,15 +165,22 @@ class EdoniqSessionTrainerImportTestCase(TestCase): ) self.assertEqual( - f"{TEST_SESSION[f'{circle_name} Raum']}, {TEST_SESSION[f'{circle_name} Standort']}, {TEST_SESSION[f'{circle_name} Adresse']}", - csac.location) - self.assertEqual(f"{TEST_SESSION[f'{circle_name} Start']}", - timezone.localtime(csac.due_date.start).strftime("%d.%m.%Y, %H:%M")) - self.assertEqual(f"{TEST_SESSION[f'{circle_name} Ende']}", - timezone.localtime(csac.due_date.end).strftime("%d.%m.%Y, %H:%M")) - self.assertEqual("", csac.trainer) + f"{session_data[f'{circle_name} Raum']}, {session_data[f'{circle_name} Standort']}, {session_data[f'{circle_name} Adresse']}", + csac.location, + ) + self.assertEqual( + f"{session_data[f'{circle_name} Start']}", + timezone.localtime(csac.due_date.start).strftime("%d.%m.%Y, %H:%M"), + ) + self.assertEqual( + f"{session_data[f'{circle_name} Ende']}", + timezone.localtime(csac.due_date.end).strftime("%d.%m.%Y, %H:%M"), + ) + self.assertEqual(trainer_name, csac.trainer) - def _check_assignments(self, circle_name: str, cs: CourseSession): + def _check_assignments( + self, circle_name: str, cs: CourseSession, session_data: dict + ): for assignment_slug in LP_DATA[circle_name]["de"]["assignments"]: csa = CourseSessionAssignment.objects.get( course_session=cs, @@ -102,6 +188,65 @@ class EdoniqSessionTrainerImportTestCase(TestCase): slug=f"{self.course.slug}-lp-circle-{assignment_slug}" ), ) - if csa.learning_content.assignment_type == AssignmentType.PREP_ASSIGNMENT.value: - self.assertEqual(f"{TEST_SESSION[f'{circle_name} Start']}", - timezone.localtime(csa.submission_deadline.end).strftime("%d.%m.%Y, %H:%M")) + if ( + csa.learning_content.assignment_type + == AssignmentType.PREP_ASSIGNMENT.value + ): + self.assertEqual( + f"{session_data[f'{circle_name} Start']}", + timezone.localtime(csa.submission_deadline.end).strftime( + "%d.%m.%Y, %H:%M" + ), + ) + + +class EdoniqTrainerImportTestCase(TestCase): + def setUp(self) -> None: + create_default_users() + self.course = create_test_course(with_sessions=False) + + def test_trainer_import(self): + create_or_update_course_session( + self.course, TEST_SESSION, "de", circle_keys=TEST_CIRCLES + ) + cs = CourseSession.objects.get(import_id="AG 2023 A") + + create_or_update_trainer(self.course, TEST_TRAINER) + csu = CourseSessionUser.objects.filter( + course_session=cs, + role=CourseSessionUser.Role.EXPERT, + ).first() + + self.assertEqual(csu.user.first_name, TEST_TRAINER["Vorname"]) + self.assertEqual(csu.expert.first().slug, "test-lehrgang-lp-circle-fahrzeug") + + def test_update_trainer_import(self): + create_or_update_course_session( + self.course, TEST_SESSION, "de", circle_keys=TEST_CIRCLES + ) + cs = CourseSession.objects.get(import_id="AG 2023 A") + + create_or_update_trainer(self.course, TEST_TRAINER) + UPDATE_TRAINER = { + "Name": "Hansmeier", + "Vorname": "Hans", + "Email": "Hans.muster@eiger-versicherung.ch", + "Sprache": "de", + "Generation": "AG 2023", + "Klasse": "A", + "Circles": "Fahrzeug", + "Anrede": "Herr", + "Tel. Geschäft": "031 321 32 32", + "Tel. Mobil": "079 321 32 32", + "Strasse": "Thunstrasse 1", + "PLZ": "3014", + "Wohnort": "Bern", + } + + csu = CourseSessionUser.objects.filter( + course_session=cs, + role=CourseSessionUser.Role.EXPERT, + ).first() + create_or_update_trainer(self.course, UPDATE_TRAINER) + self.assertEqual(csu.user.first_name, UPDATE_TRAINER["Vorname"]) + self.assertEqual(csu.expert.first().slug, "test-lehrgang-lp-circle-fahrzeug") diff --git a/server/vbv_lernwelt/importer/services.py b/server/vbv_lernwelt/importer/services.py index 5270f18a..eb0f2099 100644 --- a/server/vbv_lernwelt/importer/services.py +++ b/server/vbv_lernwelt/importer/services.py @@ -9,138 +9,141 @@ from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.core.models import User from vbv_lernwelt.course.consts import COURSE_UK, COURSE_UK_FR, COURSE_UK_IT from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser -from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse, CourseSessionAssignment +from vbv_lernwelt.course_session.models import ( + CourseSessionAssignment, + CourseSessionAttendanceCourse, +) from vbv_lernwelt.importer.utils import ( calc_header_tuple_list_from_pyxl_sheet, parse_circle_group_string, try_parse_datetime, ) -from vbv_lernwelt.learnpath.models import Circle, LearningContentAttendanceCourse, LearningContentAssignment +from vbv_lernwelt.learnpath.models import ( + Circle, + LearningContentAssignment, + LearningContentAttendanceCourse, +) logger = structlog.get_logger(__name__) # it's easier to define the data here, constructing slugs is error-prone and there are some exceptions LP_DATA = { - "Kickoff": - { - "de": { - "title": "Kickoff", - "slug": "kickoff", - "presence_course": "kickoff-lc-präsenzkurs-kickoff", - "assignments": [ - "kickoff-lc-versicherungswirtschaft", - "kickoff-lc-redlichkeitserklärung", - "kickoff-lc-reflexion", - ] - }, - "fr": { - "title": "Lancement", - "slug": "lancement", - "presence_course": "lancement-lc-cours-de-présence-lancement", - "assignments": [ - "lancement-lc-secteur-de-lassurance", - "lancement-lc-redlichkeitserklärung", - "lancement-lc-réflexion", - ] - }, - "it": { - "title": "Introduzione", - "slug": "introduzione", - "presence_course": "introduzione-lc-corso-di-presenza-introduzione", - "assignments": [ - "introduzione-lc-settore-assicurativo", - "introduzione-lc-redlichkeitserklärung", - "introduzione-lc-riflessione", - ] - }, + "Kickoff": { + "de": { + "title": "Kickoff", + "slug": "kickoff", + "presence_course": "kickoff-lc-präsenzkurs-kickoff", + "assignments": [ + "kickoff-lc-versicherungswirtschaft", + "kickoff-lc-redlichkeitserklärung", + "kickoff-lc-reflexion", + ], }, - "Basis": - { - "de": { - "title": "Basis", - "slug": "basis", - "presence_course": "basis-lc-präsenzkurs-basis", - "assignments": [ - "basis-lc-vorbereitungsauftrag-circle-basis", - ] - }, - "fr": { - "title": "Base", - "slug": "base", - "presence_course": "base-lc-cours-de-présence-base", - "assignments": [ - "base-lc-mandats-préparatoires-circle-base", - ] - }, - "it": { - "title": "Base", - "slug": "base", - "presence_course": "base-lc-corso-di-presenza-base", - "assignments": [ - "base-lc-vorbereitungsauftrag-circle-basis", - ] - }, + "fr": { + "title": "Lancement", + "slug": "lancement", + "presence_course": "lancement-lc-cours-de-présence-lancement", + "assignments": [ + "lancement-lc-secteur-de-lassurance", + "lancement-lc-redlichkeitserklärung", + "lancement-lc-réflexion", + ], }, - "Fahrzeug": - { - "de": { - "title": "Fahrzeug", - "slug": "fahrzeug", - "presence_course": "fahrzeug-lc-präsenzkurs-fahrzeug", - "assignments": [ - "fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice", - "fahrzeug-lc-fahrzeug-mein-erstes-auto", - ] - }, - "fr": { - "title": "Véhicule", - "slug": "véhicule", - "presence_course": "véhicule-lc-cours-de-présence-véhicule-à-moteur", - "assignments": [ - "véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur", - "véhicule-lc-véhicule-à-moteur-ma-première-voiture", - ] - }, - "it": { - "title": "Veicolo", - "slug": "veicolo", - "presence_course": "veicolo-lc-corso-di-presenza-veicolo", - "assignments": [ - "veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore", - "veicolo-lc-veicolo-la-mia-prima-auto", - ] - }, + "it": { + "title": "Introduzione", + "slug": "introduzione", + "presence_course": "introduzione-lc-corso-di-presenza-introduzione", + "assignments": [ + "introduzione-lc-settore-assicurativo", + "introduzione-lc-redlichkeitserklärung", + "introduzione-lc-riflessione", + ], }, - "Haushalt Teil 1": - { - "de": { - "title": "Haushalt Teil 1", - "slug": "haushalt-teil-1", - "presence_course": "", - "assignments": [] - }, - "fr": { - "title": "Habitat partie 1", - "slug": "habitat-partie-1", - "presence_course": "", - "assignments": [] - }, - "it": {} + }, + "Basis": { + "de": { + "title": "Basis", + "slug": "basis", + "presence_course": "basis-lc-präsenzkurs-basis", + "assignments": [ + "basis-lc-vorbereitungsauftrag-circle-basis", + ], }, + "fr": { + "title": "Base", + "slug": "base", + "presence_course": "base-lc-cours-de-présence-base", + "assignments": [ + "base-lc-mandats-préparatoires-circle-base", + ], + }, + "it": { + "title": "Base", + "slug": "base", + "presence_course": "base-lc-corso-di-presenza-base", + "assignments": [ + "base-lc-vorbereitungsauftrag-circle-basis", + ], + }, + }, + "Fahrzeug": { + "de": { + "title": "Fahrzeug", + "slug": "fahrzeug", + "presence_course": "fahrzeug-lc-präsenzkurs-fahrzeug", + "assignments": [ + "fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice", + "fahrzeug-lc-fahrzeug-mein-erstes-auto", + ], + }, + "fr": { + "title": "Véhicule", + "slug": "véhicule", + "presence_course": "véhicule-lc-cours-de-présence-véhicule-à-moteur", + "assignments": [ + "véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur", + "véhicule-lc-véhicule-à-moteur-ma-première-voiture", + ], + }, + "it": { + "title": "Veicolo", + "slug": "veicolo", + "presence_course": "veicolo-lc-corso-di-presenza-veicolo", + "assignments": [ + "veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore", + "veicolo-lc-veicolo-la-mia-prima-auto", + ], + }, + }, + "Haushalt Teil 1": { + "de": { + "title": "Haushalt Teil 1", + "slug": "haushalt-teil-1", + "presence_course": "", + "assignments": [], + }, + "fr": { + "title": "Habitat partie 1", + "slug": "habitat-partie-1", + "presence_course": "", + "assignments": [], + }, + "it": {}, + }, "Haushalt Teil 2": { "de": { "title": "Haushalt Teil 2", "slug": "haushalt-teil-2", "presence_course": "", - "assignments": [] + "assignments": [], }, "fr": { "title": "Habitat partie 2", "slug": "habitat-partie-2", "presence_course": "", - "assignments": [] + "assignments": [], }, - "it": {} + "it": {}, }, } @@ -166,7 +169,7 @@ TRANSLATIONS = { "adresse": "Adresse", "start": "Start", "ende": "Ende", - } + }, } T2L_IGNORE_FIELDS = ["Vorname", "Name", "Email", "Sprache", "Durchführungen"] @@ -304,7 +307,9 @@ def create_or_update_course_session( place = data[f"{circle} {TRANSLATIONS[language]['standort']}"] address = data[f"{circle} {TRANSLATIONS[language]['adresse']}"] location = f"{room}, {place}, {address}" - start = try_parse_datetime(data[f"{circle} {TRANSLATIONS[language]['start']}"])[1] + start = try_parse_datetime(data[f"{circle} {TRANSLATIONS[language]['start']}"])[ + 1 + ] end = try_parse_datetime(data[f"{circle} {TRANSLATIONS[language]['ende']}"])[1] if attendance_course_lc: @@ -313,8 +318,11 @@ def create_or_update_course_session( ) csa.location = location - expert = CourseSessionUser.objects.filter(course_session_id=cs.id, - role=CourseSessionUser.Role.EXPERT).first() + expert = CourseSessionUser.objects.filter( + course_session_id=cs.id, + expert__slug=f"{course.slug}-lp-circle-{circle_data['slug']}", + role=CourseSessionUser.Role.EXPERT, + ).first() if expert: csa.trainer = f"{expert.user.first_name} {expert.user.last_name}" @@ -324,10 +332,7 @@ def create_or_update_course_session( csa.save() for assignment_slug in circle_data["assignments"]: - logger.debug( - "import", - slug=f"{course.slug}-lp-circle-{assignment_slug}" - ) + logger.debug("import", slug=f"{course.slug}-lp-circle-{assignment_slug}") csa, _created = CourseSessionAssignment.objects.get_or_create( course_session=cs, @@ -336,7 +341,10 @@ def create_or_update_course_session( ), ) - if csa.learning_content.assignment_type == AssignmentType.PREP_ASSIGNMENT.value: + if ( + csa.learning_content.assignment_type + == AssignmentType.PREP_ASSIGNMENT.value + ): csa.submission_deadline.end = timezone.make_aware(start) csa.submission_deadline.save() @@ -436,7 +444,7 @@ def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de" # circle expert handling circle_data = parse_circle_group_string(data["Circles"]) for circle_key in circle_data: - circle_name = CIRCLE_DATA[circle_key][language]["title"] + circle_name = LP_DATA[circle_key][language]["title"] # print(circle_name, groups) import_id = f"{data['Generation'].strip()} {group}"