Working serialization

This commit is contained in:
Daniel Egger 2022-06-01 22:01:03 +02:00
parent 4a2d049969
commit f480665537
13 changed files with 135 additions and 150 deletions

View File

@ -18,9 +18,9 @@ from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
from vbv_lernwelt.core.views import ( from vbv_lernwelt.core.views import (
rate_limit_exceeded_view, rate_limit_exceeded_view,
permission_denied_view, permission_denied_view,
check_rate_limit, vue_home, cypress_reset_view, check_rate_limit, cypress_reset_view,
) )
from .wagtail_api import api_router from .wagtail_api import wagtail_api_router
def raise_example_error(request): def raise_example_error(request):
@ -42,6 +42,7 @@ urlpatterns = [
path('cms/', include(wagtailadmin_urls)), path('cms/', include(wagtailadmin_urls)),
path('documents/', include(wagtaildocs_urls)), path('documents/', include(wagtaildocs_urls)),
path('pages/', include(wagtail_urls)), path('pages/', include(wagtail_urls)),
path('learnpath/', include("vbv_lernwelt.learnpath.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG: if settings.DEBUG:
# Static file serving when using Gunicorn + Uvicorn for local web socket development # Static file serving when using Gunicorn + Uvicorn for local web socket development
@ -56,7 +57,7 @@ if settings.ALLOW_LOCAL_LOGIN:
urlpatterns += [ urlpatterns += [
# API base url # API base url
path("api/", include("config.api_router")), path("api/", include("config.api_router")),
path('wagtailapi/v2/', api_router.urls), path('wagtailapi/v2/', wagtail_api_router.urls),
# DRF auth token # DRF auth token
path("auth-token/", obtain_auth_token), path("auth-token/", obtain_auth_token),
@ -111,5 +112,5 @@ if settings.DEBUG:
# serve everything else via the vue app # serve everything else via the vue app
urlpatterns += [re_path(r'^.*$', vue_home, name='home')] # urlpatterns += [re_path(r'^.*$', vue_home, name='home')]

View File

@ -1,16 +1,16 @@
from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.api.v2.router import WagtailAPIRouter from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.images.api.v2.views import ImagesAPIViewSet from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.documents.api.v2.views import DocumentsAPIViewSet from wagtail.documents.api.v2.views import DocumentsAPIViewSet
from wagtail.images.api.v2.views import ImagesAPIViewSet
# Create the router. "wagtailapi" is the URL namespace # Create the router. "wagtailapi" is the URL namespace
api_router = WagtailAPIRouter('wagtailapi') wagtail_api_router = WagtailAPIRouter('wagtailapi')
# Add the three endpoints using the "register_endpoint" method. # Add the three endpoints using the "register_endpoint" method.
# The first parameter is the name of the endpoint (eg. pages, images). This # The first parameter is the name of the endpoint (eg. pages, images). This
# is used in the URL of the endpoint # is used in the URL of the endpoint
# The second parameter is the endpoint class that handles the requests # The second parameter is the endpoint class that handles the requests
api_router.register_endpoint('pages', PagesAPIViewSet) wagtail_api_router.register_endpoint('pages', PagesAPIViewSet)
api_router.register_endpoint('images', ImagesAPIViewSet) wagtail_api_router.register_endpoint('images', ImagesAPIViewSet)
api_router.register_endpoint('documents', DocumentsAPIViewSet) wagtail_api_router.register_endpoint('documents', DocumentsAPIViewSet)

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2022-06-01 13:32 # Generated by Django 3.2.12 on 2022-06-01 15:39
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -16,19 +16,6 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Circle',
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')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('description', models.TextField(blank=True, default='')),
('goals', models.TextField(blank=True, default='')),
],
options={
'verbose_name': 'Circle',
},
bases=('wagtailcore.page', models.Model),
),
migrations.CreateModel( migrations.CreateModel(
name='Competence', name='Competence',
fields=[ fields=[
@ -64,14 +51,23 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='LearningSequence', name='LearningSequence',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('title', models.CharField(default='', max_length=256)),
('circle', modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_sequences', to='learnpath.circle')),
], ],
options={ options={
'verbose_name': 'Learning Sequence', 'verbose_name': 'Learning Sequence',
}, },
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='LearningUnit',
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')),
('contents', wagtail.core.fields.StreamField([('web_based_training', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('video', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True)),
],
options={
'verbose_name': 'Learning Unit',
},
bases=('wagtailcore.page',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Topic', name='Topic',
@ -86,19 +82,6 @@ class Migration(migrations.Migration):
'verbose_name': 'Topic', 'verbose_name': 'Topic',
}, },
), ),
migrations.CreateModel(
name='LearningUnit',
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')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('contents', wagtail.core.fields.StreamField([('web_based_training', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('video', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True)),
('learning_sequence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='learning_units', to='learnpath.learningsequence')),
],
options={
'verbose_name': 'Learning Unit',
},
bases=('wagtailcore.page', models.Model),
),
migrations.CreateModel( migrations.CreateModel(
name='FullfillmentCriteria', name='FullfillmentCriteria',
fields=[ fields=[
@ -116,9 +99,17 @@ class Migration(migrations.Migration):
name='competence_page', name='competence_page',
field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='competences', to='learnpath.competencepage'), field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='competences', to='learnpath.competencepage'),
), ),
migrations.AddField( migrations.CreateModel(
model_name='circle', name='Circle',
name='topic', fields=[
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='circles', to='learnpath.topic'), ('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')),
('description', models.TextField(blank=True, default='')),
('goals', models.TextField(blank=True, default='')),
('topic', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='circles', to='learnpath.topic')),
],
options={
'verbose_name': 'Circle',
},
bases=('wagtailcore.page',),
), ),
] ]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.12 on 2022-06-01 14:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='learningunit',
name='sort_order',
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.12 on 2022-06-01 14:40
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0002_remove_learningunit_sort_order'),
]
operations = [
migrations.RemoveField(
model_name='circle',
name='sort_order',
),
]

