Merge branch 'feature/mediathek-server' into feature/mediathek-frontend
This commit is contained in:
commit
8941f4ad24
|
|
@ -4,6 +4,7 @@ import type { LearningContent, LearningSequence } from '@/types'
|
||||||
import { useCircleStore } from '@/stores/circle'
|
import { useCircleStore } from '@/stores/circle'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { humanizeDuration } from '@/utils/humanizeDuration'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningSequence: LearningSequence
|
learningSequence: LearningSequence
|
||||||
|
|
@ -73,14 +74,14 @@ const learningSequenceBorderClass = computed(() => {
|
||||||
<h3 class="text-xl font-semibold">
|
<h3 class="text-xl font-semibold">
|
||||||
{{ learningSequence.title }}
|
{{ learningSequence.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<div>{{ learningSequence.minutes }} Minuten</div>
|
<div>{{ humanizeDuration(learningSequence.minutes) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white px-4 lg:px-6 border border-gray-500" :class="learningSequenceBorderClass">
|
<div class="bg-white px-4 lg:px-6 border border-gray-500" :class="learningSequenceBorderClass">
|
||||||
<div v-for="learningUnit in learningSequence.learningUnits" :key="learningUnit.id" class="pt-3 lg:pt-6">
|
<div v-for="learningUnit in learningSequence.learningUnits" :key="learningUnit.id" class="pt-3 lg:pt-6">
|
||||||
<div class="pb-3 lg:pg-6 flex gap-4 text-blue-900" v-if="learningUnit.title">
|
<div class="pb-3 lg:pg-6 flex gap-4 text-blue-900" v-if="learningUnit.title">
|
||||||
<div class="font-semibold">{{ learningUnit.title }}</div>
|
<div class="font-semibold">{{ learningUnit.title }}</div>
|
||||||
<div>{{ learningUnit.minutes }} Minuten</div>
|
<div>{{ humanizeDuration(learningUnit.minutes) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ def main():
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
'http://localhost:8000/api/learnpath/page/unit-test-lernpfad/',
|
'http://localhost:8000/api/course/page/unit-test-lernpfad/',
|
||||||
)
|
)
|
||||||
print(response.status_code)
|
print(response.status_code)
|
||||||
print(response.json())
|
print(response.json())
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export const useLearningPathStore = defineStore({
|
||||||
if (this.learningPath && !reload) {
|
if (this.learningPath && !reload) {
|
||||||
return this.learningPath;
|
return this.learningPath;
|
||||||
}
|
}
|
||||||
const learningPathData = await itGet(`/api/learnpath/page/${slug}/`);
|
const learningPathData = await itGet(`/api/course/page/${slug}/`);
|
||||||
const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
|
const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
|
||||||
|
|
||||||
if (!learningPathData) {
|
if (!learningPathData) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
import { humanizeDuration } from '../humanizeDuration'
|
||||||
|
|
||||||
|
test('format duration for humans', () => {
|
||||||
|
expect(humanizeDuration(1)).toBe('1 Minute')
|
||||||
|
expect(humanizeDuration(15)).toBe('15 Minuten')
|
||||||
|
expect(humanizeDuration(42)).toBe('45 Minuten')
|
||||||
|
expect(humanizeDuration(60)).toBe('1 Stunde')
|
||||||
|
expect(humanizeDuration(122)).toBe('2 Stunden')
|
||||||
|
expect(humanizeDuration(120)).toBe('2 Stunden')
|
||||||
|
expect(humanizeDuration(132)).toBe('2 Stunden 15 Minuten')
|
||||||
|
expect(humanizeDuration(632)).toBe('10 Stunden')
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
function pluralize(text: string, count: number) {
|
||||||
|
if (count === 1) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return text + 'n';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function humanizeDuration(minutes: number) {
|
||||||
|
const hours = Math.floor(minutes / 60)
|
||||||
|
const remainingMinutes = minutes % 60
|
||||||
|
|
||||||
|
if (hours === 0 && minutes < 16) {
|
||||||
|
return pluralize(`${remainingMinutes} Minute`, remainingMinutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining minutes are rounded to 15 mins
|
||||||
|
const roundToMinutes = 15
|
||||||
|
const roundedMinutes = Math.round((minutes % 60) / roundToMinutes) * roundToMinutes
|
||||||
|
|
||||||
|
const hoursString = hours > 0 ? pluralize(`${hours} Stunde`, hours) : ''
|
||||||
|
|
||||||
|
const showMinutesUpToHours = 10
|
||||||
|
const minutesString = roundedMinutes > 0 && hours < showMinutesUpToHours
|
||||||
|
? pluralize(`${roundedMinutes} Minute`, roundedMinutes) : ''
|
||||||
|
|
||||||
|
const delimiter = hoursString && minutesString ? ' ' : ''
|
||||||
|
return `${hoursString}${delimiter}${minutesString}`
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import { useCircleStore } from '@/stores/circle'
|
||||||
import { useAppStore } from '@/stores/app'
|
import { useAppStore } from '@/stores/app'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { humanizeDuration } from '@/utils/humanizeDuration'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
|
@ -28,7 +29,7 @@ const circleStore = useCircleStore()
|
||||||
const duration = computed(() => {
|
const duration = computed(() => {
|
||||||
if (circleStore.circle) {
|
if (circleStore.circle) {
|
||||||
const minutes = _.sumBy(circleStore.circle.learningSequences, 'minutes')
|
const minutes = _.sumBy(circleStore.circle.learningSequences, 'minutes')
|
||||||
return `${minutes} Minuten`
|
return humanizeDuration(minutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const userStore = useUserStore()
|
||||||
<div class="mt-8 p-8 break-words bg-white max-w-xl">
|
<div class="mt-8 p-8 break-words bg-white max-w-xl">
|
||||||
<h3>Versicherungsvermittler/in</h3>
|
<h3>Versicherungsvermittler/in</h3>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<router-link class="btn-blue" to="/learn/versicherungsvermittlerin"> Weiter geht's </router-link>
|
<router-link class="btn-blue" to="/learn/versicherungsvermittlerin-lp"> Weiter geht's </router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ if [ "$SKIP_SETUP" = false ]; then
|
||||||
python3 server/manage.py createcachetable --settings="$DJANGO_SETTINGS_MODULE"
|
python3 server/manage.py createcachetable --settings="$DJANGO_SETTINGS_MODULE"
|
||||||
python3 server/manage.py migrate --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_users --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_learning_path --settings="$DJANGO_SETTINGS_MODULE"
|
||||||
python3 server/manage.py create_default_media_library --settings="$DJANGO_SETTINGS_MODULE"
|
python3 server/manage.py create_default_media_library --settings="$DJANGO_SETTINGS_MODULE"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,9 +103,10 @@ THIRD_PARTY_APPS = [
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
"vbv_lernwelt.core",
|
"vbv_lernwelt.core",
|
||||||
"vbv_lernwelt.sso",
|
"vbv_lernwelt.sso",
|
||||||
|
"vbv_lernwelt.course",
|
||||||
"vbv_lernwelt.learnpath",
|
"vbv_lernwelt.learnpath",
|
||||||
"vbv_lernwelt.completion",
|
|
||||||
"vbv_lernwelt.media_library",
|
"vbv_lernwelt.media_library",
|
||||||
|
"vbv_lernwelt.completion",
|
||||||
]
|
]
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||||
|
|
@ -514,7 +515,7 @@ if "django_redis.cache.RedisCache" in env("IT_DJANGO_CACHE_BACKEND", default="")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
CACHES["learning_path_cache"] = {
|
CACHES["api_page_cache"] = {
|
||||||
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
||||||
"LOCATION": "django_cache_learning_path",
|
"LOCATION": "django_cache_learning_path",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from vbv_lernwelt.core.views import (
|
||||||
rate_limit_exceeded_view,
|
rate_limit_exceeded_view,
|
||||||
permission_denied_view,
|
permission_denied_view,
|
||||||
check_rate_limit, cypress_reset_view, vue_home, vue_login, me_user_view, vue_logout, generate_web_component_icons, )
|
check_rate_limit, cypress_reset_view, vue_home, vue_login, me_user_view, vue_logout, generate_web_component_icons, )
|
||||||
from vbv_lernwelt.learnpath.views import page_api_view
|
from vbv_lernwelt.course.views import page_api_view
|
||||||
|
|
||||||
|
|
||||||
def raise_example_error(request):
|
def raise_example_error(request):
|
||||||
|
|
@ -47,8 +47,8 @@ urlpatterns = [
|
||||||
# core
|
# core
|
||||||
re_path(r"server/core/icons/$", generate_web_component_icons, name="generate_web_component_icons"),
|
re_path(r"server/core/icons/$", generate_web_component_icons, name="generate_web_component_icons"),
|
||||||
|
|
||||||
# learnpath
|
# course
|
||||||
path(r"api/learnpath/page/<slug:slug>/", page_api_view, name="page_api_view"),
|
path(r"api/course/page/<slug:slug>/", page_api_view, name="page_api_view"),
|
||||||
|
|
||||||
# completion
|
# completion
|
||||||
path(r"api/completion/circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
|
path(r"api/completion/circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
|
||||||
|
|
|
||||||
|
|
@ -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.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
@ -22,8 +22,8 @@ class Migration(migrations.Migration):
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
('page_key', models.UUIDField()),
|
('page_key', models.UUIDField()),
|
||||||
('page_type', models.CharField(blank=True, default='', max_length=255)),
|
('page_type', models.CharField(blank=True, default='', max_length=255)),
|
||||||
('circle_key', models.UUIDField()),
|
('circle_key', models.UUIDField(blank=True, default='')),
|
||||||
('learning_path_key', models.UUIDField()),
|
('learning_path_key', models.UUIDField(blank=True, default='')),
|
||||||
('completed', models.BooleanField(default=False)),
|
('completed', models.BooleanField(default=False)),
|
||||||
('json_data', models.JSONField(blank=True, default=dict)),
|
('json_data', models.JSONField(blank=True, default=dict)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CourseConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'vbv_lernwelt.course'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
try:
|
||||||
|
# pylint: disable=unused-import,import-outside-toplevel
|
||||||
|
import vbv_lernwelt.course.signals # noqa F401
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
COURSE_VERSICHERUNGSVERMITTLERIN = -1
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
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")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.course} / {self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class CoursePage(Page):
|
||||||
|
content_panels = Page.content_panels
|
||||||
|
subpage_types = ['learnpath.LearningPath', 'media_library.MediaLibraryPage']
|
||||||
|
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}"
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.models import CourseCategory, Course
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Course
|
||||||
|
fields = ['id', 'name', 'category_name']
|
||||||
|
|
||||||
|
|
||||||
|
class CourseCategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CourseCategory
|
||||||
|
fields = ['id', 'name', 'general',]
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import structlog
|
||||||
|
from django.core.cache import caches
|
||||||
|
from django.db.models.signals import post_delete, post_save
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_api_page_cache(sender, **kwargs):
|
||||||
|
logger.debug('invalidate api_page_cache', label='api_page_cache')
|
||||||
|
caches['api_page_cache'].clear()
|
||||||
|
|
||||||
|
|
||||||
|
for subclass in Page.__subclasses__():
|
||||||
|
post_save.connect(invalidate_api_page_cache, subclass)
|
||||||
|
post_delete.connect(invalidate_api_page_cache, subclass)
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import structlog
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
# @cache_page(60 * 60 * 8, cache="api_page_cache")
|
||||||
|
def page_api_view(request, slug):
|
||||||
|
try:
|
||||||
|
page = Page.objects.get(slug=slug, locale__language_code='de-CH')
|
||||||
|
serializer = page.specific.get_serializer_class()(page.specific)
|
||||||
|
return Response(serializer.data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
return Response({"error": str(e)}, status=404)
|
||||||
|
|
@ -5,6 +5,8 @@ from wagtail.models import Site, Page, Locale
|
||||||
from wagtail_localize.models import LocaleSynchronization
|
from wagtail_localize.models import LocaleSynchronization
|
||||||
|
|
||||||
from vbv_lernwelt.core.admin import User
|
from vbv_lernwelt.core.admin import User
|
||||||
|
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN
|
||||||
|
from vbv_lernwelt.course.models import CoursePage
|
||||||
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningContent, LearningUnit, \
|
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningContent, LearningUnit, \
|
||||||
LearningUnitQuestion
|
LearningUnitQuestion
|
||||||
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
|
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
|
||||||
|
|
@ -340,7 +342,11 @@ def create_default_learning_path(user=None, skip_locales=True):
|
||||||
|
|
||||||
# create_default_competences()
|
# create_default_competences()
|
||||||
|
|
||||||
lp = LearningPathFactory(title="Versicherungsvermittler/in", parent=site.root_page)
|
course_page = CoursePage.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN)
|
||||||
|
lp = LearningPathFactory(
|
||||||
|
title="Lernpfad",
|
||||||
|
parent=course_page,
|
||||||
|
)
|
||||||
|
|
||||||
TopicFactory(title="Basis", is_visible=False, parent=lp)
|
TopicFactory(title="Basis", is_visible=False, parent=lp)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import djclick as click
|
import djclick as click
|
||||||
|
|
||||||
from vbv_lernwelt.learnpath.create_default_learning_path import create_default_learning_path
|
from vbv_lernwelt.learnpath.create_default_learning_path import create_default_learning_path
|
||||||
from vbv_lernwelt.learnpath.tests.create_simple_test_learning_path import create_simple_test_learning_path
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
def command():
|
def command():
|
||||||
create_default_learning_path(skip_locales=True)
|
create_default_learning_path(skip_locales=True)
|
||||||
create_simple_test_learning_path(skip_locales=True)
|
# FIXME: readd
|
||||||
|
# create_simple_test_learning_path(skip_locales=True)
|
||||||
|
|
|
||||||
|
|
@ -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 15:05
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import modelcluster.fields
|
|
||||||
import wagtail.blocks
|
import wagtail.blocks
|
||||||
import wagtail.fields
|
import wagtail.fields
|
||||||
import wagtail.images.blocks
|
import wagtail.images.blocks
|
||||||
|
|
@ -31,34 +30,12 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=('wagtailcore.page',),
|
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(
|
migrations.CreateModel(
|
||||||
name='LearningContent',
|
name='LearningContent',
|
||||||
fields=[
|
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')),
|
('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)),
|
('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={
|
options={
|
||||||
'verbose_name': 'Learning Content',
|
'verbose_name': 'Learning Content',
|
||||||
|
|
@ -117,21 +94,4 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=('wagtailcore.page',),
|
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'),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -7,6 +7,7 @@ from wagtail.fields import StreamField
|
||||||
from wagtail.images.blocks import ImageChooserBlock
|
from wagtail.images.blocks import ImageChooserBlock
|
||||||
from wagtail.models import Page, Orderable
|
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, \
|
from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock, PodcastBlock, \
|
||||||
CompetenceBlock, ExerciseBlock, DocumentBlock, KnowledgeBlock
|
CompetenceBlock, ExerciseBlock, DocumentBlock, KnowledgeBlock
|
||||||
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
|
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
|
||||||
|
|
@ -17,12 +18,13 @@ class LearningPath(Page):
|
||||||
|
|
||||||
content_panels = Page.content_panels
|
content_panels = Page.content_panels
|
||||||
subpage_types = ['learnpath.Circle', 'learnpath.Topic']
|
subpage_types = ['learnpath.Circle', 'learnpath.Topic']
|
||||||
|
parent_page_types = ['course.CoursePage']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Learning Path"
|
verbose_name = "Learning Path"
|
||||||
|
|
||||||
def full_clean(self, *args, **kwargs):
|
def full_clean(self, *args, **kwargs):
|
||||||
self.slug = find_available_slug(slugify(self.title, allow_unicode=True))
|
self.slug = find_available_slug(slugify(f"{self.get_parent().slug}-lp", allow_unicode=True))
|
||||||
super(LearningPath, self).full_clean(*args, **kwargs)
|
super(LearningPath, self).full_clean(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
@ -268,36 +270,3 @@ def find_slug_with_parent_prefix(page, type_prefix):
|
||||||
slug_prefix = type_prefix
|
slug_prefix = type_prefix
|
||||||
|
|
||||||
return find_available_slug(slugify(f'{slug_prefix}-{page.title}', allow_unicode=True))
|
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
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import wagtail.api.v2.serializers as wagtail_serializers
|
import wagtail.api.v2.serializers as wagtail_serializers
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.models import CoursePage
|
||||||
|
from vbv_lernwelt.course.serializers import CourseCategorySerializer, CourseSerializer
|
||||||
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,6 +19,8 @@ class ItTypeField(wagtail_serializers.TypeField):
|
||||||
class ItBaseSerializer(wagtail_serializers.BaseSerializer):
|
class ItBaseSerializer(wagtail_serializers.BaseSerializer):
|
||||||
type = ItTypeField(read_only=True)
|
type = ItTypeField(read_only=True)
|
||||||
children = SerializerMethodField()
|
children = SerializerMethodField()
|
||||||
|
course = SerializerMethodField()
|
||||||
|
course_category = CourseCategorySerializer(read_only=True)
|
||||||
|
|
||||||
meta_fields = []
|
meta_fields = []
|
||||||
|
|
||||||
|
|
@ -30,6 +34,15 @@ class ItBaseSerializer(wagtail_serializers.BaseSerializer):
|
||||||
children = _get_children(self.descendants, obj)
|
children = _get_children(self.descendants, obj)
|
||||||
return [c.specific.get_serializer_class()(c.specific, descendants=self.descendants).data for c in children]
|
return [c.specific.get_serializer_class()(c.specific, descendants=self.descendants).data for c in children]
|
||||||
|
|
||||||
|
def get_course(self, obj):
|
||||||
|
if hasattr(obj, 'course'):
|
||||||
|
return CourseSerializer(obj.course).data
|
||||||
|
else:
|
||||||
|
course_parent_page = obj.get_ancestors().exact_type(CoursePage).last()
|
||||||
|
if course_parent_page:
|
||||||
|
return CourseSerializer(course_parent_page.specific.course).data
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def _get_descendants(pages, obj):
|
def _get_descendants(pages, obj):
|
||||||
return [c for c in pages if c.path.startswith(obj.path) and c.depth >= obj.depth]
|
return [c for c in pages if c.path.startswith(obj.path) and c.depth >= obj.depth]
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import structlog
|
|
||||||
from django.core.cache import caches
|
|
||||||
from django.db.models.signals import post_delete, post_save
|
|
||||||
from wagtail.models import Page
|
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def invalidate_learning_path_cache(sender, **kwargs):
|
|
||||||
logger.debug('invalidate learning_path_cache', label='learning_path_cache')
|
|
||||||
caches['learning_path_cache'].clear()
|
|
||||||
|
|
||||||
|
|
||||||
for subclass in Page.__subclasses__():
|
|
||||||
post_save.connect(invalidate_learning_path_cache, subclass)
|
|
||||||
post_delete.connect(invalidate_learning_path_cache, subclass)
|
|
||||||
|
|
@ -18,7 +18,7 @@ class TestRetrieveLearingPathContents(APITestCase):
|
||||||
|
|
||||||
def test_get_learnpathPage(self):
|
def test_get_learnpathPage(self):
|
||||||
learning_path = LearningPath.objects.get(slug='unit-test-lernpfad')
|
learning_path = LearningPath.objects.get(slug='unit-test-lernpfad')
|
||||||
response = self.client.get('/api/learnpath/page/unit-test-lernpfad/')
|
response = self.client.get('/api/course/page/unit-test-lernpfad/')
|
||||||
print(response)
|
print(response)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Create your views here.
|
|
||||||
|
|
||||||
import structlog
|
|
||||||
from django.views.decorators.cache import cache_page
|
|
||||||
from rest_framework.decorators import api_view
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from wagtail.models import Page
|
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET'])
|
|
||||||
@cache_page(60 * 60 * 8, cache="learning_path_cache")
|
|
||||||
def page_api_view(request, slug):
|
|
||||||
try:
|
|
||||||
page = Page.objects.get(slug=slug, locale__language_code='de-CH')
|
|
||||||
serializer = page.specific.get_serializer_class()(page.specific)
|
|
||||||
return Response(serializer.data)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
return Response({"error": str(e)}, status=404)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
from django.db import models
|
||||||
|
from wagtail import blocks
|
||||||
|
from wagtail.admin.panels import FieldPanel
|
||||||
|
from wagtail.documents.blocks import DocumentChooserBlock
|
||||||
|
from wagtail.snippets.models import register_snippet
|
||||||
|
|
||||||
|
|
||||||
|
@register_snippet
|
||||||
|
class MediaLibraryContent(models.Model):
|
||||||
|
title = models.TextField()
|
||||||
|
description = models.TextField()
|
||||||
|
link_display_text = models.CharField(max_length=255)
|
||||||
|
# TODO: Revisions only work with wagtail 4.0, can not migrate since wagtail localize is not ready yet.
|
||||||
|
# _revisions = GenericRelation("wagtailcore.Revision", related_query_name="media_library_content")
|
||||||
|
|
||||||
|
panels = [
|
||||||
|
FieldPanel('title'),
|
||||||
|
FieldPanel('description'),
|
||||||
|
FieldPanel('link_display_text'),
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def revisions(self):
|
||||||
|
return self._revisions
|
||||||
|
|
||||||
|
|
||||||
|
class AnchorBlock(blocks.PageChooserBlock):
|
||||||
|
"""
|
||||||
|
Verankerung im Lernpfad. Link to a Learning Content.
|
||||||
|
"""
|
||||||
|
page_type = 'learnpath.LearningUnit'
|
||||||
|
|
||||||
|
|
||||||
|
class LinkBlock(blocks.StructBlock):
|
||||||
|
title = blocks.TextBlock(blank=False, null=False)
|
||||||
|
description = blocks.TextBlock(default='')
|
||||||
|
link_display_text = blocks.CharBlock(max_length=255, default='Link öffnen')
|
||||||
|
url = blocks.URLBlock()
|
||||||
|
|
||||||
|
|
||||||
|
class CrossReferenceBlock(blocks.StructBlock):
|
||||||
|
title = models.TextField(blank=False, null=False)
|
||||||
|
description = blocks.TextBlock(default='')
|
||||||
|
link_display_text = blocks.CharBlock(max_length=255, default='Link öffnen')
|
||||||
|
category = blocks.PageChooserBlock(page_type='media_library.MediaCategoryPage')
|
||||||
|
|
||||||
|
|
||||||
|
class MediaContentCollection(blocks.StructBlock):
|
||||||
|
"""
|
||||||
|
Lernmedien, Links, Querverweise, Verankerung
|
||||||
|
"""
|
||||||
|
title = blocks.TextBlock()
|
||||||
|
contents = blocks.StreamBlock([
|
||||||
|
('Links', LinkBlock()),
|
||||||
|
('Documents', DocumentChooserBlock()),
|
||||||
|
('Ankers', AnchorBlock()),
|
||||||
|
('CrossReference', CrossReferenceBlock())
|
||||||
|
])
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
icon = 'link'
|
||||||
|
|
@ -3,6 +3,7 @@ import os
|
||||||
import factory
|
import factory
|
||||||
from wagtail.core.models import Collection
|
from wagtail.core.models import Collection
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.models import Course
|
||||||
from vbv_lernwelt.media_library.models import LibraryDocument
|
from vbv_lernwelt.media_library.models import LibraryDocument
|
||||||
from vbv_lernwelt.media_library.tests.media_library_factories import LibraryDocumentFactory
|
from vbv_lernwelt.media_library.tests.media_library_factories import LibraryDocumentFactory
|
||||||
|
|
||||||
|
|
@ -11,15 +12,11 @@ def create_default_collections():
|
||||||
c = Collection.objects.all().delete()
|
c = Collection.objects.all().delete()
|
||||||
|
|
||||||
root, created = Collection.objects.get_or_create(name='Root', depth=0)
|
root, created = Collection.objects.get_or_create(name='Root', depth=0)
|
||||||
versicherungsvermittler = root.add_child(name='Versicherungsvermittler/in')
|
|
||||||
handlungsfelder = versicherungsvermittler.add_child(name='Handlungsfelder')
|
|
||||||
|
|
||||||
handlungsfelder_names = ['Fahrzeug', 'Reisen', 'Einkommensicherung', 'Gesundheit', 'Haushalt', 'Sparen',
|
for course in Course.objects.all():
|
||||||
'Pensionierung', 'KMU', 'Wohneigentum', 'Rechtsstreitigkeiten', 'Erben / Vererben',
|
course_collection = root.add_child(name=course.name)
|
||||||
'Selbständigkeit']
|
for cat in course.coursecategory_set.all():
|
||||||
|
cat_collection = course_collection.add_child(name=cat.name)
|
||||||
for handlungsfeld in handlungsfelder_names:
|
|
||||||
versicherungsvermittler = handlungsfelder.add_child(name=handlungsfeld)
|
|
||||||
|
|
||||||
|
|
||||||
def create_default_documents():
|
def create_default_documents():
|
||||||
|
|
@ -33,7 +30,7 @@ def create_default_documents():
|
||||||
document = LibraryDocumentFactory(
|
document = LibraryDocumentFactory(
|
||||||
title='V1 C25 ZGB CH',
|
title='V1 C25 ZGB CH',
|
||||||
display_text='Schweizerisches Zivilgesetzbuch',
|
display_text='Schweizerisches Zivilgesetzbuch',
|
||||||
description='Ein wundervolles Dokument, Bachblüten für Leseratten und metaphysisches Wolbefinden für Handyvekäufer.',
|
description='Ein wundervolles Dokument, Bachblüten für Leseratten und metaphysisches Wohlbefinden für Handyvekäufer.',
|
||||||
link_display_text='Dokument laden',
|
link_display_text='Dokument laden',
|
||||||
file=factory.django.FileField(from_path=os.path.join(path, filename), filename=filename),
|
file=factory.django.FileField(from_path=os.path.join(path, filename), filename=filename),
|
||||||
collection=collection
|
collection=collection
|
||||||
|
|
@ -43,10 +40,8 @@ def create_default_documents():
|
||||||
document = LibraryDocumentFactory(
|
document = LibraryDocumentFactory(
|
||||||
title='V1 C25 ',
|
title='V1 C25 ',
|
||||||
display_text='Pdf showcase ',
|
display_text='Pdf showcase ',
|
||||||
description='Ein wundervolles Dokument, Bachblüten für Leseratten und metaphysisches Wolbefinden für Handyvekäufer.',
|
description='Ein wundervolles Dokument, Bachblüten für Leseratten und metaphysisches Wohlbefinden für Handyvekäufer.',
|
||||||
link_display_text='Dokument laden',
|
link_display_text='Dokument laden',
|
||||||
file=factory.django.FileField(from_path=os.path.join(path, filename), filename=filename),
|
file=factory.django.FileField(from_path=os.path.join(path, filename), filename=filename),
|
||||||
collection=collection
|
collection=collection
|
||||||
)
|
)
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN
|
||||||
|
from vbv_lernwelt.course.models import CoursePage, Course
|
||||||
|
from vbv_lernwelt.media_library.tests.media_library_factories import MediaLibraryPageFactory, MediaCategoryPageFactory, \
|
||||||
|
create_media_content_link, LinkBlockFactory, create_link_collection, create_document_collection
|
||||||
|
|
||||||
|
|
||||||
|
def create_default_media_library():
|
||||||
|
course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN)
|
||||||
|
course_page = CoursePage.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN)
|
||||||
|
|
||||||
|
media_lib_page = MediaLibraryPageFactory(
|
||||||
|
title='Mediathek',
|
||||||
|
parent=course_page,
|
||||||
|
)
|
||||||
|
|
||||||
|
for cat in course.coursecategory_set.all():
|
||||||
|
introduction_text = '''
|
||||||
|
Das Auto ist für viele der grösste Stolz! Es birgt aber auch ein grosses Gefahrenpotenzial.
|
||||||
|
Dabei geht es bei den heutigen Fahrzeugpreisen und Reparaturkosten rasch um namhafte Summen,
|
||||||
|
die der Fahrzeugbesitzer und die Fahrzeugbesitzerin in einem grösseren Schadenfall oft nur schwer selbst aufbringen kann.
|
||||||
|
'''.strip()
|
||||||
|
description = 'Supi'
|
||||||
|
body_data = json.dumps([
|
||||||
|
create_document_collection(),
|
||||||
|
create_link_collection(
|
||||||
|
links_dict=[
|
||||||
|
create_media_content_link(LinkBlockFactory(title='Nationales Versicherungsbüro', url='https://www.vbv.ch/')),
|
||||||
|
create_media_content_link(LinkBlockFactory(title='Adressen der Strassenverkehrsämter', url='https://www.vbv.ch/')),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
media_category = MediaCategoryPageFactory(
|
||||||
|
title=cat.name,
|
||||||
|
course_category=cat,
|
||||||
|
parent=media_lib_page,
|
||||||
|
introduction_text=introduction_text,
|
||||||
|
description=description,
|
||||||
|
body=body_data,
|
||||||
|
)
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import djclick as click
|
import djclick as click
|
||||||
|
|
||||||
from vbv_lernwelt.media_library.create_default_documents import create_default_collections, create_default_documents
|
from vbv_lernwelt.media_library.create_default_documents import create_default_collections, create_default_documents
|
||||||
|
from vbv_lernwelt.media_library.create_default_media_library import create_default_media_library
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
def command():
|
def command():
|
||||||
create_default_collections()
|
create_default_collections()
|
||||||
create_default_documents()
|
create_default_documents()
|
||||||
|
create_default_media_library()
|
||||||
|
|
|
||||||
|
|
@ -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-23 12:42
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import taggit.managers
|
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.models.collections
|
||||||
import wagtail.search.index
|
import wagtail.search.index
|
||||||
|
|
||||||
|
|
@ -13,14 +17,48 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtailcore', '0069_log_entry_jsonfield'),
|
('course', '0001_initial'),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('taggit', '0004_alter_taggeditem_content_type_alter_taggeditem_tag'),
|
('taggit', '0004_alter_taggeditem_content_type_alter_taggeditem_tag'),
|
||||||
|
('wagtailcore', '0069_log_entry_jsonfield'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CustomDocument',
|
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='MediaLibraryPage',
|
||||||
|
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='MediaCategoryPage',
|
||||||
|
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()), ('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.AnchorBlock()), ('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.MediaCategoryPage']))]))]))]))], null=True, use_json_field=True)),
|
||||||
|
('course_category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.coursecategory')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('wagtailcore.page',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LibraryDocument',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('title', models.CharField(max_length=255, verbose_name='title')),
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
# Generated by Django 3.2.13 on 2022-08-18 12:14
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wagtailcore', '0069_log_entry_jsonfield'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('taggit', '0004_alter_taggeditem_content_type_alter_taggeditem_tag'),
|
|
||||||
('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')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('wagtailcore.page',),
|
|
||||||
),
|
|
||||||
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',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,8 +1,74 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.text import slugify
|
||||||
# Create your models here.
|
from wagtail import fields
|
||||||
from wagtail.models import Page
|
from wagtail.admin.panels import FieldPanel, StreamFieldPanel
|
||||||
from wagtail.documents.models import AbstractDocument, Document
|
from wagtail.documents.models import AbstractDocument, Document
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.model_utils import find_available_slug
|
||||||
|
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
|
||||||
|
from vbv_lernwelt.media_library.content_blocks import MediaContentCollection
|
||||||
|
|
||||||
|
|
||||||
|
class MediaLibraryPage(Page):
|
||||||
|
parent_page_types = ['course.CoursePage']
|
||||||
|
subpage_types = ['media_library.MediaCategoryPage']
|
||||||
|
|
||||||
|
content_panels = [
|
||||||
|
FieldPanel('title', classname="full title"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def full_clean(self, *args, **kwargs):
|
||||||
|
self.slug = find_available_slug(slugify(f"{self.get_parent().slug}-media", allow_unicode=True))
|
||||||
|
super(MediaLibraryPage, self).full_clean(*args, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_serializer_class(cls):
|
||||||
|
return get_it_serializer_class(
|
||||||
|
cls, [
|
||||||
|
'id', 'title', 'slug', 'type', 'translation_key',
|
||||||
|
'course',
|
||||||
|
'children',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MediaCategoryPage(Page):
|
||||||
|
"""
|
||||||
|
Handlungsfeld. zB. Fahrzeug
|
||||||
|
"""
|
||||||
|
course_category = models.ForeignKey('course.CourseCategory', on_delete=models.CASCADE)
|
||||||
|
parent_page_types = ['media_library.MediaLibraryPage']
|
||||||
|
introduction_text = models.TextField(default='')
|
||||||
|
description = fields.RichTextField(default='')
|
||||||
|
|
||||||
|
body = fields.StreamField(
|
||||||
|
[('content_collection', MediaContentCollection())],
|
||||||
|
use_json_field=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
content_panels = [
|
||||||
|
FieldPanel('title', classname="full title"),
|
||||||
|
FieldPanel('course_category'),
|
||||||
|
FieldPanel('introduction_text', classname="introduction text"),
|
||||||
|
FieldPanel('description', classname="introduction text"),
|
||||||
|
StreamFieldPanel('body')
|
||||||
|
]
|
||||||
|
|
||||||
|
def full_clean(self, *args, **kwargs):
|
||||||
|
self.slug = find_available_slug(slugify(f"{self.get_parent()}-cat-{self.title}", allow_unicode=True))
|
||||||
|
super(MediaCategoryPage, self).full_clean(*args, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_serializer_class(cls):
|
||||||
|
return get_it_serializer_class(
|
||||||
|
cls, field_names=[
|
||||||
|
'id', 'title', 'slug', 'type', 'translation_key',
|
||||||
|
'course_category',
|
||||||
|
'introduction_text', 'description', 'body',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LibraryDocument(AbstractDocument):
|
class LibraryDocument(AbstractDocument):
|
||||||
|
|
@ -14,58 +80,6 @@ class LibraryDocument(AbstractDocument):
|
||||||
link_display_text = models.CharField(max_length=1024, default='')
|
link_display_text = models.CharField(max_length=1024, default='')
|
||||||
thumbnail = models.URLField()
|
thumbnail = models.URLField()
|
||||||
|
|
||||||
|
|
||||||
admin_form_fields = Document.admin_form_fields + (
|
admin_form_fields = Document.admin_form_fields + (
|
||||||
'display_text', 'description', 'link_display_text', 'thumbnail'
|
'display_text', 'description', 'link_display_text', 'thumbnail'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TopCategory(Page):
|
|
||||||
"""
|
|
||||||
Handlungsfelder
|
|
||||||
"""
|
|
||||||
parent_page_types = ['learnpath.LearningPath']
|
|
||||||
subpage_types = ['media_library.Category']
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Todo: use wagtail collections for this...
|
|
||||||
|
|
||||||
|
|
||||||
class Category(Page):
|
|
||||||
"""
|
|
||||||
Handlungsfeld
|
|
||||||
"""
|
|
||||||
|
|
||||||
parent_page_types = ['media_library.TopCategory']
|
|
||||||
|
|
||||||
#
|
|
||||||
# description
|
|
||||||
# thumbnail_image
|
|
||||||
# description_image
|
|
||||||
# additional_content # Rich text field
|
|
||||||
# documents = []
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class LibraryDocument(CustomDocument):
|
|
||||||
# """
|
|
||||||
# Extension from the standart Wagtail document.
|
|
||||||
# """
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class LibraryLink():
|
|
||||||
# """
|
|
||||||
# Custom Link Block
|
|
||||||
#
|
|
||||||
# """
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class LearningPathReference():
|
|
||||||
# icon
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class CrossReference():
|
|
||||||
# pass
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
import wagtail_factories
|
import wagtail_factories
|
||||||
|
|
||||||
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningContent, LearningUnit, \
|
from vbv_lernwelt.media_library.content_blocks import MediaContentCollection, AnchorBlock, LinkBlock, \
|
||||||
LearningUnitQuestion
|
CrossReferenceBlock
|
||||||
from vbv_lernwelt.media_library.models import LibraryDocument
|
from vbv_lernwelt.media_library.models import LibraryDocument, MediaLibraryPage, MediaCategoryPage
|
||||||
|
|
||||||
|
|
||||||
class LibraryDocumentFactory(wagtail_factories.DocumentFactory):
|
class LibraryDocumentFactory(wagtail_factories.DocumentFactory):
|
||||||
|
|
@ -13,3 +14,87 @@ class LibraryDocumentFactory(wagtail_factories.DocumentFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LibraryDocument
|
model = LibraryDocument
|
||||||
|
|
||||||
|
|
||||||
|
class MediaLibraryPageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = 'Mediathek'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = MediaLibraryPage
|
||||||
|
|
||||||
|
|
||||||
|
class AnchorBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
|
class Meta:
|
||||||
|
model = AnchorBlock
|
||||||
|
|
||||||
|
|
||||||
|
class LinkBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
|
title = 'Interesting link'
|
||||||
|
description = 'This link is really interesting...'
|
||||||
|
url = 'https://www.vbv.ch/'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = LinkBlock
|
||||||
|
|
||||||
|
|
||||||
|
class CrossReferenceBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
|
class Meta:
|
||||||
|
model = CrossReferenceBlock
|
||||||
|
|
||||||
|
|
||||||
|
class MediaContentCollectionFactory(wagtail_factories.StructBlockFactory):
|
||||||
|
title = 'Links'
|
||||||
|
contents = wagtail_factories.StreamFieldFactory({
|
||||||
|
'Links': LinkBlockFactory,
|
||||||
|
'Documents': LibraryDocumentFactory
|
||||||
|
})
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = MediaContentCollection
|
||||||
|
|
||||||
|
|
||||||
|
class MediaCategoryPageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = 'Fahrzeug'
|
||||||
|
introduction_text = 'Das Auto ist für viele der grösste Stolz! Es birgt aber ...'
|
||||||
|
description = 'Das erwartet dich in diesem Handlungsfeld'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = MediaCategoryPage
|
||||||
|
|
||||||
|
|
||||||
|
def create_media_content_link(link_block=None):
|
||||||
|
if link_block is None:
|
||||||
|
link_block = LinkBlockFactory()
|
||||||
|
return {
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"type": "Links",
|
||||||
|
"value": dict(link_block.items())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_link_collection(links_dict=None):
|
||||||
|
return {
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"type": "content_collection",
|
||||||
|
"value": {
|
||||||
|
"title": "Links",
|
||||||
|
"contents": [link_dict for link_dict in links_dict]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_document_collection(document_ids=None):
|
||||||
|
if document_ids is None:
|
||||||
|
document_ids = [d.id for d in LibraryDocument.objects.all()]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"type": "content_collection",
|
||||||
|
"value": {
|
||||||
|
"title": "Lernmedien",
|
||||||
|
"contents": [{
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"type": "Documents",
|
||||||
|
"value": doc_id
|
||||||
|
} for doc_id in document_ids]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
|
from vbv_lernwelt.core.tests.helpers import create_locales_for_wagtail
|
||||||
|
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory
|
||||||
|
from vbv_lernwelt.media_library.create_default_media_library import create_default_media_library
|
||||||
|
from vbv_lernwelt.media_library.models import MediaLibraryPage, MediaCategoryPage
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateDefaultDocuments(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
create_default_users()
|
||||||
|
create_locales_for_wagtail()
|
||||||
|
LearningPathFactory()
|
||||||
|
create_default_media_library()
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_default_media_library(self):
|
||||||
|
|
||||||
|
self.assertEqual(MediaLibraryPage.objects.all().count(), 1)
|
||||||
|
self.assertEqual(MediaCategoryPage.objects.all().count(), 12)
|
||||||
|
|
||||||
|
def test_create_category_fahrzeug_contains_content(self):
|
||||||
|
fahrzeug = MediaCategoryPage.objects.get(title='Fahrzeug')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
|
from vbv_lernwelt.core.tests.helpers import create_locales_for_wagtail
|
||||||
|
from vbv_lernwelt.media_library.models import MediaCategoryPage
|
||||||
|
from vbv_lernwelt.media_library.tests.media_library_factories import MediaContentCollectionFactory, MediaCategoryPageFactory, \
|
||||||
|
LinkBlockFactory, generate_default_content2, collection_body_dict
|
||||||
|
|
||||||
|
|
||||||
|
class TestMediaLibraryFactories(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
create_default_users()
|
||||||
|
create_locales_for_wagtail()
|
||||||
|
|
||||||
|
def test_content_collection_factory(self):
|
||||||
|
content_collection = MediaContentCollectionFactory()
|
||||||
|
self.assertEqual(content_collection.get('title'), 'Links')
|
||||||
|
self.assertEqual(content_collection.get('collection_type'), 'LearningMedia')
|
||||||
|
|
||||||
|
def test_link_block_factory(self):
|
||||||
|
link = LinkBlockFactory(title='MyLink')
|
||||||
|
self.assertEqual(link.get('description'), 'This link is really interesting...')
|
||||||
|
self.assertEqual(link.get('url'), 'www.example.com')
|
||||||
|
self.assertEqual(link.get('link_display_text'), 'Link öffnen')
|
||||||
|
self.assertEqual(link.get('title'), 'MyLink')
|
||||||
|
|
||||||
|
def test_category_contains_content_collection(self):
|
||||||
|
default_content = generate_default_content2()
|
||||||
|
default_content['body__content_collection__0__title'] = 'Spidf'
|
||||||
|
|
||||||
|
category = MediaCategoryPageFactory(**default_content)
|
||||||
|
print(category.body.raw_data)
|
||||||
|
self.assertNotEqual(category.body.raw_data, [])
|
||||||
|
|
||||||
|
def collection_via_dict_generation(self):
|
||||||
|
category = MediaCategoryPageFactory()
|
||||||
|
category.body = json.dumps(collection_body_dict())
|
||||||
|
category.save()
|
||||||
|
category_id = category.id
|
||||||
|
new_category = MediaCategoryPage.objects.get(id=category_id)
|
||||||
|
self.assertNotEqual(new_category.body, [])
|
||||||
|
self.assertNotEqual(new_category.body, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
Loading…
Reference in New Issue