Merge branch 'feature/daniel-2022-06-03' into develop

This commit is contained in:
Daniel Egger 2022-06-03 18:49:50 +02:00
commit 33b596c649
162 changed files with 2329 additions and 1177 deletions

View File

@ -8,7 +8,7 @@ end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.{py,rst,ini}] [*.{py,rst,ini,sh}]
indent_style = space indent_style = space
indent_size = 4 indent_size = 4

2
.gitignore vendored
View File

@ -283,3 +283,5 @@ cypress/screenshots
cypress/test-reports cypress/test-reports
/server/vbv_lernwelt/static/css/tailwind.css /server/vbv_lernwelt/static/css/tailwind.css
/server/vbv_lernwelt/static/vue/
/server/vbv_lernwelt/templates/vue/index.html

View File

@ -11,38 +11,38 @@ npm run tailwind
# run vue vite dev server # run vue vite dev server
cd client && npm run dev cd client && npm run dev
# run django dev server # reset db and run django dev server
cd server && python manage.py runserver ./prepare_server.sh
``` ```
## Installation ## Installation
See `.tool-versions` file for used django and node version See `.tool-versions` file for used django and node version
You have to set up at least the following environment variables:
```bash
export IT_APP_ENVIRONMENT=development
```
See `.env_secrets/local_daniel.env` for more possible environment variables.
Especially set correct values for `POSTGRES_*` and `DATABASE_URL`
### Server part ### Server part
Run every sub command in the `server` directory Install python dependencies:
Create a new PostgreSQL database and role
```bash ```bash
createdb vbv_lernwelt pip install -r server/requirements/requirements-dev.txt
createuser vbv_lernwelt
``` ```
Set the environment variable accordingly The "prepare_server.sh" script will create the database according to `POSTGRES_*` environment variables.
It will also setup the tables for django and run the django development server.
```bash ```bash
export VBV_DATABASE_URL='postgres://vbv_lernwelt@localhost:5432/vbv_lernwelt' # will initial`migrate` and `runserver` etc...
``` ./prepare_server.sh
Set `VBV_DJANGO_READ_DOT_ENV_FILE=True` to make the config read the `example.env` file (with direnv!?).
```bash
python manage.py migrate
# sync server
python manage.py runserver
# or async server # or async server
# uvicorn config.asgi:application --host 0.0.0.0 --reload # uvicorn config.asgi:application --host 0.0.0.0 --reload
@ -50,9 +50,9 @@ python manage.py runserver
### Client part ### Client part
Run every command in the `client` directory
```bash ```bash
cd client
npm install npm install
# run dev server # run dev server
@ -64,6 +64,7 @@ npm run dev
Cypress and TailwindCSS ist installed for client and server, so there is this package.json on the project root directory Cypress and TailwindCSS ist installed for client and server, so there is this package.json on the project root directory
```bash ```bash
# in project root directory
npm install npm install
``` ```
@ -90,8 +91,6 @@ npm install
#### Install the tailwind css Plugin from Jetbrains #### Install the tailwind css Plugin from Jetbrains
## Wagtail API intro ## Wagtail API intro
get all pages: get all pages:

View File

