diff --git a/client/src/stores/courseSessions.ts b/client/src/stores/courseSessions.ts index 3d5f9e75..d6382ef9 100644 --- a/client/src/stores/courseSessions.ts +++ b/client/src/stores/courseSessions.ts @@ -230,7 +230,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => { ): CourseSessionAttendanceCourse | undefined { if (currentCourseSession.value) { return currentCourseSession.value.attendance_courses.find( - (attendanceCourse) => attendanceCourse.learningContentId === contentId + (attendanceCourse) => attendanceCourse.learning_content === contentId ); } } diff --git a/client/src/types.ts b/client/src/types.ts index 6f25ce92..ee8187a8 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -413,7 +413,7 @@ export interface CircleDocument { } export interface CourseSessionAttendanceCourse { - learningContentId: number; + learning_content: number; start: string; end: string; location: string; diff --git a/server/config/settings/base.py b/server/config/settings/base.py index 4aaa3c86..7e92f087 100644 --- a/server/config/settings/base.py +++ b/server/config/settings/base.py @@ -116,6 +116,7 @@ LOCAL_APPS = [ "vbv_lernwelt.learnpath", "vbv_lernwelt.competence", "vbv_lernwelt.media_library", + "vbv_lernwelt.course_session", "vbv_lernwelt.feedback", "vbv_lernwelt.files", "vbv_lernwelt.notify", diff --git a/server/vbv_lernwelt/core/management/commands/reset_schema.py b/server/vbv_lernwelt/core/management/commands/reset_schema.py index 885a9e51..d0f8a469 100644 --- a/server/vbv_lernwelt/core/management/commands/reset_schema.py +++ b/server/vbv_lernwelt/core/management/commands/reset_schema.py @@ -29,4 +29,4 @@ def command(): call_command("migrate") call_command("create_default_users") call_command("create_default_courses") - call_command("create_default_duedates") + # call_command("create_default_duedates") 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 466efe4e..b43d1414 100644 --- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py +++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py @@ -1,7 +1,9 @@ import os import random +from datetime import datetime import djclick as click +from django.utils import timezone from vbv_lernwelt.assignment.creators.create_assignments import ( create_uk_basis_prep_assignment, @@ -68,6 +70,8 @@ from vbv_lernwelt.course.models import ( CourseSessionUser, ) from vbv_lernwelt.course.services import mark_course_completion +from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.feedback.creators.create_demo_feedback import create_feedback from vbv_lernwelt.importer.services import ( import_course_sessions_from_excel, @@ -237,17 +241,17 @@ def create_course_uk_de(): cs = CourseSession.objects.create( course_id=COURSE_UK, title="Bern 2023 a", - attendance_courses=[ - { - "learningContentId": LearningContentAttendanceCourse.objects.get( - slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" - ).id, - "start": "2023-05-23T08:30:00+0200", - "end": "2023-05-23T17:00:00+0200", - "location": "Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern", - "trainer": "Roland Grossenbacher, roland.grossenbacher@helvetia.ch", - } - ], + # attendance_courses=[ + # { + # "learningContentId": LearningContentAttendanceCourse.objects.get( + # slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" + # ).id, + # "start": "2023-05-23T08:30:00+0200", + # "end": "2023-05-23T17:00:00+0200", + # "location": "Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern", + # "trainer": "Roland Grossenbacher, roland.grossenbacher@helvetia.ch", + # } + # ], assignment_details_list=[ { "learningContentId": LearningContentAssignment.objects.get( @@ -266,6 +270,23 @@ def create_course_uk_de(): ], ) + csac = CourseSessionAttendanceCourse.objects.create( + course_session=cs, + learning_content=LearningContentAttendanceCourse.objects.get( + slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" + ), + due_date=DueDate.objects.create( + course_session=cs, + start=timezone.make_aware(datetime(2023, 6, 14, 8, 30)), + end=timezone.make_aware(datetime(2023, 6, 14, 17, 0)), + page=LearningContentAttendanceCourse.objects.get( + slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" + ), + ), + location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern", + trainer="Roland Grossenbacher, roland.grossenbacher@helvetia.ch", + ) + # figma demo users and data csu = CourseSessionUser.objects.create( course_session=cs, diff --git a/server/vbv_lernwelt/course/migrations/0005_remove_coursesession_attendance_courses.py b/server/vbv_lernwelt/course/migrations/0005_remove_coursesession_attendance_courses.py new file mode 100644 index 00000000..a02995df --- /dev/null +++ b/server/vbv_lernwelt/course/migrations/0005_remove_coursesession_attendance_courses.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.13 on 2023-06-14 14:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('course', '0004_import_fields'), + ] + + operations = [ + migrations.RemoveField( + model_name='coursesession', + name='attendance_courses', + ), + ] diff --git a/server/vbv_lernwelt/course/models.py b/server/vbv_lernwelt/course/models.py index 2394009d..b0910612 100644 --- a/server/vbv_lernwelt/course/models.py +++ b/server/vbv_lernwelt/course/models.py @@ -195,23 +195,23 @@ class CourseSession(models.Model): # TODO: Das wird durch event modell ersetzt - ATTENDANCE_COURSES_SCHEMA = { - "type": "array", - "items": { - "type": "object", - "properties": { - "learningContentId": { - "type": "number", - "title": "ID des Lerninhalts", - "required": True, - }, - "start": {"type": "string", "format": "datetime"}, - "end": {"type": "string", "format": "datetime"}, - "location": {"type": "string"}, - "trainer": {"type": "string"}, - }, - }, - } + # ATTENDANCE_COURSES_SCHEMA = { + # "type": "array", + # "items": { + # "type": "object", + # "properties": { + # "learningContentId": { + # "type": "number", + # "title": "ID des Lerninhalts", + # "required": True, + # }, + # "start": {"type": "string", "format": "datetime"}, + # "end": {"type": "string", "format": "datetime"}, + # "location": {"type": "string"}, + # "trainer": {"type": "string"}, + # }, + # }, + # } created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -228,9 +228,6 @@ class CourseSession(models.Model): start_date = models.DateField(null=True, blank=True) end_date = models.DateField(null=True, blank=True) - attendance_courses = JSONField( - schema=ATTENDANCE_COURSES_SCHEMA, blank=True, default=list - ) assignment_details_list = models.JSONField(default=list, blank=True) additional_json_data = models.JSONField(default=dict, blank=True) diff --git a/server/vbv_lernwelt/course/serializers.py b/server/vbv_lernwelt/course/serializers.py index 6c162cce..86ba9af0 100644 --- a/server/vbv_lernwelt/course/serializers.py +++ b/server/vbv_lernwelt/course/serializers.py @@ -7,6 +7,10 @@ from vbv_lernwelt.course.models import ( CourseCompletion, CourseSession, ) +from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.course_session.serializers import ( + CourseSessionAttendanceCourseSerializer, +) from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.duedate.serializers import DueDateSerializer @@ -52,6 +56,7 @@ class CourseSessionSerializer(serializers.ModelSerializer): competence_url = serializers.SerializerMethodField() media_library_url = serializers.SerializerMethodField() documents = serializers.SerializerMethodField() + attendance_courses = serializers.SerializerMethodField() duedates = serializers.SerializerMethodField() def get_course(self, obj): @@ -78,6 +83,11 @@ class CourseSessionSerializer(serializers.ModelSerializer): ) return CircleDocumentSerializer(documents, many=True).data + def get_attendance_courses(self, obj): + return CourseSessionAttendanceCourseSerializer( + CourseSessionAttendanceCourse.objects.filter(course_session=obj), many=True + ).data + def get_duedates(self, obj): # TODO: Filter by user / userrole duedates = DueDate.objects.filter(course_session=obj) diff --git a/server/vbv_lernwelt/course_session/__init__.py b/server/vbv_lernwelt/course_session/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/course_session/admin.py b/server/vbv_lernwelt/course_session/admin.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/course_session/apps.py b/server/vbv_lernwelt/course_session/apps.py new file mode 100644 index 00000000..851de1b9 --- /dev/null +++ b/server/vbv_lernwelt/course_session/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig + + +class CourseSessionConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "vbv_lernwelt.course_session" + + def ready(self): + try: + # pylint: disable=unused-import,import-outside-toplevel + import vbv_lernwelt.course_session.signals # noqa F401 + except ImportError: + pass diff --git a/server/vbv_lernwelt/course_session/factories.py b/server/vbv_lernwelt/course_session/factories.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/course_session/management/__init__.py b/server/vbv_lernwelt/course_session/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/course_session/management/commands/__init__.py b/server/vbv_lernwelt/course_session/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/course_session/migrations/0001_initial.py b/server/vbv_lernwelt/course_session/migrations/0001_initial.py new file mode 100644 index 00000000..64efbf20 --- /dev/null +++ b/server/vbv_lernwelt/course_session/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.13 on 2023-06-14 15:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('learnpath', '0007_learningunit_title_hidden'), + ('course', '0005_remove_coursesession_attendance_courses'), + ('duedate', '0002_auto_20230614_1500'), + ] + + operations = [ + migrations.CreateModel( + name='CourseSessionAttendanceCourse', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('location', models.CharField(blank=True, default='', max_length=255)), + ('trainer', models.CharField(blank=True, default='', max_length=255)), + ('course_session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.coursesession')), + ('due_date', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='attendance_course_due_date', to='duedate.duedate')), + ('learning_content', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='learnpath.learningcontentattendancecourse')), + ], + ), + ] diff --git a/server/vbv_lernwelt/course_session/migrations/__init__.py b/server/vbv_lernwelt/course_session/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py new file mode 100644 index 00000000..5c102872 --- /dev/null +++ b/server/vbv_lernwelt/course_session/models.py @@ -0,0 +1,23 @@ +from django.db import models + + +class CourseSessionAttendanceCourse(models.Model): + course_session = models.ForeignKey( + "course.CourseSession", + on_delete=models.CASCADE, + ) + learning_content = models.ForeignKey( + "learnpath.LearningContentAttendanceCourse", + on_delete=models.CASCADE, + ) + due_date = models.OneToOneField( + "duedate.DueDate", + on_delete=models.CASCADE, + related_name="attendance_course_due_date", + ) + + location = models.CharField(max_length=255, blank=True, default="") + trainer = models.CharField(max_length=255, blank=True, default="") + + def __str__(self): + return f"{self.course_session} - {self.learning_content}" diff --git a/server/vbv_lernwelt/course_session/serializers.py b/server/vbv_lernwelt/course_session/serializers.py new file mode 100644 index 00000000..41dbc6fb --- /dev/null +++ b/server/vbv_lernwelt/course_session/serializers.py @@ -0,0 +1,27 @@ +from rest_framework import serializers + +from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse + + +class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer): + start = serializers.SerializerMethodField() + end = serializers.SerializerMethodField() + + class Meta: + model = CourseSessionAttendanceCourse + fields = [ + "id", + "course_session", + "learning_content", + "due_date", + "location", + "trainer", + "start", + "end", + ] + + def get_start(self, obj): + return obj.due_date.start + + def get_end(self, obj): + return obj.due_date.end diff --git a/server/vbv_lernwelt/course_session/tests/__init__.py b/server/vbv_lernwelt/course_session/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/course_session/views.py b/server/vbv_lernwelt/course_session/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/server/vbv_lernwelt/course_session/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/server/vbv_lernwelt/importer/services.py b/server/vbv_lernwelt/importer/services.py index c3a29d96..c9840c17 100644 --- a/server/vbv_lernwelt/importer/services.py +++ b/server/vbv_lernwelt/importer/services.py @@ -5,6 +5,8 @@ from openpyxl.reader.excel import load_workbook from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser +from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.importer.utils import ( calc_header_tuple_list_from_pyxl_sheet, parse_circle_group_string, @@ -109,42 +111,40 @@ def create_or_update_course_session( cs.save() for circle in circles: + attendance_course_lp_qs = None if language == "de": attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter( slug=f"{course.slug}-lp-circle-{circle.lower()}-lc-präsenzkurs-{circle.lower()}" ) - add_attendance_course_date(cs, attendance_course_lp_qs, circle, data) + elif language == "fr": # todo: this is a hack remove me attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter( slug=f"{course.slug}-lp-circle-véhicule-lc-cours-de-présence-véhicule-à-moteur" ) - add_attendance_course_date(cs, attendance_course_lp_qs, circle, data) elif language == "it": # todo: this is a hack remove me attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter( slug=f"{course.slug}-lp-circle-veicolo-lc-corso-di-presenza-veicolo" ) - print(attendance_course_lp_qs) - add_attendance_course_date(cs, attendance_course_lp_qs, circle, data) + + if attendance_course_lp_qs and attendance_course_lp_qs.exists(): + CourseSessionAttendanceCourse.objects.create( + course_session=cs, + learning_content=attendance_course_lp_qs.first(), + due_date=DueDate.objects.create( + course_session=cs, + start=try_parse_datetime(data[f"{circle} Start"])[1], + end=try_parse_datetime(data[f"{circle} Ende"])[1], + page=attendance_course_lp_qs.first(), + ), + location=data[f"{circle} Raum"], + trainer="", + ) return cs -def add_attendance_course_date(course_session, attendance_course_lp_qs, circle, data): - if attendance_course_lp_qs.exists(): - course_session.attendance_courses.append( - { - "learningContentId": attendance_course_lp_qs.first().id, - "start": try_parse_datetime(data[f"{circle} Start"])[1].isoformat(), - "end": try_parse_datetime(data[f"{circle} Ende"])[1].isoformat(), - "location": data[f"{circle} Raum"], - "trainer": "", - } - ) - course_session.save() - - def import_trainers_from_excel(course: Course, filename: str, language="de"): workbook = load_workbook(filename=filename) sheet = workbook["Schulungen Trainer"] @@ -157,6 +157,7 @@ def import_trainers_from_excel(course: Course, filename: str, language="de"): def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de"): logger.debug( "create_or_update_trainer", + course=course.title, data=data, label="import", ) 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 cb63de9f..c5c72503 100644 --- a/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py +++ b/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py @@ -5,6 +5,7 @@ from openpyxl.reader.excel import load_workbook 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 from vbv_lernwelt.importer.services import create_or_update_course_session from vbv_lernwelt.importer.utils import calc_header_tuple_list_from_pyxl_sheet @@ -61,20 +62,12 @@ class CreateOrUpdateCourseSessionTestCase(TestCase): self.assertEqual(cs.region, "Deutschschweiz") self.assertEqual(cs.group, "A") - attendance_course = cs.attendance_courses[0] - attendance_course = { - k: v - for k, v in attendance_course.items() - if k not in ["learningContentId", "location"] - } - - self.assertDictEqual( - attendance_course, - { - "start": "2023-06-06T13:30:00", - "end": "2023-06-06T15:00:00", - "trainer": "", - }, + attendance_course = CourseSessionAttendanceCourse.objects.first() + self.assertEqual( + attendance_course.due_date.start.isoformat(), "2023-06-06T11:30:00+00:00" + ) + self.assertEqual( + attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00" ) def test_update_course_session(self): @@ -112,18 +105,10 @@ class CreateOrUpdateCourseSessionTestCase(TestCase): self.assertEqual(cs.region, "Deutschschweiz") self.assertEqual(cs.group, "A") - attendance_course = cs.attendance_courses[0] - attendance_course = { - k: v - for k, v in attendance_course.items() - if k not in ["learningContentId", "location"] - } - - self.assertDictEqual( - attendance_course, - { - "start": "2023-06-06T13:30:00", - "end": "2023-06-06T15:00:00", - "trainer": "", - }, + attendance_course = CourseSessionAttendanceCourse.objects.first() + self.assertEqual( + attendance_course.due_date.start.isoformat(), "2023-06-06T11:30:00+00:00" + ) + self.assertEqual( + attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00" )