Merged in feature/VBV-289-kn-auftrag-datenmodell-definieren-rebase1 (pull request #53)

Feature/VBV-289 kn auftrag datenmodell definieren rebase1

Approved-by: Elia Bieri
This commit is contained in:
Daniel Egger 2023-04-11 11:37:31 +00:00 committed by Elia Bieri
commit 87a3676ca2
31 changed files with 935 additions and 12 deletions

View File

@ -6,6 +6,7 @@ import log from "loglevel";
import type { Component } from "vue";
import { computed } from "vue";
import AssignmentBlock from "@/pages/learningPath/learningContentPage/blocks/AssignmentBlock.vue";
import AttendanceDayBlock from "@/pages/learningPath/learningContentPage/blocks/AttendanceDayBlock.vue";
import DescriptionBlock from "./blocks/DescriptionBlock.vue";
import DescriptionTextBlock from "./blocks/DescriptionTextBlock.vue";
@ -35,7 +36,7 @@ const block = computed(() => {
const COMPONENTS: Record<LearningContentType, Component> = {
placeholder: PlaceholderBlock,
video: VideoBlock,
assignment: DescriptionTextBlock,
assignment: AssignmentBlock,
resource: DescriptionTextBlock,
exercise: IframeBlock,
test: IframeBlock,

View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import { useAssignmentStore } from "@/stores/assignmentStore";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type {
Assignment,
CourseSessionAssignmentDetails,
LearningContent,
} from "@/types";
import * as log from "loglevel";
import { onMounted, reactive } from "vue";
const assignmentStore = useAssignmentStore();
interface State {
assignment: Assignment | undefined;
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
}
const state: State = reactive({
assignment: undefined,
courseSessionAssignmentDetails: undefined,
});
const props = defineProps<{
assignmentId: number;
learningContent: LearningContent;
}>();
onMounted(async () => {
log.debug("AssignmentView mounted", props.assignmentId, props.learningContent);
const courseSessionsStore = useCourseSessionsStore();
try {
state.assignment = await assignmentStore.loadAssignment(props.assignmentId);
state.courseSessionAssignmentDetails = courseSessionsStore.findAssignmentDetails(
props.learningContent.id
);
} catch (error) {
log.error(error);
}
});
</script>
<template>
<div class="container-medium">
<div v-if="state.assignment" class="lg:mt-12">
<h2>Einleitung</h2>
<h3 class="mt-8">Ausgangslage</h3>
<p>{{ state.assignment.starting_position }}</p>
<h3 class="mt-8">Abgabetermin</h3>
<p v-if="state.courseSessionAssignmentDetails">
{{ state.courseSessionAssignmentDetails }}
</p>
<p v-else>Keine Abgabedaten erfasst für diese Durchführung</p>
<pre class="mt-16">
{{ state.assignment }}
</pre>
</div>
</div>
</template>

View File

@ -0,0 +1,21 @@
<template>
<AssignmentView
:assignment-id="props.value.assignment"
:learning-content="props.content"
/>
</template>
<script setup lang="ts">
import AssignmentView from "@/pages/learningPath/learningContentPage/assignment/AssignmentView.vue";
import type { LearningContent } from "@/types";
interface Value {
description: string;
assignment: number;
}
const props = defineProps<{
value: Value;
content: LearningContent;
}>();
</script>

View File

@ -0,0 +1,31 @@
import { itGet } from "@/fetchHelpers";
import type { Assignment } from "@/types";
import log from "loglevel";
import { defineStore } from "pinia";
export type AssignmentStoreState = {
assignment: Assignment | undefined;
};
export const useAssignmentStore = defineStore({
id: "assignmentStore",
state: () => {
return {
assignment: undefined,
} as AssignmentStoreState;
},
getters: {},
actions: {
async loadAssignment(assignmentId: number) {
log.debug("load assignment", assignmentId);
const assignmentData = await itGet(`/api/course/page/${assignmentId}/`);
if (!assignmentData) {
throw `No assignment found with: ${assignmentId}`;
}
this.assignment = assignmentData;
return this.assignment;
},
},
});

View File

@ -3,6 +3,7 @@ import { deleteCircleDocument } from "@/services/files";
import type {
CircleDocument,
CourseSession,
CourseSessionAssignmentDetails,
CourseSessionAttendanceDay,
CourseSessionUser,
ExpertSessionUser,
@ -224,6 +225,16 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
}
}
function findAssignmentDetails(
contentId: number
): CourseSessionAssignmentDetails | undefined {
if (currentCourseSession.value) {
return currentCourseSession.value.assignment_details_list.find(
(assignmentDetails) => assignmentDetails.learningContentId === contentId
);
}
}
return {
uniqueCourseSessionsByCourse,
currentCourseSession,
@ -238,6 +249,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
startUpload,
removeDocument,
findAttendanceDay,
findAssignmentDetails,
// TODO: only used to be changed by router.afterEach
currentCourseSlug,

View File

@ -285,6 +285,63 @@ export interface MediaLibraryPage extends BaseCourseWagtailPage {
readonly children: MediaCategoryPage[];
}
export interface AssignmentPerformanceObjective {
readonly type: "performance_objective";
readonly id: string;
readonly value: {
text: string;
};
}
export interface AssignmentTaskBlockExplanation {
readonly type: "explanation";
readonly id: string;
readonly value: {
readonly text: string;
};
}
export interface AssignmentTaskBlockUserConfirmation {
readonly type: "user_confirmation";
readonly id: string;
readonly value: {
readonly text: string;
};
}
export interface AssignmentTaskBlockUserTextInput {
readonly type: "user_text_input";
readonly id: string;
readonly value: {
readonly text?: string;
};
}
export type AssignmentTaskBlock =
| AssignmentTaskBlockExplanation
| AssignmentTaskBlockUserConfirmation
| AssignmentTaskBlockUserTextInput;
export interface AssignmentTask {
readonly type: "task";
readonly id: string;
readonly value: {
title: string;
file_submission_required: boolean;
content: AssignmentTaskBlock[];
};
}
export interface Assignment extends BaseCourseWagtailPage {
readonly type: "assignment.Assignment";
readonly starting_position: string;
readonly effort_required: string;
readonly performance_objectives: AssignmentPerformanceObjective[];
readonly assessment_description: string;
readonly assessment_document_url: string;
readonly tasks: AssignmentTask[];
}
export interface PerformanceCriteria extends BaseCourseWagtailPage {
readonly type: "competence.PerformanceCriteria";
readonly competence_id: string;
@ -348,6 +405,11 @@ export interface CourseSessionAttendanceDay {
trainer: string;
}
export interface CourseSessionAssignmentDetails {
learningContentId: number;
deadlineDateTimeUtc: string;
}
export interface CourseSession {
id: number;
created_at: string;
@ -361,6 +423,7 @@ export interface CourseSession {
course_url: string;
media_library_url: string;
attendance_days: CourseSessionAttendanceDay[];
assignment_details_list: CourseSessionAssignmentDetails[];
documents: CircleDocument[];
users: CourseSessionUser[];
}

View File

@ -113,6 +113,7 @@ LOCAL_APPS = [
"vbv_lernwelt.feedback",
"vbv_lernwelt.files",
"vbv_lernwelt.notify",
"vbv_lernwelt.assignment",
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

View File

@ -83,7 +83,7 @@ urlpatterns = [
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
path(r"api/course/sessions/<course_slug>/users/", get_course_session_users,
name="get_course_session_users"),
path(r"api/course/page/<slug>/", course_page_api_view,
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
name="course_page_api_view"),
path(r"api/course/completion/mark/", mark_course_completion_view,
name="mark_course_completion"),

View File

@ -540,7 +540,7 @@ wagtail==3.0.1
# wagtail-grapple
# wagtail-headless-preview
# wagtail-localize
wagtail-factories==2.0.1
wagtail-factories==4.0.0
# via -r requirements.in
wagtail-grapple==0.18.0
# via -r requirements.in

View File

@ -37,7 +37,7 @@ python-json-logger
concurrent-log-handler
wagtail>=3,<4
wagtail-factories
wagtail-factories>=4
wagtail-localize
wagtail_grapple

View File

@ -282,7 +282,7 @@ wagtail==3.0.1
# wagtail-grapple
# wagtail-headless-preview
# wagtail-localize
wagtail-factories==2.0.1
wagtail-factories==4.0.0
# via -r requirements.in
wagtail-grapple==0.18.0
# via -r requirements.in

View File

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

View File

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

View File

@ -0,0 +1,285 @@
from vbv_lernwelt.assignment.models import TaskContentStreamBlock
from vbv_lernwelt.assignment.tests.assignment_factories import (
AssignmentFactory,
AssignmentListPageFactory,
ExplanationBlockFactory,
PerformanceObjectiveBlockFactory,
TaskBlockFactory,
UserTextInputBlockFactory,
)
from vbv_lernwelt.core.utils import replace_whitespace
from vbv_lernwelt.course.consts import COURSE_UK
from vbv_lernwelt.course.models import CoursePage
from wagtail.blocks import StreamValue
def create_uk_assignments(course_id=COURSE_UK):
course_page = CoursePage.objects.get(course_id=course_id)
assignment_page = AssignmentListPageFactory(
parent=course_page,
)
assignment = AssignmentFactory(
parent=assignment_page,
title="Überprüfen einer Motorfahrzeugs-Versicherungspolice",
effort_required="ca. 5 Stunden",
starting_position=replace_whitespace(
"""
Jemand aus deiner Familie oder aus deinem Freundeskreis möchte sein
Versicherungspolice überprüfen lassen. Diese Person kommt nun mit ihrer Police auf dich zu
und bittet dich als Versicherungsprofi, diese kritisch zu überprüfen und ihr ggf. Anpassungsvorschläge
zu unterbreiten. In diesem Kompetenznachweis kannst du nun dein Wissen und Können im Bereich
der Motorfahrzeugversicherung unter Beweis stellen.
"""
),
performance_objectives=[
(
"performance_objective",
PerformanceObjectiveBlockFactory(
text="Sie erläutern die Leistungen und Produkte im Versicherungsbereich."
),
),
(
"performance_objective",
PerformanceObjectiveBlockFactory(
text="Sie beurteilen gängige Versicherungslösungen fachkundig."
),
),
],
assessment_document_url="https://www.vbv.ch",
assessment_description="Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsintrument bewertet.",
)
assignment.tasks = []
assignment.tasks.append(
(
"task",
TaskBlockFactory(
title="Teilaufgabe 1: Beispiel einer Versicherungspolice finden",
# it is hard to create a StreamValue programmatically, we have to
# create a `StreamValue` manually. Ask the Daniel and/or Ramon
content=StreamValue(
TaskContentStreamBlock(),
stream_data=[
(
"explanation",
ExplanationBlockFactory(text="Dies ist ein Beispieltext."),
),
(
"user_confirmation",
ExplanationBlockFactory(
text="Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
),
),
],
),
),
)
)
assignment.tasks.append(
(
"task",
TaskBlockFactory(
title="Teilaufgabe 2: Kundensituation und Ausgangslage",
content=StreamValue(
TaskContentStreamBlock(),
stream_data=[
(
"explanation",
ExplanationBlockFactory(
text=replace_whitespace(
"""
Erläutere die Kundensituation und die Ausgangslage.
* Hast du alle Informationen, die du für den Policen-Check benötigst?
* Halte die wichtigsten Eckwerte des aktuellen Versicherungsverhältnisse in deiner Dokumentation fest (z.B wie lang wo versichert, Alter des Fahrzeugs, Kundenprofil, etc.)
"""
)
),
),
("user_text_input", UserTextInputBlockFactory()),
],
),
),
)
)
assignment.tasks.append(
(
"task",
TaskBlockFactory(
title="Teilaufgabe 3: Aktuelle Versicherung",
# TODO: add document upload
content=StreamValue(
TaskContentStreamBlock(),
stream_data=[
(
"explanation",
ExplanationBlockFactory(
text=replace_whitespace(
"""
Zeige nun detailliert auf, wie dein Kundenbeispiel momentan versichert ist.
"""
)
),
),
("user_text_input", UserTextInputBlockFactory()),
],
),
),
)
)
assignment.tasks.append(
(
"task",
TaskBlockFactory(
title="Teilaufgabe 4: Deine Empfehlungen",
content=StreamValue(
TaskContentStreamBlock(),
stream_data=[
(
"explanation",
ExplanationBlockFactory(
text=replace_whitespace(
"""
Erarbeite nun basierend auf deinen Erkenntnissen eine Empfehlung für die Person.
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest? Begründe deine Empfehlung
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
Gibt es Deckungen, die du streichen würdest? Begründe deine Empfehlung.
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
Wenn die Person gemäss deiner Einschätzung genau richtig versichert ist, argumentiere, warum dies der Fall ist.
"""
)
),
),
],
),
),
)
)
assignment.tasks.append(
(
"task",
TaskBlockFactory(
title="Teilaufgabe 5: Reflexion",
content=StreamValue(
TaskContentStreamBlock(),
stream_data=[
(
"explanation",
ExplanationBlockFactory(
text=replace_whitespace(
"""
Reflektiere dein Handeln und halte deine Erkenntnisse fest. Frage dich dabei:
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
War die Bearbeitung dieser geleiteten Fallarbeit erfolgreich? Begründe deine Einschätzung.
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
Was ist dir bei der Bearbeitung des Auftrags gut gelungen?
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
Was ist dir bei der Bearbeitung des Auftrags weniger gut gelungen?
"""
)
),
),
],
),
),
)
)
assignment.tasks.append(
(
"task",
TaskBlockFactory(
title="Teilaufgabe 6: Learnings",
content=StreamValue(
TaskContentStreamBlock(),
stream_data=[
(
"explanation",
ExplanationBlockFactory(
text=replace_whitespace(
"""
Leite aus der Teilaufgabe 5 deine persönlichen Learnings ab.
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
Was würdest du beim nächsten Mal anders machen?
"""
)
),
),
(
"user_text_input",
UserTextInputBlockFactory(
text=replace_whitespace(
"""
Was hast du beim Bearbeiten des Auftrags Neues gelernt?
"""
)
),
),
],
),
),
)
)
assignment.save()

View File

@ -0,0 +1,156 @@
# Generated by Django 3.2.13 on 2023-04-11 09:30
import django.db.models.deletion
import wagtail.blocks
import wagtail.fields
from django.db import migrations, models
import vbv_lernwelt.assignment.models
class Migration(migrations.Migration):
initial = True
dependencies = [
("wagtailcore", "0069_log_entry_jsonfield"),
]
operations = [
migrations.CreateModel(
name="Assignment",
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",
),
),
(
"starting_position",
models.TextField(help_text="Erläuterung der Ausgangslage"),
),
(
"effort_required",
models.CharField(
blank=True, help_text="Zeitaufwand als Text", max_length=100
),
),
(
"performance_objectives",
wagtail.fields.StreamField(
[
(
"performance_objective",
wagtail.blocks.StructBlock(
[("text", wagtail.blocks.TextBlock())]
),
)
],
blank=True,
help_text="Leistungsziele des Auftrags",
use_json_field=True,
),
),
(
"assessment_description",
models.TextField(
blank=True, help_text="Beschreibung der Bewertung"
),
),
(
"assessment_document_url",
models.CharField(
blank=True,
help_text="URL zum Beurteilungsinstrument",
max_length=255,
),
),
(
"tasks",
wagtail.fields.StreamField(
[
(
"task",
wagtail.blocks.StructBlock(
[
("title", wagtail.blocks.TextBlock()),
(
"file_submission_required",
wagtail.blocks.BooleanBlock(required=False),
),
(
"content",
wagtail.blocks.StreamBlock(
[
(
"explanation",
wagtail.blocks.StructBlock(
[
(
"text",
wagtail.blocks.TextBlock(),
)
]
),
),
(
"user_text_input",
vbv_lernwelt.assignment.models.UserTextInputBlock(),
),
(
"user_confirmation",
wagtail.blocks.StructBlock(
[
(
"text",
wagtail.blocks.TextBlock(),
)
]
),
),
],
blank=True,
),
),
]
),
)
],
blank=True,
help_text="Teilaufgaben",
use_json_field=True,
),
),
],
options={
"verbose_name": "Auftrag",
},
bases=("wagtailcore.page",),
),
migrations.CreateModel(
name="AssignmentListPage",
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",),
),
]

View File

@ -0,0 +1,135 @@
from django.db import models
from slugify import slugify
from wagtail import blocks
from wagtail.admin.panels import FieldPanel
from wagtail.fields import StreamField
from wagtail.models import Page
from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.course.models import CourseBasePage
class AssignmentListPage(CourseBasePage):
subpage_types = ["assignment.Assignment"]
parent_page_types = ["course.CoursePage"]
def save(self, clean=True, user=None, log_action=False, **kwargs):
self.slug = find_available_slug(
slugify(f"{self.get_parent().slug}-assignment", allow_unicode=True)
)
super(AssignmentListPage, self).save(clean, user, log_action, **kwargs)
def __str__(self):
return f"{self.title}"
# class AssignmentSubmission(modModel):
# created_at = models.DateTimeField(auto_now_add=True)
class ExplanationBlock(blocks.StructBlock):
text = blocks.TextBlock()
class Meta:
icon = "comment"
class PerformanceObjectiveBlock(blocks.StructBlock):
text = blocks.TextBlock()
class Meta:
icon = "tick"
class UserTextInputBlock(blocks.StaticBlock):
text = blocks.TextBlock(blank=True)
class Meta:
icon = "edit"
class UserConfirmationBlock(blocks.StructBlock):
text = blocks.TextBlock()
class Meta:
icon = "tick-inverse"
class TaskContentStreamBlock(blocks.StreamBlock):
explanation = ExplanationBlock()
user_text_input = UserTextInputBlock()
user_confirmation = UserConfirmationBlock()
class TaskBlock(blocks.StructBlock):
title = blocks.TextBlock()
file_submission_required = blocks.BooleanBlock(required=False)
content = TaskContentStreamBlock(
blank=True,
)
class Meta:
icon = "tasks"
label = "Teilauftrag"
class Assignment(CourseBasePage):
serialize_field_names = [
"starting_position",
"effort_required",
"performance_objectives",
"assessment_description",
"assessment_document_url",
"tasks",
]
starting_position = models.TextField(help_text="Erläuterung der Ausgangslage")
effort_required = models.CharField(
max_length=100, help_text="Zeitaufwand als Text", blank=True
)
performance_objectives = StreamField(
[
("performance_objective", PerformanceObjectiveBlock()),
],
use_json_field=True,
blank=True,
help_text="Leistungsziele des Auftrags",
)
assessment_description = models.TextField(
blank=True, help_text="Beschreibung der Bewertung"
)
assessment_document_url = models.CharField(
max_length=255,
blank=True,
help_text="URL zum Beurteilungsinstrument",
)
tasks = StreamField(
[
("task", TaskBlock()),
],
use_json_field=True,
blank=True,
help_text="Teilaufgaben",
)
content_panels = Page.content_panels + [
FieldPanel("starting_position"),
FieldPanel("effort_required"),
FieldPanel("performance_objectives"),
FieldPanel("assessment_description"),
FieldPanel("assessment_document_url"),
FieldPanel("tasks"),
]
subpage_types = []
class Meta:
verbose_name = "Auftrag"
def save(self, clean=True, user=None, log_action=False, **kwargs):
self.slug = find_available_slug(
slugify(f"{self.get_parent().slug}-{self.title}", allow_unicode=True)
)
super(Assignment, self).save(clean, user, log_action, **kwargs)

View File

@ -0,0 +1,80 @@
import wagtail_factories
from factory import SubFactory
from vbv_lernwelt.assignment.models import (
Assignment,
AssignmentListPage,
ExplanationBlock,
PerformanceObjectiveBlock,
TaskBlock,
TaskContentStreamBlock,
UserConfirmationBlock,
UserTextInputBlock,
)
from vbv_lernwelt.core.utils import replace_whitespace
class ExplanationBlockFactory(wagtail_factories.StructBlockFactory):
text = "Dies ist ein Beispieltext."
class Meta:
model = ExplanationBlock
class UserConfirmationBlockFactory(wagtail_factories.StructBlockFactory):
text = "Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
class Meta:
model = UserConfirmationBlock
class TaskContentStreamBlockFactory(wagtail_factories.StreamBlockFactory):
explanation = SubFactory(ExplanationBlockFactory)
user_confirmation = SubFactory(UserConfirmationBlockFactory)
class Meta:
model = TaskContentStreamBlock
class UserTextInputBlockFactory(wagtail_factories.StructBlockFactory):
class Meta:
model = UserTextInputBlock
class TaskBlockFactory(wagtail_factories.StructBlockFactory):
title = "Teilauftrag"
file_submission_required = False
content = TaskContentStreamBlockFactory()
class Meta:
model = TaskBlock
class PerformanceObjectiveBlockFactory(wagtail_factories.StructBlockFactory):
text = "Die Teilnehmer können die wichtigsten Eckwerte eines Versicherungsverhältnisses erfassen."
class Meta:
model = PerformanceObjectiveBlock
class AssignmentFactory(wagtail_factories.PageFactory):
title = "Auftrag"
starting_position = replace_whitespace(
"""
Jemand aus deiner Familie oder aus deinem Freundeskreis möchte sein
Versicherungspolice überprüfen lassen. Diese Person kommt nun mit ihrer Police auf dich zu
und bittet dich als Versicherungsprofi, diese kritisch zu überprüfen und ihr gg. Anpassungsvorschläge
zu unterbreiten. In diesem Kompetenznachweis kannst du nun dein Wissen und Können im Bereich
der Motorfahrzeugversicherung unter Beweis stellen.
"""
)
class Meta:
model = Assignment
class AssignmentListPageFactory(wagtail_factories.PageFactory):
title = "Aufträge"
class Meta:
model = AssignmentListPage

View File

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

View File

@ -1,4 +1,5 @@
import logging
import re
import structlog
from django.conf import settings
@ -50,3 +51,7 @@ def first_true(iterable, default=False, pred=None):
# first_true([a,b,c], x) --> a or b or c or x
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
return next(filter(pred, iterable), default)
def replace_whitespace(text, replacement=" "):
return re.sub(r"\s+", replacement, text).strip()

View File

@ -1,6 +1,7 @@
import djclick as click
from wagtail.models import Page
from vbv_lernwelt.assignment.creators.create_assignments import create_uk_assignments
from vbv_lernwelt.competence.create_uk_competence_profile import (
create_uk_competence_profile,
create_uk_fr_competence_profile,
@ -137,6 +138,7 @@ def create_course_uk_de():
create_versicherungsvermittlerin_with_categories(
course_id=COURSE_UK, title="Überbetriebliche Kurse"
)
create_uk_assignments(course_id=COURSE_UK)
create_uk_learning_path(course_id=COURSE_UK)
create_uk_competence_profile(course_id=COURSE_UK)
create_default_media_library(course_id=COURSE_UK)
@ -156,6 +158,14 @@ def create_course_uk_de():
"trainer": "Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
}
],
assignment_details_list=[
{
"learningContentId": LearningContent.objects.get(
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"
).id,
"deadlineDateTimeUtc": "2023-05-30T19:00:00Z",
}
],
)
# figma demo users and data

View File

@ -5,10 +5,12 @@ from slugify import slugify
from wagtail.models import Locale, Page, Site
from wagtail_localize.models import LocaleSynchronization
from vbv_lernwelt.assignment.models import Assignment
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.course.consts import COURSE_UK, COURSE_UK_FR
from vbv_lernwelt.course.models import CoursePage
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
AssignmentBlockFactory,
AttendanceDayBlockFactory,
CircleFactory,
FeedbackBlockFactory,
@ -282,6 +284,20 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst.
title="Reflexion",
parent=circle,
)
LearningContentFactory(
title="Überprüfen einer Motorfahrzeug-Versicherungspolice",
parent=circle,
contents=[
(
"assignment",
AssignmentBlockFactory(
assignment=Assignment.objects.get(
slug__startswith="überbetriebliche-kurse-assignment-überprüfen-einer-motorfahrzeugs"
)
),
)
],
)
LearningContentFactory(
title="Feedback",
parent=circle,

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2023-04-06 09:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("course", "0003_auto_20230404_0837"),
]
operations = [
migrations.AddField(
model_name="coursesession",
name="assignment_details_list",
field=models.JSONField(blank=True, default=list),
),
]

View File

@ -121,6 +121,7 @@ class CoursePage(CourseBasePage):
"learnpath.LearningPath",
"competence.CompetenceProfilePage",
"media_library.MediaLibraryPage",
"assignment.AssignmentListPage",
]
course = models.OneToOneField("course.Course", on_delete=models.PROTECT)
@ -185,6 +186,7 @@ class CourseSession(models.Model):
end_date = models.DateField(null=True, blank=True)
attendance_days = models.JSONField(default=list, blank=True)
assignment_details_list = models.JSONField(default=list, blank=True)
additional_json_data = models.JSONField(default=dict, blank=True)

View File

@ -83,6 +83,7 @@ class CourseSessionSerializer(serializers.ModelSerializer):
"end_date",
"additional_json_data",
"attendance_days",
"assignment_details_list",
"learning_path_url",
"competence_url",
"media_library_url",

View File

@ -33,9 +33,12 @@ logger = structlog.get_logger(__name__)
@api_view(["GET"])
def course_page_api_view(request, slug):
def course_page_api_view(request, slug_or_id):
try:
page = Page.objects.get(slug=slug, locale__language_code="de-CH")
if slug_or_id.isdigit():
page = Page.objects.get(id=slug_or_id)
else:
page = Page.objects.get(slug=slug_or_id, locale__language_code="de-CH")
if not has_course_access_by_page_request(request, page):
raise PermissionDenied()

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.13 on 2023-04-03 16:05
# Generated by Django 3.2.13 on 2023-04-04 08:28
import wagtail.blocks
import wagtail.fields
@ -104,8 +104,13 @@ class Migration(migrations.Migration):
wagtail.blocks.StructBlock(
[
("description", wagtail.blocks.TextBlock()),
("url", wagtail.blocks.TextBlock()),
("text", wagtail.blocks.RichTextBlock(required=False)),
(
"assignment",
wagtail.blocks.PageChooserBlock(
help_text="Choose the corresponding assignment.",
required=True,
),
),
]
),
),

View File

@ -1,10 +1,12 @@
from wagtail import blocks
from wagtail.blocks import PageChooserBlock
class AssignmentBlock(blocks.StructBlock):
description = blocks.TextBlock()
url = blocks.TextBlock()
text = blocks.RichTextBlock(required=False)
assignment = PageChooserBlock(
required=True, help_text="Choose the corresponding assignment."
)
class Meta:
icon = "media"