@ -28,7 +28,7 @@ cap.deploy_one_click_app(
namespace='vbv-lernwelt', namespace='vbv-lernwelt',
# check https://github.com/caprover/one-click-apps/blob/master/public/v4/apps/postgres.yml # check https://github.com/caprover/one-click-apps/blob/master/public/v4/apps/postgres.yml
app_variables={ app_variables={
'$$cap_postgres_version': '14.1', '$$cap_postgres_version': '14.2',
'$$cap_pg_user': db_user, '$$cap_pg_user': db_user,
'$$cap_pg_pass': db_pass, '$$cap_pg_pass': db_pass,
'$$cap_pg_db': db_name, '$$cap_pg_db': db_name,
@ -44,11 +44,11 @@ cap.create_and_update_app(
image_name='docker.io/iterativ/vbv-lernwelt-django', image_name='docker.io/iterativ/vbv-lernwelt-django',
environment_variables={ environment_variables={
# 'DJANGO_SETTINGS_MODULE': 'config.settings.base', # 'DJANGO_SETTINGS_MODULE': 'config.settings.base',
'VBV_DJANGO_SECRET_KEY': env.str('VBV_DJANGO_SECRET_KEY'), 'IT_DJANGO_SECRET_KEY': env.str('IT_DJANGO_SECRET_KEY'),
'VBV_DJANGO_ADMIN_URL': env.str('VBV_DJANGO_ADMIN_URL'), 'IT_DJANGO_ADMIN_URL': env.str('IT_DJANGO_ADMIN_URL'),
'VBV_DJANGO_ALLOWED_HOSTS': env.str('VBV_DJANGO_ALLOWED_HOSTS'), 'IT_DJANGO_ALLOWED_HOSTS': env.str('IT_DJANGO_ALLOWED_HOSTS'),
'VBV_SENTRY_DSN': env.str('VBV_SENTRY_DSN'), 'IT_SENTRY_DSN': env.str('IT_SENTRY_DSN'),
'VBV_DJANGO_DEV_MODE': 'caprover', 'IT_APP_ENVIRONMENT': 'caprover',
'POSTGRES_HOST': 'srv-captain--vbv-lernwelt-postgres-db', 'POSTGRES_HOST': 'srv-captain--vbv-lernwelt-postgres-db',
'POSTGRES_PORT': 5432, 'POSTGRES_PORT': 5432,
'POSTGRES_DB': db_name, 'POSTGRES_DB': db_name,

View File

@ -1,5 +1,8 @@
#!/bin/bash #!/bin/bash
# create client
npm run build
# create and push new docker container # create and push new docker container
docker build -f compose/django/Dockerfile -t iterativ/vbv-lernwelt-django . docker build -f compose/django/Dockerfile -t iterativ/vbv-lernwelt-django .
docker push iterativ/vbv-lernwelt-django docker push iterativ/vbv-lernwelt-django

View File

@ -5,6 +5,7 @@
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title> <title>Vite App</title>
<link href="/static/fonts/BuenosAires/stylesheet.css" rel="stylesheet">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -3,7 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build && cp ./dist/index.html ../server/vbv_lernwelt/templates/vue/index.html && cp -r ./dist/static/vue ../server/vbv_lernwelt/static/",
"preview": "vite preview --port 5050", "preview": "vite preview --port 5050",
"test:unit": "vitest --environment jsdom", "test:unit": "vitest --environment jsdom",
"test:e2e": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress open'", "test:e2e": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress open'",
@ -13,6 +13,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^0.26.1", "axios": "^0.26.1",
"loglevel": "^1.8.0",
"pinia": "^2.0.13", "pinia": "^2.0.13",
"vue": "^3.2.31", "vue": "^3.2.31",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -1,9 +0,0 @@
@font-face {
font-family: 'BuenosAires';
font-style: normal;
src: url("@/assets/styles/fonts/BuenosAires-Regular.woff2") format("woff2"),
url("@/assets/styles/fonts/BuenosAires-Regular.woff") format("woff");
font-weight: 400;
font-style: normal;
font-display: swap;
}

View File

@ -1,2 +1 @@
@import "fonts";
@import "../../../../server/vbv_lernwelt/static/css/tailwind.css"; @import "../../../../server/vbv_lernwelt/static/css/tailwind.css";

View File

@ -1,19 +0,0 @@
<template>
<div>
<h1>Analyse</h1>
<p>Dauer 8 Stunden</p>
<div>CircleComponent</div>
<div>CircleOverviewComponent</div>
<div>FachexpertenKontaktieren</div>
</div>
</template>
<script>
export default {
name: "CircleSidebar"
}
</script>
<style scoped>
</style>

View File

@ -1,16 +0,0 @@
<template>
<ul>
<li>Einstieg</li>
<li>Analyse</li>
</ul>
</template>
<script>
export default {
name: "LearningPath"
}
</script>
<style scoped>
</style>

View File

@ -1,29 +0,0 @@
<template>
<section>
<div>
<h2>Starten</h2>
<p>30 Minuten</p>
</div>
<ul>
<li v-for="unit in units" :key="unit">
{{unit}}
</li>
</ul>
</section>
</template>
<script>
export default {
props: ['units'],
setup (props) {
return {
}
},
}
</script>
<style scoped>
</style>

View File

@ -1,18 +1,82 @@
<template> <script setup>
<nav class="bg-blue-dark flex flex-row text-white">
<p>Ich bin ein myVBV Header</p>
<ul>
<li>Ich bin ein myVBV link</li>
</ul>
</nav>
</template>
<script> import { reactive } from 'vue';
export default {
name: "MainNavigationBar" const state = reactive({showMenu: false});
function toggleNav() {
console.log(state.showMenu);
state.showMenu = !state.showMenu;
} }
function calcNavigationMobileOpenClasses() {
return state.showMenu ? ['fixed', 'w-full', 'h-screen'] : [];
}
</script> </script>
<style scoped> <template>
<div class="navigation bg-blue-900" :class="calcNavigationMobileOpenClasses()">
<nav
class="
px-8
py-4
mx-auto
lg:flex lg:justify-start lg:items-center
"
>
<div class="flex items-center justify-between">
<router-link
to="/"
class="
font-bold
text-white
text-2xl
hover:text-sky-500
pr-10
"
>myVBV
</router-link>
</style> <!-- Mobile menu button -->
<div @click="toggleNav" class="flex lg:hidden">
<button
type="button"
class="
text-white
hover:text-sky-500
focus:outline-none focus:text-sky-500
"
>
<svg viewBox="0 0 24 24" class="w-6 h-6 fill-current">
<path
fill-rule="evenodd"
d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z"
></path>
</svg>
</button>
</div>
</div>
<!-- Mobile Menu open: "block", Menu closed: "hidden" -->
<ul
:class="state.showMenu ? 'flex' : 'hidden'"
class="
flex-auto
flex-col
mt-8
space-y-8
lg:flex lg:space-y-0 lg:flex-row lg:items-center lg:space-x-10 lg:mt-0
"
>
<li class="text-white hover:text-sky-500 text-2xl font-bold lg:text-base lg:font-normal">Lernpfad</li>
<li class="text-white hover:text-sky-500 text-2xl font-bold lg:text-base lg:font-normal">Kompetenzprofil</li>
<hr class="text-white lg:hidden">
<li class="hidden lg:list-item flex-auto"></li>
<li class="text-white hover:text-sky-500 text-2xl font-bold lg:text-base lg:font-normal">Mediathek</li>
<li class="text-white hover:text-sky-500 text-2xl font-bold lg:text-base lg:font-normal">Netzwerk</li>
<li class="text-white hover:text-sky-500 text-2xl font-bold lg:text-base lg:font-normal">Jan Baumgartner</li>
</ul>
</nav>
</div>
</template>

View File

@ -1,18 +0,0 @@
<template>
<nav class="bg-blue-dark flex flex-row text-white">
<p>Ich bin ein VBV Header</p>
<ul>
<li>Ich bin ein VBV link</li>
</ul>
</nav>
</template>
<script>
export default {
name: "VBVNavigationBar"
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
import IconCheckboxUnchecked from '@/components/icons/IconCheckboxUnchecked.vue';
import IconSmileyNeutral from '@/components/icons/IconSmileyNeutral.vue';
defineProps(['learningSequence'])
</script>
<template>
<div class="mb-8 learning-sequence">
<div class="flex items-center gap-4 mb-2">
<component :is="learningSequence.icon" />
<h3 class="text-xl">
{{ learningSequence.title }}
</h3>
<div>{{ learningSequence.minutes }} Minuten</div>
</div>
<div class="bg-white px-4 border border-gray-500 border-l-4 border-l-sky-500">
<div
v-for="learningPackage in learningSequence.learningPackages"
class="py-3"
>
<div class="pb-3 flex gap-4" v-if="learningPackage.title">
<div class="font-bold">{{ learningPackage.title }}</div>
<div>{{ learningPackage.minutes }} Minuten</div>
</div>
<div
v-for="learningUnit in learningPackage.learningUnits"
class="flex items-center gap-4 pb-3"
>
<IconSmileyNeutral v-if="learningUnit.contents[0].type === 'self_evaluation'"/>
<IconCheckboxUnchecked v-else/>
<div>{{ learningUnit.title }}</div>
</div>
<hr class="-mx-4 text-gray-500">
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M14.9187 20L6.15699 11.0386C5.94112 10.7984 5.94874 10.4269 6.17427 10.1962C6.3998 9.96556 6.76303 9.95776 6.99783 10.1786L14.9187 18.28L22.8397 10.1786C22.9902 10.0246 23.2097 9.96442 23.4154 10.0208C23.621 10.0772 23.7817 10.2415 23.8368 10.4518C23.8919 10.6622 23.8331 10.8866 23.6825 11.0406L14.9187 20Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.92857 14.9902L18.89 6.22846C19.1302 6.01259 19.5017 6.02021 19.7323 6.24574C19.963 6.47127 19.9708 6.83451 19.75 7.0693L11.6486 14.9902L19.75 22.9111C19.904 23.0617 19.9642 23.2812 19.9078 23.4868C19.8514 23.6925 19.6871 23.8532 19.4767 23.9083C19.2664 23.9634 19.0419 23.9046 18.8879 23.754L9.92857 14.9902Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.9285 15.0098L10.9671 23.7715C10.7269 23.9874 10.3554 23.9798 10.1248 23.7543C9.89408 23.5287 9.88629 23.1655 10.1071 22.9307L18.2085 15.0098L10.1071 7.08886C9.95309 6.9383 9.89295 6.71884 9.94931 6.51317C10.0057 6.3075 10.17 6.14684 10.3804 6.09174C10.5907 6.03662 10.8152 6.09543 10.9692 6.24599L19.9285 15.0098Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M14.9383 10L23.7001 18.9614C23.916 19.2016 23.9084 19.5731 23.6828 19.8038C23.4573 20.0344 23.0941 20.0422 22.8593 19.8214L14.9383 11.72L7.01742 19.8214C6.86685 19.9754 6.6474 20.0356 6.44173 19.9792C6.23605 19.9228 6.0754 19.7585 6.02029 19.5482C5.96518 19.3378 6.02398 19.1134 6.17455 18.9594L14.9383 10Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.1529 18.7528L8.30897 14.9088L7 16.2086L12.1529 21.3615L23.2147 10.2998L21.9149 9L12.1529 18.7528Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="28" height="28" rx="1" stroke="#00224D" stroke-width="2"/>
<path d="M12.1529 18.7528L8.30897 14.9088L7 16.2086L12.1529 21.3615L23.2147 10.2998L21.9149 9L12.1529 18.7528Z"
fill="#00224D"/>
</svg>
</template>

View File

@ -0,0 +1,5 @@
<template>
<svg width="30" height="30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="28" height="28" rx="1" stroke="#B1C1CA" stroke-width="2"/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.473 15.3L26.457 5.31901C26.6885 5.11428 26.7873 4.79797 26.7135 4.49786C26.6396 4.19776 26.4053 3.96344 26.1052 3.88957C25.8051 3.8157 25.4887 3.91448 25.284 4.14601L15.3 14.127L5.31901 4.14301C5.11428 3.91148 4.79797 3.8127 4.49786 3.88657C4.19776 3.96044 3.96344 4.19476 3.88957 4.49486C3.8157 4.79497 3.91448 5.11128 4.14601 5.31601L14.127 15.3L4.14301 25.281C3.91148 25.4857 3.8127 25.8021 3.88657 26.1022C3.96044 26.4023 4.19476 26.6366 4.49486 26.7105C4.79497 26.7843 5.11128 26.6855 5.31601 26.454L15.3 16.473L25.281 26.457C25.61 26.7479 26.1086 26.7326 26.4191 26.4221C26.7296 26.1116 26.7449 25.613 26.454 25.284L16.473 15.3Z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@ -0,0 +1,8 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M15 5C9.48 5 5 9.48 5 15C5 20.52 9.48 25 15 25C20.52 25 25 20.52 25 15C25 9.48 20.52 5 15 5ZM14 10V12H16V10H14ZM14 14V20H16V14H14ZM7 15C7 19.41 10.59 23 15 23C19.41 23 23 19.41 23 15C23 10.59 19.41 7 15 7C10.59 7 7 10.59 7 15Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.00703 30L7.67733 28.6597L10.1185 26.1991C10.589 25.7217 11.1488 25.3433 11.7655 25.0856C12.3822 24.828 13.0436 24.6963 13.7111 24.6982C13.7173 24.6968 13.7238 24.6968 13.73 24.6982C14.4467 24.7002 15.1566 24.5588 15.8187 24.2823C16.4809 24.0058 17.082 23.5996 17.5873 23.0873L27.688 12.9061C27.8662 12.7299 27.9676 12.4895 27.9699 12.2379C27.9723 11.9863 27.8754 11.7441 27.7005 11.5645C27.5257 11.385 27.2873 11.2827 27.0377 11.2804C26.7881 11.278 26.5478 11.3757 26.3696 11.5519L22.3655 15.5892C22.2787 15.6798 22.1749 15.7521 22.0602 15.8019C21.9454 15.8516 21.822 15.8779 21.697 15.879C21.5721 15.8802 21.4482 15.8562 21.3326 15.8086C21.2169 15.761 21.1119 15.6906 21.0235 15.6016C20.9351 15.5126 20.8652 15.4068 20.8178 15.2902C20.7705 15.1737 20.7466 15.0489 20.7476 14.9229C20.7487 14.797 20.7746 14.6726 20.8238 14.5568C20.8731 14.4411 20.9447 14.3364 21.0345 14.2489L25.0261 10.2242L25.0487 10.2015L26.3558 8.88393C26.4432 8.79597 26.5125 8.6915 26.5598 8.57651C26.6071 8.46152 26.6315 8.33826 26.6315 8.21378C26.6315 8.08929 26.6071 7.96603 26.5598 7.85104C26.5125 7.73605 26.4432 7.63159 26.3558 7.54362C26.1775 7.37106 25.9401 7.27437 25.693 7.27367C25.4458 7.27296 25.2079 7.36829 25.0286 7.53983L19.7036 12.9061C19.6167 12.9966 19.5129 13.0688 19.3981 13.1185C19.2833 13.1681 19.1598 13.1942 19.0349 13.1953C18.91 13.1963 18.7861 13.1722 18.6705 13.1245C18.5549 13.0768 18.4499 13.0063 18.3616 12.9172C18.2733 12.8281 18.2035 12.7222 18.1562 12.6056C18.109 12.4891 18.0852 12.3642 18.0864 12.2383C18.0875 12.1124 18.1135 11.9879 18.1629 11.8723C18.2122 11.7566 18.284 11.652 18.3739 11.5645L23.6951 6.20079L23.6989 6.19574L25.0261 4.86049C25.116 4.77306 25.1877 4.66845 25.2371 4.55277C25.2864 4.43709 25.3125 4.31267 25.3136 4.18675C25.3147 4.06083 25.291 3.93595 25.2437 3.81938C25.1965 3.70281 25.1267 3.5969 25.0384 3.50781C24.9501 3.41873 24.8451 3.34827 24.7295 3.30053C24.6139 3.25279 24.49 3.22874 24.3651 3.22977C24.2402 3.23081 24.1167 3.25691 24.0019 3.30655C23.8871 3.3562 23.7832 3.42839 23.6964 3.51893L22.4131 4.80992L22.3667 4.86049L17.0404 10.2242C16.862 10.3917 16.6262 10.4829 16.3824 10.4786C16.1387 10.4743 15.9061 10.3747 15.7338 10.201C15.5614 10.0272 15.4626 9.79283 15.4583 9.54715C15.454 9.30147 15.5445 9.0637 15.7107 8.88393L21.0608 3.48985C21.2335 3.30825 21.3275 3.06496 21.3223 2.81349C21.317 2.56201 21.2128 2.32296 21.0326 2.14891C20.8525 1.97487 20.6111 1.88008 20.3616 1.88542C20.1121 1.89076 19.875 1.99577 19.7023 2.17736L11.0568 10.8944C10.9315 11.0204 10.7734 11.1082 10.6008 11.1478C10.4282 11.1873 10.2481 11.177 10.081 11.118C9.91397 11.0589 9.76685 10.9536 9.65653 10.8141C9.54621 10.6745 9.47718 10.5065 9.45737 10.3292L9.03337 6.50679C9.01696 6.26148 8.9176 6.02925 8.75187 5.84885C8.58614 5.66845 8.36405 5.55078 8.12265 5.51547C7.88001 5.49686 7.63895 5.56797 7.44455 5.71552C7.25016 5.86306 7.11573 6.07693 7.06642 6.31712L5.44946 14.474C5.17023 15.9066 5.36639 17.3919 6.00769 18.701C6.09438 18.8785 6.12381 19.0789 6.09187 19.2741C6.05993 19.4694 5.96821 19.6496 5.82956 19.7897L2.3297 23.3175L1 21.9772L4.04325 18.9084C3.44134 17.3822 3.2889 15.7135 3.6042 14.1023L5.22241 5.94538C5.36371 5.25421 5.74879 4.63811 6.30629 4.21125C6.86379 3.78439 7.5559 3.57571 8.25437 3.62388C8.93668 3.68601 9.57568 3.98766 10.0599 4.47618C10.5441 4.9647 10.8427 5.60908 10.9037 6.29689L11.1095 8.15561L18.3726 0.834531C18.6339 0.569543 18.9447 0.359361 19.287 0.216124C19.6294 0.0728871 19.9965 -0.000567264 20.3671 3.29854e-06H20.3697C20.9204 8.15468e-05 21.4591 0.162733 21.9192 0.467878C22.3794 0.773023 22.7407 1.2073 22.9588 1.71711C23.4964 1.40621 24.1207 1.28236 24.7351 1.36466C25.3495 1.44697 25.92 1.73085 26.3584 2.17249C26.7968 2.61413 27.0789 3.18896 27.1609 3.80823C27.243 4.42751 27.1205 5.05679 26.8124 5.59892C27.1765 5.75843 27.5032 5.99343 27.7712 6.28863C28.0392 6.58382 28.2426 6.93261 28.368 7.31227C28.4934 7.69193 28.5382 8.09396 28.4992 8.49219C28.4603 8.89042 28.3386 9.27593 28.1421 9.62362C28.5651 9.80721 28.9371 10.0925 29.2254 10.4545C29.5138 10.8165 29.7097 11.2443 29.7961 11.7003C29.8825 12.1563 29.8567 12.6267 29.721 13.0703C29.5853 13.514 29.3438 13.9173 29.0177 14.2451L18.917 24.4264C18.2377 25.1153 17.4296 25.6616 16.5395 26.0336C15.6493 26.4056 14.6948 26.5959 13.7312 26.5936C13.7241 26.5929 13.717 26.5929 13.7099 26.5936C13.2897 26.5925 12.8734 26.6755 12.4852 26.8376C12.097 26.9997 11.7445 27.2378 11.4481 27.5381L9.00703 30Z"
/>
</svg>
</template>

View File

@ -0,0 +1,10 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M1 2C1 1.44772 1.44772 1 2 1H20.1568C23.9162 1 26.9542 4.03802 26.9542 7.79736C26.9542 11.5567 23.9162 14.5947 20.1568 14.5947H7.79736C5.14259 14.5947 3 16.7373 3 19.3921C3 22.0468 5.14259 24.1894 7.79736 24.1894H25.9542C26.5065 24.1894 26.9542 24.6371 26.9542 25.1894C26.9542 25.7417 26.5065 26.1894 25.9542 26.1894H7.79736C4.03802 26.1894 1 23.1514 1 19.3921C1 15.6327 4.03802 12.5947 7.79736 12.5947H20.1568C22.8116 12.5947 24.9542 10.4521 24.9542 7.79736C24.9542 5.14259 22.8116 3 20.1568 3H2C1.44772 3 1 2.55228 1 2Z"
/>
<path
d="M24.3507 28.7665C26.3262 28.7665 27.9277 27.165 27.9277 25.1894C27.9277 23.2139 26.3262 21.6123 24.3507 21.6123C22.3751 21.6123 20.7736 23.2139 20.7736 25.1894C20.7736 27.165 22.3751 28.7665 24.3507 28.7665Z"
/>
</svg>
</template>

View File

@ -0,0 +1,14 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_34_3392)">
<path
d="M4.6875 28.125C3.58314 28.126 2.51388 27.7371 1.66826 27.0268C0.822635 26.3165 0.254953 25.3304 0.0653082 24.2425C-0.124337 23.1545 0.0762339 22.0345 0.631655 21.08C1.18708 20.1255 2.06168 19.3977 3.10125 19.025L3.73625 20.79C3.12508 21.0095 2.60868 21.4337 2.27472 21.9906C1.94076 22.5475 1.80981 23.2029 1.90409 23.8454C1.99838 24.4879 2.3121 25.078 2.79199 25.5155C3.27189 25.953 3.88839 26.2109 4.53685 26.2455C5.18531 26.2801 5.82577 26.0893 6.34952 25.7054C6.87327 25.3215 7.24803 24.7681 7.41019 24.1393C7.57235 23.5105 7.51191 22.845 7.23915 22.2557C6.96638 21.6663 6.49808 21.1896 5.91375 20.9063C5.80211 20.8522 5.70231 20.7765 5.62014 20.6835C5.53797 20.5906 5.47507 20.4823 5.43507 20.3648C5.39507 20.2474 5.37878 20.1232 5.38714 19.9994C5.39549 19.8756 5.42833 19.7548 5.48375 19.6438L10.4213 11.8738C10.8507 11.1967 11.4254 10.6237 12.1038 10.1963C11.6231 9.82891 11.2203 9.36963 10.9188 8.84516C10.6173 8.32069 10.4231 7.74149 10.3475 7.14126C10.2553 6.39834 10.3422 5.64415 10.6009 4.94166C10.8596 4.23917 11.2827 3.60877 11.8347 3.10311C12.3867 2.59745 13.0517 2.2312 13.7742 2.03495C14.4966 1.83871 15.2555 1.81816 15.9875 1.97501C17.0893 2.22083 18.0668 2.85271 18.7434 3.75643C19.4199 4.66015 19.7508 5.77608 19.6763 6.90251C19.6221 7.53446 19.4413 8.14903 19.1445 8.70956C18.8477 9.27008 18.441 9.76508 17.9488 10.165C18.5589 10.5587 19.0766 11.0798 19.4663 11.6925L22.375 16.2675C22.934 17.1389 23.2682 18.1353 23.3475 19.1675C24.1934 18.7893 25.1305 18.6634 26.0463 18.805C26.8982 18.9395 27.6966 19.3063 28.3536 19.8651C29.0107 20.4239 29.5009 21.1531 29.7703 21.9724C30.0398 22.7918 30.0781 23.6696 29.881 24.5093C29.6839 25.349 29.2591 26.1181 28.6533 26.732C28.0474 27.3459 27.284 27.7809 26.447 27.989C25.6099 28.1972 24.7317 28.1705 23.9089 27.9119C23.086 27.6533 22.3504 27.1728 21.783 26.5232C21.2155 25.8736 20.8382 25.0801 20.6925 24.23C20.6675 24.08 20.6488 23.93 20.6388 23.78C19.9203 24.1671 19.1174 24.3711 18.3013 24.3738H11.25V22.5H18.3C18.8129 22.4866 19.3159 22.3564 19.7708 22.1192C20.2257 21.882 20.6204 21.5441 20.925 21.1313C21.0675 20.9496 21.215 20.7775 21.3675 20.615C21.5031 20.0482 21.522 19.4597 21.4232 18.8853C21.3244 18.3109 21.1098 17.7627 20.7925 17.2738L17.8838 12.7013C17.6013 12.2554 17.2104 11.8885 16.7476 11.6347C16.2848 11.3809 15.7653 11.2486 15.2375 11.25C15.2313 11.2487 15.2249 11.2487 15.2188 11.25H15C14.4026 11.2412 13.813 11.3866 13.2881 11.6721C12.7633 11.9576 12.3209 12.3737 12.0038 12.88L7.61375 19.7775C8.3727 20.3839 8.92446 21.2111 9.1927 22.1447C9.46094 23.0784 9.43241 24.0723 9.11105 24.9891C8.78968 25.9058 8.19138 26.7 7.3989 27.2618C6.60641 27.8236 5.65892 28.1253 4.6875 28.125V28.125ZM23.025 21.6075C22.9525 21.8193 22.8678 22.0268 22.7712 22.2288C22.5875 22.616 22.4945 23.04 22.4994 23.4686C22.5043 23.8972 22.6069 24.319 22.7994 24.702C22.9919 25.0849 23.2692 25.4189 23.6103 25.6785C23.9513 25.9381 24.3471 26.1165 24.7675 26.2C25.5023 26.3321 26.2595 26.1669 26.8725 25.7407C27.4855 25.3146 27.9041 24.6623 28.0363 23.9275C28.1684 23.1927 28.0032 22.4355 27.577 21.8225C27.1508 21.2095 26.4986 20.7909 25.7638 20.6588C25.2651 20.5962 24.7588 20.6494 24.2841 20.8141C23.8093 20.9789 23.3789 21.2508 23.0263 21.6088L23.025 21.6075ZM15.005 3.75001C14.6054 3.74953 14.2104 3.83414 13.8461 3.99823C13.4817 4.16232 13.1566 4.40211 12.8921 4.70166C12.6277 5.0012 12.4301 5.35362 12.3125 5.73548C12.1949 6.11733 12.1599 6.51986 12.21 6.91626C12.3289 7.60041 12.6842 8.22109 13.214 8.66999C13.7438 9.1189 14.4144 9.36754 15.1088 9.37251C15.797 9.33434 16.4485 9.04951 16.9439 8.57019C17.4393 8.09087 17.7454 7.44914 17.8063 6.76251C17.8523 6.08945 17.6561 5.4221 17.2531 4.88107C16.8501 4.34004 16.2668 3.96102 15.6088 3.81251C15.4101 3.77142 15.2078 3.75048 15.005 3.75001Z"
/>
</g>
<defs>
<clipPath id="clip0_34_3392">
<rect width="30" height="30" fill="white"/>
</clipPath>
</defs>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M27.1875 23.4246H14.45C14.0794 23.4243 13.7172 23.3141 13.4092 23.1081C13.1011 22.902 12.8611 22.6093 12.7193 22.2669C12.5775 21.9245 12.5404 21.5478 12.6126 21.1843C12.6848 20.8208 12.8631 20.4868 13.125 20.2246L16.515 16.8334C15.7379 15.9557 14.7829 15.2536 13.7135 14.7736C12.644 14.2937 11.4847 14.0469 10.3125 14.0496C9.51738 14.0487 8.72615 14.1606 7.9625 14.3821L7.43875 12.5796C8.37284 12.3101 9.3403 12.1737 10.3125 12.1746C11.9136 12.1715 13.4927 12.5474 14.9207 13.2715C16.3487 13.9956 17.5851 15.0474 18.5287 16.3409C18.6626 16.5214 18.7272 16.744 18.7107 16.9682C18.6942 17.1924 18.5977 17.4031 18.4388 17.5621L14.4512 21.5496H27.1875C27.436 21.5493 27.6743 21.4504 27.85 21.2746C28.0258 21.0989 28.1247 20.8606 28.125 20.6121V7.8746L25.1437 10.8559C24.9879 11.0115 24.7823 11.1074 24.5628 11.1266C24.3434 11.1458 24.1242 11.0871 23.9438 10.9609C21.5997 9.3114 18.8037 8.42567 15.9375 8.4246C12.2092 8.4289 8.63486 9.91187 5.99856 12.5482C3.36226 15.1845 1.8793 18.7588 1.875 22.4871H0C0.0046321 18.2616 1.68524 14.2106 4.6731 11.2227C7.66096 8.23485 11.712 6.55423 15.9375 6.5496C18.9192 6.54907 21.8399 7.39475 24.36 8.98835L26.7975 6.55085C27.0596 6.2881 27.3937 6.10904 27.7577 6.03633C28.1216 5.96363 28.4989 6.00056 28.8418 6.14244C29.1848 6.28432 29.4779 6.52477 29.684 6.83335C29.8902 7.14192 30.0002 7.50474 30 7.87585V20.6121C29.999 21.3577 29.7024 22.0725 29.1751 22.5997C28.6479 23.127 27.9331 23.4236 27.1875 23.4246V23.4246Z"
/>
</svg>
</template>

View File

@ -0,0 +1,10 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M1.19556 3.57709C1.19556 3.0248 1.64327 2.57709 2.19556 2.57709H20.3524C24.1117 2.57709 27.1497 5.61511 27.1497 9.37444C27.1497 13.1338 24.1117 16.1718 20.3524 16.1718H7.99291C5.33815 16.1718 3.19556 18.3144 3.19556 20.9692C3.19556 23.6239 5.33815 25.7665 7.99291 25.7665H26.1497C26.702 25.7665 27.1497 26.2142 27.1497 26.7665C27.1497 27.3188 26.702 27.7665 26.1497 27.7665H7.99291C4.23358 27.7665 1.19556 24.7285 1.19556 20.9692C1.19556 17.2098 4.23358 14.1718 7.99291 14.1718H20.3524C23.0071 14.1718 25.1497 12.0292 25.1497 9.37444C25.1497 6.71968 23.0071 4.57709 20.3524 4.57709H2.19556C1.64327 4.57709 1.19556 4.12937 1.19556 3.57709Z"
/>
<path
d="M3.57709 7.15418C5.55267 7.15418 7.15418 5.55267 7.15418 3.57709C7.15418 1.60152 5.55267 0 3.57709 0C1.60152 0 0 1.60152 0 3.57709C0 5.55267 1.60152 7.15418 3.57709 7.15418Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M23.6543 29.3854H4.73087C3.47667 29.3838 2.27433 28.8848 1.38748 27.998C0.500634 27.1111 0.00166892 25.9088 0 24.6546L0 5.73112C0.00166892 4.47693 0.500634 3.27458 1.38748 2.38774C2.27433 1.50089 3.47667 1.00192 4.73087 1.00025H18.2738C19.2138 0.990463 20.1349 1.26459 20.9166 1.78679C21.6983 2.30898 22.3043 3.05493 22.6552 3.92708C23.0245 4.7918 23.1241 5.7479 22.9408 6.67017C22.7576 7.59243 22.3001 8.43786 21.6283 9.09571L13.9151 16.8077C13.7376 16.9848 13.4972 17.0844 13.2464 17.0844C12.9957 17.0844 12.7552 16.9848 12.5778 16.8077L8.51556 12.7454L7.0143 14.2467L11.6846 18.917C12.0993 19.3298 12.6606 19.5616 13.2458 19.5616C13.831 19.5616 14.3923 19.3298 14.807 18.917L28.6627 5.06249L30 6.39975L16.1455 20.2555C15.3751 21.022 14.3325 21.4522 13.2458 21.4522C12.1591 21.4522 11.1165 21.022 10.3461 20.2555L5.00841 14.9153C4.83122 14.7379 4.7317 14.4974 4.7317 14.2467C4.7317 13.9959 4.83122 13.7555 5.00841 13.578L7.84693 10.7395C8.02434 10.5623 8.26482 10.4628 8.51556 10.4628C8.7663 10.4628 9.00678 10.5623 9.18419 10.7395L13.2464 14.8018L20.291 7.75845C20.6893 7.35957 20.9605 6.85156 21.0702 6.29861C21.1799 5.74566 21.1233 5.1726 20.9074 4.65185C20.6915 4.1311 20.3261 3.68604 19.8573 3.3729C19.3886 3.05977 18.8375 2.89263 18.2738 2.8926H4.73087C3.97835 2.8936 3.25694 3.19298 2.72484 3.72509C2.19273 4.2572 1.89335 4.9786 1.89235 5.73112V24.6546C1.89335 25.4071 2.19273 26.1285 2.72484 26.6606C3.25694 27.1927 3.97835 27.4921 4.73087 27.4931H23.6543C24.4068 27.4921 25.1283 27.1927 25.6604 26.6606C26.1925 26.1285 26.4918 25.4071 26.4928 24.6546V17.0852H28.3852V24.6546C28.3835 25.9088 27.8846 27.1111 26.9977 27.998C26.1109 28.8848 24.9085 29.3838 23.6543 29.3854V29.3854Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.3243 24.0741C11.1019 24.0885 9.89084 23.8389 8.77374 23.3425C5.73691 21.9189 3.01649 19.9006 0.773451 17.407C0.276227 16.851 0.000924871 16.1316 2.3261e-06 15.3857C-0.000920219 14.6398 0.272601 13.9197 0.768448 13.3625C4.05636 9.74564 9.48409 5.95873 15.0156 6.00125L15.2045 6C20.601 6 25.9799 9.78441 29.2303 13.36C29.7265 13.9169 30.0005 14.6368 30 15.3827C29.9995 16.1286 29.7247 16.8482 29.2278 17.4045C26.9326 19.9602 24.1345 22.0145 21.0087 23.4388L20.2583 21.7192C23.1375 20.3939 25.717 18.4965 27.8396 16.1426C28.0268 15.9335 28.1305 15.6628 28.131 15.3821C28.1315 15.1014 28.0286 14.8303 27.8421 14.6206C24.8306 11.3089 19.8718 7.75589 15.0156 7.8772C10.1557 7.81467 5.16942 11.3077 2.1554 14.6231C1.96956 14.8331 1.86739 15.104 1.86831 15.3844C1.86923 15.6648 1.97319 15.9351 2.1604 16.1439C4.2327 18.4504 6.74639 20.3182 9.55288 21.6366C10.673 22.0941 11.886 22.2783 13.0915 22.1739C14.297 22.0695 15.4603 21.6796 16.4851 21.0363C17.3579 20.4896 18.0707 19.7218 18.551 18.8107C19.0314 17.8997 19.2622 16.8778 19.2203 15.8487C19.2201 15.1173 19.0299 14.3985 18.6683 13.7628C18.3067 13.127 17.7861 12.5961 17.1575 12.2221C16.529 11.8482 15.814 11.644 15.0827 11.6295C14.3515 11.6151 13.629 11.7909 12.9862 12.1397C12.3433 12.4886 11.8022 12.9985 11.4157 13.6195C11.0293 14.2405 10.8109 14.9512 10.7818 15.682C10.7528 16.4128 10.9142 17.1387 11.2501 17.7883C11.5861 18.438 12.0851 18.9893 12.6982 19.388L11.6727 20.9588C10.7875 20.3824 10.0672 19.5859 9.58242 18.6474C9.09766 17.7088 8.86508 16.6604 8.90746 15.6049C8.94984 14.5494 9.26574 13.5231 9.82418 12.6264C10.3826 11.7297 11.1645 10.9936 12.0931 10.49C13.0217 9.98645 14.0652 9.73281 15.1213 9.75393C16.1774 9.77506 17.21 10.0702 18.1177 10.6105C19.0254 11.1508 19.7772 11.9176 20.2993 12.8359C20.8215 13.7542 21.0961 14.7924 21.0962 15.8487C21.1347 17.1925 20.823 18.5231 20.1918 19.71C19.5605 20.8969 18.6314 21.8992 17.4956 22.6184C15.9421 23.578 14.1503 24.082 12.3243 24.0729V24.0741Z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M25 9C25 7.9 24.1 7 23 7H7C5.9 7 5 7.9 5 9V21C5 22.1 5.9 23 7 23H23C24.1 23 25 22.1 25 21V9ZM23 9L15 14L7 9H23ZM15 16L7 11V21H23V11L15 16Z"
/>
</svg>
</template>

View File

@ -0,0 +1,14 @@
<template>
<svg width="32" height="32" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="15.2143" cy="15.2143" rx="15.2143" ry="15.2143" fill="#3EDF9C"/>
<path
d="M21.534 17.1559C21.3316 16.9246 20.9874 16.8836 20.7362 17.0607C19.0544 18.2438 17.0558 18.8942 14.9999 18.9275C12.9439 18.8942 10.9452 18.2438 9.26351 17.0607C9.0124 16.8836 8.66818 16.9246 8.46579 17.1559C8.35321 17.2831 8.30005 17.4522 8.3191 17.6211C8.33835 17.7899 8.42833 17.9427 8.56665 18.0415C10.4494 19.3743 12.6935 20.1019 15 20.1274C17.3065 20.1019 19.5505 19.3743 21.4333 18.0415C21.5716 17.9427 21.6616 17.7899 21.6808 17.6211C21.6999 17.4524 21.6467 17.2833 21.5341 17.1559H21.534Z"
fill="#0A0A0A"/>
<path
d="M11.4555 10.3117C11.4555 10.9625 10.9279 11.49 10.2771 11.49C9.62633 11.49 9.09879 10.9625 9.09879 10.3117C9.09879 9.6609 9.62633 9.13312 10.2771 9.13312C10.9279 9.13312 11.4555 9.66087 11.4555 10.3117Z"
fill="#0A0A0A"/>
<path
d="M20.9008 10.3117C20.9008 10.9625 20.3732 11.49 19.7224 11.49C19.0717 11.49 18.5441 10.9625 18.5441 10.3117C18.5441 9.6609 19.0717 9.13312 19.7224 9.13312C20.3732 9.13312 20.9008 9.66087 20.9008 10.3117Z"
fill="#0A0A0A"/>
</svg>
</template>

View File

@ -0,0 +1,13 @@
<template>
<svg width="32" height="32" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M16 31C13.0333 31 10.1335 30.1203 7.66655 28.4721C5.19962 26.8239 3.27707 24.4813 2.1418 21.7404C1.00658 18.9995 0.709425 15.9837 1.2882 13.0737C1.86704 10.164 3.29566 7.49114 5.39328 5.39331C7.49111 3.29569 10.1638 1.86707 13.0737 1.28824C15.9835 0.709405 18.9992 1.00657 21.7404 2.14183C24.4813 3.27705 26.824 5.19976 28.4721 7.66658C30.1202 10.1334 31 13.0333 31 16C30.9956 19.9768 29.4138 23.7898 26.6017 26.6017C23.7897 29.4137 19.9772 30.9956 16.0001 30.9999L16 31ZM16 2.20039C12.3401 2.20039 8.83011 3.65435 6.24211 6.24224C3.65416 8.83019 2.20025 12.3402 2.20025 16.0001C2.20025 19.6601 3.65421 23.17 6.24211 25.758C8.83006 28.346 12.3401 29.7999 16 29.7999C19.66 29.7999 23.1699 28.3459 25.7579 25.758C28.3459 23.1701 29.7998 19.6601 29.7998 16.0001C29.7956 12.3414 28.3404 8.83399 25.7533 6.24653C23.1662 3.65944 19.6586 2.20424 15.9997 2.20006L16 2.20039Z"
fill="#B1C1CA" stroke="#B1C1CA"/>
<path
d="M12.9786 11.5732C12.9786 12.3684 12.334 13.013 11.5388 13.013C10.7435 13.013 10.0989 12.3684 10.0989 11.5732C10.0989 10.778 10.7435 10.1331 11.5388 10.1331C12.334 10.1331 12.9786 10.7779 12.9786 11.5732Z"
fill="#B1C1CA"/>
<path
d="M22.424 11.5732C22.424 12.3684 21.7793 13.013 20.9841 13.013C20.1889 13.013 19.5442 12.3684 19.5442 11.5732C19.5442 10.778 20.1889 10.1331 20.9841 10.1331C21.7793 10.1331 22.424 10.7779 22.424 11.5732Z"
fill="#B1C1CA"/>
</svg>
</template>

View File

@ -0,0 +1,21 @@
<template>
<svg width="32" height="32" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="15.7857" cy="15.7857" rx="15.2143" ry="15.2143" fill="#FE955A"/>
<path
d="M12.4555 14.8195C12.4555 15.4703 11.9279 15.9979 11.2771 15.9979C10.6263 15.9979 10.0988 15.4703 10.0988 14.8195C10.0988 14.1687 10.6263 13.6412 11.2771 13.6412C11.9279 13.6412 12.4555 14.1687 12.4555 14.8195Z"
fill="#0A0A0A"/>
<path
d="M22.0464 14.8195C22.0464 15.5503 21.454 16.1427 20.7233 16.1427C19.9926 16.1427 19.4001 15.5502 19.4001 14.8195C19.4001 14.0888 19.9926 13.4962 20.7233 13.4962C21.454 13.4962 22.0464 14.0888 22.0464 14.8195Z"
fill="#0A0A0A"/>
<path
d="M17.2104 11.7145C17.1179 11.5839 17.0813 11.422 17.1089 11.2644C17.1363 11.1068 17.2257 10.9666 17.3569 10.8752C19.2025 9.58825 21.3336 8.21234 22.0085 8.21234C22.6608 8.21234 23.5424 8.87485 24.1432 9.40493H24.1434C24.2665 9.51438 24.3391 9.66944 24.3443 9.83392C24.3498 9.99839 24.2872 10.1579 24.1715 10.275C23.9509 10.5013 23.5926 10.5172 23.3531 10.3115C23.0385 10.031 22.7004 9.77805 22.3423 9.55541C22.1534 9.44596 21.9219 9.43864 21.7263 9.53574C20.4546 10.2397 19.2244 11.0161 18.0421 11.8615C17.7715 12.0492 17.4003 11.9835 17.2105 11.7146L17.2104 11.7145Z"
fill="#0A0A0A"/>
<path
d="M12.9546 11.7612C12.1783 11.5094 10.6871 11.1041 9.47296 11.2397L9.47275 11.2395C9.1825 11.2688 8.91422 11.0821 8.84056 10.7998C8.79473 10.6303 8.82549 10.4495 8.92447 10.3047C9.02366 10.1597 9.18103 10.0655 9.35555 10.0467C10.8028 9.89457 12.4534 10.3357 13.321 10.6171C13.4788 10.668 13.6085 10.7818 13.6793 10.9319C13.75 11.0817 13.7554 11.2543 13.6941 11.4083L13.6927 11.4119V11.4117C13.5776 11.7049 13.2543 11.8578 12.9546 11.7611L12.9546 11.7612Z"
fill="#0A0A0A"/>
<path
d="M17.5309 22.2942C16.4685 21.9876 15.0658 22.022 14.3116 22.0711C14.0103 22.0906 13.7464 21.8707 13.7113 21.5708L13.7039 21.5101C13.6851 21.3546 13.7309 21.1981 13.8312 21.0776C13.9312 20.957 14.0766 20.8832 14.2329 20.8731C15.0727 20.8185 16.6325 20.7854 17.8925 21.1516C18.0509 21.1985 18.181 21.3117 18.2495 21.4622C18.3179 21.6124 18.3177 21.7851 18.2491 21.9351L18.2252 21.9878H18.225C18.1026 22.2488 17.8063 22.3796 17.5309 22.2942L17.5309 22.2942Z"
fill="#0A0A0A"/>
</svg>
</template>

View File

@ -1,20 +1,37 @@
import { createApp } from 'vue' import {createApp} from 'vue'
import { createPinia } from 'pinia' import {createPinia} from 'pinia'
import {setupI18n, loadLocaleMessages} from './i18n' import {setupI18n} from './i18n'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import IconLsApply from '@/components/icons/IconLsApply.vue';
import IconLsWatch from '@/components/icons/IconLsWatch.vue';
import IconLsTest from '@/components/icons/IconLsTest.vue';
import IconLsPractice from '@/components/icons/IconLsPractice.vue';
import IconLsNetwork from '@/components/icons/IconLsNetwork.vue';
import IconLsStart from '@/components/icons/IconLsStart.vue';
import IconLsEnd from '@/components/icons/IconLsEnd.vue';
import '@/assets/styles/index.scss' import '@/assets/styles/index.scss'
const i18n = setupI18n() const i18n = setupI18n()
const app = createApp(App) const app = createApp(App)
// todo: define lang setup // todo: define lang setup
await loadLocaleMessages(i18n, 'de') // await loadLocaleMessages(i18n, 'de')
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(i18n) app.use(i18n)
app.mount('#app') app.mount('#app')
// register icons globally
app.component('IconLsApply', IconLsApply)
app.component('IconLsWatch', IconLsWatch)
app.component('IconLsTest', IconLsTest)
app.component('IconLsPractice', IconLsPractice)
app.component('IconLsNetwork', IconLsNetwork)
app.component('IconLsStart', IconLsStart)
app.component('IconLsEnd', IconLsEnd)

View File

@ -27,21 +27,22 @@ const router = createRouter({
} }
}, },
{ {
path: '/analyse', path: '/circle/:circleSlug',
component: () => import('../views/CircleAnalyseExampleView.vue'), component: () => import('../views/CircleView.vue'),
props: true
}, },
{ {
path: '/profile', path: '/profile',
component: () => import('../views/ProfileView.vue'), component: () => import('../views/ProfileView.vue'),
}, },
{ {
path: '/learningpath/:learningPathId', path: '/styleguide',
component: () => import('../views/LearningPathOverview.vue'), component: () => import('../views/StyelGuideView.vue'),
}, },
{ {
path: '/learningpath/:learningPathId/circle/:circleId', path: '/:pathMatch(.*)*',
component: () => import('../views/CircleView.vue'), component: () => import('../views/404View.vue'),
} },
] ]
}) })

