Fix test lerngang

This commit is contained in:
Daniel Egger 2022-09-28 14:44:24 +02:00
parent d5e484a6c7
commit b5804c2696
34 changed files with 296 additions and 359 deletions

View File

@ -63,9 +63,6 @@ if [ "$SKIP_SETUP" = false ]; then
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_courses --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_learning_path --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_competence_profile --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

@ -107,7 +107,6 @@ LOCAL_APPS = [
"vbv_lernwelt.learnpath",
"vbv_lernwelt.competence",
"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

@ -10,8 +10,6 @@ from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.documents import urls as wagtaildocs_urls
from vbv_lernwelt.completion.views import request_learning_path_completion, request_circle_completion, \
mark_circle_completion
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
from vbv_lernwelt.core.views import (
rate_limit_exceeded_view,
@ -51,9 +49,9 @@ urlpatterns = [
path(r"api/course/page/<slug:slug>/", page_api_view, name="page_api_view"),
# completion
path(r"api/completion/circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
path(r"api/completion/learning_path/<uuid:learning_path_key>/", request_learning_path_completion, name="request_learning_path_completion"),
path(r"api/completion/circle/mark/", mark_circle_completion, name="mark_circle_completion"),
# path(r"api/completion/circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
# path(r"api/completion/learning_path/<uuid:learning_path_key>/", request_learning_path_completion, name="request_learning_path_completion"),
# path(r"api/completion/circle/mark/", mark_circle_completion, name="mark_circle_completion"),
# testing and debug
path('server/raise_error/', user_passes_test(lambda u: u.is_superuser, login_url='/login/')(raise_example_error), ),

View File

@ -1,14 +1,14 @@
from vbv_lernwelt.competence.factories import CompetenceProfilePageFactory, PerformanceCriteriaFactory, \
CompetencePageFactory
from vbv_lernwelt.competence.models import CompetencePage
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
from vbv_lernwelt.course.models import CoursePage, Course
from vbv_lernwelt.learnpath.models import LearningUnit
def create_default_competence_profile():
course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN)
course_page = CoursePage.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN)
course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
course_page = CoursePage.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
competence_profile_page = CompetenceProfilePageFactory(
title='Kompetenzprofil',

View File

@ -1,8 +0,0 @@
import djclick as click
from vbv_lernwelt.competence.create_default_competence_profile import create_default_competence_profile
@click.command()
def command():
create_default_competence_profile()

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.13 on 2022-09-28 10:46
# Generated by Django 3.2.13 on 2022-09-28 12:51
from django.db import migrations, models
import django.db.models.deletion
@ -12,7 +12,6 @@ class Migration(migrations.Migration):
dependencies = [
('wagtailcore', '0069_log_entry_jsonfield'),
('learnpath', '0001_initial'),
]
operations = [
@ -43,7 +42,6 @@ class Migration(migrations.Migration):
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')),
('competence_id', models.TextField(default='A1.1')),
('learning_unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='learnpath.learningunit')),
],
options={
'abstract': False,

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.13 on 2022-09-28 12:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('learnpath', '0001_initial'),
('competence', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='performancecriteria',
name='learning_unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='learnpath.learningunit'),
),
]

View File

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

View File

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

View File

@ -1,36 +0,0 @@
# 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
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='CircleCompletion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('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(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)),
],
),
migrations.AddConstraint(
model_name='circlecompletion',
constraint=models.UniqueConstraint(fields=('user', 'page_key'), name='unique_user_page_key'),
),
]

View File

@ -1,29 +0,0 @@
from django.db import models
from django.db.models import UniqueConstraint
from vbv_lernwelt.core.models import User
class CircleCompletion(models.Model):
# Page can either be a LearningContent or a LearningUnitQuestion for now
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
# Page can either be a LearningContent or a LearningUnitQuestion for now
page_key = models.UUIDField()
page_type = models.CharField(max_length=255, default='', blank=True)
circle_key = models.UUIDField(blank=True, default='')
learning_path_key = models.UUIDField(blank=True, default='')
completed = models.BooleanField(default=False)
json_data = models.JSONField(default=dict, blank=True)
class Meta:
constraints = [
UniqueConstraint(
fields=['user', 'page_key', ],
name='unique_user_page_key'
)
]

View File

@ -1,13 +0,0 @@
from rest_framework import serializers
from vbv_lernwelt.completion.models import CircleCompletion
class CircleCompletionSerializer(serializers.ModelSerializer):
class Meta:
model = CircleCompletion
fields = [
'id', 'created_at', 'updated_at', 'user', 'page_key', 'page_type', 'circle_key',
'learning_path_key', 'completed', 'json_data',
]