View File

@ -6,10 +6,10 @@ from wagtail.api import APIField
from wagtail.core.blocks import StreamBlock from wagtail.core.blocks import StreamBlock
from wagtail.core.fields import StreamField from wagtail.core.fields import StreamField
from wagtail.core.models import Page, Orderable from wagtail.core.models import Page, Orderable
from wagtail.snippets.models import register_snippet
from vbv_lernwelt.learnpath.models_competences import * from vbv_lernwelt.learnpath.models_competences import *
from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
@register_query_field("learning_path") @register_query_field("learning_path")
@ -22,7 +22,6 @@ class LearningPath(Page):
subpage_types = ['learnpath.Circle'] subpage_types = ['learnpath.Circle']
class Meta: class Meta:
verbose_name = "Learning Path" verbose_name = "Learning Path"
@ -83,23 +82,20 @@ class Circle(Page):
) )
parent_page_types = ['learnpath.LearningPath'] parent_page_types = ['learnpath.LearningPath']
subpage_types = ['learnpath.LearningUnit'] subpage_types = ['learnpath.LearningSequence', 'learnpath.LearningUnit']
content_panels = Page.content_panels + [ content_panels = Page.content_panels + [
FieldPanel('description'), FieldPanel('description'),
FieldPanel('goals'), FieldPanel('goals'),
InlinePanel('learning_sequences', label="Learning Sequences"),
] ]
api_fields = [ api_fields = [
APIField('title'), APIField('title'),
APIField('description'), APIField('description'),
APIField('topic'),
APIField('learning_sequences'),
] ]
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(Circle, slugify(self.title, allow_unicode=True)) # self.slug = find_available_slug(Circle, slugify(self.title, allow_unicode=True))
super(Circle, self).full_clean(*args, **kwargs) super(Circle, self).full_clean(*args, **kwargs)
class Meta: class Meta:
@ -109,33 +105,21 @@ class Circle(Page):
return f"{self.title}" return f"{self.title}"
@register_snippet class LearningSequence(Page):
class LearningSequence(Orderable): parent_page_types = ['learnpath.Circle']
# TODO: How to do a icon choice field?
title = models.CharField(max_length=256, default='')
circle = ParentalKey(
'learnpath.Circle',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='learning_sequences',
)
panels = [FieldPanel('title'), FieldPanel('circle')] panels = [FieldPanel('title'), FieldPanel('circle')]
api_fields = [
APIField('title'),
APIField('category'),
APIField('learning_packages'),
]
class Meta: class Meta:
verbose_name = "Learning Sequence" verbose_name = "Learning Sequence"
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
@classmethod
def get_serializer_class(cls):
return get_it_serializer_class(cls, field_names=['id', 'title', 'slug', 'type'])
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
super(LearningSequence, self).full_clean(*args, **kwargs) super(LearningSequence, self).full_clean(*args, **kwargs)
@ -146,14 +130,7 @@ class LearningUnit(Page):
""" """
# TODO: Review model architecture, is the stream field the right thing here? # TODO: Review model architecture, is the stream field the right thing here?
parent_page_types = ['learnpath.Circle'] parent_page_types = ['learnpath.LearningSequence']
learning_sequence = models.ForeignKey(
'learnpath.LearningSequence',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='learning_units',
)
content_blocks = [ content_blocks = [
('web_based_training', WebBasedTrainingBlock()), ('web_based_training', WebBasedTrainingBlock()),
@ -166,14 +143,13 @@ class LearningUnit(Page):
content_panels = [ content_panels = [
FieldPanel('title', classname="full title"), FieldPanel('title', classname="full title"),
FieldPanel('learning_sequence'), # FieldPanel('learning_sequence'),
StreamFieldPanel('contents'), StreamFieldPanel('contents'),
] ]
api_fields = [ api_fields = [
APIField('title'), APIField('title'),
APIField('contents'), APIField('contents'),
APIField('content_blocks'),
] ]
subpage_types = [] subpage_types = []
@ -185,6 +161,10 @@ class LearningUnit(Page):
self.slug = find_available_slug(LearningUnit, slugify(self.title, allow_unicode=True)) self.slug = find_available_slug(LearningUnit, slugify(self.title, allow_unicode=True))
super(LearningUnit, self).full_clean(*args, **kwargs) super(LearningUnit, self).full_clean(*args, **kwargs)
@classmethod
def get_serializer_class(cls):
return get_it_serializer_class(cls, field_names=['id', 'title', 'contents', 'slug', 'type'])
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"

View File

@ -11,9 +11,6 @@ class VideoBlock(blocks.StructBlock):
class Meta: class Meta:
icon = 'media' icon = 'media'
def get_api_representation(self, value, context=None):
return {'sdfsdf': 1,
'sldkfm': 3}
RISE = 'rise' RISE = 'rise'
@ -32,8 +29,3 @@ class WebBasedTrainingBlock(blocks.StructBlock):
class Meta: class Meta:
icon = 'media' icon = 'media'
def get_api_representation(self, value, context=None):
return {'sdfsdf': 1,
'sldkfm': 3}

View File

@ -0,0 +1,15 @@
import wagtail.api.v2.serializers as wagtail_serializers
def get_it_serializer_class(model, field_names):
return wagtail_serializers.get_serializer_class(model, field_names=field_names, meta_fields=[], base=ItBaseSerializer)
class ItTypeField(wagtail_serializers.TypeField):
def to_representation(self, obj):
name = type(obj)._meta.app_label + '.' + type(obj).__name__
return name
class ItBaseSerializer(wagtail_serializers.BaseSerializer):
type = ItTypeField(read_only=True)

View File

@ -0,0 +1,20 @@
from rest_framework import serializers
from vbv_lernwelt.learnpath.models import Circle
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
class CircleSerializer(get_it_serializer_class(Circle, [])):
children = serializers.SerializerMethodField()
meta_fields = []
def get_children(self, obj):
return [c.specific.get_serializer_class()(c.specific).data for c in obj.get_children()]
def get_meta_label(self, obj):
return obj._meta.label
class Meta:
model = Circle
fields = ['id', 'title', 'slug', 'children', 'type']

View File

@ -62,10 +62,10 @@ von Neukunden zu benützen
Lösungsvorschläge zu skizzieren und Lösungsvorschläge zu skizzieren und
zu visualisieren""") zu visualisieren""")
sequence_1 = LearningSequenceFactory(title="Starten", circle=circle_4) sequence_1 = LearningSequenceFactory(title="Starten", parent=circle_4)
learning_unit = LearningUnitFactory(title='Einleitung Circle "Anlayse"', parent=circle_4, learning_sequence=sequence_1) learning_unit = LearningUnitFactory(title='Einleitung Circle "Anlayse"', parent=circle_4)
learning_unit = LearningUnitFactory.create(title='** Einstieg Video"', parent=circle_4, learning_sequence=sequence_1) learning_unit = LearningUnitFactory.create(title='** Einstieg Video"', parent=circle_4)
video_url = "https://www.vbv.ch/fileadmin/vbv/Videos/Statements_Externe/Janos_M/Testimonial_Janos_Mischler_PositiveEffekte.mp4" video_url = "https://www.vbv.ch/fileadmin/vbv/Videos/Statements_Externe/Janos_M/Testimonial_Janos_Mischler_PositiveEffekte.mp4"
video_title = "Ausbildung ist pflicht" video_title = "Ausbildung ist pflicht"
video_description = "Erfahren Sie, was für Janos Mischler die positiven Aspekte von ständiger Weiterbildung sind aus fachlicher und aus persönlicher Sicht." video_description = "Erfahren Sie, was für Janos Mischler die positiven Aspekte von ständiger Weiterbildung sind aus fachlicher und aus persönlicher Sicht."
@ -73,38 +73,38 @@ von Neukunden zu benützen
learning_unit.contents.append(('video', video_block)) learning_unit.contents.append(('video', video_block))
learning_unit.save() learning_unit.save()
learning_unit = LearningUnitFactory.create(title='** Web Based Training"', parent=circle_4, learning_sequence=sequence_1) learning_unit = LearningUnitFactory.create(title='** Web Based Training"', parent=circle_4)
wbt_url = "web_based_trainings/rise_cmi5_test_export/scormcontent/index.html" wbt_url = "web_based_trainings/rise_cmi5_test_export/scormcontent/index.html"
wbt_block = WebBasedTrainingBlockFactory(content_type="web_based_training", url=wbt_url) wbt_block = WebBasedTrainingBlockFactory(content_type="web_based_training", url=wbt_url)
learning_unit.contents.append(('web_based_training', wbt_block)) learning_unit.contents.append(('web_based_training', wbt_block))
learning_unit.save() learning_unit.save()
learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4, learning_sequence=sequence_1) learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4)
sequence_2 = LearningSequenceFactory.create(title="Beobachten", circle=circle_4) sequence_2 = LearningSequenceFactory.create(title="Beobachten", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Mein Motorfahrzeug kaufen", parent=circle_4, learning_sequence=sequence_2) learning_unit = LearningUnitFactory.create(title="Mein Motorfahrzeug kaufen", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Sich selbständig machen", parent=circle_4, learning_sequence=sequence_2) learning_unit = LearningUnitFactory.create(title="Sich selbständig machen", parent=circle_4)
sequence_3 = LearningSequenceFactory.create(title="Anwenden", circle=circle_4) sequence_3 = LearningSequenceFactory.create(title="Anwenden", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Nora kauft sich ein neues Auto", parent=circle_4, learning_sequence=sequence_3) learning_unit = LearningUnitFactory.create(title="Nora kauft sich ein neues Auto", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Manuel träumt von einem neuen Tesla", parent=circle_4, learning_sequence=sequence_3) learning_unit = LearningUnitFactory.create(title="Manuel träumt von einem neuen Tesla", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Deine Erkenntnisse und Learnings", parent=circle_4, learning_sequence=sequence_3) learning_unit = LearningUnitFactory.create(title="Deine Erkenntnisse und Learnings", parent=circle_4)
sequence_4 = LearningSequenceFactory.create(title="Üben", circle=circle_4) sequence_4 = LearningSequenceFactory.create(title="Üben", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Ermittlung des Kundenbedarfs", parent=circle_4, learning_sequence=sequence_4) learning_unit = LearningUnitFactory.create(title="Ermittlung des Kundenbedarfs", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Aktives Zuhören", parent=circle_4, learning_sequence=sequence_4) learning_unit = LearningUnitFactory.create(title="Aktives Zuhören", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="In Bildern Sprechen", parent=circle_4, learning_sequence=sequence_4) learning_unit = LearningUnitFactory.create(title="In Bildern Sprechen", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Priorisieren des Bedarfs", parent=circle_4, learning_sequence=sequence_4) learning_unit = LearningUnitFactory.create(title="Priorisieren des Bedarfs", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Zusammenfassung des Bedarfs", parent=circle_4, learning_sequence=sequence_4) learning_unit = LearningUnitFactory.create(title="Zusammenfassung des Bedarfs", parent=circle_4)
sequence_5 = LearningSequenceFactory.create(title="Testen", circle=circle_4) sequence_5 = LearningSequenceFactory.create(title="Testen", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Bedarfsfragen", parent=circle_4, learning_sequence=sequence_5) learning_unit = LearningUnitFactory.create(title="Bedarfsfragen", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Andwendung der Fragetechniken", parent=circle_4, learning_sequence=sequence_5) learning_unit = LearningUnitFactory.create(title="Andwendung der Fragetechniken", parent=circle_4)
sequence_5 = LearningSequenceFactory.create(title="Vernetzen", circle=circle_4) sequence_5 = LearningSequenceFactory.create(title="Vernetzen", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Online Training", parent=circle_4, learning_sequence=sequence_5) learning_unit = LearningUnitFactory.create(title="Online Training", parent=circle_4)
sequence_6 = LearningSequenceFactory.create(title="Beenden", circle=circle_4) sequence_6 = LearningSequenceFactory.create(title="Beenden", parent=circle_4)
learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4, learning_sequence=sequence_6) learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4)
circle_5 = CircleFactory.create(title="Lösung", circle_5 = CircleFactory.create(title="Lösung",
parent=lp, parent=lp,

View File

@ -27,7 +27,7 @@ class CircleFactory(wagtail_factories.PageFactory):
model = Circle model = Circle
class LearningSequenceFactory(factory.django.DjangoModelFactory): class LearningSequenceFactory(wagtail_factories.PageFactory):
title = "Grundlagen" title = "Grundlagen"
class Meta: class Meta:

View File

@ -0,0 +1,10 @@
from django.conf.urls import url, include
from django.urls import path
from rest_framework.routers import DefaultRouter
from . import views
from .views import circle_view
urlpatterns = [
path(r"api/circle/<slug:slug>/", circle_view, name="circle_view"),
]

View File

@ -1,3 +1,13 @@
from django.shortcuts import render
# Create your views here. # Create your views here.
from rest_framework.decorators import api_view
from rest_framework.response import Response
from vbv_lernwelt.learnpath.models import Circle
from vbv_lernwelt.learnpath.serializers import CircleSerializer
@api_view(['GET'])
def circle_view(request, slug):
circle = Circle.objects.get(slug=slug)
serializer = CircleSerializer(circle)
return Response(serializer.data)