View File

@ -0,0 +1,11 @@
<template>
<main>
<h1>404 - Not Found as Vue view...</h1>
</main>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@ -1,41 +0,0 @@
<script>
import axios from 'axios';
export default {
data() {
return {
count: 0,
circleData: {}
}
},
mounted() {
console.log('CircleAnalyseExampleView mounted');
axios({
method: 'get',
url: 'http://localhost:8000/wagtailapi/v2/pages/?type=learnpath.Circle&slug=analyse&fields=title,description,learning_sequences'
}).then((response) => {
this.circleData = response.data.items[0];
});
}
}
</script>
<template>
<div class="circle">
<h1 class="text-6xl">
Hello world!
</h1>
<div v-for="learningSequence in circleData.learning_sequences">
<h2>{{learningSequence.title}}</h2>
<div v-for="learningPackage in learningSequence.learning_packages">
<h3>{{learningPackage.id}}</h3>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,36 +1,119 @@
<template>
<VBVNavigationBar />
<MainNavigationBar />
<div class="flex flex-row">
<CircleSidebar />
<main class="flex-grow">
circle main content
<LearningSequence :units="units"/>
</main>
</div>
</template>
<script> <script>
import MainNavigationBar from "../components/MainNavigationBar.vue"; import axios from 'axios';
import VBVNavigationBar from "../components/VBVNavigationBar.vue"; import * as log from 'loglevel';
import CircleSidebar from "../components/CircleSidebar.vue";
import LearningSequence from "../components/LearningSequence.vue"; import MainNavigationBar from '../components/MainNavigationBar.vue';
import {reactive} from "vue"; import LearningSequence from '../components/circle/LearningSequence.vue';
export default { export default {
name: "CircleView", components: { LearningSequence, MainNavigationBar },
components: {LearningSequence, CircleSidebar, MainNavigationBar, VBVNavigationBar}, props: ['circleSlug',],
setup () { data() {
const units = reactive(['video bla', 'fachwissen'])
return { return {
units count: 0,
circleData: {},
learningSequences: [],
} }
},
mounted() {
log.debug('CircleView mounted', this.circleSlug);
axios({
method: 'get',
url: `/learnpath/api/circle/${this.circleSlug}/`,
}).then((response) => {
log.debug(response.data);
this.circleData = response.data;
// aggregate wagtail data into LearningSequence > LearningPackages > LearningUnit hierarchy
let learningSequence = null;
let learningPackageIndex = 0;
this.circleData.children.forEach((child) => {
if (child.type === 'learnpath.LearningSequence') {
if (learningSequence) {
this.learningSequences.push(learningSequence);
}
learningSequence = Object.assign(child, { learningPackages: [] });
learningPackageIndex = 0;
} else {
if (learningSequence.learningPackages.length === 0) {
learningSequence.learningPackages.push({
title: child.package,
learningUnits: [],
})
}
if (learningSequence.learningPackages[learningPackageIndex].title !== child.package) {
learningPackageIndex += 1;
learningSequence.learningPackages.push({
title: child.package,
learningUnits: [],
})
}
learningSequence.learningPackages[learningPackageIndex].learningUnits.push(child);
}
});
this.learningSequences.push(learningSequence);
// sum minutes
this.learningSequences.forEach((learningSequence) => {
learningSequence.minutes = 0;
learningSequence.learningPackages.forEach((learningPackage) => {
learningPackage.minutes = 0;
learningPackage.learningUnits.forEach((learningUnit) => {
learningPackage.minutes += learningUnit.minutes;
});
learningSequence.minutes += learningPackage.minutes;
});
});
log.debug(this.learningSequences);
});
} }
} }
</script> </script>
<style scoped> <template>
<MainNavigationBar/>
<div class="circle">
<div class="flex flex-col lg:flex-row">
<div class="flex-initial lg:w-128 px-8 py-8">
<h1 class="text-blue-dark text-7xl">
{{ circleData.title }}
</h1>
<div class="mt-8">
<img src="@/assets/circle-analyse.svg" alt="">
</div>
<div class="outcome border border-gray-500 mt-8 p-6">
<h3 class="text-blue-dark">Das lernst du in diesem Circle.</h3>
<div class="prose mt-4">
{{ circleData.description }}
</div>
<button class="btn-primary mt-4">Erfahre mehr dazu</button>
</div>
<div class="expert border border-gray-500 mt-8 p-6">
<h3 class="text-blue-dark">Hast du Fragen?</h3>
<div class="prose mt-4">Tausche dich mit der Fachexpertin aus für den Circle Analyse aus.</div>
<button class="btn-secondary mt-4">
Fachexpertin kontaktieren
</button>
</div>
</div>
<div class="flex-auto bg-gray-100 px-4 py-8 lg:px-24">
<div v-for="learningSequence in learningSequences">
<LearningSequence :learning-sequence="learningSequence"></LearningSequence>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style> </style>