View File

@ -1,70 +0,0 @@
import structlog
from rest_framework.decorators import api_view
from rest_framework.response import Response
from wagtail.models import Page
from vbv_lernwelt.completion.models import CircleCompletion
from vbv_lernwelt.completion.serializers import CircleCompletionSerializer
from vbv_lernwelt.learnpath.models import Circle, LearningPath
from vbv_lernwelt.learnpath.utils import get_wagtail_type
logger = structlog.get_logger(__name__)
@api_view(['GET'])
def request_circle_completion(request, circle_key):
response_data = CircleCompletionSerializer(
CircleCompletion.objects.filter(user=request.user, circle_key=circle_key),
many=True,
).data
return Response(status=200, data=response_data)
@api_view(['GET'])
def request_learning_path_completion(request, learning_path_key):
response_data = CircleCompletionSerializer(
CircleCompletion.objects.filter(user=request.user, learning_path_key=learning_path_key),
many=True,
).data
return Response(status=200, data=response_data)
@api_view(['POST'])
def mark_circle_completion(request):
page_key = request.data.get('page_key')
completed = request.data.get('completed', True)
page = Page.objects.get(translation_key=page_key, locale__language_code='de-CH')
page_type = get_wagtail_type(page.specific)
circle = Circle.objects.ancestor_of(page).first()
learning_path = LearningPath.objects.ancestor_of(page).first()
cc, created = CircleCompletion.objects.get_or_create(
user=request.user,
page_key=page_key,
circle_key=circle.translation_key,
learning_path_key=learning_path.translation_key,
)
cc.page_type = page_type
cc.completed = completed
cc.save()
response_data = CircleCompletionSerializer(
CircleCompletion.objects.filter(user=request.user, circle_key=circle.translation_key),
many=True,
).data
logger.debug(
'page completed',
label='completion_api',
circle_key=circle.translation_key,
circle_title=circle.title,
page_key=page_key,
page_type=page_type,
page_title=page.title,
user_id=request.user.id,
)
return Response(status=200, data=response_data)

View File

@ -1,17 +1,10 @@
import djclick as click
from vbv_lernwelt.completion.models import CircleCompletion
from vbv_lernwelt.learnpath.create_default_learning_path import create_default_learning_path, \
delete_default_learning_path
from vbv_lernwelt.course.models import CourseCompletion
@click.command()
@click.option('--reset-learning-path', default=False)
def command(reset_learning_path):
def command():
print("cypress reset data")
if reset_learning_path:
delete_default_learning_path()
create_default_learning_path(skip_locales=True)
CircleCompletion.objects.all().delete()
CourseCompletion.objects.all().delete()

View File

@ -1 +1,2 @@
COURSE_VERSICHERUNGSVERMITTLERIN = -1
COURSE_TEST_ID = -1
COURSE_VERSICHERUNGSVERMITTLERIN_ID = -2

View File

