Add course app with default course model

This commit is contained in:
Daniel Egger 2022-09-19 16:30:17 +02:00
parent a75a29f57c
commit ee4f6fb565
25 changed files with 278 additions and 183 deletions

View File

@ -62,8 +62,9 @@ if [ "$SKIP_SETUP" = false ]; then
python3 server/manage.py createcachetable --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py migrate --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_users --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_learning_path --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_media_library --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_courses --settings="$DJANGO_SETTINGS_MODULE"
# python3 server/manage.py create_default_learning_path --settings="$DJANGO_SETTINGS_MODULE"
# python3 server/manage.py create_default_media_library --settings="$DJANGO_SETTINGS_MODULE"
# make django translations
(cd server && python3 manage.py compilemessages --settings="$DJANGO_SETTINGS_MODULE")

View File

@ -103,9 +103,10 @@ THIRD_PARTY_APPS = [
LOCAL_APPS = [
"vbv_lernwelt.core",
"vbv_lernwelt.sso",
"vbv_lernwelt.course",
"vbv_lernwelt.learnpath",
"vbv_lernwelt.completion",
"vbv_lernwelt.media_library",
"vbv_lernwelt.completion",
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.13 on 2022-07-04 09:58
# Generated by Django 3.2.13 on 2022-09-19 14:37
from django.conf import settings
from django.db import migrations, models
@ -22,8 +22,8 @@ class Migration(migrations.Migration):
('updated_at', models.DateTimeField(auto_now=True)),
('page_key', models.UUIDField()),
('page_type', models.CharField(blank=True, default='', max_length=255)),
('circle_key', models.UUIDField()),
('learning_path_key', models.UUIDField()),
('circle_key', models.UUIDField(blank=True, default='')),
('learning_path_key', models.UUIDField(blank=True, default='')),
('completed', models.BooleanField(default=False)),
('json_data', models.JSONField(blank=True, default=dict)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),

View File

@ -0,0 +1,34 @@
from wagtail.models import Page
def find_available_slug(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.
"""
pages = Page.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

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CourseConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'vbv_lernwelt.course'

View File

@ -0,0 +1 @@
COURSE_VERSICHERUNGSVERMITTLERIN = -1

View File

@ -0,0 +1,45 @@
import wagtail_factories
from django.conf import settings
from wagtail.models import Site
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN
from vbv_lernwelt.course.factories import CoursePageFactory
def create_versicherungsvermittlerin_with_categories(apps=None, schema_editor=None):
if apps is not None:
Course = apps.get_model('course', 'Course')
CourseCategory = apps.get_model('course', 'CourseCategory')
else:
# pylint: disable=import-outside-toplevel
from vbv_lernwelt.course.models import Course, CourseCategory
course, _ = Course.objects.get_or_create(
id=COURSE_VERSICHERUNGSVERMITTLERIN,
name='Versicherungsvermittler/in',
category_name='Handlungsfeld'
)
CourseCategory.objects.get_or_create(course=course, name='Allgemein', general=True)
for cat in [
'Fahrzeug', 'Reisen', 'Einkommensicherung', 'Gesundheit', 'Haushalt', 'Sparen',
'Pensionierung', 'KMU', 'Wohneigentum', 'Rechtsstreitigkeiten', 'Erben / Vererben',
'Selbständigkeit',
]:
CourseCategory.objects.get_or_create(course=course, name=cat)
# create default course page
site = Site.objects.filter(is_default_site=True).first()
if not site:
site = wagtail_factories.SiteFactory(is_default_site=True)
if settings.APP_ENVIRONMENT == 'development':
site.port = 8000
site.save()
course_page = CoursePageFactory(
title="Versicherungsvermittler/in",
parent=site.root_page,
course=course,
)

View File

@ -0,0 +1,19 @@
import wagtail_factories
from factory.django import DjangoModelFactory
from vbv_lernwelt.course.models import CoursePage, Course
class CourseFactory(DjangoModelFactory):
class Meta:
model = Course
name = 'Versicherungsvermittler/in'
category_name = 'Handlungsfeld'
class CoursePageFactory(wagtail_factories.PageFactory):
title = "Versicherungsvermittler/in"
class Meta:
model = CoursePage

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
#
# Iterativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2015 Iterativ GmbH. All rights reserved.
#
# Created on 2022-03-31
# @author: lorenz.padberg@iterativ.ch

View File

@ -0,0 +1,8 @@
import djclick as click
from vbv_lernwelt.course.creators import create_versicherungsvermittlerin_with_categories
@click.command()
def command():
create_versicherungsvermittlerin_with_categories()

View File

@ -0,0 +1,47 @@
# Generated by Django 3.2.13 on 2022-09-19 14:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('wagtailcore', '0069_log_entry_jsonfield'),
]
operations = [
migrations.CreateModel(
name='Course',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Titel')),
('category_name', models.CharField(default='Kategorie', max_length=255, verbose_name='Kategorie-Name')),
],
options={
'verbose_name': 'Lerngang',
},
),
migrations.CreateModel(
name='CoursePage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.course')),
],
options={
'verbose_name': 'Lerngang-Seite',
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='CourseCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Titel')),
('general', models.BooleanField(default=False, verbose_name='Allgemein')),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.course')),
],
),
]

View File

@ -0,0 +1,37 @@
from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from wagtail.models import Page
from vbv_lernwelt.core.model_utils import find_available_slug
class Course(models.Model):
name = models.CharField(_('Titel'), max_length=255)
category_name = models.CharField(_('Kategorie-Name'), max_length=255, default='Kategorie')
class Meta:
verbose_name = _("Lerngang")
class CourseCategory(models.Model):
# Die Handlungsfelder im "Versicherungsvermittler/in"
name = models.CharField(_('Titel'), max_length=255, blank=True)
course = models.ForeignKey('course.Course', on_delete=models.CASCADE)
general = models.BooleanField(_('Allgemein'), default=False)
class CoursePage(Page):
content_panels = Page.content_panels
subpage_types = ['learnpath.LearningPath', 'media_library.MediaLibrary']
course = models.ForeignKey('course.Course', on_delete=models.CASCADE)
class Meta:
verbose_name = _("Lerngang-Seite")
def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(slugify(self.title, allow_unicode=True))
super(CoursePage, self).full_clean(*args, **kwargs)
def __str__(self):
return f"{self.title}"

View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -1,8 +1,7 @@
# Generated by Django 3.2.13 on 2022-06-22 15:48
# Generated by Django 3.2.13 on 2022-09-19 14:37
from django.db import migrations, models
import django.db.models.deletion
import modelcluster.fields
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
@ -31,34 +30,12 @@ class Migration(migrations.Migration):
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='Competence',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('category_short', models.CharField(default='', max_length=3)),
('name', models.CharField(max_length=2048)),
],
options={
'verbose_name': 'Competence',
},
),
migrations.CreateModel(
name='CompetencePage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'verbose_name': 'Learning Path',
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='LearningContent',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('minutes', models.PositiveIntegerField(default=15)),
('contents', wagtail.fields.StreamField([('video', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('web_based_training', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('podcast', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('competence', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('exercise', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('document', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())]))], use_json_field=None)),
('contents', wagtail.fields.StreamField([('video', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('web_based_training', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('podcast', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('competence', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('exercise', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('document', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())]))], use_json_field=None)),
],
options={
'verbose_name': 'Learning Content',
@ -117,21 +94,4 @@ class Migration(migrations.Migration):
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='FullfillmentCriteria',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('name', models.CharField(max_length=2048)),
('competence', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='learnpath.competence')),
],
options={
'verbose_name': 'Fullfillment Criteria',
},
),
migrations.AddField(
model_name='competence',
name='competence_page',
field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='competences', to='learnpath.competencepage'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.13 on 2022-08-24 14:47
from django.db import migrations
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='learningcontent',
name='contents',
field=wagtail.fields.StreamField([('video', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('web_based_training', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('podcast', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('competence', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('exercise', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('document', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())]))], use_json_field=None),
),
]

View File

@ -7,6 +7,7 @@ from wagtail.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
from wagtail.models import Page, Orderable
from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock, PodcastBlock, \
CompetenceBlock, ExerciseBlock, DocumentBlock, KnowledgeBlock
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
@ -268,36 +269,3 @@ def find_slug_with_parent_prefix(page, type_prefix):
slug_prefix = type_prefix
return find_available_slug(slugify(f'{slug_prefix}-{page.title}', allow_unicode=True))
def find_available_slug(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.
"""
pages = Page.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

View File

@ -1,10 +1,9 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from wagtail import blocks
from wagtail.admin.panels import FieldPanel
from wagtail.snippets.models import register_snippet
from wagtail.documents.blocks import DocumentChooserBlock
from django.utils.translation import gettext_lazy as _
from wagtail.snippets.models import register_snippet
class VisualisationType(models.TextChoices):

View File

@ -2,8 +2,7 @@ import json
from vbv_lernwelt.learnpath.models import LearningPath
from vbv_lernwelt.media_library.tests.media_library_factories import MediaLibraryFactory, TopCategoryFactory, \
CategoryFactory, ContentCollectionFactory, collection_body_dict
from vbv_lernwelt.media_library.models import Category
CategoryFactory, collection_body_dict
def create_default_media_library():

View File

@ -1,9 +1,13 @@
# Generated by Django 3.2.13 on 2022-08-16 08:35
# Generated by Django 3.2.13 on 2022-09-19 14:37
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
import vbv_lernwelt.media_library.content_blocks
import wagtail.blocks
import wagtail.documents.blocks
import wagtail.fields
import wagtail.models.collections
import wagtail.search.index
@ -13,14 +17,56 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('wagtailcore', '0069_log_entry_jsonfield'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('taggit', '0004_alter_taggeditem_content_type_alter_taggeditem_tag'),
('wagtailcore', '0069_log_entry_jsonfield'),
]
operations = [
migrations.CreateModel(
name='CustomDocument',
name='Category',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('introduction_text', models.TextField(default='')),
('description', wagtail.fields.RichTextField(default='')),
('body', wagtail.fields.StreamField([('content_collection', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('collection_type', wagtail.blocks.MultipleChoiceBlock(choices=[('LearningMedia', 'Lernmedien'), ('Link', 'Links'), ('Anker', 'Verankerung'), ('CrossReference', 'Querverweise')], max_length=20)), ('contents', wagtail.blocks.StreamBlock([('Links', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock(blank=False, null=False)), ('description', wagtail.blocks.TextBlock(default='')), ('link_display_text', wagtail.blocks.CharBlock(default='Link öffnen', max_length=255)), ('url', wagtail.blocks.URLBlock())])), ('Documents', wagtail.documents.blocks.DocumentChooserBlock()), ('Ankers', vbv_lernwelt.media_library.content_blocks.AnkerBlock()), ('CrossReference', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock(default='')), ('link_display_text', wagtail.blocks.CharBlock(default='Link öffnen', max_length=255)), ('category', wagtail.blocks.PageChooserBlock(page_type=['media_library.Category']))]))]))]))], null=True, use_json_field=True)),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='MediaLibrary',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='MediaLibraryContent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.TextField()),
('description', models.TextField()),
('link_display_text', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='TopCategory',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='LibraryDocument',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='title')),