View File

@ -1,8 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
</script> import MainNavigationBar from '@/components/MainNavigationBar.vue';</script>
<template> <template>
<main> <MainNavigationBar/>
<p>Hello from Home View</p>
<main class="px-8 py-8">
<h1>myVBV Start Page</h1>
<div class="mt-8 flex flex-row justify-start gap-4">
<router-link class="link text-xl" to="/styleguide">Styelguide</router-link>
<a class="link text-xl" href="/login/">Login</a>
<router-link class="link text-xl" to="/circle/analyse">Circle "Analyse" (Login benötigt)</router-link>
</div>
</main> </main>
</template> </template>
<style scoped>
</style>

View File

@ -1,16 +0,0 @@
<template>
<h1>Lernpfad Overview</h1>
<LearningPath />
</template>
<script>
import LearningPath from "../components/LearningPath.vue";
export default {
name: 'LearningPathOverview',
components: {LearningPath}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,201 @@
<script setup lang="ts">
import MainNavigationBar from '@/components/MainNavigationBar.vue';
import IconLsApply from '@/components/icons/IconLsApply.vue';
import IconLsNetwork from '@/components/icons/IconLsNetwork.vue';
import IconLsWatch from '@/components/icons/IconLsWatch.vue';
import IconLsPractice from '@/components/icons/IconLsPractice.vue';
import IconLsTest from '@/components/icons/IconLsTest.vue';
import IconLsStart from '@/components/icons/IconLsStart.vue';
import IconLsEnd from '@/components/icons/IconLsEnd.vue';
import IconSmileyHappy from '@/components/icons/IconSmileyHappy.vue';
import IconSmileyThinking from '@/components/icons/IconSmileyThinking.vue';
import IconSmileyNeutral from '@/components/icons/IconSmileyNeutral.vue';
import IconMessage from '@/components/icons/IconMessage.vue';
import IconArrowUp from '@/components/icons/IconArrowUp.vue';
import IconArrowDown from '@/components/icons/IconArrowDown.vue';
import IconArrowLeft from '@/components/icons/IconArrowLeft.vue';
import IconArrowRight from '@/components/icons/IconArrowRight.vue';
import IconClose from '@/components/icons/IconClose.vue';
import IconCheck from '@/components/icons/IconCheck.vue';
import IconInfo from '@/components/icons/IconInfo.vue';
import IconCheckboxChecked from '@/components/icons/IconCheckboxChecked.vue';
import IconCheckboxUnchecked from '@/components/icons/IconCheckboxUnchecked.vue';
const colors = ['blue', 'sky', 'orange', 'green', 'red', 'gray',];
const colorValues = [100, 300, 500, 700, 900,];
function colorBgClass(color: string, value: number) {
return `bg-${color}-${value}`;
}
</script>
<template>
<MainNavigationBar/>
<main class="px-8 py-4">
<h1>Style Guide</h1>
<div class="border-b text-gray-700 pb-2 mt-12">
<h2 class="heading-1">Icons</h2>
</div>
<div class="mt-8 mb-8 flex gap-4">
<div>
IconMessage
<IconMessage/>
</div>
<div>
IconArrowUp
<IconArrowUp/>
</div>
<div>
IconArrowDown
<IconArrowDown/>
</div>
<div>
IconArrowLeft
<IconArrowLeft/>
</div>
<div>
IconArrowRight
<IconArrowRight/>
</div>
<div>
IconClose
<IconClose/>
</div>
<div>
IconCheck
<IconCheck/>
</div>
<div>
IconInfo
<IconInfo/>
</div>
<div>
IconCheckboxChecked
<IconCheckboxChecked/>
</div>
<div>
IconCheckboxUnchecked
<IconCheckboxUnchecked/>
</div>
</div>
<div class="mt-8 mb-8 flex gap-4">
<div>
IconLsApply
<IconLsApply/>
</div>
<div>
IconLsWatch
<IconLsWatch/>
</div>
<div>
IconLsTest
<IconLsTest/>
</div>
<div>
IconLsPractice
<IconLsPractice/>
</div>
<div>
IconLsNetwork
<IconLsNetwork/>
</div>
<div>
IconLsStart
<IconLsStart/>
</div>
<div>
IconLsEnd
<IconLsEnd/>
</div>
</div>
<div class="mt-8 mb-8 flex gap-4">
<div>
IconSmileyHappy
<IconSmileyHappy/>
</div>
<div>
IconSmileyThinking
<IconSmileyThinking/>
</div>
<div>
IconSmileyNeutral
<IconSmileyNeutral/>
</div>
</div>
<div class="border-b text-gray-700 pb-2 mt-8">
<h2 class="heading-1">Colors</h2>
</div>
<table class="text-gray-700">
<tr class="h-12 md:h-18 lg:h-24">
<td></td>
<td class="text-center" v-for="value in colorValues">{{value}}</td>
</tr>
<tr v-for="color in colors" class="h-12 md:h-18 lg:h-24">
<td>{{color}}</td>
<td v-for="value in colorValues" class="px-2">
<div class="w-8 h-8 md:w-12 md:h-12 lg:w-16 lg:h-16 rounded-full" :class="[colorBgClass(color, value)]"></div>
</td>
</tr>
</table>
<div class="border-b text-gray-700 pb-2 mt-12">
<h2 class="heading-1">Typography</h2>
</div>
<h1 class="mt-8">Heading 1</h1>
<h2 class="mt-8">Heading 2</h2>
<h3 class="mt-8">Heading 3</h3>
<div class="mt-8 text-xl font-bold">Text Large Bold</div>
<div class="mt-8 text-xl">Text Large</div>
<div class="mt-8 link text-xl">Link Large</div>
<div class="mt-8 font-bold">Text Bold</div>
<div class="mt-8">Text</div>
<div class="mt-8 link">Link</div>
<div class="mt-8 text-sm">Text Small</div>
<div class="mt-8 link text-sm">Link Small</div>
<div class="border-b text-gray-700 pb-2 mt-12">
<h2 class="heading-1">Components</h2>
</div>
<h2 class="mt-8 mb-8">Buttons</h2>
<div class="flex w-128 content-center justify-between mb-16">
<button class="btn-primary">Primary</button>
<a class="btn-primary inline-block" href="/">Primary Link</a>
<button class="btn-secondary">Secondary</button>
<button class="btn-blue">Blue</button>
</div>
</main>
</template>
<style scoped>
</style>

View File

@ -1,18 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
module.exports = {
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
],
theme: {
colors: {
'white': '#FFFFFF',
'blue-dark': '#00224D'
},
fontFamily: {
sans: ['BuenosAires', 'sans-serif'],
},
extend: {},
},
plugins: [],
}

View File

@ -1,9 +1,8 @@
import path from 'path'
import {fileURLToPath, URL} from 'url' import {fileURLToPath, URL} from 'url'
import {defineConfig, loadEnv} from 'vite' import {defineConfig, loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueI18n from '@intlify/vite-plugin-vue-i18n' // import vueI18n from '@intlify/vite-plugin-vue-i18n'
import alias from '@rollup/plugin-alias' import alias from '@rollup/plugin-alias'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@ -12,17 +11,18 @@ export default ({mode}) => {
return defineConfig({ return defineConfig({
plugins: [ plugins: [
vue(), vue(),
vueI18n({ // vueI18n({
include: path.resolve(__dirname, './locales/**') // include: path.resolve(__dirname, './locales/**')
}), // }),
// won't work in vite's resolve.alias, so we'll make the alias here // won't work in vite's resolve.alias, so we'll make the alias here
alias({ alias({
entries: [ // TODO: why is that used?
{ // entries: [
find: 'vue-i18n', // {
replacement: path.resolve(__dirname, './node_modules/vue-i18n/dist/vue-i18n.runtime.esm-bundler.js') // find: 'vue-i18n',
} // replacement: path.resolve(__dirname, './node_modules/vue-i18n/dist/vue-i18n.runtime.esm-bundler.js')
] // }
// ]
}) })
], ],
resolve: { resolve: {
@ -30,5 +30,8 @@ export default ({mode}) => {
'@': fileURLToPath(new URL('./src', import.meta.url)), '@': fileURLToPath(new URL('./src', import.meta.url)),
}, },
}, },
build: {
assetsDir: 'static/vue',
}
}) })
} }

View File

@ -13,10 +13,7 @@ FROM node:16-bullseye-slim as client-builder
ARG APP_HOME=/app ARG APP_HOME=/app
WORKDIR ${APP_HOME} WORKDIR ${APP_HOME}
COPY ./server/package.json ${APP_HOME}
RUN npm install && npm cache clean --force
COPY ./server ${APP_HOME} COPY ./server ${APP_HOME}
RUN npm run build
# define an alias for the specfic python version used in this file. # define an alias for the specfic python version used in this file.
FROM python:${PYTHON_VERSION} as python FROM python:${PYTHON_VERSION} as python

View File

@ -13,9 +13,9 @@ if [ -z "${POSTGRES_USER}" ]; then
base_postgres_image_default_user='postgres' base_postgres_image_default_user='postgres'
export POSTGRES_USER="${base_postgres_image_default_user}" export POSTGRES_USER="${base_postgres_image_default_user}"
fi fi
export VBV_DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
echo $VBV_DATABASE_URL echo $DATABASE_URL
postgres_ready() { postgres_ready() {
python << END python << END

View File

@ -4,9 +4,8 @@ set -o errexit
set -o pipefail set -o pipefail
set -o nounset set -o nounset
python /app/manage.py collectstatic --noinput python /app/manage.py collectstatic --noinput
python /app/manage.py migrate
python /app/manage.py createcachetable python /app/manage.py createcachetable
python /app/manage.py migrate
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:80 --chdir=/app -k uvicorn.workers.UvicornWorker /usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:80 --chdir=/app -k uvicorn.workers.UvicornWorker

View File

@ -8,5 +8,5 @@ POSTGRES_PASSWORD=hNqfCdG6bwCLcnfboDtNM1L2Hiwp8GuKp1DJ6t2rcKl15Vls2QbByoIZ6IQlci
# General # General
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
VBV_DJANGO_LOCAL_DOCKER=True IT_DJANGO_LOCAL_DOCKER=True
IPYTHONDIR=/app/.ipython IPYTHONDIR=/app/.ipython

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
example.env Normal file
View File

@ -0,0 +1 @@
export IT_APP_ENVIRONMENT='development'

View File

@ -1,45 +0,0 @@
#!/bin/bash
#export DATABASE_HOST=postgres
#export DATABASE_PORT=5432
#export DATABASE_URL=postgres://$DATABASE_USER:$PG_PASSWORD@$DATABASE_HOST:$DATABASE_PORT/$DATABASE_NAME
#
#echo $DATABASE_URL
#DJANGO_SETTINGS_MODULE=config.settings.base
#DATABASE_NAME=vbv_lernwelt
SKIP_SETUP=false
##
echo "Setting up VBV Project for Local usage"
if [ "$SKIP_SETUP" = false ]; then
if [ -z "$PG_PORT" ]; then # if the port is set in the env, use iterg
DB_PORT="";
else
DB_PORT="-p $PG_PORT";
fi
if [ -z "$PG_USER" ]; then # if the user is set in the env, use it
DB_USER="";
else
DB_USER="-U $PG_USER";
fi
echo "psql -h localhost --port=$DB_PORT --username=$DB_USER -c 'drop database if exists' $DATABASE_NAME;"
echo "Drop all connections to the database"
psql -h localhost --port=$DB_PORT --username=$DB_USER -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$DATABASE_NAME' AND pid <> pg_backend_pid();"
echo "Drop database: $DATABASE_NAME"
psql -h localhost --port=$DB_PORT --username=$DB_USER -c "drop database if exists $DATABASE_NAME;"
echo "Create database: $DATABASE_NAME"
psql -h localhost --port=$DB_PORT --username=$DB_USER -c "create database $DATABASE_NAME;"
# reset data
python3 server/manage.py createcachetable --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py migrate --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_users --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_learningpath --settings="$DJANGO_SETTINGS_MODULE"
#
# # make django translations
(cd server && python3 manage.py compilemessages --settings="$DJANGO_SETTINGS_MODULE")
fi

View File

@ -2,6 +2,8 @@
"name": "vbv_lernwelt_cypress", "name": "vbv_lernwelt_cypress",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"build": "npm install --prefix client && npm run build --prefix client && npm run build:tailwind",
"build:tailwind": "tailwindcss -i ./tailwind/input.css -o ./server/vbv_lernwelt/static/css/tailwind.css --minify",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"cypress:open": "cypress open", "cypress:open": "cypress open",
"cypress:run": "cypress run", "cypress:run": "cypress run",
@ -10,6 +12,8 @@
"tailwind": "tailwindcss -i ./tailwind/input.css -o ./server/vbv_lernwelt/static/css/tailwind.css --watch" "tailwind": "tailwindcss -i ./tailwind/input.css -o ./server/vbv_lernwelt/static/css/tailwind.css --watch"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.2",
"cypress": "^9.4.1", "cypress": "^9.4.1",
"tailwindcss": "^3.0.24" "tailwindcss": "^3.0.24"
} }

