VBV-450: Custom CourseSessionAttendanceCourse model

This commit is contained in:
Daniel Egger 2023-06-14 17:12:37 +02:00 committed by Lorenz Padberg
parent e631cc4d13
commit a5acc66981
22 changed files with 207 additions and 80 deletions

View File

@ -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
);
}
}

View File

@ -413,7 +413,7 @@ export interface CircleDocument {
}
export interface CourseSessionAttendanceCourse {
learningContentId: number;
learning_content: number;
start: string;
end: string;
location: string;

View File

@ -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",

View File

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

View File

@ -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,

View File

@ -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',
),
]

View File

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

View File

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

View File

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

View File

@ -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')),
],
),
]

View File

@ -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}"

View File

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

View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -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",
)

View File

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