diff --git a/server/config/urls.py b/server/config/urls.py index 1ab6159f..9e8dbc6f 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -21,6 +21,7 @@ from vbv_lernwelt.core.views import ( ) from vbv_lernwelt.course.views import ( course_page_api_view, + get_course_sessions, mark_course_completion, request_course_completion, ) @@ -59,6 +60,7 @@ urlpatterns = [ name="generate_web_component_icons"), # course + path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"), path(r"api/course/page//", course_page_api_view, name="course_page_api_view"), path(r"api/course/completion/mark/", mark_course_completion, 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 66b42e80..f7bdbab2 100644 --- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py +++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py @@ -3,10 +3,15 @@ import djclick as click from vbv_lernwelt.competence.create_default_competence_profile import ( create_default_competence_profile, ) +from vbv_lernwelt.course.consts import ( + COURSE_TEST_ID, + COURSE_VERSICHERUNGSVERMITTLERIN_ID, +) from vbv_lernwelt.course.creators.test_course import create_test_course from vbv_lernwelt.course.creators.versicherungsvermittlerin import ( create_versicherungsvermittlerin_with_categories, ) +from vbv_lernwelt.course.models import CourseSession from vbv_lernwelt.learnpath.create_default_learning_path import ( create_default_learning_path, ) @@ -24,7 +29,6 @@ def command(): create_versicherungsvermittlerin_with_categories() create_default_learning_path() - create_default_competence_profile() # media library @@ -34,3 +38,13 @@ def command(): # test course create_test_course() + + # course sessions + CourseSession.objects.create( + course_id=COURSE_TEST_ID, + title="Test Lehrgang Session", + ) + CourseSession.objects.create( + course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, + title="Versicherungsvermittler/in Session", + ) diff --git a/server/vbv_lernwelt/course/migrations/0003_alter_coursepage_course.py b/server/vbv_lernwelt/course/migrations/0003_alter_coursepage_course.py new file mode 100644 index 00000000..25335d46 --- /dev/null +++ b/server/vbv_lernwelt/course/migrations/0003_alter_coursepage_course.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.13 on 2022-11-07 13:30 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("course", "0002_auto_20221014_0933"), + ] + + operations = [ + migrations.AlterField( + model_name="coursepage", + name="course", + field=models.OneToOneField( + on_delete=django.db.models.deletion.PROTECT, to="course.course" + ), + ), + ] diff --git a/server/vbv_lernwelt/course/models.py b/server/vbv_lernwelt/course/models.py index fa4f55fd..e0ed641b 100644 --- a/server/vbv_lernwelt/course/models.py +++ b/server/vbv_lernwelt/course/models.py @@ -18,6 +18,30 @@ class Course(models.Model): class Meta: verbose_name = _("Lehrgang") + def get_learning_path_url(self): + from vbv_lernwelt.learnpath.models import LearningPath + + learning_path_page = ( + self.coursepage.get_children().exact_type(LearningPath).first() + ) + return learning_path_page.specific.get_frontend_url() + + def get_competence_url(self): + from vbv_lernwelt.competence.models import CompetenceProfilePage + + competence_page = ( + self.coursepage.get_children().exact_type(CompetenceProfilePage).first() + ) + return competence_page.specific.get_frontend_url() + + def get_media_library_url(self): + from vbv_lernwelt.media_library.models import MediaLibraryPage + + media_library_page = ( + self.coursepage.get_children().exact_type(MediaLibraryPage).first() + ) + return media_library_page.specific.get_frontend_url() + def __str__(self): return f"{self.title}" @@ -86,8 +110,12 @@ class CourseBasePage(Page): class CoursePage(CourseBasePage): content_panels = Page.content_panels - subpage_types = ["learnpath.LearningPath", "media_library.MediaLibraryPage"] - course = models.ForeignKey("course.Course", on_delete=models.PROTECT) + subpage_types = [ + "learnpath.LearningPath", + "competence.CompetenceProfilePage", + "media_library.MediaLibraryPage", + ] + course = models.OneToOneField("course.Course", on_delete=models.PROTECT) class Meta: verbose_name = _("Lehrgang-Seite") diff --git a/server/vbv_lernwelt/course/permissions.py b/server/vbv_lernwelt/course/permissions.py index 84c2ea40..aafad8d2 100644 --- a/server/vbv_lernwelt/course/permissions.py +++ b/server/vbv_lernwelt/course/permissions.py @@ -1,4 +1,4 @@ -from vbv_lernwelt.course.models import CourseSessionUser +from vbv_lernwelt.course.models import CourseSession, CourseSessionUser def has_course_access_by_page_request(request, obj): @@ -21,3 +21,14 @@ def has_course_access(user, course): # TODO check school class access return False + + +def course_sessions_for_user_qs(user): + if user.is_superuser: + return CourseSession.objects.all() + + course_sessions = CourseSession.objects.filter( + course_session_user__user=user + ).distinct() + + return course_sessions diff --git a/server/vbv_lernwelt/course/serializers.py b/server/vbv_lernwelt/course/serializers.py index 49bf8b6d..6d2840a9 100644 --- a/server/vbv_lernwelt/course/serializers.py +++ b/server/vbv_lernwelt/course/serializers.py @@ -1,6 +1,11 @@ from rest_framework import serializers -from vbv_lernwelt.course.models import Course, CourseCategory, CourseCompletion +from vbv_lernwelt.course.models import ( + Course, + CourseCategory, + CourseCompletion, + CourseSession, +) class CourseSerializer(serializers.ModelSerializer): @@ -34,3 +39,38 @@ class CourseCompletionSerializer(serializers.ModelSerializer): "completion_status", "additional_json_data", ] + + +class CourseSessionSerializer(serializers.ModelSerializer): + learning_path_url = serializers.SerializerMethodField() + competence_url = serializers.SerializerMethodField() + media_library_url = serializers.SerializerMethodField() + course = serializers.SerializerMethodField() + + def get_course(self, obj): + return CourseSerializer(obj.course).data + + def get_learning_path_url(self, obj): + return obj.course.get_learning_path_url() + + def get_media_library_url(self, obj): + return obj.course.get_media_library_url() + + def get_competence_url(self, obj): + return obj.course.get_competence_url() + + class Meta: + model = CourseSession + fields = [ + "id", + "created_at", + "updated_at", + "course", + "title", + "start_date", + "end_date", + "additional_json_data", + "learning_path_url", + "competence_url", + "media_library_url", + ] diff --git a/server/vbv_lernwelt/course/views.py b/server/vbv_lernwelt/course/views.py index 8405f31b..8b2ea11d 100644 --- a/server/vbv_lernwelt/course/views.py +++ b/server/vbv_lernwelt/course/views.py @@ -7,9 +7,13 @@ from wagtail.models import Page from vbv_lernwelt.core.utils import api_page_cache_get_or_set from vbv_lernwelt.course.models import CourseCompletion from vbv_lernwelt.course.permissions import ( + course_sessions_for_user_qs, has_course_access_by_page_request, ) -from vbv_lernwelt.course.serializers import CourseCompletionSerializer +from vbv_lernwelt.course.serializers import ( + CourseCompletionSerializer, + CourseSessionSerializer, +) from vbv_lernwelt.learnpath.utils import get_wagtail_type logger = structlog.get_logger(__name__) @@ -97,3 +101,17 @@ def mark_course_completion(request): except Exception as e: logger.error(e) return Response({"error": str(e)}, status=404) + + +@api_view(["GET"]) +def get_course_sessions(request): + try: + course_sessions = course_sessions_for_user_qs(request.user) + return Response( + status=200, data=CourseSessionSerializer(course_sessions, many=True).data + ) + except PermissionDenied as e: + raise e + except Exception as e: + logger.error(e) + return Response({"error": str(e)}, status=404) diff --git a/server/vbv_lernwelt/learnpath/migrations/0008_alter_learningcontent_contents.py b/server/vbv_lernwelt/learnpath/migrations/0008_alter_learningcontent_contents.py new file mode 100644 index 00000000..ce15e8c4 --- /dev/null +++ b/server/vbv_lernwelt/learnpath/migrations/0008_alter_learningcontent_contents.py @@ -0,0 +1,125 @@ +# Generated by Django 3.2.13 on 2022-11-07 13:30 + +import wagtail.blocks +import wagtail.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("learnpath", "0007_alter_learningcontent_contents"), + ] + + operations = [ + migrations.AlterField( + model_name="learningcontent", + name="contents", + field=wagtail.fields.StreamField( + [ + ( + "video", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "resource", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ("text", wagtail.blocks.RichTextBlock(required=False)), + ] + ), + ), + ( + "exercise", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "learningmodule", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "online_training", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "media_library", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "document", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "test", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "book", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "assignment", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ("text", wagtail.blocks.RichTextBlock(required=False)), + ] + ), + ), + ( + "placeholder", + wagtail.blocks.StructBlock( + [ + ("description", wagtail.blocks.TextBlock()), + ("url", wagtail.blocks.TextBlock()), + ] + ), + ), + ], + use_json_field=None, + ), + ), + ]