Add Edoniq testblock

WIP: Add sso code

Update content name

WIP: Add redirect endpoint

mend

Fix after rebase

WIP: Update model

WIP: Add extended time test url

Update trufflehog config, use sso

Update test
This commit is contained in:
Christian Cueni 2023-07-18 16:31:23 +02:00
parent f7de5bae47
commit c140f225ea
30 changed files with 796 additions and 373 deletions

View File

@ -106,6 +106,9 @@
"noDueDatesAvailable": "Keine Termine vorhanden", "noDueDatesAvailable": "Keine Termine vorhanden",
"showAllDueDates": "Alle Termine anzeigen" "showAllDueDates": "Alle Termine anzeigen"
}, },
"edoniqTest": {
"qualifiesForExtendedTime": "Ich habe Anrecht auf einen Nachteilsausgleich."
},
"feedback": { "feedback": {
"answers": "Antworten", "answers": "Antworten",
"areYouSatisfied": "Wie zufrieden bist du?", "areYouSatisfied": "Wie zufrieden bist du?",

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue"; import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue";
import DocumentListBlock from "@/pages/learningPath/learningContentPage/blocks/DocumentListBlock.vue"; import DocumentListBlock from "@/pages/learningPath/learningContentPage/blocks/DocumentListBlock.vue";
import TestBlock from "@/pages/learningPath/learningContentPage/blocks/TestBlock.vue"; import EdoniqTestBlock from "@/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue";
import { useCircleStore } from "@/stores/circle"; import { useCircleStore } from "@/stores/circle";
import type { LearningContent, LearningContentType } from "@/types"; import type { LearningContent, LearningContentType } from "@/types";
import eventBus from "@/utils/eventBus"; import eventBus from "@/utils/eventBus";
@ -35,7 +35,7 @@ const COMPONENTS: Record<LearningContentType, Component> = {
"learnpath.LearningContentMediaLibrary": MediaLibraryBlock, "learnpath.LearningContentMediaLibrary": MediaLibraryBlock,
"learnpath.LearningContentPlaceholder": PlaceholderBlock, "learnpath.LearningContentPlaceholder": PlaceholderBlock,
"learnpath.LearningContentRichText": RichTextBlock, "learnpath.LearningContentRichText": RichTextBlock,
"learnpath.LearningContentTest": TestBlock, "learnpath.LearningContentEdoniqTest": EdoniqTestBlock,
"learnpath.LearningContentVideo": VideoBlock, "learnpath.LearningContentVideo": VideoBlock,
}; };
const DEFAULT_BLOCK = PlaceholderBlock; const DEFAULT_BLOCK = PlaceholderBlock;

View File

@ -0,0 +1,118 @@
<script setup lang="ts">
import { useTranslation } from "i18next-vue";
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
import type { LearningContentEdoniqTest } from "@/types";
import { ref } from "vue";
import * as log from "loglevel";
import { itPost } from "@/fetchHelpers";
const { t } = useTranslation();
const props = defineProps<{
content: LearningContentEdoniqTest;
}>();
const termsAccepted = ref(false);
const extendedTimeTest = ref(false);
async function startTest() {
log.info("start test", props.content);
const response = await itPost("/api/core/edoniq-test/redirect/", {
learning_content_id: props.content.id,
extended_time_test: extendedTimeTest.value,
});
log.info(response.redirect_url);
window.open(response.redirect_url, "_blank");
}
</script>
<template>
<LearningContentSimpleLayout
:title="props.content.title"
:learning-content="props.content"
>
<!-- eslint-disable vue/no-v-html -->
<div class="container-medium">
<p
v-if="props.content.description"
class="default-wagtail-rich-text text-large my-4"
v-html="props.content.description"
></p>
<div class="my-8">
<ItCheckbox
v-if="props.content.checkbox_text"
:checkbox-item="{
label: props.content.checkbox_text,
value: termsAccepted,
checked: termsAccepted,
}"
@toggle="termsAccepted = !termsAccepted"
/>
</div>
<div class="my-8">
<ItCheckbox
v-if="props.content.has_extended_time_test"
:checkbox-item="{
label: t('edoniqTest.qualifiesForExtendedTime'),
value: extendedTimeTest,
checked: extendedTimeTest,
}"
@toggle="extendedTimeTest = !extendedTimeTest"
/>
</div>
<div class="my-8">
<button
:disabled="!termsAccepted"
class="btn-primary inline-flex items-center"
@click="startTest()"
>
Test starten
<it-icon-external-link class="it-icon ml-2 h-5 w-5"></it-icon-external-link>
</button>
</div>
</div>
</LearningContentSimpleLayout>
</template>
<!--<template>-->
<!-- <div class="container-medium">-->
<!-- <div class="lg:mt-8">-->
<!-- <h1>{{ content.title }}</h1>-->
<!-- <p class="text-large my-4 lg:my-8">{{ value.description }}</p>-->
<!-- <a :href="value.url" target="_blank" class="button btn-primary">Test starten</a>-->
<!-- </div>-->
<!-- </div>-->
<!--</template>-->
<!--<script setup lang="ts">-->
<!--import type { LearningContent } from "@/types";-->
<!--interface Value {-->
<!-- description: string;-->
<!-- url: string;-->
<!--}-->
<!--defineProps<{-->
<!-- value: Value;-->
<!-- content: LearningContent;-->
<!--}>();-->
<!--</script>-->

View File

@ -1,59 +0,0 @@
<script setup lang="ts">
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
import type { LearningContentTest } from "@/types";
import { ref } from "vue";
const props = defineProps<{
content: LearningContentTest;
}>();
const checked = ref(false);
function toggleChecked() {
checked.value = !checked.value;
}
function startTest() {
window.open(props.content.content_url, "_blank");
}
</script>
<template>
<LearningContentSimpleLayout
:title="props.content.title"
:learning-content="props.content"
>
<!-- eslint-disable vue/no-v-html -->
<div class="container-medium">
<p
v-if="props.content.description"
class="default-wagtail-rich-text text-large my-4"
v-html="props.content.description"
></p>
<div class="my-8">
<ItCheckbox
v-if="props.content.checkbox_text"
:checkbox-item="{
label: props.content.checkbox_text,
value: checked,
checked: checked,
}"
@toggle="toggleChecked()"
/>
</div>
<div class="my-8">
<button
:disabled="!checked"
class="btn-primary inline-flex items-center"
@click="startTest()"
>
Test starten
<it-icon-external-link class="it-icon ml-2 h-5 w-5"></it-icon-external-link>
</button>
</div>
</div>
</LearningContentSimpleLayout>
</template>

View File

@ -26,7 +26,7 @@ function isLearningContentType(object: any): object is LearningContent {
object?.content_type === "learnpath.LearningContentMediaLibrary" || object?.content_type === "learnpath.LearningContentMediaLibrary" ||
object?.content_type === "learnpath.LearningContentPlaceholder" || object?.content_type === "learnpath.LearningContentPlaceholder" ||
object?.content_type === "learnpath.LearningContentRichText" || object?.content_type === "learnpath.LearningContentRichText" ||
object?.content_type === "learnpath.LearningContentTest" || object?.content_type === "learnpath.LearningContentEdoniqTest" ||
object?.content_type === "learnpath.LearningContentVideo" object?.content_type === "learnpath.LearningContentVideo"
); );
} }

View File

