import re from django.db import models from django.utils.text import slugify from wagtail.admin.panels import FieldPanel, PageChooserPanel from wagtail.fields import RichTextField, StreamField from wagtail.models import Page from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.core.constants import DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER from vbv_lernwelt.core.model_utils import find_available_slug from vbv_lernwelt.course.models import CourseBasePage, CoursePage from vbv_lernwelt.media_library.content_blocks import LearnMediaBlock class LearningPath(CourseBasePage): serialize_field_names = ["children", "course"] content_panels = Page.content_panels subpage_types = ["learnpath.Circle", "learnpath.Topic"] parent_page_types = ["course.CoursePage"] class Meta: verbose_name = "Learning Path" def save(self, clean=True, user=None, log_action=False, **kwargs): self.slug = find_available_slug( slugify(f"{self.get_parent().slug}-lp", allow_unicode=True), ignore_page_id=self.id, ) super(LearningPath, self).save(clean, user, log_action, **kwargs) def __str__(self): return f"{self.title}" def get_frontend_url(self): return f"/course/{self.slug.replace('-lp', '')}/learn" def get_cockpit_url(self): return f"/course/{self.slug.replace('-lp', '')}/cockpit" class Topic(CourseBasePage): serialize_field_names = ["is_visible"] is_visible = models.BooleanField(default=True) parent_page_types = ["learnpath.LearningPath"] panels = [ FieldPanel("title"), FieldPanel("is_visible"), ] def save(self, clean=True, user=None, log_action=False, **kwargs): self.slug = find_slug_with_parent_prefix(self, "topic") super(Topic, self).save(clean, user, log_action, **kwargs) def get_admin_display_title(self): return f"Thema: {self.draft_title}" class Meta: verbose_name = "Topic" def __str__(self): return f"{self.title}" class Circle(CourseBasePage): parent_page_types = ["learnpath.LearningPath"] subpage_types = [ "learnpath.LearningSequence", "learnpath.LearningUnit", "learnpath.LearningContentAssignment", "learnpath.LearningContentAttendanceCourse", "learnpath.LearningContentFeedback", "learnpath.LearningContentLearningModule", "learnpath.LearningContentMediaLibrary", "learnpath.LearningContentPlaceholder", "learnpath.LearningContentRichText", "learnpath.LearningContentTest", "learnpath.LearningContentVideo", ] serialize_field_names = [ "children", "description", "goals", ] description = models.TextField(default="", blank=True) goals = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER) content_panels = Page.content_panels + [ FieldPanel("description"), FieldPanel("goals"), ] def get_frontend_url(self): r = re.compile(r"^(?P.+?)-lp-circle-(?P.+?)$") m = r.match(self.slug) if m is None: return "ERROR: could not parse slug" return f"/course/{m.group('coursePart')}/learn/{m.group('circlePart')}" def save(self, clean=True, user=None, log_action=False, **kwargs): self.slug = find_slug_with_parent_prefix(self, "circle") super(Circle, self).save(clean, user, log_action, **kwargs) class Meta: verbose_name = "Circle" def __str__(self): return f"{self.title}" class LearningSequence(CourseBasePage): serialize_field_names = ["icon"] parent_page_types = ["learnpath.Circle"] subpage_types = [] icon = models.CharField(max_length=255, default="it-icon-ls-start") content_panels = Page.content_panels + [ FieldPanel("icon"), ] class Meta: verbose_name = "Learning Sequence" def __str__(self): return f"{self.title}" def get_admin_display_title(self): return f"{self.icon} {self.draft_title}" def get_admin_display_title_html(self): return f""" <{self.icon} style="height: 32px; width: 32px;"> {self.draft_title} """ def get_admin_display_title(self): return f"LS: {self.draft_title}" def save(self, clean=True, user=None, log_action=False, **kwargs): self.slug = find_slug_with_parent_prefix(self, "ls") super(LearningSequence, self).save(clean, user, log_action, **kwargs) def get_frontend_url(self): r = re.compile( r"^(?P.+?)-lp-circle-(?P.+?)-ls-(?P.+?)$" ) m = r.match(self.slug) if m is None: return "ERROR: could not parse slug" return f"/course/{m.group('coursePart')}/learn/{m.group('circlePart')}#ls-{m.group('lsPart')}" class LearningUnit(CourseBasePage): parent_page_types = ["learnpath.Circle"] subpage_types = [] course_category = models.ForeignKey( "course.CourseCategory", on_delete=models.SET_NULL, null=True, blank=True ) title_hidden = models.BooleanField(default=False) content_panels = Page.content_panels + [ FieldPanel("course_category"), FieldPanel("title_hidden"), ] class Meta: verbose_name = "Learning Unit" def __str__(self): return f"{self.title}" def save(self, clean=True, user=None, log_action=False, **kwargs): course = None course_parent_page = self.get_ancestors().exact_type(CoursePage).last() if course_parent_page: course = course_parent_page.specific.course if self.course_category is None and course: self.course_category = course.coursecategory_set.filter( general=True ).first() if self.course_category.general: self.slug = find_slug_with_parent_prefix(self, "lu") else: self.slug = find_slug_with_parent_prefix( self, "lu", self.course_category.title ) super(LearningUnit, self).save(clean, user, log_action, **kwargs) def get_frontend_url(self): r = re.compile( r"^(?P.+?)-lp-circle-(?P.+?)-lu-(?P.+?)$" ) m = r.match(self.slug) if m is None: return "ERROR: could not parse slug" return f"/course/{m.group('coursePart')}/learn/{m.group('circlePart')}#lu-{m.group('luPart')}" def get_evaluate_url(self): r = re.compile( r"^(?P.+?)-lp-circle-(?P.+?)-lu-(?P.+?)$" ) m = r.match(self.slug) return f"/course/{m.group('coursePart')}/learn/{m.group('circlePart')}/evaluate/{m.group('luPart')}" def get_admin_display_title(self): return f"LE: {self.draft_title}" @classmethod def get_serializer_class(cls): from vbv_lernwelt.learnpath.serializers import LearningUnitSerializer return LearningUnitSerializer def get_admin_display_title_html(self): return f'{self.draft_title}' class LearningContent(CourseBasePage): class Meta: abstract = True serialize_field_names = [ "minutes", "description", "content_url", ] minutes = models.PositiveIntegerField(default=15) description = RichTextField(blank=True) content_url = models.TextField(blank=True) content_panels = [ FieldPanel("title", classname="full title"), FieldPanel("minutes"), FieldPanel("content_url"), FieldPanel("description"), ] def get_admin_display_title(self): display_title = "" # if len(self.contents) > 0: # display_title += f"{self.contents[0].block_type.capitalize()}: " display_title += self.draft_title return display_title def get_admin_display_title_html(self): return f""" {self.get_admin_display_title()} """ def get_frontend_url(self): r = re.compile( r"^(?P.+?)-lp-circle-(?P.+?)-lc-(?P.+)$" ) m = r.match(self.slug) if m is None: return "ERROR: could not parse slug" return f"/course/{m.group('coursePart')}/learn/{m.group('circlePart')}/{m.group('lcPart')}" def save(self, clean=True, user=None, log_action=False, **kwargs): self.slug = find_slug_with_parent_prefix(self, "lc") super().save(**kwargs) class LearningContentAttendanceCourse(LearningContent): parent_page_types = ["learnpath.Circle"] subpage_types = [] class LearningContentVideo(LearningContent): parent_page_types = ["learnpath.Circle"] subpage_types = [] class LearningContentPlaceholder(LearningContent): parent_page_types = ["learnpath.Circle"] subpage_types = [] class LearningContentFeedback(LearningContent): parent_page_types = ["learnpath.Circle"] subpage_types = [] class LearningContentLearningModule(LearningContent): parent_page_types = ["learnpath.Circle"] subpage_types = [] class LearningContentMediaLibrary(LearningContent): parent_page_types = ["learnpath.Circle"] subpage_types = [] class LearningContentTest(LearningContent): serialize_field_names = LearningContent.serialize_field_names + [ "checkbox_text", ] parent_page_types = ["learnpath.Circle"] subpage_types = [] checkbox_text = models.TextField(blank=True) content_panels = LearningContent.content_panels + [ FieldPanel("checkbox_text", classname="Text"), ] class LearningContentRichText(LearningContent): text = RichTextField(blank=True, features=DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER) parent_page_types = ["learnpath.Circle"] serialize_field_names = LearningContent.serialize_field_names + [ "text", ] subpage_types = [] content_panels = LearningContent.content_panels + [ FieldPanel("text", classname="Text"), ] class LearningContentAssignment(LearningContent): serialize_field_names = LearningContent.serialize_field_names + [ "content_assignment_id", "assignment_type", ] parent_page_types = ["learnpath.Circle"] subpage_types = [] content_assignment = models.ForeignKey( "assignment.Assignment", on_delete=models.PROTECT, related_name="+", ) assignment_type = models.CharField( max_length=50, choices=[(tag.name, tag.name) for tag in AssignmentType], default=AssignmentType.CASEWORK.name, ) content_panels = [ FieldPanel("title", classname="full title"), FieldPanel("minutes"), PageChooserPanel("content_assignment", "assignment.Assignment"), FieldPanel("content_url"), FieldPanel("description"), ] class LearningContentDocumentList(LearningContent): serialize_field_names = LearningContent.serialize_field_names + [ "documents", ] parent_page_types = ["learnpath.Circle"] subpage_types = [] documents = StreamField( [ ("document", LearnMediaBlock()), ], use_json_field=True, blank=True, ) content_panels = [ FieldPanel("title", classname="full title"), FieldPanel("minutes"), FieldPanel("description"), FieldPanel("documents"), ] def find_slug_with_parent_prefix(page, type_prefix, slug_postfix=None): parent_slug = page.get_ancestors().exact_type(LearningPath, Circle).last().slug if parent_slug: slug_prefix = f"{parent_slug}-{type_prefix}" else: slug_prefix = type_prefix if slug_postfix is None: slug_postfix = page.title return find_available_slug( slugify(f"{slug_prefix}-{slug_postfix}", allow_unicode=True), ignore_page_id=page.id, )