@ -1,21 +1,97 @@
import wagtail_factories
from django.conf import settings
from wagtail.models import Site, Page
from wagtail.models import Site
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
LearningSequenceFactory, LearningContentFactory, \
ExerciseBlockFactory, LearningUnitFactory, DocumentBlockFactory, TestBlockFactory, OnlineTrainingBlockFactory
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.course.consts import COURSE_TEST_ID
from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import CoursePage, CourseCategory
from vbv_lernwelt.learnpath.tests.learning_path_factories import CircleFactory, LearningSequenceFactory, \
LearningContentFactory, DocumentBlockFactory, LearningUnitFactory, TestBlockFactory, ExerciseBlockFactory, \
LearningPathFactory, TopicFactory, OnlineTrainingBlockFactory
def create_circle(title, learning_path):
return CircleFactory(
title=title,
parent=learning_path,
def create_test_course():
create_locales_for_wagtail()
create_test_course_with_categories()
create_test_learning_path()
def create_test_course_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_TEST_ID,
title='Test Lerngang',
category_name='Handlungsfeld'
)
CourseCategory.objects.get_or_create(course=course, title='Allgemein', general=True)
for cat in [
'Fahrzeug', 'Reisen',
]:
CourseCategory.objects.get_or_create(course=course, title=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="Test Lehrgang",
parent=site.root_page,
course=course,
)
def create_test_learning_path(user=None, skip_locales=True):
course_page = CoursePage.objects.get(course_id=COURSE_TEST_ID)
lp = LearningPathFactory(
title="Test Lernpfad", parent=course_page
)
TopicFactory(title="Basis", is_visible=False, parent=lp)
circle_basis = CircleFactory(
title="Basis",
parent=lp,
description="Basis",
)
LearningSequenceFactory(title='Starten', parent=circle_basis, icon='it-icon-ls-start')
LearningContentFactory(
title='Einführung',
parent=circle_basis,
minutes=15,
contents=[('document', DocumentBlockFactory())]
)
LearningSequenceFactory(title='Beenden', parent=circle_basis, icon='it-icon-ls-end')
LearningContentFactory(
title='Jetzt kann es losgehen!',
parent=circle_basis,
minutes=30,
contents=[('document', DocumentBlockFactory())]
)
TopicFactory(title="Beraten der Kunden", parent=lp)
circle = CircleFactory(
title="Analyse",
parent=lp,
description="Unit-Test Circle",
job_situations=[
('job_situation', 'Absicherung der Familie'),
('job_situation', 'Reisen'),
('job_situation', 'Autoversicherung'),
('job_situation', 'Autokauf'),
],
goals=[
('goal', '... die heutige Versicherungssituation von Privat- oder Geschäftskunden einzuschätzen.'),
@ -26,11 +102,9 @@ def create_circle(title, learning_path):
]
)
def create_circle_children(circle, title):
LearningSequenceFactory(title='Starten', parent=circle, icon='it-icon-ls-start')
LearningContentFactory(
title=f'Einleitung Circle "{title}"',
title=f'Einleitung Circle "Analyse"',
parent=circle,
minutes=15,
contents=[('document', DocumentBlockFactory())]
@ -38,11 +112,12 @@ def create_circle_children(circle, title):
LearningSequenceFactory(title='Beobachten', parent=circle, icon='it-icon-ls-watch')
lu = LearningUnitFactory(
title='Absicherung der Familie',
title='Fahrzeug',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_TEST_ID, title='Fahrzeug'),
)
LearningContentFactory(
title='Ermittlung des Kundenbedarfs',
title='Rafael Fasel wechselt sein Auto',
parent=circle,
minutes=30,
contents=[('online_training', OnlineTrainingBlockFactory(
@ -51,21 +126,17 @@ def create_circle_children(circle, title):
))]
)
LearningContentFactory(
title='Kundenbedürfnisse erkennen',
title='Fachcheck Fahrzeug',
parent=circle,
minutes=30,
contents=[('test', TestBlockFactory())]
)
LearningContentFactory(
title='Was braucht eine Familie?',
lu = LearningUnitFactory(
title='Reisen',
parent=circle,
minutes=60,
contents=[('exercise', ExerciseBlockFactory(url='/static/media/web_based_trainings/story-01-a-01-patrizia-marco-sichern-sich-ab-einstieg/scormcontent/index.html'
))]
course_category=CourseCategory.objects.get(course_id=COURSE_TEST_ID, title='Reisen'),
)
lu = LearningUnitFactory(title='Reisen', parent=circle)
LearningContentFactory(
title='Reiseversicherung',
parent=circle,
@ -73,7 +144,7 @@ def create_circle_children(circle, title):
contents=[('exercise', ExerciseBlockFactory())]
)
LearningContentFactory(
title='Sorgenfrei reisen',
title='Emma und Ayla campen durch Amerika',
parent=circle,
minutes=120,
contents=[('exercise', ExerciseBlockFactory(
@ -94,69 +165,3 @@ def create_circle_children(circle, title):
contents=[('document', DocumentBlockFactory())]
)
def create_simple_test_learning_path(user=None, skip_locales=True):
if user is None:
user = User.objects.get(username='info@iterativ.ch')
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()
lp = LearningPathFactory(title="Unit-Test Lernpfad", parent=site.root_page)
TopicFactory(title="Basis", is_visible=False, parent=lp)
circle_basis = CircleFactory(
title="Basis",
parent=lp,
description="Basis von Unit-Test Lernpfad",
)
LearningSequenceFactory(title='Starten', parent=circle_basis, icon='it-icon-ls-start')
LearningContentFactory(
title='Einleitung Circle "Basis"',
parent=circle_basis,
minutes=15,
contents=[('document', DocumentBlockFactory())]
)
LearningSequenceFactory(title='Beenden', parent=circle_basis, icon='it-icon-ls-end')
LearningContentFactory(
title='Kompetenzprofil anschauen',
parent=circle_basis,
minutes=30,
contents=[('document', DocumentBlockFactory())]
)
LearningContentFactory(
title='Circle "Analyse" abschliessen',
parent=circle_basis,
minutes=30,
contents=[('document', DocumentBlockFactory())]
)
TopicFactory(title="Gewinnen von Kunden", parent=lp)
circle_analyse = create_circle('Unit-Test Circle', lp)
create_circle_children(circle_analyse, 'Unit-Test Circle')
# locales
# if not skip_locales:
# locale_de = Locale.objects.get(language_code='de-CH')
# locale_fr, _ = Locale.objects.get_or_create(language_code='fr-CH')
# LocaleSynchronization.objects.get_or_create(
# locale_id=locale_fr.id,
# sync_from_id=locale_de.id
# )
# locale_it, _ = Locale.objects.get_or_create(language_code='it-CH')
# LocaleSynchronization.objects.get_or_create(
# locale_id=locale_it.id,
# sync_from_id=locale_de.id
# )
# call_command('sync_locale_trees')
# all pages belong to 'admin' by default
Page.objects.update(owner=user)

View File

@ -2,7 +2,7 @@ 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.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
from vbv_lernwelt.course.factories import CoursePageFactory
@ -15,7 +15,7 @@ def create_versicherungsvermittlerin_with_categories(apps=None, schema_editor=No
from vbv_lernwelt.course.models import Course, CourseCategory
course, _ = Course.objects.get_or_create(
id=COURSE_VERSICHERUNGSVERMITTLERIN,
id=COURSE_VERSICHERUNGSVERMITTLERIN_ID,
title='Versicherungsvermittler/in',
category_name='Handlungsfeld'
)

View File

@ -1,8 +1,21 @@
import djclick as click
from vbv_lernwelt.course.creators import create_versicherungsvermittlerin_with_categories
from vbv_lernwelt.competence.create_default_competence_profile import create_default_competence_profile
from vbv_lernwelt.course.creators.versicherungsvermittlerin import create_versicherungsvermittlerin_with_categories
from vbv_lernwelt.learnpath.create_default_learning_path import create_default_learning_path
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()
def command():
create_versicherungsvermittlerin_with_categories()
create_default_learning_path()
create_default_competence_profile()
# media library
create_default_collections()
create_default_documents()
create_default_media_library()

View File

@ -1,5 +1,6 @@
# Generated by Django 3.2.13 on 2022-09-27 14:26
# Generated by Django 3.2.13 on 2022-09-28 12:51
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
@ -9,6 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wagtailcore', '0069_log_entry_jsonfield'),
]
@ -28,13 +30,28 @@ class Migration(migrations.Migration):
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')),
('course', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='course.course')),
],
options={
'verbose_name': 'Lerngang-Seite',
},
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='CourseCompletion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('page_key', models.UUIDField()),
('page_type', models.CharField(blank=True, default='', max_length=255)),
('page_slug', models.CharField(blank=True, default='', max_length=255)),
('completion_status', models.CharField(choices=[('unknown', 'unknown'), ('success', 'success'), ('fail', 'fail')], default='unknown', max_length=255)),
('additional_json_data', models.JSONField(default=dict)),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.course')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='CourseCategory',
fields=[
@ -44,4 +61,8 @@ class Migration(migrations.Migration):
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.course')),
],
),
migrations.AddConstraint(
model_name='coursecompletion',
constraint=models.UniqueConstraint(fields=('user', 'page_key'), name='course_completion_unique_user_page_key'),
),
]