74
prepare_server.sh Executable file
View File

@ -0,0 +1,74 @@
#!/bin/bash
if [ -z "$CI" ];
then
# kill all subprocess on exit so that Bitbucket Pipelines process will not hang
trap "exit" INT TERM ERR
trap "kill 0" EXIT
else echo "CI is set to $CI";
fi
# script should fail when any process returns non zero code
set -ev
# handle arguments
SKIP_SETUP=false
START_BACKGROUND=false
for i in "$@"
do
case $i in
--start-background)
START_BACKGROUND=true
shift # past argument
;;
--skip-setup)
SKIP_SETUP=true
shift # past argument with no value
;;
*)
# unknown option
;;
esac
done
echo "SKIP_SETUP = ${SKIP_SETUP}"
POSTGRES_DB=${POSTGRES_DB:-vbv_lernwelt}
POSTGRES_HOST=${POSTGRES_HOST:-localhost}
POSTGRES_PORT=${POSTGRES_PORT:-5432}
POSTGRES_USER=${POSTGRES_USER:-postgres}
DJANGO_PORT=${DJANGO_PORT:-8000}
mypsql() {
psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" "$@"
}
if [ "$SKIP_SETUP" = false ]; then
# TODO: in heroku we must do a `pg:resets` to reset the db
echo "Drop all connections to $POSTGRES_DB"
mypsql -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$POSTGRES_DB' AND pid <> pg_backend_pid();" > /dev/null 2>&1
echo "Drop database: $POSTGRES_DB"
mypsql -c "drop database if exists $POSTGRES_DB;"
echo "Create database: $POSTGRES_DB"
mypsql -c "create database $POSTGRES_DB;"
echo "initialize database for django"
python3 server/manage.py createcachetable --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py migrate --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_users --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py create_default_learning_path --settings="$DJANGO_SETTINGS_MODULE"
# make django translations
(cd server && python3 manage.py compilemessages --settings="$DJANGO_SETTINGS_MODULE")
else
# TODO: can we reset important data without resetting the database?
echo "Skip database setup"
# python3 src/manage.py cypress_reset --settings="$DJANGO_SETTINGS_MODULE"
fi
if [ "$START_BACKGROUND" = true ]; then
python3 server/manage.py runserver "${DJANGO_PORT}" --settings="$DJANGO_SETTINGS_MODULE" > /dev/null &
else
python3 server/manage.py runserver "${DJANGO_PORT}" --settings="$DJANGO_SETTINGS_MODULE"
fi

View File

@ -1,91 +1,6 @@
#!/bin/bash export IT_APP_ENVIRONMENT=development
export DJANGO_SETTINGS_MODULE=config.settings.test_cypress
export DJANGO_PORT=8001
export POSTGRES_DB=vbv_lernwelt_cypress
if [ -z "$CI" ]; ./prepare_server.sh "$@"
then
# kill all subprocess on exit
trap "exit" INT TERM ERR
trap "kill 0" EXIT
else echo "CI is set to $CI";
fi
# script should fail when any process returns non zero code
set -ev
# handle arguments
SKIP_SETUP=false
START_BACKGROUND=false
PROXY_VUE=false
for i in "$@"
do
case $i in
--start-background)
START_BACKGROUND=true
shift # past argument
;;
--skip-setup)
SKIP_SETUP=true
shift # past argument with no value
;;
--proxy-vue)
PROXY_VUE=true
shift # past argument with no value
;;
*)
# unknown option
;;
esac
done
echo "SKIP_SETUP = ${SKIP_SETUP}"
DJANGO_SETTINGS_MODULE=config.settings.test_cypress
CYPRESS_DB=vbv_lernwelt_cypress
if [ "$SKIP_SETUP" = false ]; then
if [ -z "$PG_PORT" ]; then # if the port is set in the env, use iterg
DB_PORT="";
else
DB_PORT="-p $PG_PORT";
fi
if [ -z "$PG_USER" ]; then # if the user is set in the env, use it
DB_USER="";
else
DB_USER="-U $PG_USER";
fi
echo "psql -h localhost $DB_PORT $DB_USER -c 'drop database if exists $CYPRESS_DB;'"
# create database
psql -h localhost $DB_PORT $DB_USER -c "drop database if exists $CYPRESS_DB;"
psql -h localhost $DB_PORT $DB_USER -c "create database $CYPRESS_DB;"
# reset data
# python3 src/manage.py randomdata --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py createcachetable --settings="$DJANGO_SETTINGS_MODULE"
python3 server/manage.py migrate --settings="$DJANGO_SETTINGS_MODULE"
# make django translations
(cd server && python3 manage.py compilemessages --settings="$DJANGO_SETTINGS_MODULE")
else
echo "else"
# python3 src/manage.py recreate_customer_data_for_integration_tests --settings="$DJANGO_SETTINGS_MODULE"
fi
if [ "$PROXY_VUE" = true ]; then
export DJANGO_VUE_LANDINGPAGE_PROXY=http://localhost:8080/
fi
# install cypress here to avoid problems with `npm install` on the iesc servers
#CYPRESS_INSTALLED=0
##npx --no-install cypress --version || CYPRESS_INSTALLED=$?
#if [ $CYPRESS_INSTALLED -ne 0 ]; then
# echo "install cypress"
## npm install cypress@5.6.0 @testing-library/cypress@7.0.2 --no-save
#fi
if [ "$START_BACKGROUND" = true ]; then
python3 server/manage.py runserver 8001 --settings="$DJANGO_SETTINGS_MODULE" > /dev/null &
else
python3 server/manage.py runserver 8001 --settings="$DJANGO_SETTINGS_MODULE"
fi

View File

@ -1,46 +0,0 @@
version: '3'
volumes:
production_postgres_data: {}
production_postgres_data_backups: {}
production_traefik: {}
services:
django:
build:
context: .
dockerfile: ./compose/production/django/Dockerfile
image: vbv_lernwelt_production_django
depends_on:
- postgres
- redis
env_file:
- env_secrets/production.env
command: /start
postgres:
build:
context: .
dockerfile: ./compose/production/postgres/Dockerfile
image: vbv_lernwelt_production_postgres
volumes:
- production_postgres_data:/var/lib/postgresql/data:Z
- production_postgres_data_backups:/backups:z
env_file:
- env_secrets/production.env
traefik:
build:
context: .
dockerfile: ./compose/production/traefik/Dockerfile
image: vbv_lernwelt_production_traefik
depends_on:
- django
volumes:
- production_traefik:/etc/traefik/acme:z
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
redis:
image: redis:6

View File

