Merge branch 'feature/competence-circles' into develop

This commit is contained in:
Daniel Egger 2022-10-11 16:56:58 +02:00
commit a736d22b21
47 changed files with 490 additions and 256 deletions

View File

@ -106,9 +106,9 @@ const profileDropdownData = [
<nav class="px-8 py-2 mx-auto lg:flex lg:justify-start lg:items-center lg:py-4"> <nav class="px-8 py-2 mx-auto lg:flex lg:justify-start lg:items-center lg:py-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<a href="https://www.vbv.ch" class="flex"> <router-link to="/" class="flex">
<it-icon-vbv class="h-8 w-16 mr-3 -mt-6 -ml-3" /> <it-icon-vbv class="h-8 w-16 mr-3 -mt-6 -ml-3" />
</a> </router-link>
<router-link to="/" class="flex"> <router-link to="/" class="flex">
<div class="text-white text-2xl pr-10 pl-3 ml-1 border-l border-white"> <div class="text-white text-2xl pr-10 pl-3 ml-1 border-l border-white">
myVBV myVBV

View File

@ -40,23 +40,28 @@ const clickLink = (to: string) => {
class="mt-2 inline-block flex items-center" class="mt-2 inline-block flex items-center"
@click="clickLink('/profile')" @click="clickLink('/profile')"
> >
<IconSettings class="inline-block" /><span class="ml-3" <IconSettings class="inline-block" />
>Kontoeinstellungen</span <span class="ml-3"> Kontoeinstellungen </span>
>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<div v-if="learningPathName" class="mt-6 pb-6 border-b border-gray-500"> <div v-if="true" class="mt-6 pb-6 border-b border-gray-500">
<h4 class="text-gray-900 text-sm">Kurs: {{ learningPathName }}</h4> <h4 class="text-gray-900 text-sm">Kurs: Versicherungsvermittler/in</h4>
<ul class="mt-6"> <ul class="mt-6">
<li> <li>
<button @click="clickLink(`/learningpath/${learningPathSlug}`)"> <button @click="clickLink(`/learn/versicherungsvermittlerin-lp`)">
Lernpfad Lernpfad
</button> </button>
</li> </li>
<li class="mt-6">Kompetenzprofil</li> <li class="mt-6">
<button
@click="clickLink(`/competence/versicherungsvermittlerin-competence`)"
>
Kompetenzprofil
</button>
</li>
</ul> </ul>
</div> </div>
<div class="mt-6 pb-6 border-b border-gray-500"> <div class="mt-6 pb-6 border-b border-gray-500">
@ -75,7 +80,8 @@ const clickLink = (to: string) => {
class="mt-6 items-center flex" class="mt-6 items-center flex"
@click="userStore.handleLogout()" @click="userStore.handleLogout()"
> >
<IconLogout class="inline-block" /><span class="ml-1">Abmelden</span> <IconLogout class="inline-block" />
<span class="ml-1">Abmelden</span>
</button> </button>
</div> </div>
</div> </div>

View File

@ -34,12 +34,16 @@ const togglePerformanceCriteria = () => {
</button> </button>
</div> </div>
<ComptenceProgress <ComptenceProgress
:status-count="competenceStore.calcStatusCount(competence.children)" :status-count="
competenceStore.calcStatusCount(
competenceStore.criteriaByCompetence(competence)
)
"
></ComptenceProgress> ></ComptenceProgress>
</div> </div>
<ul v-if="isOpen"> <ul v-if="isOpen">
<li <li
v-for="performanceCriteria in competence.children" v-for="performanceCriteria in competenceStore.criteriaByCompetence(competence)"
:key="performanceCriteria.id" :key="performanceCriteria.id"
class="mb-4 pb-4 border-b border-gray-500" class="mb-4 pb-4 border-b border-gray-500"
> >

View File

@ -30,16 +30,16 @@ const props = withDefaults(defineProps<Props>(), {
</h4> </h4>
<p> <p>
Lerneinheit: Lerneinheit:
<a class="link" :href="criteria.learning_unit.frontend_url"> <router-link class="link" :to="criteria.learning_unit.frontend_url">
{{ criteria.learning_unit.title }} {{ criteria.learning_unit.title }}
</a> </router-link>
</p> </p>
</div> </div>
</div> </div>
<span class="whitespace-nowrap"> <span class="whitespace-nowrap">
<a class="link" :href="criteria.learning_unit.evaluate_url"> <router-link class="link" :to="criteria.learning_unit.evaluate_url">
Sich nochmals einschätzen Sich nochmals einschätzen
</a> </router-link>
</span> </span>
</div> </div>
</template> </template>

View File

@ -178,7 +178,7 @@ function render() {
translate = [translate[0], translate[1] + 20]; translate = [translate[0], translate[1] + 20];
return "translate(" + translate + ")"; return "translate(" + translate + ")";
}) })
.attr("class", "circlesText text-xl font-bold") .attr("class", "circlesText text-large font-bold")
.style("text-anchor", "middle"); .style("text-anchor", "middle");
const iconWidth = 25; const iconWidth = 25;
@ -194,7 +194,8 @@ function render() {
let translate = wedgeGenerator.centroid(d); let translate = wedgeGenerator.centroid(d);
translate = [translate[0] - iconWidth / 2, translate[1] - iconWidth]; translate = [translate[0] - iconWidth / 2, translate[1] - iconWidth];
return "translate(" + translate + ")"; return "translate(" + translate + ")";
}); })
.attr("class", "filter-blue-900");
// Create Arrows // Create Arrows
const arrow = d3 const arrow = d3

View File