@ -33,7 +33,7 @@ export type LearningContent =
| LearningContentMediaLibrary | LearningContentMediaLibrary
| LearningContentPlaceholder | LearningContentPlaceholder
| LearningContentRichText | LearningContentRichText
| LearningContentTest | LearningContentEdoniqTest
| LearningContentVideo; | LearningContentVideo;
export type LearningContentType = LearningContent["content_type"]; export type LearningContentType = LearningContent["content_type"];
@ -93,9 +93,10 @@ export interface LearningContentRichText extends LearningContentInterface {
readonly text: string; readonly text: string;
} }
export interface LearningContentTest extends LearningContentInterface { export interface LearningContentEdoniqTest extends LearningContentInterface {
readonly content_type: "learnpath.LearningContentTest"; readonly content_type: "learnpath.LearningContentEdoniqTest";
readonly checkbox_text: string; readonly checkbox_text: string;
readonly has_extended_time_test: boolean;
} }
export interface LearningContentVideo extends LearningContentInterface { export interface LearningContentVideo extends LearningContentInterface {

View File

@ -47,7 +47,7 @@ export function learningContentTypeData(
return { title: t("mediaLibrary.title"), icon: "it-icon-lc-media-library" }; return { title: t("mediaLibrary.title"), icon: "it-icon-lc-media-library" };
case "learnpath.LearningContentVideo": case "learnpath.LearningContentVideo":
return { title: t("learningContentTypes.video"), icon: "it-icon-lc-video" }; return { title: t("learningContentTypes.video"), icon: "it-icon-lc-video" };
case "learnpath.LearningContentTest": case "learnpath.LearningContentEdoniqTest":
return { title: t("learningContentTypes.test"), icon: "it-icon-lc-test" }; return { title: t("learningContentTypes.test"), icon: "it-icon-lc-test" };
case "learnpath.LearningContentRichText": case "learnpath.LearningContentRichText":
return { title: t("learningContentTypes.text"), icon: "it-icon-lc-resource" }; return { title: t("learningContentTypes.text"), icon: "it-icon-lc-resource" };

Binary file not shown.

View File

@ -640,6 +640,11 @@ GRAPHENE = {
# ], # ],
# } # }
# edoniq
EDONIQ_ENV = env("IT_EDONIQ_ENV", default="STEAGTEST")
EDONIQ_CERTIFICATE = env("IT_EDONIQ_CERTIFICATE", default="")
# Notifications # Notifications
# django-notifications # django-notifications
DJANGO_NOTIFICATIONS_CONFIG = {"SOFT_DELETE": True} DJANGO_NOTIFICATIONS_CONFIG = {"SOFT_DELETE": True}

View File

@ -8,8 +8,8 @@ from django.urls import include, path, re_path, register_converter
from django.urls.converters import IntConverter from django.urls.converters import IntConverter
from django.views import defaults as default_views from django.views import defaults as default_views
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django_ratelimit.exceptions import Ratelimited
from graphene_django.views import GraphQLView from graphene_django.views import GraphQLView
from ratelimit.exceptions import Ratelimited
from vbv_lernwelt.assignment.views import request_assignment_completion_status from vbv_lernwelt.assignment.views import request_assignment_completion_status
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
@ -41,6 +41,7 @@ from vbv_lernwelt.edoniq_test.views import (
export_students, export_students,
export_students_and_trainers, export_students_and_trainers,
export_trainers, export_trainers,
get_edoniq_token_redirect,
) )
from vbv_lernwelt.feedback.views import ( from vbv_lernwelt.feedback.views import (
get_expert_feedbacks_for_course, get_expert_feedbacks_for_course,
@ -146,10 +147,9 @@ urlpatterns = [
path(r'api/core/feedback/<str:course_session_id>/<str:circle_id>/', get_feedback_for_circle, path(r'api/core/feedback/<str:course_session_id>/<str:circle_id>/', get_feedback_for_circle,
name='feedback_for_circle'), name='feedback_for_circle'),
path("server/graphql/",
csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))),
# edoniq test # edoniq test
path(r'api/core/edoniq-test/redirect/', get_edoniq_token_redirect,
name='get_edoniq_token_redirect'),
path(r'api/core/edoniq-test/export-users/', export_students, name='edoniq_export_students'), path(r'api/core/edoniq-test/export-users/', export_students, name='edoniq_export_students'),
path(r'api/core/edoniq-test/export-trainers/', export_trainers, name='edoniq_export_trainers'), path(r'api/core/edoniq-test/export-trainers/', export_trainers, name='edoniq_export_trainers'),
path(r'api/core/edoniq-test/export-users-trainers/', export_students_and_trainers, path(r'api/core/edoniq-test/export-users-trainers/', export_students_and_trainers,
@ -172,6 +172,8 @@ urlpatterns = [
name="t2l_sync", name="t2l_sync",
), ),
path("server/graphql/",
csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))),
# testing and debug # testing and debug
path('server/raise_error/', path('server/raise_error/',
user_passes_test(lambda u: u.is_superuser, login_url='/login/')( user_passes_test(lambda u: u.is_superuser, login_url='/login/')(

View File

@ -2,13 +2,13 @@
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile with python 3.10
# To update, run: # To update, run:
# #
# pip-compile --output-file=requirements-dev.txt requirements-dev.in # pip-compile requirements-dev.in
# #
aniso8601==9.0.1 aniso8601==9.0.1
# via graphene # via graphene
anyascii==0.3.1 anyascii==0.3.2
# via wagtail # via wagtail
anyio==3.5.0 anyio==3.7.1
# via watchfiles # via watchfiles
appnope==0.1.3 appnope==0.1.3
# via ipython # via ipython
@ -16,64 +16,64 @@ 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.7.2
# via django # via django
astroid==2.11.2 astroid==2.15.6
# via pylint # via pylint
asttokens==2.0.5 asttokens==2.2.1
# via stack-data # via stack-data
async-timeout==4.0.2 async-timeout==4.0.2
# via redis # via redis
attrs==21.4.0 attrs==23.1.0
# via # via
# jsonschema # jsonschema
# pytest # referencing
# usort # usort
authlib==1.0.0 authlib==1.2.1
# via -r requirements.in # via -r requirements.in
azure-core==1.26.4 azure-core==1.29.1
# via # via
# azure-identity # azure-identity
# azure-storage-blob # azure-storage-blob
azure-identity==1.13.0 azure-identity==1.14.0
# via -r requirements.in # via -r requirements.in
azure-storage-blob==12.16.0 azure-storage-blob==12.17.0
# via # via
# -r requirements.in # -r requirements.in
# django-storages # django-storages
backcall==0.2.0 backcall==0.2.0
# via ipython # via ipython
beautifulsoup4==4.9.3 beautifulsoup4==4.11.2
# via wagtail # via wagtail
black==22.10.0 black==23.7.0
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# ufmt # ufmt
boto3==1.26.11 boto3==1.28.23
# via -r requirements.in # via -r requirements.in
botocore==1.29.11 botocore==1.31.23
# via # via
# boto3 # boto3
# s3transfer # s3transfer
brotli==1.0.9 brotli==1.0.9
# via whitenoise # via whitenoise
build==0.8.0 build==0.10.0
# via pip-tools # via pip-tools
caprover-api @ git+https://github.com/iterativ/Caprover-API.git@5013f8fc929e8e3281b9d609e968a782e8e99530 caprover-api @ git+https://github.com/iterativ/Caprover-API.git@5013f8fc929e8e3281b9d609e968a782e8e99530
# via -r requirements-dev.in # via -r requirements-dev.in
certifi==2021.10.8 certifi==2023.7.22
# via # via
# requests # requests
# sentry-sdk # sentry-sdk
cffi==1.15.0 cffi==1.15.1
# via # via
# argon2-cffi-bindings # argon2-cffi-bindings
# cryptography # cryptography
cfgv==3.3.1 cfgv==3.3.1
# via pre-commit # via pre-commit
charset-normalizer==2.0.12 charset-normalizer==3.2.0
# via requests # via requests
click==8.1.1 click==8.1.6
# via # via
# -r requirements.in # -r requirements.in
# black # black
@ -83,17 +83,13 @@ click==8.1.1
# ufmt # ufmt
# usort # usort
# uvicorn # uvicorn
concurrent-log-handler==0.9.20 concurrent-log-handler==0.9.24
# via -r requirements.in # via -r requirements.in
coreapi==2.3.3 coverage==7.2.7
# via djangorestframework-stubs
coreschema==0.0.4
# via coreapi
coverage==6.3.2
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# django-coverage-plugin # django-coverage-plugin
cryptography==36.0.2 cryptography==41.0.3
# via # via
# authlib # authlib
# azure-identity # azure-identity
@ -104,15 +100,15 @@ decorator==5.1.1
# via # via
# ipdb # ipdb
# ipython # ipython
deprecated==1.2.13 defusedxml==0.7.1
# via redis # via willow
dill==0.3.4 dill==0.3.7
# via pylint # via pylint
distlib==0.3.4 distlib==0.3.7
# via virtualenv # via virtualenv
dj-database-url==1.0.0 dj-database-url==2.0.0
# via -r requirements.in # via -r requirements.in
django==3.2.13 django==3.2.20
# via # via
# -r requirements.in # -r requirements.in
# dj-database-url # dj-database-url
@ -138,27 +134,26 @@ django==3.2.13
# graphene-django # graphene-django
# jsonfield # jsonfield
# wagtail # wagtail
# wagtail-grapple
# wagtail-localize # wagtail-localize
django-click==2.3.0 django-click==2.3.0
# via -r requirements.in # via -r requirements.in
django-cors-headers==3.11.0 django-cors-headers==4.2.0
# via -r requirements.in # via -r requirements.in
django-coverage-plugin==2.0.2 django-coverage-plugin==3.1.0
# via -r requirements-dev.in # via -r requirements-dev.in
django-csp==3.7 django-csp==3.7
# via -r requirements.in # via -r requirements.in
django-debug-toolbar==3.2.4 django-debug-toolbar==4.1.0
# via -r requirements-dev.in # via -r requirements-dev.in
django-extensions==3.2.0 django-extensions==3.2.3
# via -r requirements-dev.in # via -r requirements-dev.in
django-filter==21.1 django-filter==23.2
# via wagtail # via wagtail
django-ipware==4.0.2 django-ipware==5.0.0
# via -r requirements.in # via -r requirements.in
django-jsonform==2.17.0 django-jsonform==2.18.0
# via -r requirements.in # via -r requirements.in
django-model-utils==4.2.0 django-model-utils==4.3.1
# via # via
# -r requirements.in # -r requirements.in
# django-notifications-hq # django-notifications-hq
@ -168,64 +163,70 @@ django-notifications-hq==1.8.2
# via -r requirements.in # via -r requirements.in
django-permissionedforms==0.1 django-permissionedforms==0.1
# via wagtail # via wagtail
django-ratelimit==3.0.1 django-ratelimit==4.1.0
# via -r requirements.in # via -r requirements.in
django-redis==5.2.0 django-redis==5.3.0
# via -r requirements.in # via -r requirements.in
django-storages[azure]==1.13.1 django-storages[azure]==1.13.2
# via -r requirements.in # via -r requirements.in
django-stubs==1.10.1 django-stubs==4.2.3
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# djangorestframework-stubs # djangorestframework-stubs
django-stubs-ext==0.4.0 django-stubs-ext==4.2.2
# via django-stubs # via django-stubs
django-taggit==2.1.0 django-taggit==4.0.0
# via wagtail # via wagtail
django-treebeard==4.5.1 django-treebeard==4.7
# via wagtail # via wagtail
django-watchfiles @ https://github.com/q0w/django-watchfiles/archive/issue-1.zip django-watchfiles @ https://github.com/q0w/django-watchfiles/archive/issue-1.zip
# via -r requirements-dev.in # via -r requirements-dev.in
djangorestframework==3.13.1 djangorestframework==3.14.0
# via # via
# -r requirements.in # -r requirements.in
# drf-spectacular # drf-spectacular
# wagtail # wagtail
djangorestframework-stubs==1.4.0 djangorestframework-stubs==3.14.2
# via -r requirements-dev.in # via -r requirements-dev.in
draftjs-exporter==2.1.7 draftjs-exporter==2.1.7
# via wagtail # via wagtail
drf-spectacular==0.22.0 drf-spectacular==0.26.4
# via -r requirements.in # via -r requirements.in
environs==9.5.0 environs==9.5.0
# via -r requirements.in # via -r requirements.in
et-xmlfile==1.1.0 et-xmlfile==1.1.0
# via openpyxl # via openpyxl
executing==0.8.3 exceptiongroup==1.1.2
# via
# anyio
# pytest
executing==1.2.0
# via stack-data # via stack-data
factory-boy==3.2.1 factory-boy==3.3.0
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# wagtail-factories # wagtail-factories
faker==13.3.4 faker==19.3.0
# via factory-boy # via factory-boy
filelock==3.6.0 filelock==3.12.2
# via virtualenv # via virtualenv
flake8==4.0.1 filetype==1.2.0
# via willow
flake8==6.1.0
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# flake8-isort # flake8-isort
flake8-isort==4.1.1 flake8-isort==6.0.0
# via -r requirements-dev.in # via -r requirements-dev.in
gitdb==4.0.9 gitdb==4.0.10
# via gitdb2 # via gitdb2
gitdb2==4.0.2 gitdb2==4.0.2
# via gitpython # via gitpython
gitpython==3.0.6 gitpython==3.0.6
# via trufflehog # via trufflehog
graphene==3.2.2 graphene==3.3
# via graphene-django # via graphene-django
graphene-django==3.0.0 graphene-django==3.1.5
# via wagtail-grapple # via wagtail-grapple
graphql-core==3.2.3 graphql-core==3.2.3
# via # via
@ -236,63 +237,59 @@ graphql-relay==3.2.0
# via # via
# graphene # graphene
# graphene-django # graphene-django
gunicorn==20.1.0 gunicorn==21.2.0
# via -r requirements.in # via -r requirements.in
h11==0.13.0 h11==0.14.0
# via uvicorn # via uvicorn
html5lib==1.1 html5lib==1.1
# via wagtail # via wagtail
httptools==0.4.0 httptools==0.6.0
# via uvicorn # via uvicorn
identify==2.4.12 identify==2.5.26
# via pre-commit # via pre-commit
idna==3.3 idna==3.4
# via # via
# anyio # anyio
# requests # requests
inflection==0.5.1 inflection==0.5.1
# via drf-spectacular # via drf-spectacular
iniconfig==1.1.1 iniconfig==2.0.0
# via pytest # via pytest
ipdb==0.13.9 ipdb==0.13.13
# via -r requirements-dev.in # via -r requirements-dev.in
ipython==8.2.0 ipython==8.14.0
# via ipdb # via ipdb
isodate==0.6.1 isodate==0.6.1
# via azure-storage-blob # via azure-storage-blob
isort==5.10.1 isort==5.12.0
# via # via
# flake8-isort # flake8-isort
# pylint # pylint
itypes==1.2.0 jedi==0.19.0
# via coreapi
jedi==0.18.1
# via ipython # via ipython
jinja2==3.1.1
# via coreschema
jmespath==1.0.1 jmespath==1.0.1
# via # via
# boto3 # boto3
# botocore # botocore
jsonfield==3.1.0 jsonfield==3.1.0
# via django-notifications-hq # via django-notifications-hq
jsonschema==4.4.0 jsonschema==4.19.0
# via drf-spectacular # via drf-spectacular
jsonschema-specifications==2023.7.1
# via jsonschema
l18n==2021.3 l18n==2021.3
# via wagtail # via wagtail
lazy-object-proxy==1.7.1 lazy-object-proxy==1.9.0
# via astroid # via astroid
libcst==0.4.7 libcst==1.0.1
# via # via
# ufmt # ufmt
# usort # usort
markupsafe==2.1.1 marshmallow==3.20.1
# via jinja2
marshmallow==3.15.0
# via environs # via environs
matplotlib-inline==0.1.3 matplotlib-inline==0.1.6
# via ipython # via ipython
mccabe==0.6.1 mccabe==0.7.0
# via # via
# flake8 # flake8
# pylint # pylint
@ -300,127 +297,127 @@ moreorless==0.4.0
# via # via
# ufmt # ufmt
# usort # usort
msal==1.22.0 msal==1.23.0
# via # via
# azure-identity # azure-identity
# msal-extensions # msal-extensions
msal-extensions==1.0.0 msal-extensions==1.0.0
# via azure-identity # via azure-identity
mypy==0.942 mypy==1.4.1
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# django-stubs # django-stubs
# djangorestframework-stubs # djangorestframework-stubs
mypy-extensions==0.4.3 mypy-extensions==1.0.0
# via # via
# black # black
# mypy # mypy
# typing-inspect # typing-inspect
nodeenv==1.6.0 nodeenv==1.8.0
# via pre-commit # via pre-commit
openpyxl==3.1.2 openpyxl==3.1.2
# via # via
# -r requirements.in # -r requirements.in
# wagtail # wagtail
packaging==21.3 packaging==23.1
# via # via
# black
# build # build
# gunicorn
# marshmallow # marshmallow
# pytest # pytest
# pytest-sugar # pytest-sugar
# redis
parso==0.8.3 parso==0.8.3
# via jedi # via jedi
pathspec==0.9.0 pathspec==0.11.2
# via # via
# black # black
# trailrunner # trailrunner
pep517==0.12.0
# via build
pexpect==4.8.0 pexpect==4.8.0
# via ipython # via ipython
pickleshare==0.7.5 pickleshare==0.7.5
# via ipython # via ipython
pillow==9.0.1 pillow==10.0.0
# via # via
# -r requirements.in # -r requirements.in
# pillow-heif
# wagtail # wagtail
pip-tools==6.9.0 pillow-heif==0.13.0
# via willow
pip-tools==7.3.0
# via -r requirements-dev.in # via -r requirements-dev.in
platformdirs==2.5.1 platformdirs==3.10.0
# via # via
# black # black
# pylint # pylint
# virtualenv # virtualenv
pluggy==1.0.0 pluggy==1.2.0
# via pytest # via pytest
polib==1.1.1 polib==1.2.0
# via wagtail-localize # via wagtail-localize
portalocker==2.4.0 portalocker==2.7.0
# via # via
# concurrent-log-handler # concurrent-log-handler
# msal-extensions # msal-extensions
pre-commit==2.17.0 pre-commit==3.3.3
# via -r requirements-dev.in # via -r requirements-dev.in
promise==2.3 promise==2.3
# via graphene-django # via graphene-django
prompt-toolkit==3.0.28 prompt-toolkit==3.0.39
# via ipython # via ipython
psycopg2-binary==2.9.3 psycopg2-binary==2.9.7
# via -r requirements.in # via -r requirements.in
ptyprocess==0.7.0 ptyprocess==0.7.0
# via pexpect # via pexpect
pure-eval==0.2.2 pure-eval==0.2.2
# via stack-data # via stack-data
py==1.11.0 pycodestyle==2.11.0
# via pytest
pycodestyle==2.8.0
# via flake8 # via flake8
pycparser==2.21 pycparser==2.21
# via cffi # via cffi
pyflakes==2.4.0 pycryptodome==3.18.0
# via -r requirements.in
pyflakes==3.1.0
# via flake8 # via flake8
pygments==2.11.2 pygments==2.16.1
# via ipython # via ipython
pyjwt[crypto]==2.7.0 pyjwt[crypto]==2.8.0
# via msal # via msal
pylint==2.13.4 pylint==2.17.5
# via # via
# pylint-django # pylint-django
# pylint-plugin-utils # pylint-plugin-utils
pylint-django==2.5.3 pylint-django==2.5.3
# via -r requirements-dev.in # via -r requirements-dev.in
pylint-plugin-utils==0.7 pylint-plugin-utils==0.8.2
# via pylint-django # via pylint-django
pyparsing==3.0.7 pyproject-hooks==1.0.0
# via packaging # via build
pyrsistent==0.18.1 pytest==7.4.0
# via jsonschema
pytest==7.1.3
# via # via
# -r requirements-dev.in # -r requirements-dev.in
# pytest-django # pytest-django
# pytest-sugar # pytest-sugar
pytest-django==4.5.2 pytest-django==4.5.2
# via -r requirements-dev.in # via -r requirements-dev.in
pytest-sugar==0.9.4 pytest-sugar==0.9.7
# via -r requirements-dev.in # via -r requirements-dev.in
python-dateutil==2.8.2 python-dateutil==2.8.2
# via # via
# -r requirements.in # -r requirements.in
# botocore # botocore
# faker # faker
python-dotenv==0.20.0 python-dotenv==1.0.0
# via # via
# environs # environs
# uvicorn # uvicorn
python-http-client==3.3.7 python-http-client==3.3.7
# via sendgrid # via sendgrid
python-json-logger==2.0.2 python-json-logger==2.0.7
# via -r requirements.in # via -r requirements.in
python-slugify==6.1.1 python-slugify==8.0.1
# via -r requirements.in # via -r requirements.in
pytz==2022.1 pytz==2023.3
# via # via
# -r requirements.in # -r requirements.in
# django # django
@ -428,94 +425,97 @@ pytz==2022.1
# django-notifications-hq # django-notifications-hq
# djangorestframework # djangorestframework
# l18n # l18n
pyyaml==6.0 pyyaml==6.0.1
# via # via
# caprover-api # caprover-api
# drf-spectacular # drf-spectacular
# libcst # libcst
# pre-commit # pre-commit
# uvicorn # uvicorn
redis==4.2.1 redis==4.6.0
# via # via
# -r requirements.in # -r requirements.in
# django-redis # django-redis
requests==2.27.1 referencing==0.30.2
# via
# jsonschema
# jsonschema-specifications
requests==2.31.0
# via # via
# azure-core # azure-core
# caprover-api # caprover-api
# coreapi
# djangorestframework-stubs # djangorestframework-stubs
# msal # msal
# wagtail # wagtail
s3transfer==0.6.0 rpds-py==0.9.2
# via
# jsonschema
# referencing
s3transfer==0.6.1
# via boto3 # via boto3
sendgrid==6.9.7 sendgrid==6.10.0
# via -r requirements.in # via -r requirements.in
sentry-sdk==1.5.8 sentry-sdk==1.29.2
# via -r requirements.in # via -r requirements.in
six==1.16.0 six==1.16.0
# via # via
# asttokens # asttokens
# azure-core # azure-core
# azure-identity
# django-coverage-plugin
# html5lib # html5lib
# isodate # isodate
# l18n # l18n
# promise # promise
# python-dateutil # python-dateutil
# virtualenv
smmap==5.0.0 smmap==5.0.0
# via gitdb # via gitdb
sniffio==1.2.0 sniffio==1.3.0
# via anyio # via anyio
soupsieve==2.3.2.post1 soupsieve==2.4.1
# via beautifulsoup4 # via beautifulsoup4
sqlparse==0.4.2 sqlparse==0.4.4
# via # via
# django # django
# django-debug-toolbar # django-debug-toolbar
stack-data==0.2.0 stack-data==0.6.2
# via ipython # via ipython
starkbank-ecdsa==2.2.0 starkbank-ecdsa==2.2.0
# via sendgrid # via sendgrid
stdlibs==2022.6.8 stdlibs==2022.10.9
# via usort # via usort
structlog==21.5.0 structlog==23.1.0
# via -r requirements.in # via -r requirements.in
swapper==1.3.0 swapper==1.3.0
# via django-notifications-hq # via django-notifications-hq
telepath==0.2 telepath==0.3.1
# via wagtail # via wagtail
termcolor==1.1.0 termcolor==2.3.0
# via pytest-sugar # via pytest-sugar
testfixtures==6.18.5
# via flake8-isort
text-unidecode==1.3 text-unidecode==1.3
# via # via
# graphene-django # graphene-django
# python-slugify # python-slugify
toml==0.10.2 toml==0.10.2
# via # via usort
# ipdb
# pre-commit
# usort
tomli==2.0.1 tomli==2.0.1
# via # via
# black # black
# build # build
# django-stubs # django-stubs
# ipdb
# mypy # mypy
# pep517 # pip-tools
# pylint # pylint
# pyproject-hooks
# pytest # pytest
tomlkit==0.11.5 tomlkit==0.12.1
# via ufmt # via
trailrunner==1.2.1 # pylint
# ufmt
trailrunner==1.4.0
# via # via
# ufmt # ufmt
# usort # usort
traitlets==5.1.1 traitlets==5.9.0
# via # via
# ipython # ipython
# matplotlib-inline # matplotlib-inline
@ -523,14 +523,23 @@ trufflehog==2.2.1
# via -r requirements-dev.in # via -r requirements-dev.in
trufflehogregexes==0.0.7 trufflehogregexes==0.0.7
# via trufflehog # via trufflehog
types-pytz==2021.3.6 types-pytz==2023.3.0.0
# via django-stubs # via django-stubs
types-pyyaml==6.0.5 types-pyyaml==6.0.12.11
# via django-stubs
typing-extensions==4.5.0
# via # via
# django-stubs
# djangorestframework-stubs
types-requests==2.31.0.2
# via djangorestframework-stubs
types-urllib3==1.26.25.14
# via types-requests
typing-extensions==4.7.1
# via
# asgiref
# astroid
# azure-core # azure-core
# azure-storage-blob # azure-storage-blob
# dj-database-url
# django-stubs # django-stubs
# django-stubs-ext # django-stubs-ext
# djangorestframework-stubs # djangorestframework-stubs
@ -538,63 +547,60 @@ typing-extensions==4.5.0
# mypy # mypy
# typing-inspect # typing-inspect
# ufmt # ufmt
# uvicorn
# wagtail-localize # wagtail-localize
typing-inspect==0.8.0 typing-inspect==0.9.0
# via libcst # via libcst
ufmt==2.0.1 ufmt==2.2.0
# via -r requirements-dev.in # via -r requirements-dev.in
uritemplate==4.1.1 uritemplate==4.1.1
# via # via drf-spectacular
# coreapi urllib3==1.26.16
# drf-spectacular
urllib3==1.26.9
# via # via
# botocore # botocore
# requests # requests
# sentry-sdk # sentry-sdk
usort==1.0.5 usort==1.0.7
# via ufmt # via ufmt
uvicorn[standard]==0.18.3 uvicorn[standard]==0.23.2
# via -r requirements.in # via -r requirements.in
uvloop==0.16.0 uvloop==0.17.0
# via uvicorn # via uvicorn
virtualenv==20.14.0 virtualenv==20.24.2
# via pre-commit # via pre-commit
wagtail==4.2.2 wagtail==5.1
# via # via
# -r requirements.in # -r requirements.in
# wagtail-factories # wagtail-factories
# wagtail-grapple # wagtail-grapple
# wagtail-headless-preview # wagtail-headless-preview
# wagtail-localize # wagtail-localize
wagtail-factories==4.0.0 wagtail-factories==4.1.0
# via -r requirements.in # via -r requirements.in
wagtail-grapple==0.19.2 wagtail-grapple==0.20.0
# via -r requirements.in # via -r requirements.in
wagtail-headless-preview==0.4.0 wagtail-headless-preview==0.6.0
# via wagtail-grapple # via wagtail-grapple
wagtail-localize==1.5 wagtail-localize==1.5.1
# via -r requirements.in # via -r requirements.in
watchfiles==0.17.0 watchfiles==0.19.0
# via # via
# django-watchfiles # django-watchfiles
# uvicorn # uvicorn
wcwidth==0.2.5 wcwidth==0.2.6
# via prompt-toolkit # via prompt-toolkit
webencodings==0.5.1 webencodings==0.5.1
# via html5lib # via html5lib
websockets==10.2 websockets==11.0.3
# via uvicorn # via uvicorn
wheel==0.37.1 wheel==0.41.1
# via pip-tools # via pip-tools
whitenoise[brotli]==6.0.0 whitenoise[brotli]==6.5.0
# via -r requirements.in # via -r requirements.in
willow==1.4.1 willow[heif]==1.6.1
# via wagtail # via wagtail
wrapt==1.14.0 wrapt==1.15.0
# via # via astroid
# astroid
# deprecated
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# pip # pip

View File

@ -38,6 +38,7 @@ structlog
python-json-logger python-json-logger
concurrent-log-handler concurrent-log-handler
python-dateutil python-dateutil
pycryptodome
wagtail>=4 wagtail>=4
wagtail-factories>=4 wagtail-factories>=4

View File

@ -2,75 +2,77 @@
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile with python 3.10
# To update, run: # To update, run:
# #
# pip-compile --output-file=requirements.txt requirements.in # pip-compile requirements.in
# #
aniso8601==9.0.1 aniso8601==9.0.1
# via graphene # via graphene
anyascii==0.3.1 anyascii==0.3.2
# via wagtail # via wagtail
anyio==3.5.0 anyio==3.7.1
# via watchfiles # 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.7.2
# via django # via django
async-timeout==4.0.2 async-timeout==4.0.2
# via redis # via redis
attrs==21.4.0 attrs==23.1.0
# via jsonschema # via
authlib==1.0.0 # jsonschema
# referencing
authlib==1.2.1
# via -r requirements.in # via -r requirements.in
azure-core==1.26.4 azure-core==1.29.1
# via # via
# azure-identity # azure-identity
# azure-storage-blob # azure-storage-blob
azure-identity==1.13.0 azure-identity==1.14.0
# via -r requirements.in # via -r requirements.in
azure-storage-blob==12.16.0 azure-storage-blob==12.17.0
# via # via
# -r requirements.in # -r requirements.in
# django-storages # django-storages
beautifulsoup4==4.9.3 beautifulsoup4==4.11.2
# via wagtail # via wagtail
boto3==1.26.11 boto3==1.28.23
# via -r requirements.in # via -r requirements.in
botocore==1.29.11 botocore==1.31.23
# via # via
# boto3 # boto3
# s3transfer # s3transfer
brotli==1.0.9 brotli==1.0.9
# via whitenoise # via whitenoise
certifi==2021.10.8 certifi==2023.7.22
# via # via
# requests # requests
# sentry-sdk # sentry-sdk
cffi==1.15.0 cffi==1.15.1
# via # via
# argon2-cffi-bindings # argon2-cffi-bindings
# cryptography # cryptography
charset-normalizer==2.0.12 charset-normalizer==3.2.0
# via requests # via requests
click==8.1.1 click==8.1.6
# via # via
# -r requirements.in # -r requirements.in
# django-click # django-click
# uvicorn # uvicorn
concurrent-log-handler==0.9.20 concurrent-log-handler==0.9.24
# via -r requirements.in # via -r requirements.in
cryptography==36.0.2 cryptography==41.0.3
# via # via
# authlib # authlib
# azure-identity # azure-identity
# azure-storage-blob # azure-storage-blob
# msal # msal
# pyjwt # pyjwt
deprecated==1.2.13 defusedxml==0.7.1
# via redis # via willow
dj-database-url==1.0.0 dj-database-url==2.0.0
# via -r requirements.in # via -r requirements.in
django==3.2.13 django==3.2.20
# via # via
# -r requirements.in # -r requirements.in
# dj-database-url # dj-database-url
@ -91,21 +93,20 @@ django==3.2.13
# graphene-django # graphene-django
# jsonfield # jsonfield
# wagtail # wagtail
# wagtail-grapple
# wagtail-localize # wagtail-localize
django-click==2.3.0 django-click==2.3.0
# via -r requirements.in # via -r requirements.in
django-cors-headers==3.11.0 django-cors-headers==4.2.0
# via -r requirements.in # via -r requirements.in
django-csp==3.7 django-csp==3.7
# via -r requirements.in # via -r requirements.in
django-filter==21.1 django-filter==23.2
# via wagtail # via wagtail
django-ipware==4.0.2 django-ipware==5.0.0
# via -r requirements.in # via -r requirements.in
django-jsonform==2.17.0 django-jsonform==2.18.0
# via -r requirements.in # via -r requirements.in
django-model-utils==4.2.0 django-model-utils==4.3.1
# via # via
# -r requirements.in # -r requirements.in
# django-notifications-hq # django-notifications-hq
@ -115,36 +116,40 @@ django-notifications-hq==1.8.2
# via -r requirements.in # via -r requirements.in
django-permissionedforms==0.1 django-permissionedforms==0.1
# via wagtail # via wagtail
django-ratelimit==3.0.1 django-ratelimit==4.1.0
# via -r requirements.in # via -r requirements.in
django-redis==5.2.0 django-redis==5.3.0
# via -r requirements.in # via -r requirements.in
django-storages[azure]==1.13.1 django-storages[azure]==1.13.2
# via -r requirements.in # via -r requirements.in
django-taggit==2.1.0 django-taggit==4.0.0
# via wagtail # via wagtail
django-treebeard==4.5.1 django-treebeard==4.7
# via wagtail # via wagtail
djangorestframework==3.13.1 djangorestframework==3.14.0
# via # via
# -r requirements.in # -r requirements.in
# drf-spectacular # drf-spectacular
# wagtail # wagtail
draftjs-exporter==2.1.7 draftjs-exporter==2.1.7
# via wagtail # via wagtail
drf-spectacular==0.22.0 drf-spectacular==0.26.4
# via -r requirements.in # via -r requirements.in
environs==9.5.0 environs==9.5.0
# via -r requirements.in # via -r requirements.in
et-xmlfile==1.1.0 et-xmlfile==1.1.0
# via openpyxl # via openpyxl
factory-boy==3.2.1 exceptiongroup==1.1.2
# via anyio
factory-boy==3.3.0
# via wagtail-factories # via wagtail-factories
faker==13.11.1 faker==19.3.0
# via factory-boy # via factory-boy
graphene==3.2.2 filetype==1.2.0
# via willow
graphene==3.3
# via graphene-django # via graphene-django
graphene-django==3.0.0 graphene-django==3.1.5
# via wagtail-grapple # via wagtail-grapple
graphql-core==3.2.3 graphql-core==3.2.3
# via # via
@ -155,15 +160,15 @@ graphql-relay==3.2.0
# via # via
# graphene # graphene
# graphene-django # graphene-django
gunicorn==20.1.0 gunicorn==21.2.0
# via -r requirements.in # via -r requirements.in
h11==0.13.0 h11==0.14.0
# via uvicorn # via uvicorn
html5lib==1.1 html5lib==1.1
# via wagtail # via wagtail
httptools==0.4.0 httptools==0.6.0
# via uvicorn # via uvicorn
idna==3.3 idna==3.4
# via # via
# anyio # anyio
# requests # requests
@ -177,13 +182,15 @@ jmespath==1.0.1
# botocore # botocore
jsonfield==3.1.0 jsonfield==3.1.0
# via django-notifications-hq # via django-notifications-hq
jsonschema==4.4.0 jsonschema==4.19.0
# via drf-spectacular # via drf-spectacular
jsonschema-specifications==2023.7.1
# via jsonschema
l18n==2021.3 l18n==2021.3
# via wagtail # via wagtail
marshmallow==3.15.0 marshmallow==3.20.1
# via environs # via environs
msal==1.22.0 msal==1.23.0
# via # via
# azure-identity # azure-identity
# msal-extensions # msal-extensions
@ -193,48 +200,49 @@ openpyxl==3.1.2
# via # via
# -r requirements.in # -r requirements.in
# wagtail # wagtail
packaging==21.3 packaging==23.1
# via # via
# gunicorn
# marshmallow # marshmallow
# redis pillow==10.0.0
pillow==9.0.1
# via # via
# -r requirements.in # -r requirements.in
# pillow-heif
# wagtail # wagtail
polib==1.1.1 pillow-heif==0.13.0
# via willow
polib==1.2.0
# via wagtail-localize # via wagtail-localize
portalocker==2.4.0 portalocker==2.7.0
# via # via
# concurrent-log-handler # concurrent-log-handler
# msal-extensions # msal-extensions
promise==2.3 promise==2.3
# via graphene-django # via graphene-django
psycopg2-binary==2.9.3 psycopg2-binary==2.9.7
# via -r requirements.in # via -r requirements.in
pycparser==2.21 pycparser==2.21
# via cffi # via cffi
pyjwt[crypto]==2.7.0 pycryptodome==3.18.0
# via -r requirements.in
pyjwt[crypto]==2.8.0
# via msal # via msal
pyparsing==3.0.7
# via packaging
pyrsistent==0.18.1
# via jsonschema
python-dateutil==2.8.2 python-dateutil==2.8.2
# via # via
# -r requirements.in # -r requirements.in
# botocore # botocore
# faker # faker
python-dotenv==0.20.0 python-dotenv==1.0.0
# via # via
# environs # environs
# uvicorn # uvicorn
python-http-client==3.3.7 python-http-client==3.3.7
# via sendgrid # via sendgrid
python-json-logger==2.0.2 python-json-logger==2.0.7
# via -r requirements.in # via -r requirements.in
python-slugify==6.1.1 python-slugify==8.0.1
# via -r requirements.in # via -r requirements.in
pytz==2022.1 pytz==2023.3
# via # via
# -r requirements.in # -r requirements.in
# django # django
@ -242,95 +250,100 @@ pytz==2022.1
# django-notifications-hq # django-notifications-hq
# djangorestframework # djangorestframework
# l18n # l18n
pyyaml==6.0 pyyaml==6.0.1
# via # via
# drf-spectacular # drf-spectacular
# uvicorn # uvicorn
redis==4.2.1 redis==4.6.0
# via # via
# -r requirements.in # -r requirements.in
# django-redis # django-redis
requests==2.27.1 referencing==0.30.2
# via
# jsonschema
# jsonschema-specifications
requests==2.31.0
# via # via
# azure-core # azure-core
# msal # msal
# wagtail # wagtail
s3transfer==0.6.0 rpds-py==0.9.2
# via
# jsonschema
# referencing
s3transfer==0.6.1
# via boto3 # via boto3
sendgrid==6.9.7 sendgrid==6.10.0
# via -r requirements.in # via -r requirements.in
sentry-sdk==1.5.8 sentry-sdk==1.29.2
# via -r requirements.in # via -r requirements.in
six==1.16.0 six==1.16.0
# via # via
# azure-core # azure-core
# azure-identity
# html5lib # html5lib
# isodate # isodate
# l18n # l18n
# promise # promise
# python-dateutil # python-dateutil
sniffio==1.2.0 sniffio==1.3.0
# via anyio # via anyio
soupsieve==2.3.2.post1 soupsieve==2.4.1
# via beautifulsoup4 # via beautifulsoup4
sqlparse==0.4.2 sqlparse==0.4.4
# via django # via django
starkbank-ecdsa==2.2.0 starkbank-ecdsa==2.2.0
# via sendgrid # via sendgrid
structlog==21.5.0 structlog==23.1.0
# via -r requirements.in # via -r requirements.in
swapper==1.3.0 swapper==1.3.0
# via django-notifications-hq # via django-notifications-hq
telepath==0.2 telepath==0.3.1
# via wagtail # via wagtail
text-unidecode==1.3 text-unidecode==1.3
# via # via
# graphene-django # graphene-django
# python-slugify # python-slugify
typing-extensions==4.5.0 typing-extensions==4.7.1
# via # via
# asgiref
# azure-core # azure-core
# azure-storage-blob # azure-storage-blob
# dj-database-url
# uvicorn
# wagtail-localize # wagtail-localize
uritemplate==4.1.1 uritemplate==4.1.1
# via drf-spectacular # via drf-spectacular
urllib3==1.26.9 urllib3==1.26.16
# via # via
# botocore # botocore
# requests # requests
# sentry-sdk # sentry-sdk
uvicorn[standard]==0.18.3 uvicorn[standard]==0.23.2
# via -r requirements.in # via -r requirements.in
uvloop==0.16.0 uvloop==0.17.0
# via uvicorn # via uvicorn
wagtail==4.2.2 wagtail==5.1
# via # via
# -r requirements.in # -r requirements.in
# wagtail-factories # wagtail-factories
# wagtail-grapple # wagtail-grapple
# wagtail-headless-preview # wagtail-headless-preview
# wagtail-localize # wagtail-localize
wagtail-factories==4.0.0 wagtail-factories==4.1.0
# via -r requirements.in # via -r requirements.in
wagtail-grapple==0.19.2 wagtail-grapple==0.20.0
# via -r requirements.in # via -r requirements.in
wagtail-headless-preview==0.4.0 wagtail-headless-preview==0.6.0
# via wagtail-grapple # via wagtail-grapple
wagtail-localize==1.5 wagtail-localize==1.5.1
# via -r requirements.in # via -r requirements.in
watchfiles==0.17.0 watchfiles==0.19.0
# via uvicorn # via uvicorn
webencodings==0.5.1 webencodings==0.5.1
# via html5lib # via html5lib
websockets==10.2 websockets==11.0.3
# via uvicorn # via uvicorn
whitenoise[brotli]==6.0.0 whitenoise[brotli]==6.5.0
# via -r requirements.in # via -r requirements.in
willow==1.4.1 willow[heif]==1.6.1
# via wagtail # via wagtail
wrapt==1.14.0
# via deprecated
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View File

@ -11,7 +11,7 @@ from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.template import loader from django.template import loader
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from ratelimit.decorators import ratelimit from django_ratelimit.decorators import ratelimit
from rest_framework import authentication from rest_framework import authentication
from rest_framework.decorators import ( from rest_framework.decorators import (
api_view, api_view,

View File

@ -48,6 +48,7 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory, CircleFactory,
LearningContentAssignmentFactory, LearningContentAssignmentFactory,
LearningContentAttendanceCourseFactory, LearningContentAttendanceCourseFactory,
LearningContentEdoniqTestFactory,
LearningContentFeedbackFactory, LearningContentFeedbackFactory,
LearningContentLearningModuleFactory, LearningContentLearningModuleFactory,
LearningContentMediaLibraryFactory, LearningContentMediaLibraryFactory,
@ -309,6 +310,16 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst.
slug__startswith="test-lehrgang-assignment-fahrzeug-mein-erstes-auto" slug__startswith="test-lehrgang-assignment-fahrzeug-mein-erstes-auto"
), ),
), ),
LearningContentEdoniqTestFactory(
title="Wissens- und Verständnisfragen",
parent=circle,
description=RichText(
"<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>"
),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
extended_time_test_url="https://exam2.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
)
PerformanceCriteriaFactory( PerformanceCriteriaFactory(
parent=CompetencePage.objects.get(competence_id="X1"), parent=CompetencePage.objects.get(competence_id="X1"),

View File

@ -16,10 +16,10 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
LearningContentAssignmentFactory, LearningContentAssignmentFactory,
LearningContentAttendanceCourseFactory, LearningContentAttendanceCourseFactory,
LearningContentDocumentListFactory, LearningContentDocumentListFactory,
LearningContentEdoniqTestFactory,
LearningContentFeedbackFactory, LearningContentFeedbackFactory,
LearningContentMediaLibraryFactory, LearningContentMediaLibraryFactory,
LearningContentPlaceholderFactory, LearningContentPlaceholderFactory,
LearningContentTestFactory,
LearningPathFactory, LearningPathFactory,
LearningSequenceFactory, LearningSequenceFactory,
LearningUnitFactory, LearningUnitFactory,
@ -329,14 +329,14 @@ In diesem Circle erfährst du wie die überbetrieblichen Kurse aufgebaut sind. Z
], ],
) )
LearningUnitFactory(title="Kompetenznachweis", title_hidden=True, parent=circle) LearningUnitFactory(title="Kompetenznachweis", title_hidden=True, parent=circle)
LearningContentTestFactory( LearningContentEdoniqTestFactory(
title="Wissens- und Verständnisfragen", title="Wissens- und Verständnisfragen",
parent=circle, parent=circle,
description=RichText( description=RichText(
"<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>" "<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>"
), ),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden habe und den Test durchführen möchte.", checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
content_url="https://exam.vbv-afa.ch/", test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
) )
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle) LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
LearningContentFeedbackFactory( LearningContentFeedbackFactory(
@ -444,14 +444,14 @@ Dans ce cercle, tu apprendras comment les cours interentreprises sont structuré
LearningUnitFactory( LearningUnitFactory(
title="Contrôle de compétences", title_hidden=True, parent=circle title="Contrôle de compétences", title_hidden=True, parent=circle
) )
LearningContentTestFactory( LearningContentEdoniqTestFactory(
title="Questions de connaissances et de compréhension", title="Questions de connaissances et de compréhension",
parent=circle, parent=circle,
description=RichText( description=RichText(
"<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>" "<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>"
), ),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden habe und den Test durchführen möchte.", checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
content_url="https://exam.vbv-afa.ch/", test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
) )
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle) LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
LearningContentFeedbackFactory( LearningContentFeedbackFactory(
@ -559,14 +559,14 @@ In questo Circle imparerai come sono strutturati i corsi interaziendali. Imparer
LearningUnitFactory( LearningUnitFactory(
title="Controllo delle competenze", title_hidden=True, parent=circle title="Controllo delle competenze", title_hidden=True, parent=circle
) )
LearningContentTestFactory( LearningContentEdoniqTestFactory(
title="Domande di conoscenza e di comprensione", title="Domande di conoscenza e di comprensione",
parent=circle, parent=circle,
description=RichText( description=RichText(
"<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>" "<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>"
), ),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden habe und den Test durchführen möchte.", checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
content_url="https://exam.vbv-afa.ch/", test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
) )
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle) LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
LearningContentFeedbackFactory( LearningContentFeedbackFactory(
@ -679,14 +679,15 @@ In diesem Circle lernst du die wichtigsten Grundlagen bezüglich Versicherungswi
) )
LearningSequenceFactory(title="Transfer", parent=circle, icon="it-icon-ls-end") LearningSequenceFactory(title="Transfer", parent=circle, icon="it-icon-ls-end")
LearningUnitFactory(title="Kompetenznachweis", title_hidden=True, parent=circle) LearningUnitFactory(title="Kompetenznachweis", title_hidden=True, parent=circle)
LearningContentTestFactory( LearningContentEdoniqTestFactory(
title="Wissens- und Verständnisfragen", title="Wissens- und Verständnisfragen",
parent=circle, parent=circle,
description=RichText( description=RichText(
"<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>" "<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>"
), ),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden habe und den Test durchführen möchte.", checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
content_url="https://exam.vbv-afa.ch/", test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096523730,2147466125",
extended_time_test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1691157696911,2147478636",
) )
LearningUnitFactory(title="Reflexion", title_hidden=True, parent=circle) LearningUnitFactory(title="Reflexion", title_hidden=True, parent=circle)
LearningContentAssignmentFactory( LearningContentAssignmentFactory(
@ -787,14 +788,15 @@ Dans ce cercle, tu apprends les bases les plus importantes en matière d'assuran
LearningUnitFactory( LearningUnitFactory(
title="Contrôle de compétences", title_hidden=True, parent=circle title="Contrôle de compétences", title_hidden=True, parent=circle
) )
LearningContentTestFactory( LearningContentEdoniqTestFactory(
title="Questions de connaissances et de compréhension", title="Questions de connaissances et de compréhension",
parent=circle, parent=circle,
description=RichText( description=RichText(
"<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>" "<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>"
), ),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden habe und den Test durchführen möchte.", checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
content_url="https://exam.vbv-afa.ch/", test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096523730,2147466125",
extended_time_test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1691157696911,2147478636",
) )
LearningUnitFactory(title="Réflexion", title_hidden=True, parent=circle) LearningUnitFactory(title="Réflexion", title_hidden=True, parent=circle)
LearningContentAssignmentFactory( LearningContentAssignmentFactory(
@ -895,14 +897,15 @@ In questo Circle imparerai le basi più importanti del settore assicurativo e de
LearningUnitFactory( LearningUnitFactory(
title="Controllo delle competenze", title_hidden=True, parent=circle title="Controllo delle competenze", title_hidden=True, parent=circle
) )
LearningContentTestFactory( LearningContentEdoniqTestFactory(
title="Domande di conoscenza e di comprensione", title="Domande di conoscenza e di comprensione",
parent=circle, parent=circle,
description=RichText( description=RichText(
"<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>" "<p>Folgender Test mit Wissens- und Verständnisfragen ist Teil des Kompetenznachweises. Der Test kann nur einmal durchgeführt werden und ist notenrelevant.</p>"
), ),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden habe und den Test durchführen möchte.", checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
content_url="https://exam.vbv-afa.ch/", test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096523730,2147466125",
extended_time_test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1691157696911,2147478636",
) )
LearningUnitFactory(title="Riflessione", title_hidden=True, parent=circle) LearningUnitFactory(title="Riflessione", title_hidden=True, parent=circle)
LearningContentAssignmentFactory( LearningContentAssignmentFactory(

View File

@ -0,0 +1,52 @@
# Generated by Django 3.2.20 on 2023-08-17 13:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("learnpath", "0003_auto_20230810_0817"),
("course", "0003_alter_coursecompletion_additional_json_data"),
("duedate", "0002_alter_duedate_start"),
("course_session", "0003_initial"),
]
operations = [
migrations.CreateModel(
name="CourseSessionEdoniqTest",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"course_session",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="course.coursesession",
),
),
(
"due_date",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="edoniq_test_due_date",
to="duedate.duedate",
),
),
(
"learning_content",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="learnpath.learningcontentedoniqtest",
),
),
],
),
]

View File

@ -152,3 +152,22 @@ class CourseSessionAssignment(models.Model):
def __str__(self): def __str__(self):
return f"{self.course_session} - {self.learning_content}" return f"{self.course_session} - {self.learning_content}"
class CourseSessionEdoniqTest(models.Model):
course_session = models.ForeignKey(
"course.CourseSession",
on_delete=models.CASCADE,
)
learning_content = models.ForeignKey(
"learnpath.LearningContentEdoniqTest",
on_delete=models.CASCADE,
)
due_date = models.OneToOneField(
"duedate.DueDate",
on_delete=models.CASCADE,
related_name="edoniq_test_due_date",
)
def __str__(self):
return f"{self.course_session} - {self.learning_content}"

View File

@ -4,7 +4,7 @@ from wagtail.models import Page
from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.duedate.models import DueDate
from vbv_lernwelt.learnpath.models import ( from vbv_lernwelt.learnpath.models import (
LearningContentAttendanceCourse, LearningContentAttendanceCourse,
LearningContentTest, LearningContentEdoniqTest,
) )
@ -23,7 +23,7 @@ class DueDateAdmin(admin.ModelAdmin):
csd = DueDate.objects.get(id=object_id) csd = DueDate.objects.get(id=object_id)
kwargs["queryset"] = Page.objects.descendant_of( kwargs["queryset"] = Page.objects.descendant_of(
csd.course_session.course.coursepage csd.course_session.course.coursepage
).exact_type(LearningContentAttendanceCourse, LearningContentTest) ).exact_type(LearningContentAttendanceCourse, LearningContentEdoniqTest)
else: else:
kwargs["queryset"] = Page.objects.none() kwargs["queryset"] = Page.objects.none()
return super().formfield_for_foreignkey(db_field, request, **kwargs) return super().formfield_for_foreignkey(db_field, request, **kwargs)

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.20 on 2023-08-17 13:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("duedate", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="duedate",
name="start",
field=models.DateTimeField(blank=True, db_index=True, null=True),
),
]

View File

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

View File

@ -0,0 +1,45 @@
import base64
from datetime import datetime
import structlog
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
from django.conf import settings
logger = structlog.get_logger(__name__)
CIPHER_TRANSFORMATION = "RSA/ECB/PKCS1Padding"
def create_token(user_id: str):
try:
token = (
f"user:{user_id};system:{settings.EDONIQ_ENV};date:"
+ str(int(datetime.now().timestamp()))
+ ";"
)
encrypted = _encrypt_message(
token, settings.EDONIQ_CERTIFICATE.replace("\\n", "\n")
)
return str(base64.b64encode(encrypted), "utf-8")
except Exception as e:
logger.debug(
"get_edoniq_test_fail",
label="edoniq_test_api",
error=e,
)
def _encrypt_message(token: str, key: str):
key = RSA.import_key(key)
plainbytes = token.encode("utf-8")
cipherbytes = _encrypt(plainbytes, key)
encrypted_token = base64.b64encode(cipherbytes)
return encrypted_token
def _encrypt(token, key):
cipher = PKCS1_v1_5.new(key)
return cipher.encrypt(token)

View File

@ -0,0 +1,41 @@
from rest_framework.test import APITestCase
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.course.creators.test_course import create_test_course
from vbv_lernwelt.learnpath.models import LearningContentEdoniqTest
class RedirectTestCase(APITestCase):
def setUp(self) -> None:
create_default_users()
create_test_course(with_sessions=True)
def test_redirects_normal_test(self):
test = LearningContentEdoniqTest.objects.get(
title="Wissens- und Verständnisfragen"
)
data = {"learning_content_id": test.id, "extended_time_test": False}
self.client.login(username="admin", password="test")
self._test_redirect(
data,
"https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
)
def test_redirects_extended_test(self):
test = LearningContentEdoniqTest.objects.get(
title="Wissens- und Verständnisfragen"
)
data = {"learning_content_id": test.id, "extended_time_test": True}
self.client.login(username="admin", password="test")
self._test_redirect(
data,
"https://exam2.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
)
def _test_redirect(self, data, expected_url):
response = self.client.post(
"/api/core/edoniq-test/redirect/", data, format="json"
)
self.assertEqual(200, response.status_code)
content = response.json()
self.assertTrue(content["redirect_url"].startswith(expected_url))

View File

@ -1,3 +1,14 @@
import structlog
from django.core.exceptions import PermissionDenied
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework.decorators import api_view
from vbv_lernwelt.course.permissions import has_course_access_by_page_request
from vbv_lernwelt.edoniq_test.edoniq_sso import create_token
from vbv_lernwelt.learnpath.models import LearningContentEdoniqTest
logger = structlog.get_logger(__name__)
import csv import csv
from datetime import date from datetime import date
from itertools import chain from itertools import chain
@ -15,6 +26,62 @@ UK_COURSE_IDS = [COURSE_UK, COURSE_UK_FR, COURSE_UK_IT]
DEFAULT_EXCLUDED_DOMAINS = ["eiger-versicherung.ch", "assurance.ch", "example.com"] DEFAULT_EXCLUDED_DOMAINS = ["eiger-versicherung.ch", "assurance.ch", "example.com"]
@api_view(["POST"])
def get_edoniq_token_redirect(request):
page_id = request.data.get("learning_content_id", "")
extended_time_test = request.data.get("extended_time_test", False)
logger.debug(
"get_edoniq_test_redirect",
label="edoniq_test_api",
page_id=page_id,
user_id=request.user.id,
extended_time_test=extended_time_test,
)
if not page_id:
return JsonResponse({"error": "learning_content_id is required"}, status=400)
try:
test_page = get_object_or_404(LearningContentEdoniqTest, id=page_id)
if not has_course_access_by_page_request(request, test_page):
logger.debug(
"get_edoniq_test_redirect_permission_denied",
label="edoniq_test_api",
page_id=page_id,
page_slug=test_page.slug,
page_title=test_page.title,
user_id=request.user.id,
)
raise PermissionDenied()
url = (
test_page.test_url
if not extended_time_test
else test_page.extended_time_test_url
)
token = create_token(
str(request.user.id),
)
redirect_url = f"{url}&ssoToken={token}"
response_data = {"redirect_url": redirect_url}
logger.debug(
"get_edoniq_test_redirect_permission_success",
label="edoniq_test_api",
page_id=page_id,
page_slug=test_page.slug,
page_title=test_page.title,
user_id=request.user.id,
)
return JsonResponse(response_data, status=200)
except Exception as e:
logger.error(e, exc_info=True)
return JsonResponse({"error": str(e)}, status=404)
@staff_member_required @staff_member_required
def export_students(request): def export_students(request):
course_session_users = fetch_course_session_users(UK_COURSE_IDS) course_session_users = fetch_course_session_users(UK_COURSE_IDS)
@ -35,6 +102,14 @@ def export_students_and_trainers(request):
return generate_export_response(course_session_users) return generate_export_response(course_session_users)
def fetch_course_session_users(courses: List[int]):
# if a user is in multiple courses, he should be exported multiple times
# todo: check if this is the case otherwise use .distinct("user")
return CourseSessionUser.objects.filter(
course_session__course__id__in=courses, role=CourseSessionUser.Role.MEMBER
).order_by("user__email")
def fetch_course_session_users( def fetch_course_session_users(
courses: List[int], role=CourseSessionUser.Role.MEMBER, excluded_domains=None courses: List[int], role=CourseSessionUser.Role.MEMBER, excluded_domains=None
): ):

View File

@ -13,12 +13,12 @@ from vbv_lernwelt.course.models import CourseCategory, CoursePage
from vbv_lernwelt.learnpath.tests.learning_path_factories import ( from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory, CircleFactory,
LearningContentAssignmentFactory, LearningContentAssignmentFactory,
LearningContentEdoniqTestFactory,
LearningContentFeedbackFactory, LearningContentFeedbackFactory,
LearningContentLearningModuleFactory, LearningContentLearningModuleFactory,
LearningContentMediaLibraryFactory, LearningContentMediaLibraryFactory,
LearningContentPlaceholderFactory, LearningContentPlaceholderFactory,
LearningContentRichTextFactory, LearningContentRichTextFactory,
LearningContentTestFactory,
LearningContentVideoFactory, LearningContentVideoFactory,
LearningPathFactory, LearningPathFactory,
LearningSequenceFactory, LearningSequenceFactory,
@ -353,13 +353,13 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
title="Praxisauftrag", title="Praxisauftrag",
parent=circle, parent=circle,
) )
LearningContentTestFactory( LearningContentEdoniqTestFactory(
title="Fachcheck Fahrzeug", title="Fachcheck Fahrzeug",
parent=circle, parent=circle,
description=RichText( description=RichText(
"<p>Teste dein Wissen mit dem Fachcheck. Der Test kann nur einmal durchgeführt werden.</p>" "<p>Teste dein Wissen mit dem Fachcheck. Der Test kann nur einmal durchgeführt werden.</p>"
), ),
checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden habe und den Test durchführen möchte.", checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.",
content_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/fachcheck-fahrzeug-xapi-LFv8YiyM/index.html#/", content_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/fachcheck-fahrzeug-xapi-LFv8YiyM/index.html#/",
) )
LearningContentAssignmentFactory( LearningContentAssignmentFactory(

View File

@ -9,12 +9,12 @@ from vbv_lernwelt.learnpath.models import (
LearningContentAssignment, LearningContentAssignment,
LearningContentAttendanceCourse, LearningContentAttendanceCourse,
LearningContentDocumentList, LearningContentDocumentList,
LearningContentEdoniqTest,
LearningContentFeedback, LearningContentFeedback,
LearningContentLearningModule, LearningContentLearningModule,
LearningContentMediaLibrary, LearningContentMediaLibrary,
LearningContentPlaceholder, LearningContentPlaceholder,
LearningContentRichText, LearningContentRichText,
LearningContentTest,
LearningContentVideo, LearningContentVideo,
LearningPath, LearningPath,
LearningSequence, LearningSequence,
@ -46,7 +46,7 @@ class LearningContentInterface(CoursePageInterface):
return LearningContentPlaceholderObjectType return LearningContentPlaceholderObjectType
elif isinstance(instance, LearningContentRichText): elif isinstance(instance, LearningContentRichText):
return LearningContentRichTextObjectType return LearningContentRichTextObjectType
elif isinstance(instance, LearningContentTest): elif isinstance(instance, LearningContentEdoniqTest):
return LearningContentTestObjectType return LearningContentTestObjectType
elif isinstance(instance, LearningContentVideo): elif isinstance(instance, LearningContentVideo):
return LearningContentVideoObjectType return LearningContentVideoObjectType
@ -104,7 +104,7 @@ class LearningContentMediaLibraryObjectType(DjangoObjectType):
class LearningContentTestObjectType(DjangoObjectType): class LearningContentTestObjectType(DjangoObjectType):
class Meta: class Meta:
model = LearningContentTest model = LearningContentEdoniqTest
interfaces = (LearningContentInterface,) interfaces = (LearningContentInterface,)
fields = [] fields = []

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.13 on 2023-08-10 06:17
import django.db.models.deletion
import wagtail.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("course", "0003_alter_coursecompletion_additional_json_data"),
("wagtailcore", "0083_workflowcontenttype"),
("wagtailredirects", "0008_add_verbose_name_plural"),
("wagtailforms", "0005_alter_formsubmission_form_data"),
("duedate", "0001_initial"),
("learnpath", "0002_alter_learningcontentassignment_assignment_type"),
]
operations = [
migrations.CreateModel(
name="LearningContentEdoniqTest",
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",
),
),
("minutes", models.PositiveIntegerField(default=15)),
("description", wagtail.fields.RichTextField(blank=True)),
("content_url", models.TextField(blank=True)),
("has_course_completion_status", models.BooleanField(default=True)),
(
"can_user_self_toggle_course_completion",
models.BooleanField(default=False),
),
("checkbox_text", models.TextField(blank=True)),
("test_url", models.TextField(blank=True)),
("extended_time_test_url", models.TextField(blank=True)),
],
options={
"abstract": False,
},
bases=("wagtailcore.page",),
),
migrations.DeleteModel(
name="LearningContentTest",
),
]

View File

@ -77,7 +77,7 @@ class Circle(CourseBasePage):
"learnpath.LearningContentMediaLibrary", "learnpath.LearningContentMediaLibrary",
"learnpath.LearningContentPlaceholder", "learnpath.LearningContentPlaceholder",
"learnpath.LearningContentRichText", "learnpath.LearningContentRichText",
"learnpath.LearningContentTest", "learnpath.LearningContentEdoniqTest",
"learnpath.LearningContentVideo", "learnpath.LearningContentVideo",
] ]
@ -321,19 +321,28 @@ class LearningContentMediaLibrary(LearningContent):
can_user_self_toggle_course_completion = models.BooleanField(default=True) can_user_self_toggle_course_completion = models.BooleanField(default=True)
class LearningContentTest(LearningContent): class LearningContentEdoniqTest(LearningContent):
serialize_field_names = LearningContent.serialize_field_names + [ serialize_field_names = LearningContent.serialize_field_names + [
"checkbox_text", "checkbox_text",
"has_extended_time_test",
] ]
parent_page_types = ["learnpath.Circle"] parent_page_types = ["learnpath.Circle"]
subpage_types = [] subpage_types = []
checkbox_text = models.TextField(blank=True) checkbox_text = models.TextField(blank=True)
test_url = models.TextField(blank=True)
extended_time_test_url = models.TextField(blank=True)
content_panels = LearningContent.content_panels + [ content_panels = LearningContent.content_panels + [
FieldPanel("checkbox_text", classname="Text"), FieldPanel("checkbox_text", classname="Text"),
FieldPanel("test_url", classname="Text"),
FieldPanel("extended_time_test_url", classname="Text"),
] ]
@property
def has_extended_time_test(self):
return bool(self.extended_time_test_url)
class LearningContentRichText(LearningContent): class LearningContentRichText(LearningContent):
text = RichTextField(blank=True, features=DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER) text = RichTextField(blank=True, features=DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER)

