# Create your models here. from django.utils.text import slugify from wagtail.core.blocks import StreamBlock from wagtail.core.fields import StreamField from wagtail.core.models import Page, Orderable from wagtail.api import APIField from vbv_lernwelt.learnpath.models_competences import * from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock from grapple.helpers import register_query_field from collections import OrderedDict import graphene from grapple.models import ( GraphQLString, GraphQLPage, GraphQLStreamfield, GraphQLBoolean, GraphQLInt, GraphQLForeignKey, GraphQLField ) @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('content_structure'), ] @property def content_structure(self): 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'), ] @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