@ -10,21 +10,17 @@ from environs import Env
from vbv_lernwelt.core.utils import structlog_add_app_info from vbv_lernwelt.core.utils import structlog_add_app_info
SERVER_ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent SERVER_ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
# vbv_lernwelt/
APPS_DIR = SERVER_ROOT_DIR / "vbv_lernwelt" APPS_DIR = SERVER_ROOT_DIR / "vbv_lernwelt"
env = Env() env = Env()
READ_DOT_ENV_FILE = env.bool("VBV_DJANGO_READ_DOT_ENV_FILE", default=False) # set to "development" for local development
if READ_DOT_ENV_FILE: APP_ENVIRONMENT = env("IT_APP_ENVIRONMENT")
# OS environment variables take precedence over variables from .env
env.read_env(str(SERVER_ROOT_DIR / "example.env"))
DJANGO_DEV_MODE = env("VBV_DJANGO_DEV_MODE", default="development")
# GENERAL # GENERAL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#debug # https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = env.bool("VBV_DJANGO_DEBUG", False) DEBUG = env.bool("IT_DJANGO_DEBUG", True if APP_ENVIRONMENT == "development" else False)
# Local time zone. Choices are # Local time zone. Choices are
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# though not all of them may be available with every OS. # though not all of them may be available with every OS.
@ -48,8 +44,8 @@ LOCALE_PATHS = [str(SERVER_ROOT_DIR / "locale")]
# https://docs.djangoproject.com/en/dev/ref/settings/#databases # https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = { DATABASES = {
"default": env.dj_db_url( "default": env.dj_db_url(
"VBV_DATABASE_URL", "DATABASE_URL",
default="postgres://vbv_lernwelt@localhost:5432/vbv_lernwelt", default="postgres://postgres@localhost:5432/vbv_lernwelt",
) )
} }
DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405 DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405
@ -128,12 +124,12 @@ AUTHENTICATION_BACKENDS = [
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model # https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
AUTH_USER_MODEL = "core.User" AUTH_USER_MODEL = "core.User"
# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url # https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
# LOGIN_REDIRECT_URL = "users:redirect"
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url # https://docs.djangoproject.com/en/dev/ref/settings/#login-url
# FIXME make configurable!? # FIXME make configurable!?
# LOGIN_URL = "/sso/login/" # LOGIN_URL = "/sso/login/"
LOGIN_URL = "/login/" LOGIN_URL = "/login/"
LOGIN_REDIRECT_URL = "/"
ALLOW_LOCAL_LOGIN = env.bool("IT_ALLOW_LOCAL_LOGIN", default=False) ALLOW_LOCAL_LOGIN = env.bool("IT_ALLOW_LOCAL_LOGIN", default=False)
@ -308,11 +304,14 @@ MANAGERS = ADMINS
# https://docs.djangoproject.com/en/dev/ref/settings/#logging # https://docs.djangoproject.com/en/dev/ref/settings/#logging
# See https://docs.djangoproject.com/en/dev/topics/logging for # See https://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration. # more details on how to customize your logging configuration.
VBV_DJANGO_LOGGING_CONF = env(
"VBV_DJANGO_LOGGING_CONF", default="VBV_DJANGO_LOGGING_CONF_JSON_FILE"
)
if VBV_DJANGO_LOGGING_CONF == "VBV_DJANGO_LOGGING_CONF_CONSOLE_COLOR": logging_conf_default = "IT_DJANGO_LOGGING_CONF_JSON_FILE"
if DEBUG:
logging_conf_default = "IT_DJANGO_LOGGING_CONF_CONSOLE_COLOR"
IT_DJANGO_LOGGING_CONF = env("IT_DJANGO_LOGGING_CONF", default=logging_conf_default)
if IT_DJANGO_LOGGING_CONF == "IT_DJANGO_LOGGING_CONF_CONSOLE_COLOR":
timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S") timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
LOGGING = { LOGGING = {
"version": 1, "version": 1,
@ -474,14 +473,17 @@ SPECTACULAR_SETTINGS = {
# Your stuff... # Your stuff...
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
if DEBUG:
SECRET_KEY = env(
"IT_DJANGO_SECRET_KEY",
default="J9FiYN31FuY7lHrmx9Mpai3GGpTVCxakEclOfCLretDe7bTf2DtTsgazJ0aIMtbq",
)
else:
SECRET_KEY = env("IT_DJANGO_SECRET_KEY")
SECRET_KEY = env(
"VBV_DJANGO_SECRET_KEY",
default="J9FiYN31FuY7lHrmx9Mpai3GGpTVCxakEclOfCLretDe7bTf2DtTsgazJ0aIMtbq",
)
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = env.list( ALLOWED_HOSTS = env.list(
"VBV_DJANGO_ALLOWED_HOSTS", default=["localhost", "0.0.0.0", "127.0.0.1"] "IT_DJANGO_ALLOWED_HOSTS", default=["localhost", "0.0.0.0", "127.0.0.1"]
) )
@ -489,21 +491,21 @@ ALLOWED_HOSTS = env.list(
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": env( "BACKEND": env(
"VBV_DJANGO_CACHE_BACKEND", "IT_DJANGO_CACHE_BACKEND",
default="django.core.cache.backends.db.DatabaseCache", default="django.core.cache.backends.db.DatabaseCache",
), ),
"LOCATION": env("VBV_DJANGO_CACHE_LOCATION", default="django_cache_table"), "LOCATION": env("IT_DJANGO_CACHE_LOCATION", default="django_cache_table"),
} }
} }
if "django_redis.cache.RedisCache" in env("VBV_DJANGO_CACHE_BACKEND", default=""): if "django_redis.cache.RedisCache" in env("IT_DJANGO_CACHE_BACKEND", default=""):
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": env( "BACKEND": env(
"VBV_DJANGO_CACHE_BACKEND", "IT_DJANGO_CACHE_BACKEND",
default="django.core.cache.backends.db.DatabaseCache", default="django.core.cache.backends.db.DatabaseCache",
), ),
"LOCATION": env("VBV_DJANGO_CACHE_LOCATION", default="django_cache_table"), "LOCATION": env("IT_DJANGO_CACHE_LOCATION", default="django_cache_table"),
"OPTIONS": { "OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient", "CLIENT_CLASS": "django_redis.client.DefaultClient",
# Mimicing memcache behavior. # Mimicing memcache behavior.
@ -530,7 +532,7 @@ OAUTH = {
} }
} }
if DJANGO_DEV_MODE == "development": if APP_ENVIRONMENT == "development":
# http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development
INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405 INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405
@ -547,7 +549,7 @@ if DJANGO_DEV_MODE == "development":
# } # }
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips
INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"] INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"]
if env.bool("VBV_DJANGO_LOCAL_DOCKER", False): if env.bool("IT_DJANGO_LOCAL_DOCKER", False):
import socket import socket
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
@ -564,13 +566,13 @@ if DJANGO_DEV_MODE == "development":
# 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"] # noqa F405
if DJANGO_DEV_MODE in ["production", "caprover"]: if APP_ENVIRONMENT in ["production", "caprover"]:
# SECURITY # SECURITY
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header # https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect # https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
SECURE_SSL_REDIRECT = env.bool("VBV_DJANGO_SECURE_SSL_REDIRECT", default=True) SECURE_SSL_REDIRECT = env.bool("IT_DJANGO_SECURE_SSL_REDIRECT", default=True)
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure
@ -604,7 +606,7 @@ if DJANGO_DEV_MODE in ["production", "caprover"]:
default="VBV Lernwelt <info@iterativ.ch>", default="VBV Lernwelt <info@iterativ.ch>",
) )
# https://docs.djangoproject.com/en/dev/ref/settings/#server-email # https://docs.djangoproject.com/en/dev/ref/settings/#server-email
SERVER_EMAIL = env("VBV_DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) SERVER_EMAIL = env("IT_DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
# https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
EMAIL_SUBJECT_PREFIX = env( EMAIL_SUBJECT_PREFIX = env(
"DJANGO_EMAIL_SUBJECT_PREFIX", "DJANGO_EMAIL_SUBJECT_PREFIX",
@ -614,7 +616,7 @@ if DJANGO_DEV_MODE in ["production", "caprover"]:
# ADMIN # ADMIN
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Django Admin URL regex. # Django Admin URL regex.
ADMIN_URL = env("VBV_DJANGO_ADMIN_URL") ADMIN_URL = env("IT_DJANGO_ADMIN_URL", 'admin/')
# Anymail # Anymail
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -633,7 +635,7 @@ if DJANGO_DEV_MODE in ["production", "caprover"]:
from sentry_sdk.integrations.logging import LoggingIntegration from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.redis import RedisIntegration
SENTRY_DSN = env("VBV_SENTRY_DSN") SENTRY_DSN = env("IT_SENTRY_DSN")
SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO) SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO)
sentry_logging = LoggingIntegration( sentry_logging = LoggingIntegration(

View File

@ -1,42 +0,0 @@
# pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position
import getpass
import os
from .base import * # noqa
from .base import env
# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
SECRET_KEY = env(
"VBV_DJANGO_SECRET_KEY",
default="1NpUCSvAKLpDZL9e3tqDaUesdfsadfasdfasdfMD3UjB72ZS",
)
# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
TEST_RUNNER = "django.test.runner.DiscoverRunner"
# PASSWORDS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# EMAIL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
class DisableMigrations(dict):
def __contains__(self, item):
return True
def __getitem__(self, item):
return None
#MIGRATION_MODULES = DisableMigrations()
# Your stuff...
# ------------------------------------------------------------------------------

View File

@ -1,27 +1,17 @@
# pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position # pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position
import getpass
import os import os
os.environ['IT_APP_ENVIRONMENT'] = 'development'
from .base import * # noqa from .base import * # noqa
from .base import env from .base import env
# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
SECRET_KEY = env(
"VBV_DJANGO_SECRET_KEY",
default="1NpUCSvAKLpDZL9e3tqDaUe8Kk2xAuF1tXosFjBanc4lFCgNcfBp02MD3UjB72ZS",
)
# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
TEST_RUNNER = "django.test.runner.DiscoverRunner" TEST_RUNNER = "django.test.runner.DiscoverRunner"
# PASSWORDS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# EMAIL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
@ -35,18 +25,3 @@ class DisableMigrations(dict):
MIGRATION_MODULES = DisableMigrations() MIGRATION_MODULES = DisableMigrations()
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "vbv_lernwelt_test",
"USER": os.environ.get("PG_USER", getpass.getuser()),
"PASSWORD": os.environ.get("PG_PASSWORD"),
"HOST": "localhost",
"PORT": os.environ.get("PG_PORT", ""),
}
}
# Your stuff...
# ------------------------------------------------------------------------------

View File

@ -1,17 +1,11 @@
# pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position # pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position
import getpass
import os
from .base import * # noqa from .base import * # noqa
from .base import env
# GENERAL # GENERAL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
SECRET_KEY = env( DATABASES['default']['NAME'] = 'vbv_lernwelt_cypress'
"VBV_DJANGO_SECRET_KEY",
default="1LhwZ0DvP4cGBgbBdCfaBQV7eiaOc4jWKdzO9WEXLFT7AaqBN6jqd0uyaZeAZ19K",
)
# EMAIL # EMAIL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -19,19 +13,6 @@ SECRET_KEY = env(
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
CYPRESS_TEST = True CYPRESS_TEST = True
DEBUG = True
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "vbv_lernwelt_cypress",
"USER": os.environ.get("PG_USER", getpass.getuser()),
"PASSWORD": os.environ.get("PG_PASSWORD"),
"HOST": "localhost",
"PORT": os.environ.get("PG_PORT", ""),
}
}
# Your stuff... # Your stuff...
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -1,4 +1,3 @@
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
@ -19,9 +18,9 @@ from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
from vbv_lernwelt.core.views import ( from vbv_lernwelt.core.views import (
rate_limit_exceeded_view, rate_limit_exceeded_view,
permission_denied_view, permission_denied_view,
check_rate_limit, vue_home, check_rate_limit, cypress_reset_view, vue_home,
) )
from .wagtail_api import api_router from .wagtail_api import wagtail_api_router
def raise_example_error(request): def raise_example_error(request):
@ -43,6 +42,7 @@ urlpatterns = [
path('cms/', include(wagtailadmin_urls)), path('cms/', include(wagtailadmin_urls)),
path('documents/', include(wagtaildocs_urls)), path('documents/', include(wagtaildocs_urls)),
path('pages/', include(wagtail_urls)), path('pages/', include(wagtail_urls)),
path('learnpath/', include("vbv_lernwelt.learnpath.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG: if settings.DEBUG:
# Static file serving when using Gunicorn + Uvicorn for local web socket development # Static file serving when using Gunicorn + Uvicorn for local web socket development
@ -57,7 +57,7 @@ if settings.ALLOW_LOCAL_LOGIN:
urlpatterns += [ urlpatterns += [
# API base url # API base url
path("api/", include("config.api_router")), path("api/", include("config.api_router")),
path('wagtailapi/v2/', api_router.urls), path('wagtailapi/v2/', wagtail_api_router.urls),
# DRF auth token # DRF auth token
path("auth-token/", obtain_auth_token), path("auth-token/", obtain_auth_token),
@ -65,6 +65,11 @@ urlpatterns += [
path("api/docs/", SpectacularSwaggerView.as_view(url_name="api-schema"), name="api-docs",), path("api/docs/", SpectacularSwaggerView.as_view(url_name="api-schema"), name="api-docs",),
path("", include(grapple_urls)), path("", include(grapple_urls)),
] ]
if settings.APP_ENVIRONMENT != 'production':
urlpatterns += [
re_path(r'cypressreset/$', cypress_reset_view, name='cypress_reset_view'),
]
# fmt: on # fmt: on

View File

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

View File

@ -1,11 +0,0 @@
export VBV_DATABASE_URL='postgres://vbv_lernwelt@localhost:5432/vbv_lernwelt'
#export VBV_DJANGO_LOGGING_CONF=VBV_DJANGO_LOGGING_CONF_CONSOLE_COLOR
export VBV_DJANGO_DEBUG=True
# oauth is for the moment not used
export OAUTH_CLIENT_ID=iterativ
export OAUTH_CLIENT_SECRET=abced-1234
export OAUTH_ACCESS_TOKEN_URL=https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/token
export OAUTH_AUTHORIZE_URL=https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/auth
export OAUTH_API_BASE_URL=https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/
export OAUTH_LOCAL_REDIRECT_URI=http://localhost:8000/api/oauth/callback/

View File

@ -1,5 +0,0 @@
python manage.py migrate
python manage.py createcachetable
python manage.py create_default_users
#python manage.py create_default_learingpath

View File

@ -4,19 +4,24 @@ from django.contrib.auth.models import Group
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
def create_default_users(user_model=User, group_model=Group): def create_default_users(user_model=User, group_model=Group, default_password=None):
admin_group, created = group_model.objects.get_or_create(name='admin_group') admin_group, created = group_model.objects.get_or_create(name='admin_group')
content_creator_grop, created = group_model.objects.get_or_create(name='content_creator_grop') content_creator_grop, created = group_model.objects.get_or_create(name='content_creator_grop')
student_group, created = group_model.objects.get_or_create(name='student_group') student_group, created = group_model.objects.get_or_create(name='student_group')
admin_user, created = _get_or_create_user(user_model=user_model, admin_password = default_password
username='admin', if not admin_password:
password='admin') admin_password = 'admin'
admin_user.is_superuser=True admin_user, created = _get_or_create_user(user_model=user_model, username='admin', password=admin_password)
admin_user.is_superuser = True
admin_user.is_staff = True
admin_user.groups.add(admin_group) admin_user.groups.add(admin_group)
admin_user.save() admin_user.save()
student_user, created = _get_or_create_user(user_model=user_model, username='student', password='student')
student_user_password = default_password
if not student_user_password:
student_user_password = 'student'
student_user, created = _get_or_create_user(user_model=user_model, username='student', password=student_user_password)
student_user.groups.add(student_group) student_user.groups.add(student_group)
student_user.save() student_user.save()

View File

@ -1,8 +1,7 @@
from vbv_lernwelt.core.create_default_users import create_default_users
import djclick as click import djclick as click
from vbv_lernwelt.core.create_default_users import create_default_users
@click.command() @click.command()
def command(): def command():

View File

@ -1,7 +1,7 @@
# pylint: disable=import-outside-toplevel
import djclick as click import djclick as click
from django.contrib.auth import get_user_model
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path, \
delete_default_learning_path
@click.command() @click.command()
@ -9,17 +9,5 @@ from django.contrib.auth import get_user_model
def command(customer_language): def command(customer_language):
print("cypress reset data") print("cypress reset data")
User = get_user_model() delete_default_learning_path()
create_default_learning_path()
users = [
"cypress@example.com",
]
for user in users:
User.objects.filter(username=user).delete()
user = User.objects.create(
username=user,
email=user,
)
user.set_password("test")
user.save()

View File

@ -1,20 +1,16 @@
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import migrations from django.db import migrations
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
def create_iterativ_users(apps, schema_editor): def create_users(apps, schema_editor):
for username in [ default_password = 'ACEEs0DCmNaPxdoNV8vhccuCTRl9b'
"info@iterativ.ch", if settings.APP_ENVIRONMENT == 'development':
]: default_password = None
user = User.objects.create( create_default_users(user_model=User, group_model=Group, default_password=default_password)
username=username,
email=username,
is_superuser=True,
is_staff=True,
)
user.set_password("ACEEs0DCmNaPxdoNV8vhccuCTRl9b")
user.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -23,5 +19,5 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.RunPython(create_iterativ_users), migrations.RunPython(create_users),
] ]

View File

@ -1,17 +0,0 @@
<div class="container mx-auto bg-blue-900">
<div class="flex flex-col md:flex-row items-center p-4 text-white space-x-8">
<div class="text-2xl mx-4">
<a href="/">
VBV Ausbildungsportal
</a>
</div>
<div>
<a href="/todo/">
SimpleTodo App
</a>
</div>
<div>Infos zu Berufen</div>
<div>Branchennews</div>
</div>
</div>

View File

@ -10,8 +10,8 @@ def structlog_add_app_info(
logger: logging.Logger, method_name: str, event_dict: EventDict logger: logging.Logger, method_name: str, event_dict: EventDict
) -> EventDict: ) -> EventDict:
event_dict["django_app"] = "vbv_lernwelt" event_dict["django_app"] = "vbv_lernwelt"
event_dict["django_dev_mode"] = settings.DJANGO_DEV_MODE event_dict["APP_ENVIRONMENT"] = settings.APP_ENVIRONMENT
event_dict["django_app_dev_mode"] = f"vbv_lernwelt_{settings.DJANGO_DEV_MODE}" event_dict["django_app_dev_mode"] = f"vbv_lernwelt_{settings.APP_ENVIRONMENT}"
return event_dict return event_dict

View File

@ -1,10 +1,14 @@
# Create your views here. # Create your views here.
import requests import requests
from django.conf import settings from django.conf import settings
from django.http import JsonResponse, HttpResponse from django.core.management import call_command
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from ratelimit.decorators import ratelimit from ratelimit.decorators import ratelimit
from rest_framework import authentication
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.permissions import IsAdminUser
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
@ -19,11 +23,12 @@ def vue_home(request):
content_type = headers.get('content-type', 'text/html') content_type = headers.get('content-type', 'text/html')
return HttpResponse(res.text, content_type=content_type) return HttpResponse(res.text, content_type=content_type)
except Exception as e: except Exception as e:
print(f'Can not connect to vue dev server at {settings.IT_SERVE_VUE_URL}:', e) return HttpResponse(
return f'Can not connect to vue dev server at {settings.IT_SERVE_VUE_URL}: {e}'
)
# render index.html from `npm run build` # render index.html from `npm run build`
return render(request, 'index.html', {}) return render(request, 'vue/index.html', {})
def permission_denied_view(request, exception): def permission_denied_view(request, exception):
@ -50,3 +55,13 @@ def server_json_error(request, *args, **kwargs):
@django_view_authentication_exempt @django_view_authentication_exempt
def check_rate_limit(request): def check_rate_limit(request):
return HttpResponse(content=b"Hello") return HttpResponse(content=b"Hello")
@api_view(['POST'])
@authentication_classes((authentication.SessionAuthentication,))
@permission_classes((IsAdminUser,))
def cypress_reset_view(request):
if settings.APP_ENVIRONMENT != 'production':
call_command('cypress_reset')
return HttpResponseRedirect('/admin/')

View File

@ -1,7 +1,4 @@
# pylint: disable=import-outside-toplevel
import djclick as click import djclick as click
from django.contrib.auth import get_user_model
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path

View File

@ -0,0 +1,8 @@
import djclick as click
from vbv_lernwelt.learnpath.tests.create_default_learning_path import delete_default_learning_path
@click.command()
def command():
delete_default_learning_path()

View File

@ -1,14 +0,0 @@
# pylint: disable=import-outside-toplevel
import djclick as click
from django.contrib.auth import get_user_model
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path, \
delete_default_learning_path
import djclick as click
@click.command()
def command():
delete_default_learning_path()

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2022-05-04 15:52 # Generated by Django 3.2.12 on 2022-06-03 13:11
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -16,19 +16,6 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Circle',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('description', models.TextField(blank=True, default='')),
('goals', models.TextField(blank=True, default='')),
],
options={
'verbose_name': 'Circle',
},
bases=('wagtailcore.page', models.Model),
),
migrations.CreateModel( migrations.CreateModel(
name='Competence', name='Competence',
fields=[ fields=[
@ -64,15 +51,26 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='LearningSequence', name='LearningSequence',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)), ('icon', models.CharField(default='IconLsStart', max_length=255)),
('title', models.CharField(default='', max_length=256)),
('category', models.CharField(choices=[('INCIRCLE', 'In Circle'), ('START', 'Start'), ('END', 'End')], default='INCIRCLE', max_length=16)),
('circle', modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_sequences', to='learnpath.circle')),
], ],
options={ options={
'verbose_name': 'Learning Sequence', 'verbose_name': 'Learning Sequence',
}, },
bases=('wagtailcore.page',),
),
migrations.CreateModel(
name='LearningUnit',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('minutes', models.PositiveIntegerField(default=15)),
('package', models.CharField(blank=True, default='', max_length=255)),
('contents', wagtail.core.fields.StreamField([('video', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('rise_training', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('podcast', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('competence', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock())])), ('exercise', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock())])), ('self_evaluation', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock())])), ('document', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock())])), ('knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.TextBlock())]))])),
],
options={
'verbose_name': 'Learning Unit',
},
bases=('wagtailcore.page',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Topic', name='Topic',
@ -87,23 +85,11 @@ class Migration(migrations.Migration):
'verbose_name': 'Topic', 'verbose_name': 'Topic',
}, },
), ),
migrations.CreateModel(
name='LearningUnit',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('contents', wagtail.core.fields.StreamField([('web_based_training', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('video', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True)),
('learning_sequence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_units', to='learnpath.learningsequence')),
],
options={
'verbose_name': 'Learning Unit',
},
bases=('wagtailcore.page', models.Model),
),
migrations.CreateModel( migrations.CreateModel(
name='FullfillmentCriteria', name='FullfillmentCriteria',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('name', models.CharField(max_length=2048)), ('name', models.CharField(max_length=2048)),
('competence', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='learnpath.competence')), ('competence', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='learnpath.competence')),
], ],
@ -116,9 +102,17 @@ class Migration(migrations.Migration):
name='competence_page', name='competence_page',
field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='competences', to='learnpath.competencepage'), field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='competences', to='learnpath.competencepage'),
), ),
migrations.AddField( migrations.CreateModel(
model_name='circle', name='Circle',
name='topic', fields=[
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='circles', to='learnpath.topic'), ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('description', models.TextField(blank=True, default='')),
('goals', models.TextField(blank=True, default='')),
('topic', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='circles', to='learnpath.topic')),
],
options={
'verbose_name': 'Circle',
},
bases=('wagtailcore.page',),
), ),
] ]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-04 16:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='fullfillmentcriteria',
name='sort_order',
field=models.IntegerField(blank=True, editable=False, null=True),
),
]

View File

@ -1,36 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-12 12:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0002_fullfillmentcriteria_sort_order'),
]
operations = [
migrations.RemoveField(
model_name='learningunit',
name='learning_sequence',
),
migrations.CreateModel(
name='LearningPackage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
('title', models.CharField(default='', max_length=256)),
('learning_sequence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_packages', to='learnpath.learningsequence')),
],
options={
'ordering': ['sort_order'],
'abstract': False,
},
),
migrations.AddField(
model_name='learningunit',
name='learning_package',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='learning_units', to='learnpath.learningpackage'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-12 12:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0003_auto_20220512_1456'),
]
operations = [
migrations.AlterField(
model_name='learningpackage',
name='learning_sequence',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='learning_packages', to='learnpath.learningsequence'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-12 12:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('learnpath', '0004_alter_learningpackage_learning_sequence'),
]
operations = [
migrations.AlterField(
model_name='learningunit',
name='learning_package',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='learning_units', to='learnpath.learningpackage'),
),
]

View File

