diff --git a/server/vbv_lernwelt/course/creators/test_course.py b/server/vbv_lernwelt/course/creators/test_course.py index aaa0d743..09ce6bff 100644 --- a/server/vbv_lernwelt/course/creators/test_course.py +++ b/server/vbv_lernwelt/course/creators/test_course.py @@ -1,7 +1,9 @@ import json +from datetime import datetime import wagtail_factories from django.conf import settings +from django.utils import timezone from slugify import slugify from wagtail.models import Site from wagtail.rich_text import RichText @@ -34,7 +36,8 @@ from vbv_lernwelt.course.models import ( CourseSession, CourseSessionUser, ) -from vbv_lernwelt.learnpath.models import Circle +from vbv_lernwelt.course_session.models import CourseSessionAssignment, CourseSessionAttendanceCourse +from vbv_lernwelt.learnpath.models import Circle, LearningContentAssignment, LearningContentAttendanceCourse from vbv_lernwelt.learnpath.tests.learning_path_factories import ( CircleFactory, LearningContentAssignmentFactory, @@ -79,16 +82,20 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False): create_test_media_library() if with_sessions: + now = timezone.now() # course sessions cs_bern = CourseSession.objects.create( course_id=COURSE_TEST_ID, title="Test Bern 2022 a", id=TEST_COURSE_SESSION_BERN_ID, + start_date=now, ) cs_zurich = CourseSession.objects.create( course_id=COURSE_TEST_ID, title="Test Zürich 2022 a", id=TEST_COURSE_SESSION_ZURICH_ID, + start_date=now, + ) trainer1 = User.objects.get(email="test-trainer1@example.com") @@ -115,6 +122,12 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False): course_session=cs_zurich, user=student2, ) + for cs in CourseSession.objects.all(): + for assignment in LearningContentAssignment.objects.all(): + create_course_session_assignment(cs, assignment) + + for attendance_course in LearningContentAttendanceCourse.objects.all(): + create_course_session_attendance_course(cs, attendance_course) return course @@ -143,6 +156,28 @@ def create_test_assignment_submitted_data(assignment, course_session, user): ) +def create_course_session_assignment(course_session, assignment): + csa, created = CourseSessionAssignment.objects.get_or_create( + course_session=course_session, + learning_content=assignment, + ) + return csa + + +def create_course_session_attendance_course(course_session, course): + casc = CourseSessionAttendanceCourse.objects.create( + course_session=course_session, + learning_content=course, + location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern", + trainer="Roland Grossenbacher, roland.grossenbacher@helvetia.ch", + ) + + casc.due_date.start = timezone.make_aware(datetime(2023, 6, 14, 8, 30)) + casc.due_date.end = timezone.make_aware(datetime(2023, 6, 14, 17, 0)) + casc.due_date.save() + return casc + + def create_test_course_with_categories(apps=None, schema_editor=None): if apps is not None: Course = apps.get_model("course", "Course") 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 b43d1414..c9fb5ae2 100644 --- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py +++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py @@ -1,6 +1,6 @@ import os import random -from datetime import datetime +from datetime import datetime, timedelta import djclick as click from django.utils import timezone @@ -70,7 +70,7 @@ 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.course_session.models import CourseSessionAttendanceCourse, CourseSessionAssignment from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.feedback.creators.create_demo_feedback import create_feedback from vbv_lernwelt.importer.services import ( @@ -84,9 +84,9 @@ from vbv_lernwelt.learnpath.create_vv_new_learning_path import ( from vbv_lernwelt.learnpath.models import ( Circle, LearningContent, - LearningContentAssignment, LearningContentAttendanceCourse, ) +from vbv_lernwelt.learnpath.models import LearningContentAssignment from vbv_lernwelt.media_library.create_default_media_library import ( create_default_media_library, ) @@ -252,22 +252,22 @@ def create_course_uk_de(): # "trainer": "Roland Grossenbacher, roland.grossenbacher@helvetia.ch", # } # ], - assignment_details_list=[ - { - "learningContentId": LearningContentAssignment.objects.get( - slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice" - ).id, - "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", - "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", - }, - { - "learningContentId": LearningContentAssignment.objects.get( - slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto" - ).id, - "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", - "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", - }, - ], + # assignment_details_list=[ + # { + # "learningContentId": LearningContentAssignment.objects.get( + # slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice" + # ).id, + # "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", + # "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", + # }, + # { + # "learningContentId": LearningContentAssignment.objects.get( + # slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto" + # ).id, + # "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", + # "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", + # }, + # ], ) csac = CourseSessionAttendanceCourse.objects.create( @@ -559,8 +559,25 @@ def create_course_training_de(): ) for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING): + csa = CourseSessionAssignment.objects.create( + course_session=cs, + learning_content=LearningContentAssignment.objects.get( + slug=f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice" + ), + ) + print(csa) + + submission_deadline = csa.submission_deadline + submission_deadline.end = cs.start_date + timedelta(days=14) + submission_deadline.save() + + evaluation_deadline = csa.evaluation_deadline + evaluation_deadline.end = cs.start_date + timedelta(days=28) + evaluation_deadline.save() + cs.assignment_details_list = [ { + "learningContentId": LearningContentAssignment.objects.get( slug=f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice" ).id, @@ -643,22 +660,22 @@ def create_course_training_fr(): ) for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_FR): - cs.assignment_details_list = [ - { - "learningContentId": LearningContentAssignment.objects.get( - slug=f"{course.slug}-lp-circle-véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur" - ).id, - "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", - "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", - }, - { - "learningContentId": LearningContentAssignment.objects.get( - slug=f"{course.slug}-lp-circle-véhicule-lc-véhicule-à-moteur-ma-première-voiture" - ).id, - "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", - "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", - }, - ] + # cs.assignment_details_list = [ + # { + # "learningContentId": LearningContentAssignment.objects.get( + # slug=f"{course.slug}-lp-circle-véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur" + # ).id, + # "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", + # "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", + # }, + # { + # "learningContentId": LearningContentAssignment.objects.get( + # slug=f"{course.slug}-lp-circle-véhicule-lc-véhicule-à-moteur-ma-première-voiture" + # ).id, + # "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", + # "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", + # }, + # ] cs.save() # attach users as trainers to ÜK course @@ -730,22 +747,22 @@ def create_course_training_it(): ) for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_IT): - cs.assignment_details_list = [ - { - "learningContentId": LearningContentAssignment.objects.get( - slug=f"{course.slug}-lp-circle-veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore" - ).id, - "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z", - "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", - }, - { - "learningContentId": LearningContentAssignment.objects.get( - slug=f"{course.slug}-lp-circle-veicolo-lc-veicolo-la-mia-prima-auto" - ).id, - "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z", - "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", - }, - ] + # cs.assignment_details_list = [ + # { + # "learningContentId": LearningContentAssignment.objects.get( + # slug=f"{course.slug}-lp-circle-veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore" + # ).id, + # "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z", + # "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", + # }, + # { + # "learningContentId": LearningContentAssignment.objects.get( + # slug=f"{course.slug}-lp-circle-veicolo-lc-veicolo-la-mia-prima-auto" + # ).id, + # "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z", + # "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", + # }, + # ] cs.save() # attach users as trainers to ÜK course diff --git a/server/vbv_lernwelt/course_session/admin.py b/server/vbv_lernwelt/course_session/admin.py index e69de29b..6e43ba5f 100644 --- a/server/vbv_lernwelt/course_session/admin.py +++ b/server/vbv_lernwelt/course_session/admin.py @@ -0,0 +1,26 @@ +from django.contrib import admin + +from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse, CourseSessionAssignment + + +@admin.register(CourseSessionAttendanceCourse) +class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin): + # Inline fields are not possible for the DueDate model, because it is not a ForeignKey relatoion. + readonly_fields = ['course_session', 'learning_content', 'due_date'] + list_display = [ + "course_session", + "learning_content", + "trainer", + ] + list_filter = ["course_session__course"] + + +@admin.register(CourseSessionAssignment) +class CourseSessionAssignmentAdmin(admin.ModelAdmin): + # Inline fields are not possible for the DueDate model, because it is not a ForeignKey relatoion. + readonly_fields = ['course_session', 'learning_content'] + list_display = [ + "course_session", + "learning_content", + ] + list_filter = ["course_session__course"] diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py index 5c102872..f4d06266 100644 --- a/server/vbv_lernwelt/course_session/models.py +++ b/server/vbv_lernwelt/course_session/models.py @@ -1,7 +1,15 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ + +from vbv_lernwelt.duedate.models import DueDate class CourseSessionAttendanceCourse(models.Model): + """ + Präsenzkurs Durchührung + + Kann über einen Zeitraum von meheren Tagen gehen. + """ course_session = models.ForeignKey( "course.CourseSession", on_delete=models.CASCADE, @@ -19,5 +27,68 @@ class CourseSessionAttendanceCourse(models.Model): location = models.CharField(max_length=255, blank=True, default="") trainer = models.CharField(max_length=255, blank=True, default="") + def save(self, *args, **kwargs): + if not self.pk: + title = "" + page = None + if self.learning_content_id: + title = self.learning_content.title + page = self.learning_content.page_ptr + + self.due_date = DueDate.objects.create( + title=f"{title} {_('Präsenzkurs')}", + course_session=self.course_session, + page=page) + super().save(*args, **kwargs) + + def __str__(self): + return f"{self.course_session} - {self.learning_content}" + + +class CourseSessionAssignment(models.Model): + """ + Auftrag + - Geletitete Fallarbeit ist eine speziefische ausprägung eines Auftrags (assignment_type) + + """ + course_session = models.ForeignKey( + "course.CourseSession", + on_delete=models.CASCADE, + ) + learning_content = models.ForeignKey( + "learnpath.LearningContentAssignment", + on_delete=models.CASCADE, + ) + submission_deadline = models.OneToOneField( + "duedate.DueDate", + on_delete=models.CASCADE, + related_name="assignment_submission_deadline", + ) + + evaluation_deadline = models.OneToOneField( + "duedate.DueDate", + on_delete=models.CASCADE, + related_name="assignment_evaluation_deadline", + ) + + def save(self, *args, **kwargs): + if not self.pk: + title = "" + page = None + if self.learning_content_id: + title = self.learning_content.title + page = self.learning_content.page_ptr + + self.submission_deadline = DueDate.objects.create( + title=f"{title} {_('Submission Deadline')}", + course_session=self.course_session, + page=page) + + self.evaluation_deadline = DueDate.objects.create( + title=f"{title} {_('Evaluation Deadline')}", + course_session=self.course_session, + page=page) + super().save(*args, **kwargs) + def __str__(self): return f"{self.course_session} - {self.learning_content}" diff --git a/server/vbv_lernwelt/course_session/tests/test_factories.py b/server/vbv_lernwelt/course_session/tests/test_factories.py new file mode 100644 index 00000000..6bcbaa07 --- /dev/null +++ b/server/vbv_lernwelt/course_session/tests/test_factories.py @@ -0,0 +1,47 @@ +from datetime import timedelta + +from django.test import TestCase + +from vbv_lernwelt.core.create_default_users import create_default_users +from vbv_lernwelt.core.models import User +from vbv_lernwelt.course.consts import COURSE_TEST_ID +from vbv_lernwelt.course.creators.test_course import create_test_course +from vbv_lernwelt.course.models import CourseSession, CourseSessionUser +from vbv_lernwelt.course_session.models import CourseSessionAssignment +from vbv_lernwelt.learnpath.models import Circle + + +class CourseSessionModelsTestCase(TestCase): + def setUp(self) -> None: + create_default_users() + create_test_course() + + self.user = User.objects.get(username="student") + self.expert = User.objects.get( + username="patrizia.huggel@eiger-versicherungen.ch" + ) + + self.course_session = CourseSession.objects.create( + course_id=COURSE_TEST_ID, + title="Test Lehrgang Session", + ) + + csu = CourseSessionUser.objects.create( + course_session=self.course_session, + user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"), + role=CourseSessionUser.Role.EXPERT, + ) + csu.expert.add(Circle.objects.get(slug="test-lehrgang-lp-circle-fahrzeug")) + + def test_course_session_assignment(self): + csa = CourseSessionAssignment.objects.create( + course_session=self.course_session, + # cs learning_content=LearningContentAssignment.objects.get( + # slug=f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice" + # ), + ) + print(csa) + + submission_deadline = csa.submission_deadline + submission_deadline.end = self.course_session.start_date + timedelta(days=14) + submission_deadline.save() diff --git a/server/vbv_lernwelt/course_session/tests/test_models.py b/server/vbv_lernwelt/course_session/tests/test_models.py new file mode 100644 index 00000000..ce7e54b5 --- /dev/null +++ b/server/vbv_lernwelt/course_session/tests/test_models.py @@ -0,0 +1,39 @@ +from datetime import datetime + +from django.test import TestCase +from django.utils import timezone + +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_session.models import CourseSessionAssignment, CourseSessionAttendanceCourse +from vbv_lernwelt.duedate.models import DueDate + + +class CourseSessionModelsTestCase(TestCase): + def setUp(self) -> None: + create_default_users() + create_test_course(with_sessions=True) + + def test_course_session_assignment(self): + csa = CourseSessionAssignment.objects.all().first() + + submission_deadline = csa.submission_deadline + + deadline_date = datetime(2023, 7, 6, 8, 30, tzinfo=timezone.get_current_timezone()) + submission_deadline.end = deadline_date + submission_deadline.save() + + this_date = DueDate.objects.get(pk=submission_deadline.pk) + self.assertEqual(this_date.end, deadline_date) + + def test_course_session_attendance_course(self): + csac = CourseSessionAttendanceCourse.objects.all().first() + + due_date = csac.due_date + + deadline_date = datetime(2023, 7, 6, 8, 30, tzinfo=timezone.get_current_timezone()) + due_date.end = deadline_date + due_date.save() + + this_date = DueDate.objects.get(pk=due_date.pk) + self.assertEqual(this_date.end, deadline_date) diff --git a/server/vbv_lernwelt/duedate/admin.py b/server/vbv_lernwelt/duedate/admin.py index d01132ef..1f0ad4db 100644 --- a/server/vbv_lernwelt/duedate/admin.py +++ b/server/vbv_lernwelt/duedate/admin.py @@ -14,6 +14,7 @@ class DueDateAdmin(admin.ModelAdmin): date_hierarchy = "end" list_display = ["title", "course_session", "start", "end", "unset"] list_filter = ["course_session"] + readonly_fields = ["course_session", "page"] def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "page":