292 lines
8.8 KiB
Python
292 lines
8.8 KiB
Python
# Create your models here.
|
|
|
|
from collections import OrderedDict
|
|
|
|
from django.utils.text import slugify
|
|
from grapple.helpers import register_query_field
|
|
from wagtail.api import APIField
|
|
from wagtail.core.blocks import StreamBlock
|
|
from wagtail.core.fields import StreamField
|
|
from wagtail.core.models import Page, Orderable
|
|
|
|
from vbv_lernwelt.learnpath.models_competences import *
|
|
from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock
|
|
|
|
|
|
@register_query_field("learning_path")
|
|
class LearningPath(Page):
|
|
# PageChooserPanel('related_page', 'demo.PublisherPage'),
|
|
|
|
content_panels = Page.content_panels + [
|
|
InlinePanel('topics', label="Topics"),
|
|
]
|
|
|
|
subpage_types = ['learnpath.Circle']
|
|
|
|
|
|
class Meta:
|
|
verbose_name = "Learning Path"
|
|
|
|
def __str__(self):
|
|
return f"{self.title}"
|
|
|
|
|
|
class Topic(Orderable):
|
|
title = models.TextField(default='')
|
|
is_visible = models.BooleanField(default=True)
|
|
|
|
learning_path = ParentalKey('learnpath.LearningPath',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.CASCADE,
|
|
related_name='topics',
|
|
)
|
|
|
|
panels = [FieldPanel('title'),
|
|
FieldPanel('is_visible'),
|
|
]
|
|
|
|
api_fields = [
|
|
APIField('title'),
|
|
APIField('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_available_slug(Topic, slugify(self.title, allow_unicode=True))
|
|
super(Topic, self).full_clean(*args, **kwargs)
|
|
|
|
class Meta:
|
|
verbose_name = "Topic"
|
|
|
|
def __str__(self):
|
|
return f"{self.title}"
|
|
|
|
|
|
class Circle(Page, Orderable):
|
|
description = models.TextField(default="", blank=True)
|
|
goals = models.TextField(default="", blank=True)
|
|
topic = models.ForeignKey(
|
|
'learnpath.Topic',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name='circles'
|
|
)
|
|
|
|
parent_page_types = ['learnpath.Learningpath']
|
|
subpage_types = ['learnpath.LearningUnit']
|
|
|
|
content_panels = Page.content_panels + [
|
|
FieldPanel('description'),
|
|
FieldPanel('goals'),
|
|
InlinePanel('learning_sequences', label="Learning Sequences"),
|
|
]
|
|
|
|
# Export fields over the API
|
|
api_fields = [
|
|
APIField('title'),
|
|
APIField('description'),
|
|
APIField('topic'),
|
|
APIField('learning_sequences'),
|
|
# APIField('content_structure'),
|
|
]
|
|
|
|
@property
|
|
def content_structure(self):
|
|
# FIXME: not needed!?
|
|
# you can access nested content with the following url scheme (look a the nested parentheses!):
|
|
# /wagtailapi/v2/pages/?type=learnpath.Circle&slug=analyse&fields=title,description,learning_sequences(title,learning_packages(title,my_title,learning_units(title)))
|
|
learning_sequences = LearningSequence.objects.filter(circle_id=self.id).values()
|
|
learning_packages = LearningPackage.objects.filter(learning_sequence__circle_id=self.id).values()
|
|
learning_units = LearningUnit.objects.filter(learning_package__learning_sequence__circle_id=self.id).values('learning_package_id', 'title')
|
|
|
|
content = OrderedDict()
|
|
content['learning_sequences'] = []
|
|
|
|
for learning_sequence in learning_sequences:
|
|
this_learning_packages = []
|
|
related_learning_packages = [lp for lp in learning_packages if lp['learning_sequence_id'] == learning_sequence['id']]
|
|
for learning_package in related_learning_packages:
|
|
related_learning_units = [lu for lu in learning_units if
|
|
lu['learning_package_id'] == learning_package['id']]
|
|
this_learning_units = [learning_unit for learning_unit in related_learning_units]
|
|
learning_package['learning_units'] = this_learning_units
|
|
this_learning_packages.append(learning_package)
|
|
learning_sequence['learning_packages'] = this_learning_packages
|
|
content['learning_sequences'].append(learning_sequence)
|
|
|
|
return content
|
|
|
|
def full_clean(self, *args, **kwargs):
|
|
self.slug = find_available_slug(Circle, slugify(self.title, allow_unicode=True))
|
|
super(Circle, self).full_clean(*args, **kwargs)
|
|
|
|
class Meta:
|
|
verbose_name = "Circle"
|
|
|
|
def __str__(self):
|
|
return f"{self.title}"
|
|
|
|
|
|
IN_CIRCLE = 'INCIRCLE'
|
|
START = 'START'
|
|
END = 'END'
|
|
|
|
LEARNING_SEQUENCE_CATEGORIES = [
|
|
(IN_CIRCLE, 'In Circle'),
|
|
(START, 'Start'),
|
|
(END, 'End')
|
|
]
|
|
|
|
|
|
class LearningSequence(Orderable):
|
|
# TODO: How to do a icon choice field?
|
|
title = models.CharField(max_length=256, default='')
|
|
category = models.CharField(max_length=16, choices=LEARNING_SEQUENCE_CATEGORIES, default=IN_CIRCLE)
|
|
|
|
circle = ParentalKey(
|
|
'learnpath.Circle',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.CASCADE,
|
|
related_name='learning_sequences',
|
|
)
|
|
|
|
panels = [FieldPanel('title'), FieldPanel('category'), FieldPanel('circle')]
|
|
|
|
api_fields = [
|
|
APIField('title'),
|
|
APIField('category'),
|
|
APIField('learning_packages'),
|
|
]
|
|
|
|
class Meta:
|
|
verbose_name = "Learning Sequence"
|
|
|
|
def __str__(self):
|
|
return f"{self.title}"
|
|
|
|
def full_clean(self, *args, **kwargs):
|
|
self.slug = find_available_slug(LearningSequence, slugify(self.title, allow_unicode=True))
|
|
super(LearningSequence, self).full_clean(*args, **kwargs)
|
|
|
|
|
|
class LearningPackage(Orderable):
|
|
title = models.CharField(max_length=256, default='')
|
|
|
|
learning_sequence = models.ForeignKey(
|
|
'learnpath.LearningSequence',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name='learning_packages',
|
|
)
|
|
panels = [FieldPanel('title')]
|
|
|
|
api_fields = [
|
|
APIField('title'),
|
|
APIField('my_title'),
|
|
APIField('learning_units'),
|
|
]
|
|
|
|
@property
|
|
def my_title(self):
|
|
return self.title
|
|
|
|
def full_clean(self, *args, **kwargs):
|
|
self.slug = find_available_slug(LearningPackage, slugify(self.title, allow_unicode=True))
|
|
super(LearningPackage, self).full_clean(*args, **kwargs)
|
|
|
|
|
|
class LearningUnit(Page, Orderable):
|
|
"""
|
|
This is a group of contents, with the übung and test it is one unit. ... more of a structural charactacter.
|
|
|
|
"""
|
|
# TODO: Review model architecture, is the stream field the right thing here?
|
|
parent_page_types = ['learnpath.Circle']
|
|
learning_package = models.ForeignKey(
|
|
'learnpath.LearningPackage',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name='learning_units',
|
|
)
|
|
|
|
content_blocks = [
|
|
('web_based_training', WebBasedTrainingBlock()),
|
|
('video', VideoBlock()),
|
|
]
|
|
|
|
contents = StreamField(StreamBlock(content_blocks),
|
|
null=True, blank=True, min_num=1, max_num=1)
|
|
|
|
content_panels = [
|
|
FieldPanel('title', classname="full title"),
|
|
FieldPanel('learning_package'),
|
|
StreamFieldPanel('contents'),
|
|
]
|
|
|
|
api_fields = [
|
|
APIField('title'),
|
|
APIField('contents'),
|
|
APIField('content_blocks'),
|
|
]
|
|
|
|
subpage_types = []
|
|
|
|
class Meta:
|
|
verbose_name = "Learning Unit"
|
|
|
|
def full_clean(self, *args, **kwargs):
|
|
self.slug = find_available_slug(LearningUnit, slugify(self.title, allow_unicode=True))
|
|
super(LearningUnit, self).full_clean(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
return f"{self.title}"
|
|
|
|
|
|
|
|
def find_available_slug(model, requested_slug, ignore_page_id=None):
|
|
"""
|
|
Finds an available slug within the specified parent.
|
|
|
|
If the requested slug is not available, this adds a number on the end, for example:
|
|
|
|
- 'requested-slug'
|
|
- 'requested-slug-1'
|
|
- 'requested-slug-2'
|
|
|
|
And so on, until an available slug is found.
|
|
|
|
The `ignore_page_id` keyword argument is useful for when you are updating a page,
|
|
you can pass the page being updated here so the page's current slug is not
|
|
treated as in use by another page.
|
|
"""
|
|
|
|
# TODO: In comparison ot wagtails own function, I look for the same model instead of the parent
|
|
pages = model.objects.filter(slug__startswith=requested_slug)
|
|
|
|
if ignore_page_id:
|
|
pages = pages.exclude(id=ignore_page_id)
|
|
|
|
existing_slugs = set(pages.values_list("slug", flat=True))
|
|
slug = requested_slug
|
|
number = 1
|
|
|
|
while slug in existing_slugs:
|
|
slug = requested_slug + "-" + str(number)
|
|
number += 1
|
|
|
|
return slug
|