View File

@ -6,12 +6,12 @@ from vbv_lernwelt.learnpath.models import (
LearningContentAssignment, LearningContentAssignment,
LearningContentAttendanceCourse, LearningContentAttendanceCourse,
LearningContentDocumentList, LearningContentDocumentList,
LearningContentEdoniqTest,
LearningContentFeedback, LearningContentFeedback,
LearningContentLearningModule, LearningContentLearningModule,
LearningContentMediaLibrary, LearningContentMediaLibrary,
LearningContentPlaceholder, LearningContentPlaceholder,
LearningContentRichText, LearningContentRichText,
LearningContentTest,
LearningContentVideo, LearningContentVideo,
LearningPath, LearningPath,
LearningSequence, LearningSequence,
@ -149,14 +149,14 @@ class LearningContentMediaLibraryFactory(wagtail_factories.PageFactory):
model = LearningContentMediaLibrary model = LearningContentMediaLibrary
class LearningContentTestFactory(wagtail_factories.PageFactory): class LearningContentEdoniqTestFactory(wagtail_factories.PageFactory):
title = "Fachcheck" title = "Fachcheck"
minutes = 0 minutes = 0
content_url = "" content_url = ""
description = RichText("") description = RichText("")
class Meta: class Meta:
model = LearningContentTest model = LearningContentEdoniqTest
class LearningContentRichTextFactory(wagtail_factories.PageFactory): class LearningContentRichTextFactory(wagtail_factories.PageFactory):

View File

@ -5,6 +5,7 @@ env/docker_local.env
server/vbv_lernwelt/assignment/creators/create_assignments.py server/vbv_lernwelt/assignment/creators/create_assignments.py
server/vbv_lernwelt/static/ server/vbv_lernwelt/static/
server/vbv_lernwelt/media/ server/vbv_lernwelt/media/
server/vbv_lernwelt/edoniq_test/certificates/test.key
supabase.md supabase.md
scripts/supabase/init.sql scripts/supabase/init.sql
ramon.wenger@iterativ.ch.gpg ramon.wenger@iterativ.ch.gpg