View File

@ -1,68 +0,0 @@
# Generated by Django 3.2.13 on 2022-09-14 13:02
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import vbv_lernwelt.media_library.content_blocks
import wagtail.blocks
import wagtail.documents.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('taggit', '0004_alter_taggeditem_content_type_alter_taggeditem_tag'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wagtailcore', '0069_log_entry_jsonfield'),
('media_library', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('introduction_text', models.TextField(default='')),
('description', wagtail.fields.RichTextField(default='')),
('body', wagtail.fields.StreamField([('content_collection', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('collection_type', wagtail.blocks.MultipleChoiceBlock(choices=[('LearningMedia', 'Lernmedien'), ('Link', 'Links'), ('Anker', 'Verankerung'), ('CrossReference', 'Querverweise')], max_length=20)), ('contents', wagtail.blocks.StreamBlock([('Links', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock(blank=False, null=False)), ('description', wagtail.blocks.TextBlock(default='')), ('link_display_text', wagtail.blocks.CharBlock(default='Link öffnen', max_length=255)), ('url', wagtail.blocks.URLBlock())])), ('Documents', wagtail.documents.blocks.DocumentChooserBlock()), ('Ankers', vbv_lernwelt.media_library.content_blocks.AnkerBlock()), ('CrossReference', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock(default='')), ('link_display_text', wagtail.blocks.CharBlock(default='Link öffnen', max_length=255)), ('category', wagtail.blocks.PageChooserBlock(page_type=['media_library.Category']))]))]))]))], null=True, use_json_field=True)),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='MediaLibrary',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='MediaLibraryContent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.TextField()),
('description', models.TextField()),
('link_display_text', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='TopCategory',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.RenameModel(
old_name='CustomDocument',
new_name='LibraryDocument',
),
]

View File

@ -1,12 +1,9 @@
from wagtail import blocks, fields
from wagtail.admin.panels import FieldPanel, StreamFieldPanel
from django.db import models
from wagtail import fields
from wagtail.admin.panels import FieldPanel, StreamFieldPanel
from wagtail.documents.models import AbstractDocument, Document
# Create your models here.
from wagtail.models import Page
from wagtail.documents.models import AbstractDocument, Document
from wagtail.snippets.blocks import SnippetChooserBlock
from vbv_lernwelt.media_library.content_blocks import ContentCollection