View File

@ -1,9 +1,11 @@
from django.db import models
from django.db.models import UniqueConstraint
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
from vbv_lernwelt.core.models import User
class Course(models.Model):
@ -30,7 +32,7 @@ class CourseCategory(models.Model):
class CoursePage(Page):
content_panels = Page.content_panels
subpage_types = ['learnpath.LearningPath', 'media_library.MediaLibraryPage']
course = models.ForeignKey('course.Course', on_delete=models.CASCADE)
course = models.ForeignKey('course.Course', on_delete=models.PROTECT)
class Meta:
verbose_name = _("Lerngang-Seite")
@ -41,3 +43,36 @@ class CoursePage(Page):
def __str__(self):
return f"{self.title}"
class CourseCompletion(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
# page can logically be a LearningContent or a PerformanceCriteria for now
page_key = models.UUIDField()
page_type = models.CharField(max_length=255, default='', blank=True)
page_slug = models.CharField(max_length=255, default='', blank=True)
course = models.ForeignKey('course.Course', on_delete=models.CASCADE)
completion_status = models.CharField(
max_length=255,
choices=[
('unknown', 'unknown'),
('success', 'success'),
('fail', 'fail'),
],
default='unknown',
)
additional_json_data = models.JSONField(default=dict)
class Meta:
constraints = [
UniqueConstraint(
fields=['user', 'page_key', ],
name='course_completion_unique_user_page_key'
)
]

View File

@ -1,6 +1,6 @@
from rest_framework import serializers
from vbv_lernwelt.course.models import CourseCategory, Course
from vbv_lernwelt.course.models import CourseCategory, Course, CourseCompletion
class CourseSerializer(serializers.ModelSerializer):
@ -13,3 +13,13 @@ class CourseCategorySerializer(serializers.ModelSerializer):
class Meta:
model = CourseCategory
fields = ['id', 'title', 'general',]
class CourseCompletionSerializer(serializers.ModelSerializer):
class Meta:
model = CourseCompletion
fields = [
'id', 'created_at', 'updated_at', 'user',
'page_key', 'page_type', 'page_slug',
'course', 'completion_status', 'additional_json_data',
]

View File

@ -6,10 +6,9 @@ from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.tests.helpers import create_locales_for_wagtail
from vbv_lernwelt.learnpath.models import LearningContent
from vbv_lernwelt.learnpath.tests.create_simple_test_learning_path import create_simple_test_learning_path
class CompletionApiTestCase(APITestCase):
class CourseCompletionApiTestCase(APITestCase):
def setUp(self) -> None:
create_locales_for_wagtail()
create_default_users()

View File

@ -3,6 +3,10 @@ from rest_framework.decorators import api_view
from rest_framework.response import Response
from wagtail.models import Page
from vbv_lernwelt.course.models import CourseCompletion, CoursePage
from vbv_lernwelt.course.serializers import CourseCompletionSerializer
from vbv_lernwelt.learnpath.utils import get_wagtail_type
logger = structlog.get_logger(__name__)
@ -16,3 +20,52 @@ def page_api_view(request, slug):
except Exception as e:
logger.error(e)
return Response({"error": str(e)}, status=404)
@api_view(['GET'])
def request_course_completion(request, course_id):
response_data = CourseCompletionSerializer(
CourseCompletion.objects.filter(user=request.user, circle_id=course_id),
many=True,
).data
return Response(status=200, data=response_data)
@api_view(['POST'])
def mark_course_completion(request):
page_key = request.data.get('page_key')
completion_status = request.data.get('completion_status', 'success')
page = Page.objects.get(translation_key=page_key, locale__language_code='de-CH')
page_type = get_wagtail_type(page.specific)
course = CoursePage.objects.ancestor_of(page).first().specific.course
cc, created = CourseCompletion.objects.get_or_create(
user=request.user,
page_key=page_key,
course_id=course.id,
)
cc.page_slug = page.slug
cc.page_type = page_type
cc.completion_status = completion_status
cc.save()
response_data = CourseCompletionSerializer(
CourseCompletion.objects.filter(user=request.user, circle_id=course_id),
many=True,
).data
logger.debug(
'mark_course_completion successful',
label='completion_api',
page_key=page_key,
page_type=page_type,
page_slug=page.slug,
page_title=page.title,
user_id=request.user.id,
course_id=course.id,
completion_status=completion_status,
)
return Response(status=200, data=response_data)

View File

@ -5,9 +5,8 @@ from wagtail.models import Site, Page, Locale
from wagtail_localize.models import LocaleSynchronization
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
from vbv_lernwelt.course.models import CoursePage, CourseCategory
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningContent, LearningUnit
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
LearningSequenceFactory, LearningContentFactory, VideoBlockFactory, ResourceBlockFactory, \
ExerciseBlockFactory, DocumentBlockFactory, LearningUnitFactory, AssignmentBlockFactory, BookBlockFactory, \
@ -62,7 +61,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Absicherung der Familie',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Einkommenssicherung')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Einkommenssicherung')
)
LearningContentFactory(
title='Ermittlung des Kundenbedarfs',
@ -134,7 +133,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Sich selbständig machen',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Selbständigkeit')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Selbständigkeit')
)
LearningContentFactory(
title='GmbH oder AG',
@ -152,7 +151,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Auto verkaufen',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Fahrzeug')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Fahrzeug')
)
LearningContentFactory(
title='Motorfahrzeugversicherung',
@ -182,7 +181,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Pensionierung',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Pensionierung')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Pensionierung')
)
LearningContentFactory(
title='3-Säulen-Prinzip',
@ -212,7 +211,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Reisen',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Reisen')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Reisen')
)
LearningContentFactory(
title='Reiseversicherung',
@ -231,7 +230,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Haushalt',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Haushalt')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Haushalt')
)
LearningContentFactory(
title='Privathaftpflicht',
@ -256,7 +255,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Kind zieht von zu Hause aus',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Einkommenssicherung')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Einkommenssicherung')
)
LearningContentFactory(
title='Hausrat',
@ -281,7 +280,7 @@ def create_circle_children(circle, title):
lu = LearningUnitFactory(
title='Kind zieht von zu Hause aus "Testen"',
parent=circle,
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN, title='Einkommenssicherung')
course_category=CourseCategory.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, title='Einkommenssicherung')
)
LearningContentFactory(
title='Das erwartet dich im Test',
@ -334,7 +333,7 @@ def create_default_learning_path(user=None, skip_locales=True):
# create_default_competences()
course_page = CoursePage.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN)
course_page = CoursePage.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
lp = LearningPathFactory(
title="Lernpfad",
parent=course_page,
@ -443,12 +442,3 @@ Neukundinnen und -kunden.""",
# all pages belong to 'admin' by default
Page.objects.update(owner=user)
def delete_default_learning_path():
LearningContent.objects.all().delete()
LearningUnit.objects.all().delete()
LearningSequence.objects.all().delete()
Circle.objects.all().delete()
Topic.objects.all().delete()
LearningPath.objects.all().delete()

View File

@ -1,10 +0,0 @@
import djclick as click
from vbv_lernwelt.learnpath.create_default_learning_path import create_default_learning_path
@click.command()
def command():
create_default_learning_path(skip_locales=True)
# FIXME: readd
# create_simple_test_learning_path(skip_locales=True)

View File

@ -1,8 +0,0 @@
import djclick as click
from vbv_lernwelt.learnpath.create_default_learning_path import delete_default_learning_path
@click.command()
def command():
delete_default_learning_path()

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.13 on 2022-09-28 10:30
# Generated by Django 3.2.13 on 2022-09-28 12:51
from django.db import migrations, models
import django.db.models.deletion

View File

@ -2,30 +2,27 @@ from rest_framework.test import APITestCase
from vbv_lernwelt.core.admin import User
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.course.creators.test_course import create_test_course
from vbv_lernwelt.learnpath.models import LearningPath
from vbv_lernwelt.learnpath.tests.create_simple_test_learning_path import create_simple_test_learning_path
class TestRetrieveLearingPathContents(APITestCase):
def setUp(self) -> None:
create_locales_for_wagtail()
create_default_users()
create_simple_test_learning_path()
create_test_course()
self.user = User.objects.get(username='student')
self.client.login(username='student', password='test')
def test_get_learnpathPage(self):
learning_path = LearningPath.objects.get(slug='unit-test-lernpfad')
response = self.client.get('/api/course/page/unit-test-lernpfad/')
print(response)
slug = 'test-lehrgang-lp'
learning_path = LearningPath.objects.get(slug=slug)
response = self.client.get(f'/api/course/page/{slug}/')
self.assertEqual(response.status_code, 200)
data = response.json()
# print(data)
self.assertEqual(learning_path.title, data['title'])
# topic and circle
# topics and circles
self.assertEqual(4, len(data['children']))
# circle "unit-test-circle" contents
self.assertEqual(13, len(data['children'][3]['children']))
# circle "analyse" contents
self.assertEqual(12, len(data['children'][3]['children']))

View File

@ -1,14 +1,14 @@
import json
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
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)
course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
course_page = CoursePage.objects.get(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
media_lib_page = MediaLibraryPageFactory(
title='Mediathek',

View File

@ -1,11 +0,0 @@
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_media_library import create_default_media_library
@click.command()
def command():
create_default_collections()
create_default_documents()
create_default_media_library()

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.13 on 2022-09-28 10:30
# Generated by Django 3.2.13 on 2022-09-28 12:51
from django.conf import settings
from django.db import migrations, models
@ -18,8 +18,8 @@ class Migration(migrations.Migration):
dependencies = [
('course', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wagtailcore', '0069_log_entry_jsonfield'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('taggit', '0004_alter_taggeditem_content_type_alter_taggeditem_tag'),
]