diff --git a/server/vbv_lernwelt/learnpath/migrations/0001_initial.py b/server/vbv_lernwelt/learnpath/migrations/0001_initial.py index b1abb799..6b3a574a 100644 --- a/server/vbv_lernwelt/learnpath/migrations/0001_initial.py +++ b/server/vbv_lernwelt/learnpath/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.12 on 2022-04-26 12:15 +# Generated by Django 3.2.12 on 2022-05-04 15:52 from django.db import migrations, models import django.db.models.deletion @@ -20,12 +20,35 @@ class Migration(migrations.Migration): 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( + 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( @@ -39,15 +62,17 @@ class Migration(migrations.Migration): bases=('wagtailcore.page',), ), migrations.CreateModel( - name='LearningUnit', + name='LearningSequence', 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)), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), + ('title', models.CharField(default='', max_length=256)), + ('category', models.CharField(choices=[('INCIRCLE', 'In Circle'), ('START', 'Start'), ('END', 'End')], default='INCIRCLE', max_length=16)), + ('circle', modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_sequences', to='learnpath.circle')), ], options={ - 'verbose_name': 'Learning Unit', + 'verbose_name': 'Learning Sequence', }, - bases=('wagtailcore.page',), ), migrations.CreateModel( name='Topic', @@ -63,17 +88,33 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='LearningSequence', + name='LearningUnit', 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)), - ('category', models.CharField(choices=[('INCIRCLE', 'In Circle'), ('START', 'Start'), ('END', 'End')], default='INCIRCLE', max_length=16)), - ('circle', modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_sequences', to='learnpath.circle')), + ('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.CASCADE, related_name='learning_units', to='learnpath.learningsequence')), ], options={ - 'verbose_name': 'Learning Sequence', + 'verbose_name': 'Learning Unit', }, + bases=('wagtailcore.page', models.Model), + ), + migrations.CreateModel( + name='FullfillmentCriteria', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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'), ), migrations.AddField( model_name='circle', diff --git a/server/vbv_lernwelt/learnpath/migrations/0002_auto_20220427_1058.py b/server/vbv_lernwelt/learnpath/migrations/0002_fullfillmentcriteria_sort_order.py similarity index 54% rename from server/vbv_lernwelt/learnpath/migrations/0002_auto_20220427_1058.py rename to server/vbv_lernwelt/learnpath/migrations/0002_fullfillmentcriteria_sort_order.py index 44e8a3ee..5fe74a62 100644 --- a/server/vbv_lernwelt/learnpath/migrations/0002_auto_20220427_1058.py +++ b/server/vbv_lernwelt/learnpath/migrations/0002_fullfillmentcriteria_sort_order.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.12 on 2022-04-27 08:58 +# Generated by Django 3.2.12 on 2022-05-04 16:00 from django.db import migrations, models @@ -11,12 +11,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( - model_name='circle', - name='sort_order', - field=models.IntegerField(blank=True, editable=False, null=True), - ), - migrations.AddField( - model_name='learningunit', + model_name='fullfillmentcriteria', name='sort_order', field=models.IntegerField(blank=True, editable=False, null=True), ), diff --git a/server/vbv_lernwelt/learnpath/migrations/0003_learningunit_learning_sequence.py b/server/vbv_lernwelt/learnpath/migrations/0003_learningunit_learning_sequence.py deleted file mode 100644 index af75d54f..00000000 --- a/server/vbv_lernwelt/learnpath/migrations/0003_learningunit_learning_sequence.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.2.12 on 2022-05-03 11:03 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('learnpath', '0002_auto_20220427_1058'), - ] - - operations = [ - migrations.AddField( - model_name='learningunit', - name='learning_sequence', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='circles', to='learnpath.learningsequence'), - ), - ] diff --git a/server/vbv_lernwelt/learnpath/migrations/0004_alter_learningunit_learning_sequence.py b/server/vbv_lernwelt/learnpath/migrations/0004_alter_learningunit_learning_sequence.py deleted file mode 100644 index 385e7a36..00000000 --- a/server/vbv_lernwelt/learnpath/migrations/0004_alter_learningunit_learning_sequence.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.2.12 on 2022-05-03 11:22 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('learnpath', '0003_learningunit_learning_sequence'), - ] - - operations = [ - migrations.AlterField( - model_name='learningunit', - name='learning_sequence', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_units', to='learnpath.learningsequence'), - ), - ] diff --git a/server/vbv_lernwelt/learnpath/models.py b/server/vbv_lernwelt/learnpath/models.py index bd890d39..34b8bd28 100644 --- a/server/vbv_lernwelt/learnpath/models.py +++ b/server/vbv_lernwelt/learnpath/models.py @@ -9,7 +9,9 @@ from wagtail.core.models import Page, Orderable from django.utils.text import slugify + from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock +from vbv_lernwelt.learnpath.models_competences import * class LearningPath(Page): @@ -119,6 +121,7 @@ class LearningSequence(Orderable): on_delete=models.CASCADE, related_name='learning_sequences', ) + icon = "" panels = [FieldPanel('title'), FieldPanel('category')] @@ -130,14 +133,14 @@ class LearningSequence(Orderable): class LearningUnit(Page, Orderable): - # TODO: Review model architecture, is the stream fiel the right thing here? + # TODO: Review model architecture, is the stream field the right thing here? parent_page_types = ['learnpath.Circle'] learning_sequence = models.ForeignKey( 'learnpath.LearningSequence', null=True, blank=True, on_delete=models.CASCADE, - related_name='learning_units' + related_name='learning_units', ) content_blocks = [ @@ -150,8 +153,8 @@ class LearningUnit(Page, Orderable): content_panels = [ FieldPanel('title', classname="full title"), + FieldPanel('learning_sequence'), StreamFieldPanel('contents'), - FieldPanel('learning_sequence') ] subpage_types = [] diff --git a/server/vbv_lernwelt/learnpath/models_competences.py b/server/vbv_lernwelt/learnpath/models_competences.py new file mode 100644 index 00000000..f7d70107 --- /dev/null +++ b/server/vbv_lernwelt/learnpath/models_competences.py @@ -0,0 +1,66 @@ +from django.db import models +from wagtail.core.models import Page, Orderable +from modelcluster.fields import ParentalKey +from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel, InlinePanel + + +class CompetencePage(Page): + """This is the page where the competences and Fullfillment criterias are manged + For one Learning Path""" + + + content_panels = Page.content_panels + [ + InlinePanel('competences', label="Competences"), + ] + + subpage_types = ['learnpath.Circle'] + + parent_page_types = ['learnpath.LearningPath'] + + + class Meta: + verbose_name = "Learning Path" + + def __str__(self): + return f"{self.title}" + + +class Competence(Orderable): + """ In VBV Terms this is a "Handlungskompetenz""" + category_short = models.CharField(max_length=3, default='') + name = models.CharField(max_length=2048) + + + competence_page = ParentalKey('learnpath.CompetencePage', + null=True, + blank=True, + on_delete=models.CASCADE, + related_name='competences', + ) + + + + def get_short_info(self): + return f"{self.category_short}{self.sort_order}" + + def __str__(self): + return f"{self.get_short_info()}: {self.name}" + + + class Meta: + verbose_name = "Competence" + + +class FullfillmentCriteria(Orderable): + """ VBV Term Leistungskriterium""" + name = models.CharField(max_length=2048) + competence = models.ForeignKey(Competence, on_delete=models.CASCADE, null=True) + + def get_short_info(self): + return f"{self.competence.get_short_info()}.{self.sort_order}" + + def __str__(self): + return f"{self.get_short_info()}: {self.name}" + + class Meta: + verbose_name = "Fullfillment Criteria" diff --git a/server/vbv_lernwelt/learnpath/models_learning_unit_content.py b/server/vbv_lernwelt/learnpath/models_learning_unit_content.py index 9e14e920..9a1277e7 100644 --- a/server/vbv_lernwelt/learnpath/models_learning_unit_content.py +++ b/server/vbv_lernwelt/learnpath/models_learning_unit_content.py @@ -31,3 +31,11 @@ class WebBasedTrainingBlock(blocks.StructBlock): class Meta: icon = 'media' + +# 'Transver Task' +class TranverTaskBlock(blocks.StructBlock): + title = models.CharField(max_length=128, default="") + description = models.TextField(default="") + + class Meta: + icon = 'media' diff --git a/server/vbv_lernwelt/learnpath/tests/competences.json b/server/vbv_lernwelt/learnpath/tests/competences.json new file mode 100644 index 00000000..b50e04e5 --- /dev/null +++ b/server/vbv_lernwelt/learnpath/tests/competences.json @@ -0,0 +1,78 @@ +{ + "competences": [ + { + "name": "Weiterempfehlung für Neukunden generieren", + "category_short": "A", + "fullfillment_criteria": [ + { + "name": "bestehende Kunden so zu beraten, dass sie von diesen weiterempfohlen werden" + }, + { + "name": "geeignete Personen wie z.B. Garagisten, Architekten, Treuhänder auf die Vermittlung/Zusammenarbeit anzusprechen" + }, + { + "name": "verschiedene Datenquellen wie Internet, Telefonbuch, Handelszeitung, Baugesuche etc. gezielt für die Gewinnung von Neukunden zu benützen" + }, + { + "name": "ein beliebiges Gespräch resp. einen bestehenden Kontakt in die Richtung «Versicherung» zu lenken" + }, + { + "name": "das Thema Risiko und Sicherheit in einem Gespräch gezielt und auf die Situation des jeweiligen Gesprächspartners bezogen einfliessen zu lassen" + }, + { + "name": "im täglichen Kontakt potentielle Kundinnen und Kunden zu erkennen" + } + ] + }, + { + "name": "Kundengespräche vereinbaren", + "category_short": "A", + "fullfillment_criteria": [ + { + "name": "je nach (Neu-) Kunde Form und Ort für das Gespräch festzulegen" + }, + { + "name": "sich intern und extern die nötigen Informationen über den (Neu-) Kunde zu beschaffen" + }, + { + "name": "die Terminierung auf ein bestimmtes Thema wie z.B. Rechtsschutz, Vorsorge, Krankenversicherung etc. auszurichten" + }, + { + "name": "für das zu führende Gespräch eine Agenda zu erstellen" + }, + { + "name": "für das zu führende Gespräch geeignete Hilfsmittel und Unterlagen zusammenzustellen" + }, { + "name": "eine Kaltakquise durchzuführen und auf mögliche Einwände reagieren zu können" + } +] + + }, + { + "name": "Auftritt in den sozialen Medien zeitgemäss halten", + "category_short": "A", + "fullfillment_criteria": [ { + "name": "in Zusammenarbeit mit den IT-Spezialisten und der Marketingabteilung die Inhalte für den zu realisierenden Medienauftritt zielgruppengerecht festzulegen" + }, + { + "name": "für die verschiedenen Kundensegmente die passenden sozialen Medien zu definieren" + }, + { + "name": "die Inhalte compliant zu halten" + } + ] + }, + { + "name": "Kundendaten erfassen", + "category_short": "A", + "fullfillment_criteria": [] + }, + { + "name": "Wünsche, Ziele und Bedürfnisse der Kunden im Gespräch ermitteln", + "category_short": "B", + "fullfillment_criteria": [] + } + ] +} + + diff --git a/server/vbv_lernwelt/learnpath/tests/competences_factories.py b/server/vbv_lernwelt/learnpath/tests/competences_factories.py new file mode 100644 index 00000000..6a043add --- /dev/null +++ b/server/vbv_lernwelt/learnpath/tests/competences_factories.py @@ -0,0 +1,30 @@ +import factory +import wagtail_factories + +from vbv_lernwelt.learnpath.models_competences import Competence, FullfillmentCriteria, CompetencePage +from vbv_lernwelt.learnpath.tests.learningpath_factories import LearningPathFactory + + +class CompetencePageFactory(wagtail_factories.PageFactory): + # learning_path = factory.SubFactory(LearningPathFactory) + + class Meta: + model = CompetencePage + + +class CompetenceFactory(factory.django.DjangoModelFactory): + category_short = 'A' + name = "Weiterempfehung für neukunden generieren" + competence_page = factory.SubFactory(CompetencePageFactory) + + class Meta: + model = Competence + + +class FullfilmentCriteriaFactory(factory.django.DjangoModelFactory): + name = 'Bestehende Kunden so zu beraten, dass sie von diesen weiterempfohlen werden' + competence = factory.SubFactory(CompetenceFactory) + + class Meta: + model = FullfillmentCriteria + diff --git a/server/vbv_lernwelt/learnpath/tests/create_default_competences.py b/server/vbv_lernwelt/learnpath/tests/create_default_competences.py new file mode 100644 index 00000000..9a8716ae --- /dev/null +++ b/server/vbv_lernwelt/learnpath/tests/create_default_competences.py @@ -0,0 +1,23 @@ +import json + + +import os.path + +from vbv_lernwelt.learnpath.tests.competences_factories import CompetenceFactory, FullfilmentCriteriaFactory + +competences_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'competences.json') + + +def create_default_competences(competences_json=competences_file): + with open(competences_json) as f: + competences_json = json.load(f) + + for index, compentence in enumerate(competences_json['competences']): + + competence_model = CompetenceFactory(name=compentence['name'], category_short=compentence['category_short'], sort_order=index) + print(competence_model) + + for criteria_index, criteria in enumerate(compentence['fullfillment_criteria']): + criteria_model = FullfilmentCriteriaFactory(name=criteria['name'], competence=competence_model, sort_order=criteria_index) + print(criteria_model) + diff --git a/server/vbv_lernwelt/learnpath/tests/create_default_learning_path.py b/server/vbv_lernwelt/learnpath/tests/create_default_learning_path.py index 3358f9ff..82df2b20 100644 --- a/server/vbv_lernwelt/learnpath/tests/create_default_learning_path.py +++ b/server/vbv_lernwelt/learnpath/tests/create_default_learning_path.py @@ -2,16 +2,22 @@ import wagtail_factories from wagtail.core.models import Site from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningUnit +from vbv_lernwelt.learnpath.tests.create_default_competences import create_default_competences from vbv_lernwelt.learnpath.tests.learningpath_factories import LearningPathFactory, TopicFactory, CircleFactory, \ LearningSequenceFactory, LearningUnitFactory, VideoBlockFactory, WebBasedTrainingBlockFactory + + + def create_default_learning_path(): site = Site.objects.filter(is_default_site=True).first() if not site: site = wagtail_factories.SiteFactory(is_default_site=True) + create_default_competences() + lp = LearningPathFactory(title="Versicherungsvermittler/in", parent=site.root_page) tp = TopicFactory(title="Basis", is_visible=False, learning_path=lp) @@ -129,6 +135,9 @@ von Neukunden zu benützen circle_7 = CircleFactory.create(title="Prüfungsvorbereitung", parent=lp, topic=tp) + + + def delete_default_learning_path(): LearningUnit.objects.all().delete() LearningSequence.objects.all().delete() diff --git a/server/vbv_lernwelt/learnpath/tests/test_competences_factories.py b/server/vbv_lernwelt/learnpath/tests/test_competences_factories.py new file mode 100644 index 00000000..baf72c70 --- /dev/null +++ b/server/vbv_lernwelt/learnpath/tests/test_competences_factories.py @@ -0,0 +1,18 @@ +from django.test import TestCase + +from vbv_lernwelt.learnpath.models_competences import Competence, FullfillmentCriteria +from vbv_lernwelt.learnpath.tests.competences_factories import CompetencePageFactory, CompetenceFactory, \ + FullfilmentCriteriaFactory + + +class TestCompetencesFactories(TestCase): + def test_create_competences_page(self): + CompetencePageFactory() + + def test_create_competence(self): + CompetenceFactory(name='Boogie Woogie') + self.assertEqual(Competence.objects.filter(name='Boogie Woogie').count(), 1) + + def test_create_fullfillment_criteria(self): + FullfilmentCriteriaFactory(name='shuffle like ...') + self.assertEqual(FullfillmentCriteria.objects.filter(name='shuffle like ...').count(), 1) diff --git a/server/vbv_lernwelt/learnpath/tests/test_create_default_competences.py b/server/vbv_lernwelt/learnpath/tests/test_create_default_competences.py new file mode 100644 index 00000000..714dc179 --- /dev/null +++ b/server/vbv_lernwelt/learnpath/tests/test_create_default_competences.py @@ -0,0 +1,13 @@ +from django.conf import settings +from django.test import TestCase +from wagtail.core.models import Locale + +from vbv_lernwelt.learnpath.models import LearningPath +from vbv_lernwelt.learnpath.tests.create_default_competences import create_default_competences +from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path + + +class TestCreateDefaultCompetences(TestCase): + def test_create_default_competeneces(self): + create_default_competences() + diff --git a/server/vbv_lernwelt/learnpath/tests/test_whatever.py b/server/vbv_lernwelt/learnpath/tests/test_whatever.py deleted file mode 100644 index ae8c47b6..00000000 --- a/server/vbv_lernwelt/learnpath/tests/test_whatever.py +++ /dev/null @@ -1,14 +0,0 @@ - -from django.test import TestCase - -from server.vbv_lernwelt.core.models import User - - -class TestWhatever(TestCase): - def test_print(self): - print("hallo " * 80) - - def test_user(self): - - User.objects.get_or_create(name='hallo ') -