diff --git a/server/vbv_lernwelt/competence/models.py b/server/vbv_lernwelt/competence/models.py index f9779974..81abb841 100644 --- a/server/vbv_lernwelt/competence/models.py +++ b/server/vbv_lernwelt/competence/models.py @@ -6,10 +6,16 @@ from wagtail.fields import StreamField from wagtail.models import Page from vbv_lernwelt.core.model_utils import find_available_slug -from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class +from vbv_lernwelt.course.models import CourseBasePage -class CompetenceProfilePage(Page): +class CompetenceProfilePage(CourseBasePage): + serialize_field_names = [ + "course", + "circles", + "children", + ] + parent_page_types = ["course.CoursePage"] subpage_types = ["competence.CompetencePage"] @@ -26,19 +32,13 @@ class CompetenceProfilePage(Page): def get_frontend_url(self): return f"/competence/{self.slug}" - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - [ - "course", - "circles", - "children", - ], - ) +class CompetencePage(CourseBasePage): + serialize_field_names = [ + "competence_id", + "children", + ] -class CompetencePage(Page): parent_page_types = ["competence.CompetenceProfilePage"] subpage_types = ["competence.PerformanceCriteria"] competence_id = models.TextField(default="A1") @@ -63,18 +63,8 @@ class CompetencePage(Page): ) super(CompetencePage, self).full_clean(*args, **kwargs) - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - [ - "competence_id", - "children", - ], - ) - -class PerformanceCriteria(Page): +class PerformanceCriteria(CourseBasePage): parent_page_types = ["competence.CompetenceProfilePage"] competence_id = models.TextField(default="A1.1") learning_unit = models.ForeignKey( diff --git a/server/vbv_lernwelt/competence/serializers.py b/server/vbv_lernwelt/competence/serializers.py index 062009bf..a50eb387 100644 --- a/server/vbv_lernwelt/competence/serializers.py +++ b/server/vbv_lernwelt/competence/serializers.py @@ -1,19 +1,16 @@ from rest_framework import serializers from vbv_lernwelt.competence.models import PerformanceCriteria -from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class -from vbv_lernwelt.course.serializers import CourseCategorySerializer +from vbv_lernwelt.course.serializers import ( + CourseCategorySerializer, +) +from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class class PerformanceCriteriaSerializer( - get_it_serializer_class( + get_course_serializer_class( PerformanceCriteria, - [ - "id", - "title", - "slug", - "type", - "translation_key", + field_names=[ "competence_id", "learning_unit", "circle", @@ -43,14 +40,9 @@ class PerformanceCriteriaSerializer( class PerformanceCriteriaLearningPathSerializer( - get_it_serializer_class( + get_course_serializer_class( PerformanceCriteria, - [ - "id", - "title", - "slug", - "type", - "translation_key", + field_names=[ "competence_id", ], ) diff --git a/server/vbv_lernwelt/core/serializer_helpers.py b/server/vbv_lernwelt/core/serializer_helpers.py index b1d1ef61..2f643c5c 100644 --- a/server/vbv_lernwelt/core/serializer_helpers.py +++ b/server/vbv_lernwelt/core/serializer_helpers.py @@ -1,41 +1,46 @@ import wagtail.api.v2.serializers as wagtail_serializers from rest_framework.fields import SerializerMethodField -from vbv_lernwelt.course.models import CoursePage -from vbv_lernwelt.course.serializers import CourseCategorySerializer, CourseSerializer from vbv_lernwelt.learnpath.utils import get_wagtail_type -def get_it_serializer_class(model, field_names): - base_field_names = [ - "id", - "title", - "slug", - "type", - "translation_key", - "frontend_url", - ] +def get_it_serializer_class( + model, field_names=None, base_field_names=None, base_class=None +): + if field_names is None: + field_names = [] + + if base_field_names is None: + base_field_names = [ + "id", + "title", + "slug", + "type", + "translation_key", + "frontend_url", + ] + + if base_class is None: + base_class = ItWagtailBaseSerializer + return wagtail_serializers.get_serializer_class( model, field_names=base_field_names + field_names, meta_fields=[], - base=ItBaseSerializer, + base=base_class, ) -class ItTypeField(wagtail_serializers.TypeField): +class ItWagtailTypeField(wagtail_serializers.TypeField): def to_representation(self, obj): name = get_wagtail_type(obj) return name -class ItBaseSerializer(wagtail_serializers.BaseSerializer): - type = ItTypeField(read_only=True) +class ItWagtailBaseSerializer(wagtail_serializers.BaseSerializer): + type = ItWagtailTypeField(read_only=True) children = SerializerMethodField() - course = SerializerMethodField() - course_category = CourseCategorySerializer(read_only=True) frontend_url = SerializerMethodField() - circles = SerializerMethodField() meta_fields = [] @@ -54,36 +59,6 @@ class ItBaseSerializer(wagtail_serializers.BaseSerializer): for c in children ] - def get_course(self, obj): - if hasattr(obj, "course"): - return CourseSerializer(obj.course).data - else: - course_parent_page = obj.get_ancestors().exact_type(CoursePage).last() - if course_parent_page: - return CourseSerializer(course_parent_page.specific.course).data - return "" - - def get_circles(self, obj): - course_parent_page = obj.get_ancestors().exact_type(CoursePage).last() - - if course_parent_page: - from vbv_lernwelt.learnpath.models import Circle, LearningPath - - circles = ( - course_parent_page.get_children() - .exact_type(LearningPath) - .first() - .get_children() - .exact_type(Circle) - ) - - return [ - {"id": c.id, "title": c.title, "translation_key": c.translation_key} - for c in circles - ] - - return [] - def get_frontend_url(self, obj): if hasattr(obj, "get_frontend_url"): return obj.get_frontend_url() diff --git a/server/vbv_lernwelt/course/models.py b/server/vbv_lernwelt/course/models.py index 9095a0ad..daffc51e 100644 --- a/server/vbv_lernwelt/course/models.py +++ b/server/vbv_lernwelt/course/models.py @@ -6,6 +6,7 @@ from wagtail.models import Page from vbv_lernwelt.core.model_utils import find_available_slug from vbv_lernwelt.core.models import User +from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class class Course(models.Model): @@ -31,7 +32,60 @@ class CourseCategory(models.Model): return f"{self.course} / {self.title}" -class CoursePage(Page): +class CourseBasePage(Page): + class Meta: + abstract = True + + serialize_field_names = [] + serialize_base_field_names = [ + "id", + "title", + "slug", + "type", + "translation_key", + "frontend_url", + ] + + def get_course_parent(self): + return self.get_ancestors(inclusive=True).exact_type(CoursePage).last() + + def get_course(self): + course_parent_page = self.get_course_parent() + if course_parent_page: + return course_parent_page.specific.course + return None + + def get_circles(self): + course_parent_page = self.get_course_parent() + + if course_parent_page: + from vbv_lernwelt.learnpath.models import LearningPath + from vbv_lernwelt.learnpath.models import Circle + + circles = ( + course_parent_page.get_children() + .exact_type(LearningPath) + .first() + .get_children() + .exact_type(Circle) + ) + return circles + + return None + + @classmethod + def get_serializer_class(cls): + return get_course_serializer_class( + cls, + field_names=cls.serialize_field_names, + base_field_names=cls.serialize_base_field_names, + ) + + def __str__(self): + return f"{self.title}" + + +class CoursePage(CourseBasePage): content_panels = Page.content_panels subpage_types = ["learnpath.LearningPath", "media_library.MediaLibraryPage"] course = models.ForeignKey("course.Course", on_delete=models.PROTECT) diff --git a/server/vbv_lernwelt/course/permissions.py b/server/vbv_lernwelt/course/permissions.py new file mode 100644 index 00000000..f766ae37 --- /dev/null +++ b/server/vbv_lernwelt/course/permissions.py @@ -0,0 +1,12 @@ +from rest_framework import permissions + + +class CourseAccessPermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + # Instance must have an attribute named `owner`. + return obj.owner == request.user diff --git a/server/vbv_lernwelt/course/serializer_helpers.py b/server/vbv_lernwelt/course/serializer_helpers.py new file mode 100644 index 00000000..6ba4c494 --- /dev/null +++ b/server/vbv_lernwelt/course/serializer_helpers.py @@ -0,0 +1,42 @@ +from rest_framework.fields import SerializerMethodField + +from vbv_lernwelt.core.serializer_helpers import ItWagtailBaseSerializer, \ + get_it_serializer_class + + +class CourseBaseSerializer(ItWagtailBaseSerializer): + course = SerializerMethodField() + course_category = SerializerMethodField() + circles = SerializerMethodField() + + meta_fields = [] + + def get_course(self, obj): + course = obj.get_course() + if course: + from vbv_lernwelt.course.serializers import CourseSerializer + + return CourseSerializer(course).data + return "" + + def get_course_category(self, obj): + from vbv_lernwelt.course.serializers import CourseCategorySerializer + + return CourseCategorySerializer(obj.course_category).data + + def get_circles(self, obj): + circles = obj.get_circles() + + if circles: + return [ + {"id": c.id, "title": c.title, "translation_key": c.translation_key} + for c in circles + ] + + return [] + + +def get_course_serializer_class(model, field_names=None, base_field_names=None): + return get_it_serializer_class( + model, field_names, base_field_names, CourseBaseSerializer + ) diff --git a/server/vbv_lernwelt/course/serializers.py b/server/vbv_lernwelt/course/serializers.py index 49bf8b6d..869b04c0 100644 --- a/server/vbv_lernwelt/course/serializers.py +++ b/server/vbv_lernwelt/course/serializers.py @@ -1,6 +1,10 @@ from rest_framework import serializers -from vbv_lernwelt.course.models import Course, CourseCategory, CourseCompletion +from vbv_lernwelt.course.models import ( + Course, + CourseCategory, + CourseCompletion, +) class CourseSerializer(serializers.ModelSerializer): diff --git a/server/vbv_lernwelt/learnpath/models.py b/server/vbv_lernwelt/learnpath/models.py index bdef9301..876be769 100644 --- a/server/vbv_lernwelt/learnpath/models.py +++ b/server/vbv_lernwelt/learnpath/models.py @@ -8,8 +8,7 @@ from wagtail.images.blocks import ImageChooserBlock from wagtail.models import Page from vbv_lernwelt.core.model_utils import find_available_slug -from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class -from vbv_lernwelt.course.models import CoursePage +from vbv_lernwelt.course.models import CoursePage, CourseBasePage from vbv_lernwelt.learnpath.models_learning_unit_content import ( AssignmentBlock, BookBlock, @@ -25,7 +24,8 @@ from vbv_lernwelt.learnpath.models_learning_unit_content import ( ) -class LearningPath(Page): +class LearningPath(CourseBasePage): + serialize_field_names = ["children", "course"] content_panels = Page.content_panels subpage_types = ["learnpath.Circle", "learnpath.Topic"] parent_page_types = ["course.CoursePage"] @@ -45,19 +45,10 @@ class LearningPath(Page): def get_frontend_url(self): return f"/learn/{self.slug}" - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - [ - "children", - "course", - ], - ) +class Topic(CourseBasePage): + serialize_field_names = ["is_visible"] -class Topic(Page): - # title = models.TextField(default='') is_visible = models.BooleanField(default=True) parent_page_types = ["learnpath.LearningPath"] @@ -67,27 +58,10 @@ class Topic(Page): FieldPanel("is_visible"), ] - # content_panels = Page.content_panels + [ - # FieldPanel('is_visible', classname="full"), - # PageChooserPanel('learning_path', 'learnpath.LearningPath'), - # ] - - # parent_page_types = ['learnpath.LearningPath'] - # subpage_types = ['learnpath.Circle'] - def full_clean(self, *args, **kwargs): self.slug = find_slug_with_parent_prefix(self, "topic") super(Topic, self).full_clean(*args, **kwargs) - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - field_names=[ - "is_visible", - ], - ) - def get_admin_display_title(self): return f"Thema: {self.draft_title}" @@ -109,7 +83,7 @@ class PersonBlock(blocks.StructBlock): icon = "user" -class Circle(Page): +class Circle(CourseBasePage): parent_page_types = ["learnpath.LearningPath"] subpage_types = [ "learnpath.LearningSequence", @@ -117,6 +91,16 @@ class Circle(Page): "learnpath.LearningContent", ] + serialize_field_names = [ + "children", + "description", + "goal_description", + "goals", + "job_situation_description", + "job_situations", + "experts", + ] + description = models.TextField(default="", blank=True) goal_description = models.TextField(default="", blank=True) @@ -148,21 +132,6 @@ class Circle(Page): FieldPanel("experts"), ] - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - field_names=[ - "children", - "description", - "goal_description", - "goals", - "job_situation_description", - "job_situations", - "experts", - ], - ) - def get_frontend_url(self): short_slug = self.slug.replace(f"{self.get_parent().slug}-circle-", "") return f"{self.get_parent().specific.get_frontend_url()}/{short_slug}" @@ -178,7 +147,9 @@ class Circle(Page): return f"{self.title}" -class LearningSequence(Page): +class LearningSequence(CourseBasePage): + serialize_field_names = ["icon"] + parent_page_types = ["learnpath.Circle"] subpage_types = [] @@ -194,10 +165,6 @@ class LearningSequence(Page): def __str__(self): return f"{self.title}" - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class(cls, field_names=["icon"]) - def get_admin_display_title(self): return f"{self.icon} {self.draft_title}" @@ -220,7 +187,7 @@ class LearningSequence(Page): return f"{self.get_parent().specific.get_frontend_url()}#{short_slug}" -class LearningUnit(Page): +class LearningUnit(CourseBasePage): parent_page_types = ["learnpath.Circle"] subpage_types = [] course_category = models.ForeignKey( @@ -277,7 +244,12 @@ class LearningUnit(Page): return f'{self.draft_title}' -class LearningContent(Page): +class LearningContent(CourseBasePage): + serialize_field_names = [ + "minutes", + "contents", + ] + parent_page_types = ["learnpath.Circle"] subpage_types = [] @@ -335,19 +307,6 @@ class LearningContent(Page): self.slug = find_slug_with_parent_prefix(self, "lc") super(LearningContent, self).full_clean(*args, **kwargs) - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - field_names=[ - "minutes", - "contents", - ], - ) - - def __str__(self): - return f"{self.title}" - def find_slug_with_parent_prefix(page, type_prefix, slug_postfix=None): parent_slug = page.get_ancestors().exact_type(LearningPath, Circle).last().slug diff --git a/server/vbv_lernwelt/learnpath/serializers.py b/server/vbv_lernwelt/learnpath/serializers.py index 3cb8d2f3..3cfa7587 100644 --- a/server/vbv_lernwelt/learnpath/serializers.py +++ b/server/vbv_lernwelt/learnpath/serializers.py @@ -3,14 +3,14 @@ from rest_framework.fields import SerializerMethodField from vbv_lernwelt.competence.serializers import ( PerformanceCriteriaLearningPathSerializer, ) -from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class +from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class from vbv_lernwelt.learnpath.models import LearningUnit class LearningUnitSerializer( - get_it_serializer_class( + get_course_serializer_class( LearningUnit, - [ + field_names=[ "evaluate_url", "course_category", "children", @@ -30,9 +30,9 @@ class LearningUnitSerializer( class LearningUnitPerformanceCriteriaSerializer( - get_it_serializer_class( + get_course_serializer_class( LearningUnit, - [ + field_names=[ "evaluate_url", "course_category", ], diff --git a/server/vbv_lernwelt/media_library/models.py b/server/vbv_lernwelt/media_library/models.py index e3e1fe2a..534c2a96 100644 --- a/server/vbv_lernwelt/media_library/models.py +++ b/server/vbv_lernwelt/media_library/models.py @@ -7,11 +7,13 @@ from wagtail.fields import StreamField from wagtail.models import Page from vbv_lernwelt.core.model_utils import find_available_slug -from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class +from vbv_lernwelt.course.models import CourseBasePage from vbv_lernwelt.media_library.content_blocks import MediaContentCollection -class MediaLibraryPage(Page): +class MediaLibraryPage(CourseBasePage): + serialize_field_names = ["course", "children"] + parent_page_types = ["course.CoursePage"] subpage_types = ["media_library.MediaCategoryPage"] @@ -28,22 +30,23 @@ class MediaLibraryPage(Page): def get_frontend_url(self): return f"/media/{self.slug}" - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - [ - "course", - "children", - ], - ) - -class MediaCategoryPage(Page): +class MediaCategoryPage(CourseBasePage): """ Handlungsfeld. zB. Fahrzeug """ + serialize_field_names = [ + "course_category", + "introduction_text", + "overview_icon", + "detail_image", + "description_title", + "description_text", + "items", + "body", + ] + course_category = models.ForeignKey( "course.CourseCategory", on_delete=models.SET_NULL, null=True, blank=True ) @@ -89,22 +92,6 @@ class MediaCategoryPage(Page): short_slug = self.slug.replace(f"{self.get_parent().slug}-cat-", "") return f"{self.get_parent().specific.get_frontend_url()}/category/{short_slug}" - @classmethod - def get_serializer_class(cls): - return get_it_serializer_class( - cls, - field_names=[ - "course_category", - "introduction_text", - "overview_icon", - "detail_image", - "description_title", - "description_text", - "items", - "body", - ], - ) - class LibraryDocument(AbstractDocument): # Todo: check https://filepreviews.io/