vbv/server/vbv_lernwelt/learnpath/models.py

312 lines
9.4 KiB
Python

import re
from django.db import models
from django.utils.text import slugify
from wagtail.admin.panels import FieldPanel, StreamFieldPanel
from wagtail.blocks import StreamBlock
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.course.models import CourseBasePage, CoursePage
from vbv_lernwelt.learnpath.models_learning_unit_content import (
AssignmentBlock,
AttendanceDayBlock,
BookBlock,
DocumentBlock,
ExerciseBlock,
FeedbackBlock,
LearningModuleBlock,
MediaLibraryBlock,
OnlineTrainingBlock,
PlaceholderBlock,
ResourceBlock,
TestBlock,
VideoBlock,
)
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)
)
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"
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.LearningContent",
]
serialize_field_names = [
"children",
"description",
"goals",
]
description = models.TextField(default="", blank=True)
goals = RichTextField()
content_panels = Page.content_panels + [
FieldPanel("description"),
FieldPanel("goals"),
]
def get_frontend_url(self):
r = re.compile(r"^(?P<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)$")
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"""
<span style="display: inline-flex; align-items: center; font-size: 1.25rem; font-weight: 700;">
<{self.icon} style="height: 32px; width: 32px;"></{self.icon}>
<span style="margin-left: 8px;">{self.draft_title}</span>
</span>"""
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<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)-ls-(?P<lsPart>.+?)$"
)
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
)
content_panels = Page.content_panels + [
FieldPanel("course_category"),
]
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<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)-lu-(?P<luPart>.+?)$"
)
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<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)-lu-(?P<luPart>.+?)$"
)
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'<span style="font-weight: 700; font-size: 20px;">{self.draft_title}</span>'
class LearningContent(CourseBasePage):
serialize_field_names = [
"minutes",
"contents",
]
parent_page_types = ["learnpath.Circle"]
subpage_types = []
minutes = models.PositiveIntegerField(default=15)
content_blocks = [
("video", VideoBlock()),
("resource", ResourceBlock()),
("exercise", ExerciseBlock()),
("learningmodule", LearningModuleBlock()),
("online_training", OnlineTrainingBlock()),
("media_library", MediaLibraryBlock()),
("document", DocumentBlock()),
("test", TestBlock()),
("book", BookBlock()),
("assignment", AssignmentBlock()),
("placeholder", PlaceholderBlock()),
("feedback", FeedbackBlock()),
("attendance_day", AttendanceDayBlock()),
]
contents = StreamField(
StreamBlock(content_blocks), blank=False, min_num=1, max_num=1
)
content_panels = [
FieldPanel("title", classname="full title"),
FieldPanel("minutes"),
StreamFieldPanel("contents"),
]
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"""
<span style="display: inline-flex; align-items: center;">
<it-icon-checkbox-unchecked style="height: 24px; width: 24px;"></it-icon-checkbox-unchecked>
<span style="margin-left: 8px;">{self.get_admin_display_title()}</span>
</span>"""
class Meta:
verbose_name = "Learning Content"
def get_frontend_url(self):
r = re.compile(
r"^(?P<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)-lc-(?P<lcPart>.+)$"
)
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)
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)
)