@ -1,21 +1,16 @@
# Create your models here. # Create your models here.
from django.utils.text import slugify from django.utils.text import slugify
from grapple.helpers import register_query_field
from wagtail.api import APIField
from wagtail.core.blocks import StreamBlock from wagtail.core.blocks import StreamBlock
from wagtail.core.fields import StreamField from wagtail.core.fields import StreamField
from wagtail.core.models import Page, Orderable from wagtail.core.models import Page, Orderable
from wagtail.api import APIField
from vbv_lernwelt.learnpath.models_competences import * from vbv_lernwelt.learnpath.models_competences import *
from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock from vbv_lernwelt.learnpath.models_learning_unit_content import RiseTrainingBlock, VideoBlock, PodcastBlock, \
from grapple.helpers import register_query_field CompetenceBlock, ExerciseBlock, SelfEvaluationBlock, DocumentBlock, KnowledgeBlock
from collections import OrderedDict from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
import graphene
from grapple.models import (
GraphQLString, GraphQLPage,
GraphQLStreamfield, GraphQLBoolean, GraphQLInt, GraphQLForeignKey, GraphQLField
)
@register_query_field("learning_path") @register_query_field("learning_path")
@ -28,7 +23,6 @@ class LearningPath(Page):
subpage_types = ['learnpath.Circle'] subpage_types = ['learnpath.Circle']
class Meta: class Meta:
verbose_name = "Learning Path" verbose_name = "Learning Path"
@ -65,7 +59,6 @@ class Topic(Orderable):
# parent_page_types = ['learnpath.LearningPath'] # parent_page_types = ['learnpath.LearningPath']
# subpage_types = ['learnpath.Circle'] # subpage_types = ['learnpath.Circle']
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(Topic, slugify(self.title, allow_unicode=True)) self.slug = find_available_slug(Topic, slugify(self.title, allow_unicode=True))
super(Topic, self).full_clean(*args, **kwargs) super(Topic, self).full_clean(*args, **kwargs)
@ -77,9 +70,10 @@ class Topic(Orderable):
return f"{self.title}" return f"{self.title}"
class Circle(Page, Orderable): class Circle(Page):
description = models.TextField(default="", blank=True) description = models.TextField(default="", blank=True)
goals = models.TextField(default="", blank=True) goals = models.TextField(default="", blank=True)
topic = models.ForeignKey( topic = models.ForeignKey(
'learnpath.Topic', 'learnpath.Topic',
null=True, null=True,
@ -88,51 +82,21 @@ class Circle(Page, Orderable):
related_name='circles' related_name='circles'
) )
parent_page_types = ['learnpath.Learningpath'] parent_page_types = ['learnpath.LearningPath']
subpage_types = ['learnpath.LearningUnit'] subpage_types = ['learnpath.LearningSequence', 'learnpath.LearningUnit']
content_panels = Page.content_panels + [ content_panels = Page.content_panels + [
FieldPanel('description'), FieldPanel('description'),
FieldPanel('goals'), FieldPanel('goals'),
InlinePanel('learning_sequences', label="Learning Sequences"),
] ]
# Export fields over the API
api_fields = [ api_fields = [
APIField('title'), APIField('title'),
APIField('description'), APIField('description'),
APIField('topic'),
APIField('content_structure'),
] ]
@property
def content_structure(self):
learning_sequences = LearningSequence.objects.filter(circle_id=self.id).values()
learning_packages = LearningPackage.objects.filter(learning_sequence__circle_id=self.id).values()
learning_units = LearningUnit.objects.filter(learning_package__learning_sequence__circle_id=self.id).values('learning_package_id', 'title')
content = OrderedDict()
content['learning_sequences'] = []
for learning_sequence in learning_sequences:
this_learning_packages = []
related_learning_packages = [lp for lp in learning_packages if lp['learning_sequence_id'] == learning_sequence['id']]
for learning_package in related_learning_packages:
related_learning_units = [lu for lu in learning_units if
lu['learning_package_id'] == learning_package['id']]
this_learning_units = [learning_unit for learning_unit in related_learning_units]
learning_package['learning_units'] = this_learning_units
this_learning_packages.append(learning_package)
learning_sequence['learning_packages'] = this_learning_packages
content['learning_sequences'].append(learning_sequence)
return content
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(Circle, slugify(self.title, allow_unicode=True)) # self.slug = find_available_slug(Circle, slugify(self.title, allow_unicode=True))
super(Circle, self).full_clean(*args, **kwargs) super(Circle, self).full_clean(*args, **kwargs)
class Meta: class Meta:
@ -142,116 +106,77 @@ class Circle(Page, Orderable):
return f"{self.title}" return f"{self.title}"
IN_CIRCLE = 'INCIRCLE' class LearningSequence(Page):
START = 'START' icon = models.CharField(max_length=255, default="IconLsStart")
END = 'END'
LEARNING_SEQUENCE_CATEGORIES = [ parent_page_types = ['learnpath.Circle']
(IN_CIRCLE, 'In Circle'),
(START, 'Start'),
(END, 'End')
]
panels = [
class LearningSequence(Orderable): FieldPanel('title'),
# TODO: How to do a icon choice field? FieldPanel('icon'),
title = models.CharField(max_length=256, default='')
category = models.CharField(max_length=16, choices=LEARNING_SEQUENCE_CATEGORIES, default=IN_CIRCLE)
circle = ParentalKey(
'learnpath.Circle',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='learning_sequences',
)
panels = [FieldPanel('title'), FieldPanel('category'), FieldPanel('circle')]
api_fields = [
APIField('title'),
APIField('category'),
APIField('learning_packages'),
] ]
class Meta: class Meta:
verbose_name = "Learning Sequence" verbose_name = "Learning Sequence"
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
@classmethod
def get_serializer_class(cls):
return get_it_serializer_class(cls, field_names=['id', 'title', 'icon', 'slug', 'type', 'translation_key'])
def get_admin_display_title(self):
return f'{self.icon} {self.draft_title}'
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(LearningSequence, slugify(self.title, allow_unicode=True))
super(LearningSequence, self).full_clean(*args, **kwargs) super(LearningSequence, self).full_clean(*args, **kwargs)
class LearningUnit(Page):
class LearningPackage(Orderable):
title = models.CharField(max_length=256, default='')
learning_sequence = models.ForeignKey(
'learnpath.LearningSequence',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='learning_packages',
)
panels = [FieldPanel('title')]
api_fields = [
APIField('title'),
APIField('my_title'),
]
@property
def my_title(self):
return self.title
def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(LearningPackage, slugify(self.title, allow_unicode=True))
super(LearningPackage, self).full_clean(*args, **kwargs)
class LearningUnit(Page, Orderable):
""" """
This is a group of contents, with the übung and test it is one unit. ... more of a structural charactacter. This is a group of contents, with the übung and test it is one unit. ... more of a structural charactacter.
""" """
# TODO: Review model architecture, is the stream field the right thing here? # TODO: Review model architecture, is the stream field the right thing here?
parent_page_types = ['learnpath.Circle'] parent_page_types = ['learnpath.Circle']
learning_package = models.ForeignKey( subpage_types = []
'learnpath.LearningPackage', minutes = models.PositiveIntegerField(default=15)
null=True, package = models.CharField(max_length=255, default="", blank=True)
blank=True,
on_delete=models.SET_NULL,
related_name='learning_units',
)
content_blocks = [ content_blocks = [
('web_based_training', WebBasedTrainingBlock()),
('video', VideoBlock()), ('video', VideoBlock()),
('rise_training', RiseTrainingBlock()),
('podcast', PodcastBlock()),
('competence', CompetenceBlock()),
('exercise', ExerciseBlock()),
('self_evaluation', SelfEvaluationBlock()),
('document', DocumentBlock()),
('knowledge', KnowledgeBlock()),
] ]
contents = StreamField(StreamBlock(content_blocks), contents = StreamField(
null=True, blank=True, min_num=1, max_num=1) StreamBlock(content_blocks), blank=False, min_num=1, max_num=1
)
content_panels = [ content_panels = [
FieldPanel('title', classname="full title"), FieldPanel('title', classname="full title"),
FieldPanel('learning_package'), FieldPanel('minutes'),
StreamFieldPanel('contents'), StreamFieldPanel('contents'),
] ]
api_fields = [ def get_admin_display_title(self):
APIField('title'), display_title = ''
APIField('contents'),
APIField('content_blocks'),
]
subpage_types = [] if self.package:
display_title += f'{self.package}: '
if len(self.contents) > 0:
display_title += f'{self.contents[0].block_type.capitalize()}: '
display_title += self.draft_title
return display_title
class Meta: class Meta:
verbose_name = "Learning Unit" verbose_name = "Learning Unit"
@ -260,11 +185,14 @@ class LearningUnit(Page, Orderable):
self.slug = find_available_slug(LearningUnit, slugify(self.title, allow_unicode=True)) self.slug = find_available_slug(LearningUnit, slugify(self.title, allow_unicode=True))
super(LearningUnit, self).full_clean(*args, **kwargs) super(LearningUnit, self).full_clean(*args, **kwargs)
@classmethod
def get_serializer_class(cls):
return get_it_serializer_class(cls, field_names=['id', 'title', 'minutes', 'package', 'contents', 'slug', 'type', 'translation_key'])
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
def find_available_slug(model, requested_slug, ignore_page_id=None): def find_available_slug(model, requested_slug, ignore_page_id=None):
""" """
Finds an available slug within the specified parent. Finds an available slug within the specified parent.

View File

@ -1,51 +1,61 @@
from django.db import models
from wagtail.core import blocks from wagtail.core import blocks
from wagtail.api import APIField
# 'video_block'
class VideoBlock(blocks.StructBlock): class VideoBlock(blocks.StructBlock):
# TODO: Possible video Types for the user, upload file, add URL # TODO: Possible video Types for the user, upload file, add URL
title = models.CharField(max_length=128, default="") description = blocks.TextBlock()
description = models.TextField(default="")
url = blocks.URLBlock() url = blocks.URLBlock()
class Meta: class Meta:
icon = 'media' icon = 'media'
def get_api_representation(self, value, context=None):
return {'sdfsdf': 1,
'sldkfm': 3}
RISE = 'rise'
CONTENT_TYPE_CHOICES = (
(RISE, 'Rise'),
)
# 'Web based training Block'
class WebBasedTrainingBlock(blocks.StructBlock):
class RiseTrainingBlock(blocks.StructBlock):
description = blocks.TextBlock()
url = blocks.URLBlock() url = blocks.URLBlock()
content_type = models.CharField(
max_length=100,
choices=CONTENT_TYPE_CHOICES,
default=RISE
)
class Meta: class Meta:
icon = 'media' icon = 'media'
def get_api_representation(self, value, context=None):
return {'sdfsdf': 1, class PodcastBlock(blocks.StructBlock):
'sldkfm': 3} description = blocks.TextBlock()
url = blocks.URLBlock()
# 'Transver Task' class Meta:
class TranverTaskBlock(blocks.StructBlock): icon = 'media'
title = models.CharField(max_length=128, default="")
description = models.TextField(default="")
class CompetenceBlock(blocks.StructBlock):
description = blocks.TextBlock()
class Meta:
icon = 'media'
class ExerciseBlock(blocks.StructBlock):
description = blocks.TextBlock()
class Meta:
icon = 'media'
class SelfEvaluationBlock(blocks.StructBlock):
description = blocks.TextBlock()
class Meta:
icon = 'media'
class DocumentBlock(blocks.StructBlock):
description = blocks.TextBlock()
class Meta:
icon = 'media'
class KnowledgeBlock(blocks.StructBlock):
description = blocks.TextBlock()
class Meta: class Meta:
icon = 'media' icon = 'media'

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import factory
import wagtail_factories import wagtail_factories
from vbv_lernwelt.learnpath.models_competences import Competence, FullfillmentCriteria, CompetencePage from vbv_lernwelt.learnpath.models_competences import Competence, FullfillmentCriteria, CompetencePage
from vbv_lernwelt.learnpath.tests.learningpath_factories import LearningPathFactory from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory
class CompetencePageFactory(wagtail_factories.PageFactory): class CompetencePageFactory(wagtail_factories.PageFactory):

View File

