diff --git a/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue b/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue index e4d28926..9ef40312 100644 --- a/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue +++ b/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue @@ -76,43 +76,3 @@ async function startTest() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/server/vbv_lernwelt/course_session/migrations/0004_coursesessionedoniqtest.py b/server/vbv_lernwelt/course_session/migrations/0004_coursesessionedoniqtest.py index f07826fb..900914f9 100644 --- a/server/vbv_lernwelt/course_session/migrations/0004_coursesessionedoniqtest.py +++ b/server/vbv_lernwelt/course_session/migrations/0004_coursesessionedoniqtest.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.20 on 2023-08-17 13:14 +# Generated by Django 3.2.20 on 2023-08-20 14:06 import django.db.models.deletion from django.db import migrations, models @@ -6,9 +6,9 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("learnpath", "0003_auto_20230810_0817"), ("course", "0003_alter_coursecompletion_additional_json_data"), ("duedate", "0002_alter_duedate_start"), + ("learnpath", "0003_auto_20230810_0817"), ("course_session", "0003_initial"), ] @@ -33,10 +33,11 @@ class Migration(migrations.Migration): ), ), ( - "due_date", + "deadline", models.OneToOneField( + null=True, on_delete=django.db.models.deletion.CASCADE, - related_name="edoniq_test_due_date", + related_name="test_submission_deadline", to="duedate.duedate", ), ), diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py index 68586c34..0b2f636c 100644 --- a/server/vbv_lernwelt/course_session/models.py +++ b/server/vbv_lernwelt/course_session/models.py @@ -163,11 +163,34 @@ class CourseSessionEdoniqTest(models.Model): "learnpath.LearningContentEdoniqTest", on_delete=models.CASCADE, ) - due_date = models.OneToOneField( + deadline = models.OneToOneField( "duedate.DueDate", on_delete=models.CASCADE, - related_name="edoniq_test_due_date", + related_name="test_submission_deadline", + null=True, ) def __str__(self): return f"{self.course_session} - {self.learning_content}" + + def save(self, *args, **kwargs): + if not self.pk: + title = "" + url = "" + page = None + edoniq_test_type_description = _("Wissens und Verständnisfragen") + if self.learning_content_id: + title = self.learning_content.title + page = self.learning_content.page_ptr + url = self.learning_content.get_frontend_url() + + self.deadline = DueDate.objects.create( + url=url, + title=f"{title}", + learning_content_description=edoniq_test_type_description, + description=f"{_('Abgabe Termin')}", + course_session=self.course_session, + page=page, + ) + + super().save(*args, **kwargs) diff --git a/server/vbv_lernwelt/duedate/admin.py b/server/vbv_lernwelt/duedate/admin.py index d9d5603b..83268d4f 100644 --- a/server/vbv_lernwelt/duedate/admin.py +++ b/server/vbv_lernwelt/duedate/admin.py @@ -12,7 +12,14 @@ from vbv_lernwelt.learnpath.models import ( @admin.register(DueDate) class DueDateAdmin(admin.ModelAdmin): date_hierarchy = "end" - list_display = ["title", "course_session", "start", "end", "is_undefined"] + list_display = [ + "title", + "course_session", + "learning_content_description", + "start", + "end", + "is_undefined", + ] list_filter = ["course_session"] readonly_fields = ["course_session", "page"] diff --git a/server/vbv_lernwelt/importer/services.py b/server/vbv_lernwelt/importer/services.py index df7ded31..e5a682b0 100644 --- a/server/vbv_lernwelt/importer/services.py +++ b/server/vbv_lernwelt/importer/services.py @@ -12,6 +12,7 @@ from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser from vbv_lernwelt.course_session.models import ( CourseSessionAssignment, CourseSessionAttendanceCourse, + CourseSessionEdoniqTest, ) from vbv_lernwelt.importer.utils import ( calc_header_tuple_list_from_pyxl_sheet, @@ -22,6 +23,7 @@ from vbv_lernwelt.learnpath.models import ( Circle, LearningContentAssignment, LearningContentAttendanceCourse, + LearningContentEdoniqTest, ) logger = structlog.get_logger(__name__) @@ -38,7 +40,7 @@ LP_DATA = { "kickoff-lc-redlichkeitserklärung", "kickoff-lc-reflexion", ], - "tests": ["kickoff-lc-wissens-und-verständnisfragen"], + "edoniq_tests": ["kickoff-lc-wissens-und-verständnisfragen"], }, "fr": { "title": "Lancement", @@ -49,6 +51,7 @@ LP_DATA = { "lancement-lc-redlichkeitserklärung", "lancement-lc-réflexion", ], + "edoniq_tests": [], }, "it": { "title": "Introduzione", @@ -59,6 +62,7 @@ LP_DATA = { "introduzione-lc-redlichkeitserklärung", "introduzione-lc-riflessione", ], + "edoniq_tests": [], }, }, "Basis": { @@ -69,6 +73,7 @@ LP_DATA = { "assignments": [ "basis-lc-vorbereitungsauftrag-circle-basis", ], + "edoniq_tests": [], }, "fr": { "title": "Base", @@ -77,6 +82,7 @@ LP_DATA = { "assignments": [ "base-lc-mandats-préparatoires-circle-base", ], + "edoniq_tests": [], }, "it": { "title": "Base", @@ -85,6 +91,7 @@ LP_DATA = { "assignments": [ "base-lc-vorbereitungsauftrag-circle-basis", ], + "edoniq_tests": [], }, }, "Fahrzeug": { @@ -96,6 +103,7 @@ LP_DATA = { "fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice", "fahrzeug-lc-fahrzeug-mein-erstes-auto", ], + "edoniq_tests": [], }, "fr": { "title": "Véhicule", @@ -105,6 +113,7 @@ LP_DATA = { "véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur", "véhicule-lc-véhicule-à-moteur-ma-première-voiture", ], + "edoniq_tests": [], }, "it": { "title": "Veicolo", @@ -114,6 +123,7 @@ LP_DATA = { "veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore", "veicolo-lc-veicolo-la-mia-prima-auto", ], + "edoniq_tests": [], }, }, "Haushalt Teil 1": { @@ -122,12 +132,14 @@ LP_DATA = { "slug": "haushalt-teil-1", "presence_course": "", "assignments": [], + "edoniq_tests": [], }, "fr": { "title": "Habitat partie 1", "slug": "habitat-partie-1", "presence_course": "", "assignments": [], + "edoniq_tests": [], }, "it": {}, }, @@ -137,12 +149,14 @@ LP_DATA = { "slug": "haushalt-teil-2", "presence_course": "", "assignments": [], + "edoniq_tests": [], }, "fr": { "title": "Habitat partie 2", "slug": "habitat-partie-2", "presence_course": "", "assignments": [], + "edoniq_tests": [], }, "it": {}, }, @@ -304,6 +318,7 @@ def create_or_update_course_session( cs.save() for circle in circle_keys: circle_data = lp_data[circle][language] + logger.debug("import", data=circle_data) attendance_course_lc = LearningContentAttendanceCourse.objects.filter( slug=f"{course.slug}-lp-circle-{circle_data['presence_course']}" @@ -313,10 +328,12 @@ 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 - ] - end = try_parse_datetime(data[f"{circle} {TRANSLATIONS[language]['ende']}"])[1] + presence_day_start = try_parse_datetime( + data[f"{circle} {TRANSLATIONS[language]['start']}"] + )[1] + presence_day_end = try_parse_datetime( + data[f"{circle} {TRANSLATIONS[language]['ende']}"] + )[1] if attendance_course_lc: create_or_update_course_session_attendance( @@ -325,13 +342,18 @@ def create_or_update_course_session( course.slug, circle_data["slug"], location, - start, - end, + presence_day_start, + presence_day_end, ) for assignment_slug in circle_data["assignments"]: create_or_update_course_session_assignment( - cs, course.slug, assignment_slug, start, end + cs, course.slug, assignment_slug, presence_day_start, presence_day_end + ) + + for test_slug in circle_data["edoniq_tests"]: + create_or_update_course_session_edoniq_test( + cs, course.slug, test_slug, presence_day_start ) return cs @@ -343,8 +365,8 @@ def create_or_update_course_session_attendance( course_slug: str, circle_slug: str, location: str, - start: date, - end: date, + start: datetime, + end: datetime, ): csa, _created = CourseSessionAttendanceCourse.objects.get_or_create( course_session=cs, learning_content=attendance_course_lc @@ -369,7 +391,11 @@ def create_or_update_course_session_attendance( def create_or_update_course_session_assignment( - cs: CourseSession, course_slug: str, assignment_slug: str, start: date, end: date + cs: CourseSession, + course_slug: str, + assignment_slug: str, + start: datetime, + end: datetime, ): logger.debug("import", slug=f"{course_slug}-lp-circle-{assignment_slug}") @@ -402,6 +428,22 @@ def create_or_update_course_session_assignment( csa.submission_deadline.save() +def create_or_update_course_session_edoniq_test( + cs: CourseSession, course_slug: str, test_slug: str, start: datetime +): + learning_content = LearningContentEdoniqTest.objects.filter( + slug=f"{course_slug}-lp-circle-{test_slug}" + ).first() + + if learning_content: + cset, _created = CourseSessionEdoniqTest.objects.get_or_create( + course_session=cs, learning_content=learning_content + ) + + cset.deadline.end = timezone.make_aware(start) + timezone.timedelta(days=10) + cset.deadline.save() + + def validate_row_data(data: Dict[str, any], required_headers: List[str]): logger.debug( "validate_row_data_missing_header", diff --git a/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py b/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py index e1d5c75e..8af87ee4 100644 --- a/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py +++ b/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py @@ -1,6 +1,7 @@ import os from django.test import TestCase +from django.utils.timezone import make_naive from openpyxl.reader.excel import load_workbook from vbv_lernwelt.assignment.models import AssignmentType @@ -11,14 +12,19 @@ from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.course_session.models import ( CourseSessionAssignment, CourseSessionAttendanceCourse, + CourseSessionEdoniqTest, ) from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.importer.services import ( create_or_update_course_session, + create_or_update_course_session_edoniq_test, DataImportError, validate_row_data, ) -from vbv_lernwelt.importer.utils import calc_header_tuple_list_from_pyxl_sheet +from vbv_lernwelt.importer.utils import ( + calc_header_tuple_list_from_pyxl_sheet, + try_parse_datetime, +) from vbv_lernwelt.learnpath.models import Circle test_dir = os.path.dirname(os.path.abspath(__file__)) @@ -261,3 +267,55 @@ class CreateOrUpdateCourseSessionTestCase(TestCase): "Generation", ], ) + + +class CreateOrUpdateEdoniqTestCase(TestCase): + def setUp(self): + create_default_users() + self.course = create_test_course(include_vv=False) + row = [ + ("ID", "AG 2023 A"), + ("Generation", 2023), + ("Region", "Aargau"), + ("Sprache", "de"), + ("Klasse", "A"), + ("Kickoff Start", "06.06.2023, 13:30"), + ("Kickoff Ende", "06.06.2023, 15:00"), + ( + "Kickoff Raum", + "E64", + ), + ("Kickoff Standort", "HKV Aarau"), + ("Kickoff Adresse", "Bahnhofstrasse 460, 5001, Aarau"), + ] + + data = dict(row) + + self.cs = create_or_update_course_session( + self.course, data, language="de", circle_keys=["Kickoff"] + ) + + def _create_or_update_edonqi_test(self, date: str): + start_date = try_parse_datetime(date)[1] + + create_or_update_course_session_edoniq_test( + self.cs, + "test-lehrgang", + "fahrzeug-lc-wissens-und-verständnisfragen", + make_naive(start_date), + ) + + def test_create_course_session(self): + self._create_or_update_edonqi_test("2023-06-06T11:30:00+00:00") + + test = CourseSessionEdoniqTest.objects.first() + self.assertEqual(test.deadline.end.isoformat(), "2023-06-16T11:30:00+00:00") + + def test_update_course_session(self): + self._create_or_update_edonqi_test("2023-06-06T11:30:00+00:00") + duedate_count = DueDate.objects.count() + self._create_or_update_edonqi_test("2023-06-07T11:30:00+00:00") + self.assertEqual(duedate_count, DueDate.objects.count()) + + test = CourseSessionEdoniqTest.objects.first() + self.assertEqual(test.deadline.end.isoformat(), "2023-07-16T11:30:00+00:00")