@ -15,7 +15,7 @@ const props = defineProps<{
<div v-if="circle" class="container-medium"> <div v-if="circle" class="container-medium">
<h1 class="">Überblick: Circle "{{ circle.title }}"</h1> <h1 class="">Überblick: Circle "{{ circle.title }}"</h1>
<p class="mt-8 text-xl"> <p class="mt-8 text-large">
Hier zeigen wir dir, was du in diesem Circle lernen wirst. Hier zeigen wir dir, was du in diesem Circle lernen wirst.
</p> </p>
@ -26,7 +26,7 @@ const props = defineProps<{
<li <li
v-for="goal in circle.goals" v-for="goal in circle.goals"
:key="goal.id" :key="goal.id"
class="text-xl flex items-center" class="text-large flex items-center"
> >
<it-icon-check <it-icon-check
class="mt-4 hidden lg:block w-12 h-12 text-sky-500 flex-none" class="mt-4 hidden lg:block w-12 h-12 text-sky-500 flex-none"
@ -45,7 +45,7 @@ const props = defineProps<{
<li <li
v-for="jobSituation in circle.job_situations" v-for="jobSituation in circle.job_situations"
:key="jobSituation.id" :key="jobSituation.id"
class="job-situation border border-gray-500 p-4 text-xl flex items-center" class="job-situation border border-gray-500 p-4 text-large flex items-center"
> >
{{ jobSituation.value }} {{ jobSituation.value }}
</li> </li>

View File

@ -3,9 +3,12 @@ import { useCircleStore } from "@/stores/circle";
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import * as log from "loglevel"; import * as log from "loglevel";
import { computed } from "vue"; import { computed } from "vue";
import { useRoute } from "vue-router";
log.debug("LearningContent.vue setup"); log.debug("LearningContent.vue setup");
const route = useRoute();
const circleStore = useCircleStore(); const circleStore = useCircleStore();
const props = defineProps<{ const props = defineProps<{
@ -28,7 +31,7 @@ const block = computed(() => {
> >
<button <button
type="button" type="button"
class="btn-text inline-flex items-center px-3 py-2 font-normal" class="btn-text inline-flex items-center px-3 py-2"
data-cy="close-learning-content" data-cy="close-learning-content"
@click="circleStore.closeLearningContent(props.learningContent)" @click="circleStore.closeLearningContent(props.learningContent)"
> >
@ -36,7 +39,7 @@ const block = computed(() => {
<span class="hidden lg:inline">zurück zum Circle</span> <span class="hidden lg:inline">zurück zum Circle</span>
</button> </button>
<h1 class="text-xl hidden lg:block" data-cy="ln-title"> <h1 class="text-large hidden lg:block" data-cy="ln-title">
{{ learningContent.title }} {{ learningContent.title }}
</h1> </h1>
@ -70,18 +73,21 @@ const block = computed(() => {
<div v-else-if="block.type === 'media_library'" class="mt-4 lg:mt-12"> <div v-else-if="block.type === 'media_library'" class="mt-4 lg:mt-12">
<h1>{{ learningContent.title }}</h1> <h1>{{ learningContent.title }}</h1>
<p class="text-xl my-4 lg:my-8">{{ block.value.description }}</p> <p class="text-large my-4 lg:my-8">{{ block.value.description }}</p>
<a :href="block.value.url" target="_blank" class="button btn-primary"> <router-link
:to="`${block.value.url}?back=${route.path}`"
class="button btn-primary"
>
Mediathek öffnen Mediathek öffnen
</a> </router-link>
</div> </div>
<div v-else-if="block.type === 'placeholder'" class="mt-4 lg:mt-12"> <div v-else-if="block.type === 'placeholder'" class="mt-4 lg:mt-12">
<p class="text-xl my-4">{{ block.value.description }}</p> <p class="text-large my-4">{{ block.value.description }}</p>
<h1>{{ learningContent.title }}</h1> <h1>{{ learningContent.title }}</h1>
</div> </div>
<div v-else class="text-xl my-4">{{ block.value.description }}</div> <div v-else class="text-large my-4">{{ block.value.description }}</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -89,14 +89,16 @@ export default {
mounted() { mounted() {
log.debug("LearningPathDiagram mounted"); log.debug("LearningPathDiagram mounted");
const circleWidth = this.vertical ? 60 : 200;
const radius = (circleWidth * 0.8) / 2;
if (this.vertical) { if (this.vertical) {
this.width = Math.min(960, window.innerWidth - 32); this.width = Math.min(960, window.innerWidth - 32);
this.height = 860; this.height = 860;
} else {
this.width = circleWidth * this.circles.length;
} }
const circleWidth = this.vertical ? 60 : 200;
const radius = (circleWidth * 0.8) / 2;
function getColor(d) { function getColor(d) {
let color = colors.gray[300]; let color = colors.gray[300];
if (d.someFinished) { if (d.someFinished) {
@ -208,7 +210,7 @@ export default {
for (let index = 0; index < i; index++) { for (let index = 0; index < i; index++) {
x += circleWidth * topics[index].circles.length; x += circleWidth * topics[index].circles.length;
} }
return x + 30; return x + 10;
} }
function getTopicVerticalPosition(i, d, topics) { function getTopicVerticalPosition(i, d, topics) {
@ -266,7 +268,7 @@ export default {
if (this.vertical) { if (this.vertical) {
const Circles_X = radius; const Circles_X = radius;
const Topics_X = Circles_X - radius; const Topics_X = Circles_X - circleWidth;
circle_groups.attr("transform", (d, i) => { circle_groups.attr("transform", (d, i) => {
return ( return (
@ -302,7 +304,7 @@ export default {
topicTitles.attr("y", 30); topicTitles.attr("y", 30);
} else { } else {
circle_groups.attr("transform", (d, i) => { circle_groups.attr("transform", (d, i) => {
const x_coord = (i + 1) * circleWidth - radius; const x_coord = (i + 1) * circleWidth - circleWidth / 2;
return "translate(" + x_coord + ", 200)"; return "translate(" + x_coord + ", 200)";
}); });
@ -386,7 +388,7 @@ export default {
<div class="svg-container h-full content-start"> <div class="svg-container h-full content-start">
<svg <svg
:id="identifier" :id="identifier"
class="learning-path-visualization h-full" class="learning-path-visualization h-full -mt-6 lg:mt-0"
:viewBox="viewBox" :viewBox="viewBox"
></svg> ></svg>
</div> </div>

View File

@ -87,7 +87,7 @@ const learningSequenceBorderClass = computed(() => {
<div :id="learningSequence.slug" class="mb-8 learning-sequence"> <div :id="learningSequence.slug" class="mb-8 learning-sequence">
<div class="flex items-center gap-4 mb-2 text-blue-900"> <div class="flex items-center gap-4 mb-2 text-blue-900">
<component :is="learningSequence.icon" /> <component :is="learningSequence.icon" />
<h3 class="text-xl font-semibold"> <h3 class="text-large font-semibold">
{{ learningSequence.title }} {{ learningSequence.title }}
</h3> </h3>
<div>{{ humanizeDuration(learningSequence.minutes) }}</div> <div>{{ humanizeDuration(learningSequence.minutes) }}</div>

View File

@ -38,14 +38,14 @@ function handleContinue() {
> >
<button <button
type="button" type="button"
class="btn-text inline-flex items-center px-3 py-2 font-normal" class="btn-text inline-flex items-center px-3 py-2"
@click="circleStore.closeSelfEvaluation(props.learningUnit)" @click="circleStore.closeSelfEvaluation(props.learningUnit)"
> >
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left> <it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
<span class="hidden lg:inline">zurück zum Circle</span> <span class="hidden lg:inline">zurück zum Circle</span>
</button> </button>
<h1 class="text-xl hidden lg:block" data-cy="ln-title"> <h1 class="text-large hidden lg:block" data-cy="ln-title">
Selbsteinschätzung {{ learningUnit.title }} Selbsteinschätzung {{ learningUnit.title }}
</h1> </h1>
@ -64,7 +64,7 @@ function handleContinue() {
Schritt {{ state.questionIndex + 1 }} von {{ questions.length }} Schritt {{ state.questionIndex + 1 }} von {{ questions.length }}
</div> </div>
<p class="text-xl mt-4"> <p class="text-large mt-4">
Überprüfe, ob du in der Lernheinheit Überprüfe, ob du in der Lernheinheit
<span class="font-bold">"{{ learningUnit.title }}"</span> alles verstanden <span class="font-bold">"{{ learningUnit.title }}"</span> alles verstanden
hast.<br /> hast.<br />
@ -88,7 +88,7 @@ function handleContinue() {
@click="circleStore.markCompletion(currentQuestion, 'success')" @click="circleStore.markCompletion(currentQuestion, 'success')"
> >
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy> <it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
<span class="font-bold text-xl"> Ja, ich kann das. </span> <span class="font-bold text-large"> Ja, ich kann das. </span>
</button> </button>
<button <button
class="flex-1 inline-flex items-center text-left p-4 border" class="flex-1 inline-flex items-center text-left p-4 border"

View File

@ -16,8 +16,8 @@ const props = withDefaults(defineProps<Props>(), {
<div class="bg-white p-8 flex justify-between"> <div class="bg-white p-8 flex justify-between">
<div> <div>
<h3 class="mb-4">{{ title }}</h3> <h3 class="mb-4">{{ title }}</h3>
<p class="mb-4 text-xl">{{ description }}</p> <p class="mb-4">{{ description }}</p>
<router-link :to="link" class="text-xl inline-flex items-center font-normal"> <router-link :to="link" class="btn-text inline-flex items-center pl-0 pr-3 py-2">
<span class="inline">{{ call2Action }}</span> <span class="inline">{{ call2Action }}</span>
<it-icon-arrow-right class="ml-1 h-5 w-5"></it-icon-arrow-right> <it-icon-arrow-right class="ml-1 h-5 w-5"></it-icon-arrow-right>
</router-link> </router-link>

View File

@ -29,7 +29,7 @@ const emit = defineEmits<{
leave-to-class="transform scale-95 opacity-0" leave-to-class="transform scale-95 opacity-0"
> >
<MenuItems <MenuItems
class="absolute mt-2 px-6 w-56 origin-top-right divide-y divide-gray-500 bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" class="absolute mt-2 px-6 w-56 w-max-full origin-top-right divide-y divide-gray-500 bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
:class="[align === 'left' ? 'left-0' : 'right-0']" :class="[align === 'left' ? 'left-0' : 'right-0']"
> >
<div v-for="section in listItems" :key="section" class=""> <div v-for="section in listItems" :key="section" class="">

View File

@ -38,7 +38,7 @@ const dropdownSelected = computed({
<template> <template>
<Listbox v-model="dropdownSelected" as="div"> <Listbox v-model="dropdownSelected" as="div">
<div class="mt-1 relative w-96"> <div class="mt-1 relative w-full">
<ListboxButton <ListboxButton
class="bg-white relative w-full border border-gray-500 pl-5 pr-10 py-3 text-left cursor-default font-bold" class="bg-white relative w-full border border-gray-500 pl-5 pr-10 py-3 text-left cursor-default font-bold"
> >

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
// inspiration https://vuejs.org/examples/#modal // inspiration https://vuejs.org/examples/#modal
import { onMounted, onUnmounted ,watch } from "vue"; import { onMounted, onUnmounted, watch } from "vue";
const props = defineProps<{ const props = defineProps<{
show: boolean; show: boolean;
@ -20,7 +20,7 @@ onMounted(() => {
appElement = document.getElementById("app"); appElement = document.getElementById("app");
}); });
onUnmounted( () => removeNoScroll()) onUnmounted(() => removeNoScroll());
const closeModal = () => { const closeModal = () => {
removeNoScroll(); removeNoScroll();

View File

@ -8,10 +8,6 @@ if (url.charAt(url.length - 1) !== "/") {
<template> <template>
<main class="px-4 py-8"> <main class="px-4 py-8">
<h1>404 - Not Found as Vue view...</h1> <h1>404 - Not Found as Vue view...</h1>
<div class="text-xl mt-8">Add trailing slash for django view?</div>
<div class="mt-8 text-xl">
Try this: <a class="link" :href="url">{{ url }}</a>
</div>
</main> </main>
</template> </template>

View File

@ -8,13 +8,13 @@ const userStore = useUserStore();
</script> </script>
<template> <template>
<main class="px-8 py-8 lg:px-12 lg:py-12 bg-gray-200"> <main class="py-4 lg:px-12 lg:py-12 bg-gray-200">
<div class="container-medium"> <div class="container-medium">
<h1 data-cy="welcome-message">Willkommen, {{ userStore.first_name }}</h1> <h1 data-cy="welcome-message">Willkommen, {{ userStore.first_name }}</h1>
<h2 class="mt-12">Deine Kurse</h2> <h2 class="mt-12">Deine Kurse</h2>
<div class="mt-8 p-8 break-words bg-white max-w-xl"> <div class="mt-8 p-4 lg:p-8 break-words bg-white max-w-xl">
<h3>Versicherungsvermittler/in</h3> <h3>Versicherungsvermittler/in</h3>
<div class="mt-4"> <div class="mt-4">
<router-link class="btn-blue" to="/learn/versicherungsvermittlerin-lp"> <router-link class="btn-blue" to="/learn/versicherungsvermittlerin-lp">

View File

@ -18,7 +18,7 @@ const userStore = useUserStore();
</script> </script>
<template> <template>
<main class="px-8 py-8 lg:px-12 lg:py-12 bg-gray-200"> <main class="lg:px-12 lg:py-12 bg-gray-200">
<div class="container-medium"> <div class="container-medium">
<h1 class="mb-8">Login</h1> <h1 class="mb-8">Login</h1>
@ -35,7 +35,7 @@ const userStore = useUserStore();
v-model="state.username" v-model="state.username"
type="text" type="text"
name="username" name="username"
class="py-2 px-3 border border-gray-500 mt-1 block w-96" class="py-2 px-3 border border-gray-500 mt-1 block w-96 max-w-full"
/> />
</div> </div>
<div class="mb-4"> <div class="mb-4">
@ -45,7 +45,7 @@ const userStore = useUserStore();
v-model="state.password" v-model="state.password"
type="password" type="password"
name="password" name="password"
class="py-2 px-3 border border-gray-500 mt-1 block w-96" class="py-2 px-3 border border-gray-500 mt-1 block w-96 max-w-full"
/> />
</div> </div>

View File

@ -356,15 +356,19 @@ function log(data: any) {
<h2 class="mt-8 mb-8">Dropdown (Work-in-progress)</h2> <h2 class="mt-8 mb-8">Dropdown (Work-in-progress)</h2>
<ItDropdownSelect v-model="state.dropdownSelected" :items="state.dropdownValues"> <ItDropdownSelect
v-model="state.dropdownSelected"
class="w-full lg:w-96 mt-4 lg:mt-0"
:items="state.dropdownValues"
>
</ItDropdownSelect> </ItDropdownSelect>
{{ state.dropdownSelected }} {{ state.dropdownSelected }}
<h2 class="mt-8 mb-8">Checkbox</h2> <h2 class="mt-8 mb-8">Checkbox</h2>
<ItCheckbox v-model="state.checkboxValue" :disabled="false" class="" <ItCheckbox v-model="state.checkboxValue" :disabled="false" class=""
>Label</ItCheckbox >Label
> </ItCheckbox>
<ItCheckbox disabled class="mt-4">Disabled</ItCheckbox> <ItCheckbox disabled class="mt-4">Disabled</ItCheckbox>
@ -376,8 +380,8 @@ function log(data: any) {
:list-items="dropdownData" :list-items="dropdownData"
:align="'left'" :align="'left'"
@select="log" @select="log"
>Click Me</ItDropdown >Click Me
> </ItDropdown>
</div> </div>
</main> </main>
</template> </template>

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import CompetenceProgress from "@/components/competences/CompetenceProgress.vue"; import CompetenceProgress from "@/components/competences/CompetenceProgress.vue";
import PerformanceCriteriaRow from "@/components/competences/PerformanceCriteriaRow.vue"; import PerformanceCriteriaRow from "@/components/competences/PerformanceCriteriaRow.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCompetenceStore } from "@/stores/competence"; import { useCompetenceStore } from "@/stores/competence";
import _ from "lodash"; import _ from "lodash";
import * as log from "loglevel"; import * as log from "loglevel";
@ -19,22 +20,26 @@ const failedCriteria = computed(() => {
}); });
const lastUpdatedCompetences = computed(() => { const lastUpdatedCompetences = computed(() => {
if (competenceStore.competenceProfilePage?.children.length) { return _.orderBy(
return _.orderBy( competenceStore.competences,
competenceStore.competenceProfilePage.children, [
[ (competence) => {
(competence) => { let criteria = competence.children;
return ( if (competenceStore.selectedCircle.id != "all") {
_.maxBy(competence.children, "completion_status_updated_at") criteria = criteria.filter((criteria) => {
?.completion_status_updated_at || "" return (
); criteria.circle.translation_key === competenceStore.selectedCircle.id
}, );
], });
["desc"] }
).slice(0, 3); return (
} _.maxBy(criteria, "completion_status_updated_at")
?.completion_status_updated_at || ""
return []; );
},
],
["desc"]
).slice(0, 3);
}); });
const countStatus = computed(() => { const countStatus = computed(() => {
@ -49,9 +54,11 @@ const countStatus = computed(() => {
class="flex flex-col lg:flex-row items-center justify-between mb-10" class="flex flex-col lg:flex-row items-center justify-between mb-10"
> >
<h1>Kompetenzprofil</h1> <h1>Kompetenzprofil</h1>
<!-- <ItDropdownSelect--> <ItDropdownSelect
<!-- v-model="dropdownSelected"--> v-model="competenceStore.selectedCircle"
<!-- :items="mediaStore.availableLearningPaths"></ItDropdownSelect>--> class="w-full lg:w-96 mt-4 lg:mt-0"
:items="competenceStore.availableCircles"
></ItDropdownSelect>
</div> </div>
<div class="bg-white p-8 mb-8"> <div class="bg-white p-8 mb-8">
<div> <div>
@ -66,13 +73,17 @@ const countStatus = computed(() => {
{{ competence.competence_id }} {{ competence.title }} {{ competence.competence_id }} {{ competence.title }}
</p> </p>
<CompetenceProgress <CompetenceProgress
:status-count="competenceStore.calcStatusCount(competence.children)" :status-count="
competenceStore.calcStatusCount(
competenceStore.criteriaByCompetence(competence)
)
"
></CompetenceProgress> ></CompetenceProgress>
</li> </li>
</ul> </ul>
<router-link <router-link
:to="`${competenceStore.competenceProfilePage?.frontend_url}/competences`" :to="`${competenceStore.competenceProfilePage?.frontend_url}/competences`"
class="flex items-center" class="btn-text inline-flex items-center pl-0 py-2"
> >
<span>Alle anschauen</span> <span>Alle anschauen</span>
<it-icon-arrow-right></it-icon-arrow-right> <it-icon-arrow-right></it-icon-arrow-right>
@ -102,7 +113,7 @@ const countStatus = computed(() => {
</li> </li>
<li class="flex-1"> <li class="flex-1">
<h5 class="text-gray-700 mb-4">Nicht eingeschätzt</h5> <h5 class="text-gray-700 mb-4">Nicht eingeschätzt</h5>
<div class="flex flex-row items-center border-r"> <div class="flex flex-row items-center">
<it-icon-smiley-neutral class="w-16 h-16"></it-icon-smiley-neutral> <it-icon-smiley-neutral class="w-16 h-16"></it-icon-smiley-neutral>
<p class="text-7xl font-bold inline-block ml-4"> <p class="text-7xl font-bold inline-block ml-4">
{{ countStatus.unknown }} {{ countStatus.unknown }}
@ -112,7 +123,7 @@ const countStatus = computed(() => {
</ul> </ul>
<router-link <router-link
:to="`${competenceStore.competenceProfilePage?.frontend_url}/criteria`" :to="`${competenceStore.competenceProfilePage?.frontend_url}/criteria`"
class="flex items-center" class="btn-text inline-flex items-center pl-0 py-2"
> >
<span>Alle anschauen</span> <span>Alle anschauen</span>
<it-icon-arrow-right></it-icon-arrow-right> <it-icon-arrow-right></it-icon-arrow-right>
@ -134,7 +145,7 @@ const countStatus = computed(() => {
</ul> </ul>
<router-link <router-link
:to="`${competenceStore.competenceProfilePage?.frontend_url}/criteria`" :to="`${competenceStore.competenceProfilePage?.frontend_url}/criteria`"
class="flex items-center" class="btn-text inline-flex items-center pl-0 py-2"
> >
<span>Alle anschauen</span> <span>Alle anschauen</span>
<it-icon-arrow-right></it-icon-arrow-right> <it-icon-arrow-right></it-icon-arrow-right>

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import CompetenceDetail from "@/components/competences/CompetenceDetail.vue"; import CompetenceDetail from "@/components/competences/CompetenceDetail.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCompetenceStore } from "@/stores/competence"; import { useCompetenceStore } from "@/stores/competence";
import * as log from "loglevel"; import * as log from "loglevel";
@ -10,24 +11,26 @@ const competenceStore = useCompetenceStore();
<template> <template>
<div class="container-large"> <div class="container-large">
<nav class="lg:mt-4"> <nav class="py-4 lg:pb-8">
<a <router-link
class="block mb-8 cursor-pointer flex items-center" class="btn-text inline-flex items-center pl-0"
:href="competenceStore.competenceProfilePage?.frontend_url" :to="competenceStore.competenceProfilePage?.frontend_url"
> >
<it-icon-arrow-left /> <it-icon-arrow-left />
<span>zurück</span></a <span>zurück</span>
> </router-link>
</nav> </nav>
<div class="flex flex-col lg:flex-row items-center justify-between mb-10"> <div class="flex flex-col lg:flex-row items-center justify-between mb-10">
<h1>Kompetenzen</h1> <h1>Kompetenzen</h1>
<!-- <ItDropdownSelect--> <ItDropdownSelect
<!-- v-model="dropdownSelected"--> v-model="competenceStore.selectedCircle"
<!-- :items="mediaStore.availableLearningPaths"></ItDropdownSelect>--> class="w-full lg:w-96 mt-4 lg:mt-0"
:items="competenceStore.availableCircles"
></ItDropdownSelect>
</div> </div>
<ul v-if="competenceStore.competenceProfilePage"> <ul v-if="competenceStore.competenceProfilePage">
<li <li
v-for="competence in competenceStore.competenceProfilePage.children" v-for="competence in competenceStore.competences"
:key="competence.id" :key="competence.id"
class="bg-white p-8 mb-8" class="bg-white p-8 mb-8"
> >

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { default as PerformanceCriteriaRow } from "@/components/competences/PerformanceCriteriaRow.vue"; import { default as PerformanceCriteriaRow } from "@/components/competences/PerformanceCriteriaRow.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCompetenceStore } from "@/stores/competence"; import { useCompetenceStore } from "@/stores/competence";
import type { CourseCompletionStatus } from "@/types"; import type { CourseCompletionStatus } from "@/types";
import * as log from "loglevel"; import * as log from "loglevel";
@ -20,20 +21,22 @@ const shownCriteria = computed(() => {
<template> <template>
<div class="container-large"> <div class="container-large">
<nav class="lg:mt-4"> <nav class="py-4 lg:pb-8">
<a <router-link
class="block mb-8 cursor-pointer flex items-center" class="btn-text inline-flex items-center pl-0"
:href="`${competenceStore.competenceProfilePage?.frontend_url}`" :to="`${competenceStore.competenceProfilePage?.frontend_url}`"
> >
<it-icon-arrow-left /> <it-icon-arrow-left />
<span>zurück</span></a <span>zurück</span>
> </router-link>
</nav> </nav>
<div class="flex flex-col lg:flex-row items-center justify-between mb-10"> <div class="flex flex-col lg:flex-row items-center justify-between mb-10">
<h1>Einschätzungen</h1> <h1>Einschätzungen</h1>
<!-- <ItDropdownSelect--> <ItDropdownSelect
<!-- v-model="dropdownSelected"--> v-model="competenceStore.selectedCircle"
<!-- :items="mediaStore.availableLearningPaths"></ItDropdownSelect>--> class="w-full lg:w-96 mt-4 lg:mt-0"
:items="competenceStore.availableCircles"
></ItDropdownSelect>
</div> </div>
<div class="bg-white p-8"> <div class="bg-white p-8">
<div <div

View File

@ -85,7 +85,7 @@ onMounted(async () => {
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:pt-4 bg-white"> <div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:pt-4 bg-white">
<router-link <router-link
:to="`/learn/${props.learningPathSlug}`" :to="`/learn/${props.learningPathSlug}`"
class="btn-text inline-flex items-center px-3 py-4 font-normal" class="btn-text inline-flex items-center px-3 py-4"
data-cy="back-to-learning-path-button" data-cy="back-to-learning-path-button"
> >
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left> <it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>

View File

@ -56,10 +56,10 @@ const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
<div class="learningpath flex flex-col"> <div class="learningpath flex flex-col">
<div class="flex flex-col h-max"> <div class="flex flex-col h-max">
<div class="bg-white py-8 flex flex-col"> <div class="bg-white lg:py-8 flex flex-col">
<div class="flex justify-end p-3"> <div class="flex justify-end lg:p-4">
<button <button
class="flex items-center" class="btn-text inline-flex items-center px-3 lg:py-2"
data-cy="show-list-view" data-cy="show-list-view"
@click="learningPathStore.page = 'OVERVIEW'" @click="learningPathStore.page = 'OVERVIEW'"
> >
@ -68,20 +68,20 @@ const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
</button> </button>
</div> </div>
<LearningPathDiagram <LearningPathDiagram
class="max-w-[1680px] w-full" class="mx-auto max-w-[1920px] w-full"
identifier="mainVisualization" identifier="mainVisualization"
:vertical="false" :vertical="false"
></LearningPathDiagram> ></LearningPathDiagram>
</div> </div>
<div class="container-large"> <div class="container-large pt-0 lg:pt-4">
<h1 data-cy="learning-path-title" class="mt-6 lg:mt-12 mb-6"> <h1 data-cy="learning-path-title" class="mt-6 lg:mt-12 mb-6">
{{ learningPathStore.learningPath.title }} {{ learningPathStore.learningPath.title }}
</h1> </h1>
<div <div
class="bg-white p-4 flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-gray-500 justify-start" class="bg-white p-4 flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-gray-500 justify-start"
> >
<div class="p-4 lg:p-8 flex-auto"> <div class="p-2 lg:p-8 flex-auto">
<h2>Willkommmen zurück, {{ userStore.first_name }}</h2> <h2>Willkommmen zurück, {{ userStore.first_name }}</h2>
<p class="mt-4 text-xl"></p> <p class="mt-4 text-xl"></p>
</div> </div>

View File

@ -4,12 +4,15 @@ import MediaLink from "@/components/mediaLibrary/MediaLink.vue";
import { useMediaLibraryStore } from "@/stores/mediaLibrary"; import { useMediaLibraryStore } from "@/stores/mediaLibrary";
import * as log from "loglevel"; import * as log from "loglevel";
import { computed } from "vue"; import { computed } from "vue";
import { useRoute } from "vue-router";
const props = defineProps<{ const props = defineProps<{
mediaCategorySlug: string; mediaCategorySlug: string;
}>(); }>();
log.debug("MediaCategoryDetailView created", props.mediaCategorySlug); const route = useRoute();
log.debug("MediaCategoryDetailView created", props.mediaCategorySlug, route);
const mediaStore = useMediaLibraryStore(); const mediaStore = useMediaLibraryStore();
@ -19,6 +22,14 @@ const mediaCategory = computed(() => {
); );
}); });
const backLink = computed(() => {
if (route.query.back) {
return route.query.back;
} else {
return `${mediaStore.mediaLibraryPage?.frontend_url}/category`;
}
});
const maxCardItems = 4; const maxCardItems = 4;
const maxListItems = 6; const maxListItems = 6;
@ -51,22 +62,19 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
v-if="mediaCategory" v-if="mediaCategory"
class="fixed top-0 overflow-y-scroll bg-white h-full w-full" class="fixed top-0 overflow-y-scroll bg-white h-full w-full"
> >
<div class="bg-gray-200"> <div class="bg-gray-200 pb-4 lg:pb-12">
<div class="container-large"> <div class="container-large">
<nav> <nav class="py-4 lg:pb-8">
<a <router-link class="btn-text inline-flex items-center pl-0" :to="backLink">
class="block my-9 cursor-pointer flex items-center"
:href="`${mediaStore.mediaLibraryPage.frontend_url}/category`"
>
<it-icon-arrow-left /> <it-icon-arrow-left />
<span>zurück</span></a <span>zurück</span>
> </router-link>
</nav> </nav>
<div class="flex justify-between"> <div class="flex justify-between">
<div class="lg:w-6/12"> <div class="lg:w-6/12">
<h3 class="font-normal text-large mb-3">Handlungsfeld</h3> <h3 class="font-normal text-large text-gray-900 mb-3">Handlungsfeld</h3>
<h1 class="mb-4 lg:mb-8">{{ mediaCategory.title }}</h1> <h1 class="mb-4 lg:mb-8">{{ mediaCategory.title }}</h1>
<p class="text-xl">{{ mediaCategory.introduction_text }}</p> <p class="text-large">{{ mediaCategory.introduction_text }}</p>
</div> </div>
<div> <div>
<img <img
@ -108,6 +116,9 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
content_collection.value.contents[0].type content_collection.value.contents[0].type
), ),
'border-t': !displayAsCard(content_collection.value.contents[0].type), 'border-t': !displayAsCard(content_collection.value.contents[0].type),
'border-gray-500': !displayAsCard(
content_collection.value.contents[0].type
),
'mb-6': hasMoreItemsForType( 'mb-6': hasMoreItemsForType(
content_collection.value.contents[0].type, content_collection.value.contents[0].type,
content_collection.value.contents content_collection.value.contents
@ -130,13 +141,17 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
:link-text="mediaItem.value.link_display_text" :link-text="mediaItem.value.link_display_text"
:open-window="mediaItem.value.open_window" :open-window="mediaItem.value.open_window"
/> />
<div v-else class="flex items-center justify-between border-b py-4"> <div
v-else
class="flex items-center justify-between border-b border-gray-500 py-4"
>
<h4 class="text-bold">{{ mediaItem.value.title }}</h4> <h4 class="text-bold">{{ mediaItem.value.title }}</h4>
<media-link <media-link
:blank="mediaItem.value.open_window" :blank="mediaItem.value.open_window"
:to="mediaItem.value.url" :to="mediaItem.value.url"
class="link" class="link"
>{{ mediaItem.value.link_display_text }} >
{{ mediaItem.value.link_display_text }}
</media-link> </media-link>
</div> </div>
</li> </li>
@ -149,7 +164,7 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
) )
" "
:to="`${mediaCategory.frontend_url}/media`" :to="`${mediaCategory.frontend_url}/media`"
class="flex items-center" class="btn-text inline-flex items-center pl-0 py-2"
> >
<span>Alle anschauen</span> <span>Alle anschauen</span>
<it-icon-arrow-right></it-icon-arrow-right> <it-icon-arrow-right></it-icon-arrow-right>

View File

@ -40,14 +40,14 @@ const mediaList = computed(() => {
> >
<div class="bg-gray-200"> <div class="bg-gray-200">
<div class="container-large"> <div class="container-large">
<nav> <nav class="py-4 lg:pb-8">
<a <router-link
class="block my-9 cursor-pointer flex items-center" class="btn-text inline-flex items-center pl-0"
:href="mediaStore.mediaLibraryPage.frontend_url" :to="mediaStore.mediaLibraryPage.frontend_url"
> >
<it-icon-arrow-left /> <it-icon-arrow-left />
<span>zurück</span></a <span>zurück</span>
> </router-link>
</nav> </nav>
<h1 class="mb-4">{{ mediaList.title }}</h1> <h1 class="mb-4">{{ mediaList.title }}</h1>
</div> </div>

View File

@ -25,9 +25,19 @@ onMounted(async () => {
<template> <template>
<div class="bg-gray-200"> <div class="bg-gray-200">
<nav class="px-6 py-4 border-b border-gray-500 bg-white"> <nav class="px-6 py-4 border-b border-gray-500 bg-white">
<ul class="flex text-xl flex-col lg:flex-row"> <ul v-if="mediaLibraryStore.mediaLibraryPage" class="flex flex-col lg:flex-row">
<li>Übersicht</li> <li>
<li class="lg:ml-12">Handlungsfelder</li> <router-link :to="mediaLibraryStore.mediaLibraryPage.frontend_url">
Übersicht
</router-link>
</li>
<li class="lg:ml-12">
<router-link
:to="`${mediaLibraryStore.mediaLibraryPage.frontend_url}/category`"
>
Handlungsfelder
</router-link>
</li>
<li class="lg:ml-12">Allgemeines zu Versicherungen</li> <li class="lg:ml-12">Allgemeines zu Versicherungen</li>
<li class="lg:ml-12">Lernmedien</li> <li class="lg:ml-12">Lernmedien</li>
<li class="lg:ml-12"> <li class="lg:ml-12">

View File

@ -1,11 +1,18 @@
import { itGet } from "@/fetchHelpers"; import { itGet } from "@/fetchHelpers";
import { useCompletionStore } from "@/stores/completion"; import { useCompletionStore } from "@/stores/completion";
import type { CompetenceProfilePage, PerformanceCriteria } from "@/types"; import type {
CompetencePage,
CompetenceProfilePage,
CourseWagtailPage,
PerformanceCriteria,
} from "@/types";
import _ from "lodash"; import _ from "lodash";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
export type CompetenceStoreState = { export type CompetenceStoreState = {
competenceProfilePage: CompetenceProfilePage | undefined; competenceProfilePage: CompetenceProfilePage | undefined;
selectedCircle: { id: string; name: string };
availableCircles: { id: string; name: string }[];
}; };
export const useCompetenceStore = defineStore({ export const useCompetenceStore = defineStore({
@ -13,10 +20,12 @@ export const useCompetenceStore = defineStore({
state: () => { state: () => {
return { return {
competenceProfilePage: undefined, competenceProfilePage: undefined,
selectedCircle: { id: "all", name: "Circle: Alle" },
availableCircles: [],
} as CompetenceStoreState; } as CompetenceStoreState;
}, },
getters: { getters: {
flatPerformanceCriteria: (state, circleTitle = "") => { flatPerformanceCriteria: (state) => {
if (!state.competenceProfilePage) { if (!state.competenceProfilePage) {
return []; return [];
} }
@ -29,12 +38,40 @@ export const useCompetenceStore = defineStore({
["asc"] ["asc"]
); );
if (circleTitle) { if (state.selectedCircle.id !== "all") {
criteria = criteria.filter((c) => c.circle === circleTitle); criteria = criteria.filter(
(c) => c.circle.translation_key === state.selectedCircle.id
);
} }
return criteria; return criteria;
}, },
competences: (state) => {
if (state.competenceProfilePage?.children.length) {
return state.competenceProfilePage.children.filter((competence) => {
let criteria = competence.children;
if (state.selectedCircle.id != "all") {
criteria = criteria.filter((criteria) => {
return criteria.circle.translation_key === state.selectedCircle.id;
});
}
return criteria.length > 0;
});
}
return [];
},
criteriaByCompetence: (state) => {
return (competence: CompetencePage) => {
return competence.children.filter((criteria) => {
if (state.selectedCircle.id != "all") {
return criteria.circle.translation_key === state.selectedCircle.id;
}
return competence.children;
});
};
},
}, },
actions: { actions: {
calcStatusCount(criteria: PerformanceCriteria[]) { calcStatusCount(criteria: PerformanceCriteria[]) {
@ -65,6 +102,12 @@ export const useCompetenceStore = defineStore({
} }
this.competenceProfilePage = competenceProfilePageData; this.competenceProfilePage = competenceProfilePageData;
const circles = competenceProfilePageData.circles.map((c: CourseWagtailPage) => {
return { id: c.translation_key, name: `Circle: ${c.title}` };
});
this.availableCircles = [{ id: "all", name: "Circle: Alle" }, ...circles];
await this.parseCompletionData(); await this.parseCompletionData();
return this.competenceProfilePage; return this.competenceProfilePage;

View File

@ -1,5 +1,6 @@
import { itGet } from "@/fetchHelpers"; import { itGet } from "@/fetchHelpers";
import type { MediaLibraryPage } from "@/types"; import type { MediaLibraryPage } from "@/types";
import log from "loglevel";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
export type MediaLibraryStoreState = { export type MediaLibraryStoreState = {
@ -26,6 +27,7 @@ export const useMediaLibraryStore = defineStore({
if (this.mediaLibraryPage && !reload) { if (this.mediaLibraryPage && !reload) {
return this.mediaLibraryPage; return this.mediaLibraryPage;
} }
log.debug("load mediaLibraryPageData");
const mediaLibraryPageData = await itGet(`/api/course/page/${slug}/`); const mediaLibraryPageData = await itGet(`/api/course/page/${slug}/`);
if (!mediaLibraryPageData) { if (!mediaLibraryPageData) {

View File

@ -125,6 +125,13 @@ export interface CourseWagtailPage {
completion_status_updated_at: string; completion_status_updated_at: string;
} }
export interface CircleLight {
readonly id: number;
readonly title: string;
readonly slug: string;
readonly translation_key: string;
}
export interface LearningContent extends CourseWagtailPage { export interface LearningContent extends CourseWagtailPage {
type: "learnpath.LearningContent"; type: "learnpath.LearningContent";
minutes: number; minutes: number;
@ -296,7 +303,7 @@ export interface MediaLibraryPage extends CourseWagtailPage {
export interface PerformanceCriteria extends CourseWagtailPage { export interface PerformanceCriteria extends CourseWagtailPage {
type: "competence.PerformanceCriteria"; type: "competence.PerformanceCriteria";
competence_id: string; competence_id: string;
circle: string; circle: CircleLight;
course_category: CourseCategory; course_category: CourseCategory;
learning_unit: CourseWagtailPage; learning_unit: CourseWagtailPage;
} }
@ -310,5 +317,6 @@ export interface CompetencePage extends CourseWagtailPage {
export interface CompetenceProfilePage extends CourseWagtailPage { export interface CompetenceProfilePage extends CourseWagtailPage {
type: "competence.CompetenceProfilePage"; type: "competence.CompetenceProfilePage";
course: Course; course: Course;
circles: CircleLight[];
children: CompetencePage[]; children: CompetencePage[];
} }

View File

@ -66,6 +66,11 @@ svg {
.container-large { .container-large {
@apply mx-auto max-w-6xl w-full px-4 lg:px-8 py-4; @apply mx-auto max-w-6xl w-full px-4 lg:px-8 py-4;
} }
.filter-blue-900 {
filter: invert(9%) sepia(38%) saturate(5684%) hue-rotate(200deg) brightness(95%)
contrast(105%);
}
} }
@layer components { @layer components {
@ -95,7 +100,8 @@ svg {
} }
.btn-text { .btn-text {
@apply font-semibold py-2 px-4 align-middle inline-block @apply font-normal py-2 px-4 align-middle inline-block
text-blue-900
hover:text-gray-700 hover:text-gray-700
disabled:opacity-50 disabled:cursor-not-allowed; disabled:opacity-50 disabled:cursor-not-allowed;
} }

View File

@ -1,96 +1,180 @@
# create a new version of this docker image # create a new version of this docker image
# > docker build -t iterativ/vbv-lernwelt-bitbucket . # > docker build --platform linux/amd64 -t iterativ/vbv-lernwelt-bitbucket .
# push new version to Docker Hub # push new version to Docker Hub
# > docker push iterativ/vbv-lernwelt-bitbucket # > docker push iterativ/vbv-lernwelt-bitbucket
# run locally with directory mounted # run locally with directory mounted
# > docker run -v "$(pwd)":/src -it iterativ/vbv-lernwelt-bitbucket /bin/bash # > docker run -v "$(pwd)":/src -it iterativ/vbv-lernwelt-bitbucket /bin/bash
FROM python:3.10-bullseye FROM cypress/included:10.9.0
MAINTAINER Daniel Egger <daniel.egger@iterativ.ch> MAINTAINER Daniel Egger <daniel.egger@iterativ.ch>
ARG DEBIAN_FRONTEND=noninteractive # install python see https://github.com/docker-library/python
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
# Install node prereqs, nodejs and yarn
# Ref: https://deb.nodesource.com/setup_16.x
# Ref: https://yarnpkg.com/en/docs/install
# https://github.com/nikolaik/docker-python-nodejs
RUN \
echo "deb https://deb.nodesource.com/node_16.x bullseye main" > /etc/apt/sources.list.d/nodesource.list && \
wget -qO- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list && \
wget -qO- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
apt-get update && \
apt-get install -yqq nodejs yarn && \
pip install -U pip && pip install pipenv && \
npm i -g npm@^6
# Install Cypress deps # ensure local python is preferred over distribution python
# https://github.com/cypress-io/cypress-docker-images/blob/master/base/16.5.0/Dockerfile ENV PATH /usr/local/bin:$PATH
RUN apt-get update && \
apt-get install --no-install-recommends -y \
libgtk2.0-0 \
libgtk-3-0 \
libnotify-dev \
libgconf-2-4 \
libgbm-dev \
libnss3 \
libxss1 \
libasound2 \
libxtst6 \
xauth \
xvfb \
# install text editors
vim-tiny \
nano \
# install emoji font
fonts-noto-color-emoji \
# install Chinese fonts
# this list was copied from https://github.com/jim3ma/docker-leanote
fonts-arphic-bkai00mp \
fonts-arphic-bsmi00lp \
fonts-arphic-gbsn00lp \
fonts-arphic-gkai00mp \
fonts-arphic-ukai \
fonts-arphic-uming \
ttf-wqy-zenhei \
ttf-wqy-microhei \
xfonts-wqy
RUN npm --version # http://bugs.python.org/issue19846
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
ENV LANG C.UTF-8
RUN npm install -g yarn@latest --force # runtime dependencies
RUN yarn --version RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates \
netbase \
tzdata \
; \
rm -rf /var/lib/apt/lists/*
# a few environment variables to make NPM installs easier ENV GPG_KEY A035C8C19219BA821ECEA86B64E628F8D684696D
# good colors for most applications ENV PYTHON_VERSION 3.10.7
ENV TERM xterm
# avoid million NPM install messages
ENV npm_config_loglevel warn
# allow installing when the main user is root
ENV npm_config_unsafe_perm true
# Node libraries RUN set -eux; \
RUN node -p process.versions \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends \
dpkg-dev \
gcc \
gnupg dirmngr \
libbluetooth-dev \
libbz2-dev \
libc6-dev \
libexpat1-dev \
libffi-dev \
libgdbm-dev \
liblzma-dev \
libncursesw5-dev \
libreadline-dev \
libsqlite3-dev \
libssl-dev \
make \
tk-dev \
uuid-dev \
wget \
xz-utils \
zlib1g-dev \
; \
\
wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz"; \
wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc"; \
GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$GPG_KEY"; \
gpg --batch --verify python.tar.xz.asc python.tar.xz; \
command -v gpgconf > /dev/null && gpgconf --kill all || :; \
rm -rf "$GNUPGHOME" python.tar.xz.asc; \
mkdir -p /usr/src/python; \
tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \
rm python.tar.xz; \
\
cd /usr/src/python; \
gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \
./configure \
--build="$gnuArch" \
--enable-loadable-sqlite-extensions \
--enable-optimizations \
--enable-option-checking=fatal \
--enable-shared \
--with-lto \
--with-system-expat \
--without-ensurepip \
; \
nproc="$(nproc)"; \
make -j "$nproc" \
LDFLAGS="-Wl,--strip-all" \
; \
make install; \
\
cd /; \
rm -rf /usr/src/python; \
\
find /usr/local -depth \
\( \
\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
-o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \) \) \
\) -exec rm -rf '{}' + \
; \
\
ldconfig; \
\
apt-mark auto '.*' > /dev/null; \
apt-mark manual $savedAptMark; \
find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \
| awk '/=>/ { print $(NF-1) }' \
| sort -u \
| xargs -r dpkg-query --search \
| cut -d: -f1 \
| sort -u \
| xargs -r apt-mark manual \
; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*; \
\
python3 --version
# Show where Node loads required modules from # make some useful symlinks that are expected to exist ("/usr/local/bin/python" and friends)
RUN node -p 'module.paths' RUN set -eux; \
for src in idle3 pydoc3 python3 python3-config; do \
dst="$(echo "$src" | tr -d 3)"; \
[ -s "/usr/local/bin/$src" ]; \
[ ! -e "/usr/local/bin/$dst" ]; \
ln -svT "$src" "/usr/local/bin/$dst"; \
done
# install postgresql # if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value '<VERSION>'"
RUN apt-get install -y postgresql postgresql-contrib libpq-dev ENV PYTHON_PIP_VERSION 22.2.2
# https://github.com/docker-library/python/issues/365
ENV PYTHON_SETUPTOOLS_VERSION 63.2.0
# https://github.com/pypa/get-pip
ENV PYTHON_GET_PIP_URL https://github.com/pypa/get-pip/raw/5eaac1050023df1f5c98b173b248c260023f2278/public/get-pip.py
ENV PYTHON_GET_PIP_SHA256 5aefe6ade911d997af080b315ebcb7f882212d070465df544e1175ac2be519b4
RUN set -eux; \
\
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends wget; \
\
wget -O get-pip.py "$PYTHON_GET_PIP_URL"; \
echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum -c -; \
\
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*; \
\
export PYTHONDONTWRITEBYTECODE=1; \
\
python get-pip.py \
--disable-pip-version-check \
--no-cache-dir \
--no-compile \
"pip==$PYTHON_PIP_VERSION" \
"setuptools==$PYTHON_SETUPTOOLS_VERSION" \
; \
rm -f get-pip.py; \
\
pip --version
# install postgresql \
RUN apt install curl ca-certificates gnupg
RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| gpg --dearmor \
| tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null
RUN sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" > /etc/apt/sources.list.d/postgresql.list'
RUN apt update
RUN apt-get install -y postgresql postgresql-contrib libpq-dev
# Required by python3-saml # Required by python3-saml
RUN apt-get -y install libxmlsec1-dev pkg-config gettext RUN apt-get -y install libxmlsec1-dev pkg-config gettext
# install git-crypt # install git-crypt
RUN apt-get -y install git-crypt RUN apt-get -y install git-crypt
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
# Install Google Chrome
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
RUN apt-get update && apt-get install -y google-chrome-stable
# versions of local tools # versions of local tools
RUN echo " node version: $(node -v) \n" \ RUN echo " node version: $(node -v) \n" \

View File

@ -73,7 +73,7 @@ else
fi fi
if [ "$START_BACKGROUND" = true ]; then if [ "$START_BACKGROUND" = true ]; then
python3 server/manage.py runserver "${DJANGO_PORT}" --settings="$DJANGO_SETTINGS_MODULE" > /dev/null & cd server && python3 manage.py runserver "${DJANGO_PORT}" --settings="$DJANGO_SETTINGS_MODULE" > /dev/null &
else else
python3 server/manage.py runserver "${DJANGO_PORT}" --settings="$DJANGO_SETTINGS_MODULE" cd server && python3 manage.py runserver "${DJANGO_PORT}" --settings="$DJANGO_SETTINGS_MODULE"
fi fi

View File

@ -580,7 +580,7 @@ if APP_ENVIRONMENT == "development":
# django-extensions # django-extensions
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration
INSTALLED_APPS += ["django_extensions"] # noqa F405 INSTALLED_APPS += ["django_extensions", "django_watchfiles"] # noqa F405
if APP_ENVIRONMENT in ["production", "caprover"] or APP_ENVIRONMENT.startswith( if APP_ENVIRONMENT in ["production", "caprover"] or APP_ENVIRONMENT.startswith(
"caprover" "caprover"

View File

@ -1,8 +1,6 @@
-r requirements.in -r requirements.in
Werkzeug[watchdog] # https://github.com/pallets/werkzeug
ipdb # https://github.com/gotcha/ipdb ipdb # https://github.com/gotcha/ipdb
watchgod # https://github.com/samuelcolvin/watchgod
pip-tools pip-tools
# Testing # Testing
@ -33,5 +31,8 @@ django-extensions # https://github.com/django-extensions/django-extensions
django-coverage-plugin # https://github.com/nedbat/django_coverage_plugin django-coverage-plugin # https://github.com/nedbat/django_coverage_plugin
pytest-django # https://github.com/pytest-dev/pytest-django pytest-django # https://github.com/pytest-dev/pytest-django
# django-watchfiles custom PR
https://github.com/q0w/django-watchfiles/archive/issue-1.zip
# code checking # code checking
truffleHog truffleHog

View File

@ -7,7 +7,7 @@
anyascii==0.3.1 anyascii==0.3.1
# via wagtail # via wagtail
anyio==3.5.0 anyio==3.5.0
# via watchgod # via watchfiles
appnope==0.1.2 appnope==0.1.2
# via ipython # via ipython
argon2-cffi==21.3.0 argon2-cffi==21.3.0
@ -15,9 +15,7 @@ argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0 argon2-cffi-bindings==21.2.0
# via argon2-cffi # via argon2-cffi
asgiref==3.5.0 asgiref==3.5.0
# via # via django
# django
# uvicorn
astroid==2.11.2 astroid==2.11.2
# via pylint # via pylint
asttokens==2.0.5 asttokens==2.0.5
@ -35,10 +33,12 @@ backcall==0.2.0
# via ipython # via ipython
beautifulsoup4==4.9.3 beautifulsoup4==4.9.3
# via wagtail # via wagtail
black==22.8.0 black==22.10.0
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# ufmt # ufmt
build==0.8.0
# via pip-tools
certifi==2021.10.8 certifi==2021.10.8
# via # via
# requests # requests
@ -82,11 +82,12 @@ dill==0.3.4
# via pylint # via pylint
distlib==0.3.4 distlib==0.3.4
# via virtualenv # via virtualenv
dj-database-url==0.5.0 dj-database-url==1.0.0
# via -r requirements.in # via -r requirements.in
django==3.2.13 django==3.2.13
# via # via
# -r requirements.in # -r requirements.in
# dj-database-url
# django-cors-headers # django-cors-headers
# django-csp # django-csp
# django-debug-toolbar # django-debug-toolbar
@ -100,6 +101,7 @@ django==3.2.13
# django-stubs-ext # django-stubs-ext
# django-taggit # django-taggit
# django-treebeard # django-treebeard
# django-watchfiles
# djangorestframework # djangorestframework
# drf-spectacular # drf-spectacular
# wagtail # wagtail
@ -140,6 +142,8 @@ django-taggit==2.1.0
# via wagtail # via wagtail
django-treebeard==4.5.1 django-treebeard==4.5.1
# via wagtail # via wagtail
django-watchfiles @ https://github.com/q0w/django-watchfiles/archive/issue-1.zip
# via -r requirements-dev.in
djangorestframework==3.13.1 djangorestframework==3.13.1
# via # via
# -r requirements.in # -r requirements.in
@ -222,9 +226,7 @@ libcst==0.4.7
# ufmt # ufmt
# usort # usort
markupsafe==2.1.1 markupsafe==2.1.1
# via # via jinja2
# jinja2
# werkzeug
marshmallow==3.15.0 marshmallow==3.15.0
# via environs # via environs
matplotlib-inline==0.1.3 matplotlib-inline==0.1.3
@ -253,6 +255,7 @@ openpyxl==3.0.9
# via tablib # via tablib
packaging==21.3 packaging==21.3
# via # via
# build
# marshmallow # marshmallow
# pytest # pytest
# pytest-sugar # pytest-sugar
@ -264,7 +267,7 @@ pathspec==0.9.0
# black # black
# trailrunner # trailrunner
pep517==0.12.0 pep517==0.12.0
# via pip-tools # via build
pexpect==4.8.0 pexpect==4.8.0
# via ipython # via ipython
pickleshare==0.7.5 pickleshare==0.7.5
@ -273,7 +276,7 @@ pillow==9.0.1
# via # via
# -r requirements.in # -r requirements.in
# wagtail # wagtail
pip-tools==6.6.2 pip-tools==6.9.0
# via -r requirements-dev.in # via -r requirements-dev.in
platformdirs==2.5.1 platformdirs==2.5.1
# via # via
@ -318,7 +321,7 @@ pyparsing==3.0.7
# via packaging # via packaging
pyrsistent==0.18.1 pyrsistent==0.18.1
# via jsonschema # via jsonschema
pytest==7.1.1 pytest==7.1.3
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# pytest-django # pytest-django
@ -403,6 +406,7 @@ toml==0.10.2
tomli==2.0.1 tomli==2.0.1
# via # via
# black # black
# build
# django-stubs # django-stubs
# mypy # mypy
# pep517 # pep517
@ -450,7 +454,7 @@ urllib3==1.26.9
# sentry-sdk # sentry-sdk
usort==1.0.5 usort==1.0.5
# via ufmt # via ufmt
uvicorn[standard]==0.17.6 uvicorn[standard]==0.18.3
# via -r requirements.in # via -r requirements.in
uvloop==0.16.0 uvloop==0.16.0
# via uvicorn # via uvicorn
@ -465,11 +469,9 @@ wagtail-factories==2.0.1
# via -r requirements.in # via -r requirements.in
wagtail-localize==1.2.1 wagtail-localize==1.2.1
# via -r requirements.in # via -r requirements.in
watchdog==2.1.9 watchfiles==0.17.0
# via werkzeug
watchgod==0.8.2
# via # via
# -r requirements-dev.in # django-watchfiles
# uvicorn # uvicorn
wcwidth==0.2.5 wcwidth==0.2.5
# via prompt-toolkit # via prompt-toolkit
@ -477,8 +479,6 @@ webencodings==0.5.1
# via html5lib # via html5lib
websockets==10.2 websockets==10.2
# via uvicorn # via uvicorn
werkzeug[watchdog]==2.2.0
# via -r requirements-dev.in
wheel==0.37.1 wheel==0.37.1
# via pip-tools # via pip-tools
whitenoise==6.0.0 whitenoise==6.0.0

View File

@ -7,15 +7,13 @@
anyascii==0.3.1 anyascii==0.3.1
# via wagtail # via wagtail
anyio==3.5.0 anyio==3.5.0
# via watchgod # via watchfiles
argon2-cffi==21.3.0 argon2-cffi==21.3.0
# via -r requirements.in # via -r requirements.in
argon2-cffi-bindings==21.2.0 argon2-cffi-bindings==21.2.0
# via argon2-cffi # via argon2-cffi
asgiref==3.5.0 asgiref==3.5.0
# via # via django
# django
# uvicorn
async-timeout==4.0.2 async-timeout==4.0.2
# via redis # via redis
attrs==21.4.0 attrs==21.4.0
@ -44,15 +42,15 @@ cryptography==36.0.2
# via authlib # via authlib
deprecated==1.2.13 deprecated==1.2.13
# via redis # via redis
dj-database-url==0.5.0 dj-database-url==1.0.0
# via -r requirements.in # via -r requirements.in
django==3.2.13 django==3.2.13
# via # via
# -r requirements.in # -r requirements.in
# dj-database-url
# django-cors-headers # django-cors-headers
# django-csp # django-csp
# django-filter # django-filter
# django-htmx
# django-model-utils # django-model-utils
# django-modelcluster # django-modelcluster
# django-permissionedforms # django-permissionedforms
@ -204,7 +202,7 @@ urllib3==1.26.9
# via # via
# requests # requests
# sentry-sdk # sentry-sdk
uvicorn[standard]==0.17.6 uvicorn[standard]==0.18.3
# via -r requirements.in # via -r requirements.in
uvloop==0.16.0 uvloop==0.16.0
# via uvicorn # via uvicorn
@ -217,7 +215,7 @@ wagtail-factories==2.0.1
# via -r requirements.in # via -r requirements.in
wagtail-localize==1.2.1 wagtail-localize==1.2.1
# via -r requirements.in # via -r requirements.in
watchgod==0.8.1 watchfiles==0.17.0
# via uvicorn # via uvicorn
webencodings==0.5.1 webencodings==0.5.1
# via html5lib # via html5lib

View File

@ -6,7 +6,7 @@ from wagtail.fields import StreamField
from wagtail.models import Page from wagtail.models import Page
from vbv_lernwelt.core.model_utils import find_available_slug from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class
class CompetenceProfilePage(Page): class CompetenceProfilePage(Page):
@ -32,6 +32,7 @@ class CompetenceProfilePage(Page):
cls, cls,
[ [
"course", "course",
"circles",
"children", "children",
], ],
) )

View File

@ -1,8 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from vbv_lernwelt.competence.models import PerformanceCriteria from vbv_lernwelt.competence.models import PerformanceCriteria
from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class
from vbv_lernwelt.course.serializers import CourseCategorySerializer from vbv_lernwelt.course.serializers import CourseCategorySerializer
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
class PerformanceCriteriaSerializer( class PerformanceCriteriaSerializer(
@ -33,7 +33,8 @@ class PerformanceCriteriaSerializer(
return LearningUnitPerformanceCriteriaSerializer(obj.learning_unit).data return LearningUnitPerformanceCriteriaSerializer(obj.learning_unit).data
def get_circle(self, obj): def get_circle(self, obj):
return obj.learning_unit.get_parent().specific.title c = obj.learning_unit.get_parent()
return {"id": c.id, "title": c.title, "translation_key": c.translation_key}
def get_course_category(self, obj): def get_course_category(self, obj):
if obj.learning_unit: if obj.learning_unit:

View File

@ -35,6 +35,7 @@ class ItBaseSerializer(wagtail_serializers.BaseSerializer):
course = SerializerMethodField() course = SerializerMethodField()
course_category = CourseCategorySerializer(read_only=True) course_category = CourseCategorySerializer(read_only=True)
frontend_url = SerializerMethodField() frontend_url = SerializerMethodField()
circles = SerializerMethodField()
meta_fields = [] meta_fields = []
@ -62,6 +63,27 @@ class ItBaseSerializer(wagtail_serializers.BaseSerializer):
return CourseSerializer(course_parent_page.specific.course).data return CourseSerializer(course_parent_page.specific.course).data
return "" return ""
def get_circles(self, obj):
course_parent_page = obj.get_ancestors().exact_type(CoursePage).last()
if course_parent_page:
from vbv_lernwelt.learnpath.models import Circle, LearningPath
circles = (
course_parent_page.get_children()
.exact_type(LearningPath)
.first()
.get_children()
.exact_type(Circle)
)
return [
{"id": c.id, "title": c.title, "translation_key": c.translation_key}
for c in circles
]
return []
def get_frontend_url(self, obj): def get_frontend_url(self, obj):
if hasattr(obj, "get_frontend_url"): if hasattr(obj, "get_frontend_url"):
return obj.get_frontend_url() return obj.get_frontend_url()

View File

@ -564,7 +564,7 @@ def create_circle_abschluss(lp):
def create_circle_betreuen(lp): def create_circle_betreuen(lp):
circle = CircleFactory( circle = CircleFactory(
title="Abschluss", title="Betreuen",
parent=lp, parent=lp,
) )
LearningSequenceFactory(title="Starten", parent=circle, icon="it-icon-ls-start") LearningSequenceFactory(title="Starten", parent=circle, icon="it-icon-ls-start")

View File

@ -8,6 +8,7 @@ from wagtail.images.blocks import ImageChooserBlock
from wagtail.models import Page from wagtail.models import Page
from vbv_lernwelt.core.model_utils import find_available_slug from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class
from vbv_lernwelt.course.models import CoursePage from vbv_lernwelt.course.models import CoursePage
from vbv_lernwelt.learnpath.models_learning_unit_content import ( from vbv_lernwelt.learnpath.models_learning_unit_content import (
AssignmentBlock, AssignmentBlock,
@ -21,7 +22,6 @@ from vbv_lernwelt.learnpath.models_learning_unit_content import (
TestBlock, TestBlock,
VideoBlock, VideoBlock,
) )
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
class LearningPath(Page): class LearningPath(Page):

View File

@ -3,8 +3,8 @@ from rest_framework.fields import SerializerMethodField
from vbv_lernwelt.competence.serializers import ( from vbv_lernwelt.competence.serializers import (
PerformanceCriteriaLearningPathSerializer, PerformanceCriteriaLearningPathSerializer,
) )
from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class
from vbv_lernwelt.learnpath.models import LearningUnit from vbv_lernwelt.learnpath.models import LearningUnit
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
class LearningUnitSerializer( class LearningUnitSerializer(

View File

@ -148,13 +148,15 @@ die der Fahrzeugbesitzer und die Fahrzeugbesitzerin in einem grösseren Schadenf
contents=[ contents=[
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="VBV 303/12.3 Verkehrsrechtsschutz", title="Rechtsstreitigkeiten",
description="VBV 303/12.3 Verkehrsrechtsschutz",
url="/media/versicherungsvermittlerin-media/category/rechtsstreitigkeiten", url="/media/versicherungsvermittlerin-media/category/rechtsstreitigkeiten",
) )
), ),
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="VBV 303/13 Reiseversicherung", title="Reisen",
description="VBV 303/13 Reiseversicherung",
url="/media/versicherungsvermittlerin-media/category/reisen", url="/media/versicherungsvermittlerin-media/category/reisen",
) )
), ),
@ -250,19 +252,22 @@ Diese können negative Folgen verschiedener Art nach sich ziehen, darunter recht
contents=[ contents=[
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="VBV 303/03 Hausratversicherung", title="Haushalt",
description="VBV 303/03 Hausratversicherung",
url="/media/versicherungsvermittlerin-media/category/haushalt", url="/media/versicherungsvermittlerin-media/category/haushalt",
) )
), ),
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="VBV 303/12 Rechtschutzversicherung", title="Rechtsstreitigkeiten",
desciption="VBV 303/12 Rechtschutzversicherung",
url="/media/versicherungsvermittlerin-media/category/rechtsstreitigkeiten", url="/media/versicherungsvermittlerin-media/category/rechtsstreitigkeiten",
) )
), ),
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="VBV 304/Teil E Obligatorische Krankenversicherung", title="Gesundheit",
description="VBV 304/Teil E Obligatorische Krankenversicherung",
url="/media/versicherungsvermittlerin-media/category/gesundheit", url="/media/versicherungsvermittlerin-media/category/gesundheit",
) )
), ),

View File

@ -7,7 +7,7 @@ from wagtail.fields import StreamField
from wagtail.models import Page from wagtail.models import Page
from vbv_lernwelt.core.model_utils import find_available_slug from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class from vbv_lernwelt.core.serializer_helpers import get_it_serializer_class
from vbv_lernwelt.media_library.content_blocks import MediaContentCollection from vbv_lernwelt.media_library.content_blocks import MediaContentCollection

View File

@ -99,8 +99,8 @@ class RelativeLinkBlockFactory(wagtail_factories.StructBlockFactory):
class Meta: class Meta:
model = RelativeLinkBlock model = RelativeLinkBlock
title = "Platzhalter Querverweis" title = "Fahrzeug"
description = "Handlungsfeld" description = "Platzhalter Querverweis"
link_display_text = "Handlungsfeld anzeigen" link_display_text = "Handlungsfeld anzeigen"
icon_url = "/static/icons/demo/icon-hf-reisen.svg" icon_url = "/static/icons/demo/icon-hf-reisen.svg"
url = "/media/versicherungsvermittlerin-media/category/fahrzeug" url = "/media/versicherungsvermittlerin-media/category/fahrzeug"

View File

@ -4,6 +4,7 @@
"ignore hash 3": "OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==", "ignore hash 3": "OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==",
"ignore hash 4": "1NpUCSvAKLpDZL9e3tqDaUe8Kk2xAuF1tXosFjBanc4lFCgNcfBp02MD3UjB72ZS", "ignore hash 4": "1NpUCSvAKLpDZL9e3tqDaUe8Kk2xAuF1tXosFjBanc4lFCgNcfBp02MD3UjB72ZS",
"ignore hash 5": "1LhwZ0DvP4cGBgbBdCfaBQV7eiaOc4jWKdzO9WEXLFT7AaqBN6jqd0uyaZeAZ19K", "ignore hash 5": "1LhwZ0DvP4cGBgbBdCfaBQV7eiaOc4jWKdzO9WEXLFT7AaqBN6jqd0uyaZeAZ19K",
"ignore hash 6": "A035C8C19219BA821ECEA86B64E628F8D684696D",
"json base64 content": "regex:\"content\": \"", "json base64 content": "regex:\"content\": \"",
"img base64 content": "regex:data:image/png;base64,.*" "img base64 content": "regex:data:image/png;base64,.*"
} }

View File

@ -1,5 +1,6 @@
server/requirements/ server/requirements/
env_secrets/ env_secrets/
env/bitbucket/Dockerfile
env/docker_local.env env/docker_local.env
server/vbv_lernwelt/static/ server/vbv_lernwelt/static/
server/vbv_lernwelt/media/ server/vbv_lernwelt/media/