@ -1,20 +1,26 @@
import wagtail_factories import wagtail_factories
from wagtail.core.models import Site from django.conf import settings
from wagtail.core.models import Site, Page
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningUnit from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningUnit
from vbv_lernwelt.learnpath.tests.create_default_competences import create_default_competences from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
from vbv_lernwelt.learnpath.tests.learningpath_factories import LearningPathFactory, TopicFactory, CircleFactory, \ LearningSequenceFactory, LearningUnitFactory, VideoBlockFactory, PodcastBlockFactory, CompetenceBlockFactory, \
LearningSequenceFactory, LearningUnitFactory, VideoBlockFactory, WebBasedTrainingBlockFactory, LearningPackageFactory ExerciseBlockFactory, SelfEvaluationBlockFactory, DocumentBlockFactory
def create_default_learning_path(): def create_default_learning_path(user=None):
if user is None:
user = User.objects.get(username='admin')
site = Site.objects.filter(is_default_site=True).first() site = Site.objects.filter(is_default_site=True).first()
if not site: if not site:
site = wagtail_factories.SiteFactory(is_default_site=True) site = wagtail_factories.SiteFactory(is_default_site=True)
site.port = 8000 if settings.APP_ENVIRONMENT == 'development':
site.save() site.port = 8000
site.save()
# create_default_competences() # create_default_competences()
@ -22,22 +28,10 @@ def create_default_learning_path():
tp = TopicFactory(title="Basis", is_visible=False, learning_path=lp) tp = TopicFactory(title="Basis", is_visible=False, learning_path=lp)
circle_1 = CircleFactory(title="Basis", parent=lp, topic=tp, description="""In diesem Circle erklären wir dir, wie der Lehrgang circle_1 = CircleFactory(title="Basis", parent=lp, topic=tp, description="""
Versicherungsvermittler / in " aufgebaut ist. Zudem vermitteln wir dir die wichtigsten Grundlagen, In diesem Circle erklären wir dir, wie der Lehrgang
damit erfolgreich mit deinem Lernpfad starten kannst.""") Versicherungsvermittler / in " aufgebaut ist. Zudem vermitteln wir dir die wichtigsten Grundlagen,
damit erfolgreich mit deinem Lernpfad starten kannst.""")
ls_1 = LearningSequenceFactory(title='Einleitung', circle=circle_1)
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=ls_1)
lu_1 = LearningUnitFactory(title="Herzlich Willkommmen", parent=circle_1, learning_package=lpck_1)
lu_1 = LearningUnitFactory(title="Herzlich Willkommmen 1", parent=circle_1, learning_package=lpck_1)
lu_1 = LearningUnitFactory(title="Herzlich Willkommmen 2", parent=circle_1, learning_package=lpck_1)
ls_2 = LearningSequenceFactory(title='Grundlagen', circle=circle_1)
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=ls_2)
lu_1 = LearningUnitFactory(title="Aber jetzt, Butter bei die Fische", parent=circle_1, learning_package=lpck_1)
tp = TopicFactory(title="Gewinnen von Kunden", learning_path=lp) tp = TopicFactory(title="Gewinnen von Kunden", learning_path=lp)
@ -60,7 +54,8 @@ von Neukunden zu benützen
tp = TopicFactory(title="Beraten der Kunden", learning_path=lp) tp = TopicFactory(title="Beraten der Kunden", learning_path=lp)
circle_3 = CircleFactory(title="Einstieg", parent=lp, topic=tp) circle_3 = CircleFactory(title="Einstieg", parent=lp, topic=tp)
circle_4 = CircleFactory(title="Analyse", parent=lp, topic=tp,
circe_analyse = CircleFactory(title="Analyse", parent=lp, topic=tp,
description="""Nach dem Gespräch werten sie die Analyse aus und erstellen mit den description="""Nach dem Gespräch werten sie die Analyse aus und erstellen mit den
zur Verfügung stehenden Systemen formal korrekte Lösungsvorschläge bzw. zur Verfügung stehenden Systemen formal korrekte Lösungsvorschläge bzw.
Ausschreibungen. Je nach Komplexität der Situation ziehen sie die nötigen Ausschreibungen. Je nach Komplexität der Situation ziehen sie die nötigen
@ -73,93 +68,220 @@ von Neukunden zu benützen
Lösungsvorschläge zu skizzieren und Lösungsvorschläge zu skizzieren und
zu visualisieren""") zu visualisieren""")
sequence_1 = LearningSequenceFactory(title="Starten", circle=circle_4) LearningSequenceFactory(title='Starten', parent=circe_analyse)
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_1) LearningUnitFactory(
title='Einleitung Circle "Anlayse"',
parent=circe_analyse,
minutes=15,
contents=[('video', VideoBlockFactory())]
)
learning_unit = LearningUnitFactory(title='Einleitung Circle "Anlayse"', parent=circle_4, learning_package=lpck_1) LearningSequenceFactory(title='Beobachten', parent=circe_analyse, icon='IconLsWatch')
learning_unit = LearningUnitFactory.create(title='** Einstieg Video"', parent=circle_4, learning_package=lpck_1) LearningUnitFactory(
video_url = "https://www.vbv.ch/fileadmin/vbv/Videos/Statements_Externe/Janos_M/Testimonial_Janos_Mischler_PositiveEffekte.mp4" title='Ermittlung des Kundenbedarfs',
video_title = "Ausbildung ist pflicht" parent=circe_analyse,
video_description = "Erfahren Sie, was für Janos Mischler die positiven Aspekte von ständiger Weiterbildung sind aus fachlicher und aus persönlicher Sicht." package='Absicherung der Familie',
video_block = VideoBlockFactory(content_type="video", url=video_url, title=video_title, description=video_description) minutes=30,
learning_unit.contents.append(('video', video_block)) contents=[('podcast', PodcastBlockFactory())]
learning_unit.save() )
LearningUnitFactory(
title='Kundenbedürfnisse erkennen',
parent=circe_analyse,
package='Absicherung der Familie',
minutes=30,
contents=[('competence', CompetenceBlockFactory())]
)
LearningUnitFactory(
title='Was braucht eine Familie?',
parent=circe_analyse,
package='Absicherung der Familie',
minutes=60,
contents=[('exercise', ExerciseBlockFactory())]
)
LearningUnitFactory(
title='Selbsteinschätzung',
parent=circe_analyse,
package='Absicherung der Familie',
minutes=0,
contents=[('self_evaluation', SelfEvaluationBlockFactory())]
)
learning_unit = LearningUnitFactory.create(title='** Web Based Training"', parent=circle_4, learning_package=lpck_1) LearningSequenceFactory(title='Anwenden', parent=circe_analyse, icon='IconLsApply')
wbt_url = "web_based_trainings/rise_cmi5_test_export/scormcontent/index.html" LearningUnitFactory(
wbt_block = WebBasedTrainingBlockFactory(content_type="web_based_training", url=wbt_url) title='Versicherungsbedarf für Familien',
learning_unit.contents.append(('web_based_training', wbt_block)) parent=circe_analyse,
learning_unit.save() package='Prämien einsparen',
minutes=60,
contents=[('exercise', ExerciseBlockFactory())]
)
LearningUnitFactory(
title='Alles klar?',
parent=circe_analyse,
package='Prämien einsparen',
minutes=60,
contents=[('exercise', ExerciseBlockFactory())]
)
LearningUnitFactory(
title='Selbsteinschätzung',
parent=circe_analyse,
package='Prämien einsparen',
minutes=0,
contents=[('self_evaluation', SelfEvaluationBlockFactory())]
)
learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4, learning_package=lpck_1) LearningUnitFactory(
title='GmbH oder AG',
parent=circe_analyse,
package='Sich selbständig machen',
minutes=120,
contents=[('video', VideoBlockFactory())]
)
LearningUnitFactory(
title='Tiertherapie Patrizia Feller',
parent=circe_analyse,
package='Sich selbständig machen',
minutes=120,
contents=[('exercise', ExerciseBlockFactory())]
)
LearningUnitFactory(
title='Selbsteinschätzung',
parent=circe_analyse,
package='Sich selbständig machen',
minutes=0,
contents=[('self_evaluation', SelfEvaluationBlockFactory())]
)
sequence_2 = LearningSequenceFactory.create(title="Beobachten", circle=circle_4) LearningUnitFactory(
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_2) title='Motorfahrzeugversicherung',
parent=circe_analyse,
learning_unit = LearningUnitFactory.create(title="Mein Motorfahrzeug kaufen", parent=circle_4, learning_package=lpck_1) package='Auto verkaufen',
learning_unit = LearningUnitFactory.create(title="Sich selbständig machen", parent=circle_4, learning_package=lpck_1) minutes=240,
contents=[('competence', CompetenceBlockFactory())]
sequence_3 = LearningSequenceFactory.create(title="Anwenden", circle=circle_4) )
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_3) LearningUnitFactory(
title='Nora kauft sich ein neues Auto',
learning_unit = LearningUnitFactory.create(title="Nora kauft sich ein neues Auto", parent=circle_4, learning_package=lpck_1) parent=circe_analyse,
learning_unit = LearningUnitFactory.create(title="Manuel träumt von einem neuen Tesla", parent=circle_4, learning_package=lpck_1) package='Auto verkaufen',
minutes=60,
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_3) contents=[('podcast', PodcastBlockFactory())]
)
learning_unit = LearningUnitFactory.create(title="Deine Erkenntnisse und Learnings", parent=circle_4, learning_package=lpck_1) LearningUnitFactory(
title='Ermittlung des Kundenbedarfs',
sequence_4 = LearningSequenceFactory.create(title="Üben", circle=circle_4) parent=circe_analyse,
package='Auto verkaufen',
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_4) minutes=120,
learning_unit = LearningUnitFactory.create(title="Ermittlung des Kundenbedarfs", parent=circle_4, learning_package=lpck_1) contents=[('document', DocumentBlockFactory())]
learning_unit = LearningUnitFactory.create(title="Aktives Zuhören", parent=circle_4, learning_package=lpck_1) )
learning_unit = LearningUnitFactory.create(title="In Bildern Sprechen", parent=circle_4, learning_package=lpck_1) LearningUnitFactory(
title='Motorfahrzeug kaufen',
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_4) parent=circe_analyse,
learning_unit = LearningUnitFactory.create(title="Priorisieren des Bedarfs", parent=circle_4, learning_package=lpck_1) package='Auto verkaufen',
learning_unit = LearningUnitFactory.create(title="Zusammenfassung des Bedarfs", parent=circle_4, learning_package=lpck_1) minutes=120,
contents=[('exercise', ExerciseBlockFactory())]
sequence_5 = LearningSequenceFactory.create(title="Testen", circle=circle_4) )
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_5) LearningUnitFactory(
title='Selbsteinschätzung',
learning_unit = LearningUnitFactory.create(title="Bedarfsfragen", parent=circle_4, learning_package=lpck_1) parent=circe_analyse,
learning_unit = LearningUnitFactory.create(title="Andwendung der Fragetechniken", parent=circle_4, learning_package=lpck_1) package='Auto verkaufen',
minutes=0,
sequence_5 = LearningSequenceFactory.create(title="Vernetzen", circle=circle_4) contents=[('self_evaluation', SelfEvaluationBlockFactory())]
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_5) )
learning_unit = LearningUnitFactory.create(title="Online Training", parent=circle_4, learning_package=lpck_1)
sequence_6 = LearningSequenceFactory.create(title="Beenden", circle=circle_4)
lpck_1 = LearningPackageFactory(title="Wunderbar !", learning_sequence=sequence_6)
learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4, learning_package=lpck_1)
circle_5 = CircleFactory.create(title="Lösung",
parent=lp,
topic=tp,
goals="""— Die Daten des Kunden korrekt in die notwendigen Systeme einzutragen
Fachspezialisten beizuziehen, falls dies angezeigt ist
Mit den zur Verfügung stehenden Systemen korrekte Lösungsvorschläge
(z.B. Offerten oder Ausschreibungen) zu verfassen
Falls nötig die Lösungsvorschläge dem Underwriting weiterzuleiten und
Unklarheiten zu bereinigen """)
circle_6 = CircleFactory.create(title="Abschluss",
parent=lp,
topic=tp,
goals="""— Je nach Komplexität der Lösungsvorschläge (z.B. Offerten oder Offertvergleich) einen Fachspezialisten aufzubieten
Sich kundenorientiert auf das Gespräch vorzubereiten und sich passend zu präsentieren""")
tp = TopicFactory.create(title="Betreuen und Ausbauen des Kundenstamms", learning_path=lp)
circle_7 = CircleFactory.create(title="Betreuen", parent=lp, topic=tp)
tp = TopicFactory.create(title="Prüfung", is_visible=False, learning_path=lp)
circle_7 = CircleFactory.create(title="Prüfungsvorbereitung", parent=lp, topic=tp)
LearningSequenceFactory(title='Üben', parent=circe_analyse, icon='IconLsPractice')
LearningUnitFactory(
title='Hausrat',
parent=circe_analyse,
package='Kind zieht von zu Hause aus',
minutes=120,
contents=[('competence', CompetenceBlockFactory())]
)
LearningUnitFactory(
title='Privathaftpflicht',
parent=circe_analyse,
package='Kind zieht von zu Hause aus',
minutes=60,
contents=[('competence', CompetenceBlockFactory())]
)
LearningUnitFactory(
title='Kind zieht von zu Hause aus',
parent=circe_analyse,
package='Kind zieht von zu Hause aus',
minutes=60,
contents=[('competence', CompetenceBlockFactory())]
)
LearningUnitFactory(
title='Selbsteinschätzung',
parent=circe_analyse,
package='Kind zieht von zu Hause aus',
minutes=0,
contents=[('self_evaluation', SelfEvaluationBlockFactory())]
)
# learning_unit = LearningUnitFactory.create(title='** Einstieg Video"', parent=circle_4)
# video_url = "https://www.vbv.ch/fileadmin/vbv/Videos/Statements_Externe/Janos_M/Testimonial_Janos_Mischler_PositiveEffekte.mp4"
# video_title = "Ausbildung ist pflicht"
# video_description = "Erfahren Sie, was für Janos Mischler die positiven Aspekte von ständiger Weiterbildung sind aus fachlicher und aus persönlicher Sicht."
# video_block = VideoBlockFactory(content_type="video", url=video_url, title=video_title, description=video_description)
# learning_unit.contents.append(('video', video_block))
# learning_unit.save()
#
# learning_unit = LearningUnitFactory.create(title='** Web Based Training"', parent=circle_4)
# wbt_url = "web_based_trainings/rise_cmi5_test_export/scormcontent/index.html"
# wbt_block = WebBasedTrainingBlockFactory(content_type="web_based_training", url=wbt_url)
# learning_unit.contents.append(('web_based_training', wbt_block))
# learning_unit.save()
# learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4)
#
# sequence_2 = LearningSequenceFactory.create(title="Beobachten", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Mein Motorfahrzeug kaufen", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Sich selbständig machen", parent=circle_4)
#
# sequence_3 = LearningSequenceFactory.create(title="Anwenden", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Nora kauft sich ein neues Auto", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Manuel träumt von einem neuen Tesla", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Deine Erkenntnisse und Learnings", parent=circle_4)
#
# sequence_4 = LearningSequenceFactory.create(title="Üben", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Ermittlung des Kundenbedarfs", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Aktives Zuhören", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="In Bildern Sprechen", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Priorisieren des Bedarfs", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Zusammenfassung des Bedarfs", parent=circle_4)
#
# sequence_5 = LearningSequenceFactory.create(title="Testen", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Bedarfsfragen", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Andwendung der Fragetechniken", parent=circle_4)
#
# sequence_5 = LearningSequenceFactory.create(title="Vernetzen", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Online Training", parent=circle_4)
#
# sequence_6 = LearningSequenceFactory.create(title="Beenden", parent=circle_4)
# learning_unit = LearningUnitFactory.create(title="Selbsteinschätzung", parent=circle_4)
#
# circle_5 = CircleFactory.create(title="Lösung",
# parent=lp,
# topic=tp,
# goals="""— Die Daten des Kunden korrekt in die notwendigen Systeme einzutragen
# — Fachspezialisten beizuziehen, falls dies angezeigt ist
# — Mit den zur Verfügung stehenden Systemen korrekte Lösungsvorschläge
# (z.B. Offerten oder Ausschreibungen) zu verfassen
# — Falls nötig die Lösungsvorschläge dem Underwriting weiterzuleiten und
# Unklarheiten zu bereinigen """)
#
# circle_6 = CircleFactory.create(title="Abschluss",
# parent=lp,
# topic=tp,
# goals="""— Je nach Komplexität der Lösungsvorschläge (z.B. Offerten oder Offertvergleich) einen Fachspezialisten aufzubieten
# — Sich kundenorientiert auf das Gespräch vorzubereiten und sich passend zu präsentieren""")
#
# tp = TopicFactory.create(title="Betreuen und Ausbauen des Kundenstamms", learning_path=lp)
# circle_7 = CircleFactory.create(title="Betreuen", parent=lp, topic=tp)
#
# tp = TopicFactory.create(title="Prüfung", is_visible=False, learning_path=lp)
# circle_7 = CircleFactory.create(title="Prüfungsvorbereitung", parent=lp, topic=tp)
# all pages belong to 'admin' by default
Page.objects.update(owner=user)
def delete_default_learning_path(): def delete_default_learning_path():

View File

@ -0,0 +1,101 @@
import factory
import wagtail_factories
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningUnit
from vbv_lernwelt.learnpath.models_learning_unit_content import VideoBlock, RiseTrainingBlock, PodcastBlock, \
CompetenceBlock, ExerciseBlock, SelfEvaluationBlock, DocumentBlock, KnowledgeBlock
class LearningPathFactory(wagtail_factories.PageFactory):
title = "Versicherungsvermittler/in"
class Meta:
model = LearningPath
class TopicFactory(factory.django.DjangoModelFactory):
title = "Gewinnen von Kunden"
is_visible = True
class Meta:
model = Topic
class CircleFactory(wagtail_factories.PageFactory):
title = "Gewinnen"
class Meta:
model = Circle
class LearningSequenceFactory(wagtail_factories.PageFactory):
title = "Grundlagen"
class Meta:
model = LearningSequence
class LearningUnitFactory(wagtail_factories.PageFactory):
title = "Herzlich Willkommen"
class Meta:
model = LearningUnit
class VideoBlockFactory(wagtail_factories.StructBlockFactory):
url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam"
class Meta:
model = VideoBlock
class RiseTrainingBlockFactory(wagtail_factories.StructBlockFactory):
url = "https://www.example.com"
description = "Beispiel Rise Modul"
class Meta:
model = RiseTrainingBlock
class PodcastBlockFactory(wagtail_factories.StructBlockFactory):
description = "Beispiel Podcast"
url = "https://docs.wagtail.org/en/stable/topics/streamfield.html"
class Meta:
model = PodcastBlock
class CompetenceBlockFactory(wagtail_factories.StructBlockFactory):
description = "Beispiel Kompetenz"
class Meta:
model = CompetenceBlock
class ExerciseBlockFactory(wagtail_factories.StructBlockFactory):
description = "Beispiel Aufgabe"
class Meta:
model = ExerciseBlock
class SelfEvaluationBlockFactory(wagtail_factories.StructBlockFactory):
description = "Beispiel Selbsteinschätzung"
class Meta:
model = SelfEvaluationBlock
class DocumentBlockFactory(wagtail_factories.StructBlockFactory):
description = "Beispiel Dokument"
class Meta:
model = DocumentBlock
class KnowledgeBlockFactory(wagtail_factories.StructBlockFactory):
description = "Beispiel Wissen"
class Meta:
model = KnowledgeBlock

View File

@ -1,62 +0,0 @@
import wagtail_factories
import factory
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningUnit, LearningPackage
from vbv_lernwelt.learnpath.models_learning_unit_content import VideoBlock, WebBasedTrainingBlock
class LearningPathFactory(wagtail_factories.PageFactory):
title = "Versicherungsvermittler/in"
class Meta:
model = LearningPath
class TopicFactory(factory.django.DjangoModelFactory):
title = "Gewinnen von Kunden"
is_visible = True
class Meta:
model = Topic
class CircleFactory(wagtail_factories.PageFactory):
title = "Gewinnen"
class Meta:
model = Circle
class LearningSequenceFactory(factory.django.DjangoModelFactory):
title = "Grundlagen"
class Meta:
model = LearningSequence
class LearningPackageFactory(factory.django.DjangoModelFactory):
title = "Whatever"
class Meta:
model = LearningPackage
class LearningUnitFactory(wagtail_factories.PageFactory):
title = "Herzlich Willkommen"
class Meta:
model = LearningUnit
class VideoBlockFactory(wagtail_factories.StructBlockFactory):
title = "Ausbildung ist Pflicht"
url = "https://www.vbv.ch/fileadmin/vbv/Videos/Statements_Externe/Janos_M/Testimonial_Janos_Mischler_PositiveEffekte.mp4"
class Meta:
model = VideoBlock
class WebBasedTrainingBlockFactory(wagtail_factories.StructBlockFactory):
title = "Beispiel Rise Modul"
url = "https://docs.wagtail.org/en/stable/topics/streamfield.html"
class Meta:
model = WebBasedTrainingBlock

View File

@ -1,5 +1,3 @@
from django.test import TestCase
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from vbv_lernwelt.core.admin import User from vbv_lernwelt.core.admin import User
@ -14,8 +12,8 @@ class TestRetrieveLearingPathContents(APITestCase):
def setUpClass(cls) -> None: def setUpClass(cls) -> None:
super(TestRetrieveLearingPathContents, cls).setUpClass() super(TestRetrieveLearingPathContents, cls).setUpClass()
create_locales_for_wagtail() create_locales_for_wagtail()
create_default_learning_path()
create_default_users() create_default_users()
create_default_learning_path()
def setUp(self) -> None: def setUp(self) -> None:
qs = LearningPath.objects.filter(title="Versicherungsvermittler/in") qs = LearningPath.objects.filter(title="Versicherungsvermittler/in")

View File

@ -1,10 +1,6 @@
from django.conf import settings
from django.test import TestCase from django.test import TestCase
from wagtail.core.models import Locale
from vbv_lernwelt.learnpath.models import LearningPath
from vbv_lernwelt.learnpath.tests.create_default_competences import create_default_competences from vbv_lernwelt.learnpath.tests.create_default_competences import create_default_competences
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path
class TestCreateDefaultCompetences(TestCase): class TestCreateDefaultCompetences(TestCase):

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More