Merge branch 'develop' of bitbucket.org:iterativ/vbv_lernwelt into develop
This commit is contained in:
commit
30ef9c9c39
|
|
@ -5,14 +5,14 @@ Project setup is based on [cookiecutter-django](https://github.com/cookiecutter/
|
|||
## Run for development
|
||||
|
||||
```bash
|
||||
# run tailwind cli (on project root folder!)
|
||||
npm run tailwind
|
||||
|
||||
# run vue vite dev server
|
||||
cd client && npm run dev
|
||||
|
||||
# reset db and run django dev server
|
||||
./prepare_server.sh
|
||||
|
||||
# run tailwind cli (for tailwind support on django templates)
|
||||
cd client && npm run tailwind
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
|
@ -61,7 +61,7 @@ npm run dev
|
|||
|
||||
### General part
|
||||
|
||||
Cypress and TailwindCSS ist installed for client and server, so there is this package.json on the project root directory
|
||||
Cypress is installed for client and server, so there is this package.json on the project root directory
|
||||
|
||||
```bash
|
||||
# in project root directory
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
// https://docs.cypress.io/api/introduction/api.html
|
||||
|
||||
describe('My First Test', () => {
|
||||
it('visits the app root url', () => {
|
||||
cy.visit('/')
|
||||
cy.contains('h1', 'You did it!')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/* eslint-env node */
|
||||
// ***********************************************************
|
||||
// This example plugins/index.ts can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
export default ((on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
return config
|
||||
}) as Cypress.PluginConfig
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||
"include": ["./**/*"],
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"preserveValueImports": false,
|
||||
"types": ["node", "cypress/types/cypress"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["./integration/**/*", "./support/**/*"],
|
||||
"compilerOptions": {
|
||||
"isolatedModules": false,
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"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/",
|
||||
"build:tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --minify",
|
||||
"test": "vitest run",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.6.4",
|
||||
|
|
@ -33,18 +35,19 @@
|
|||
"@vue/eslint-config-typescript": "^10.0.0",
|
||||
"@vue/test-utils": "^2.0.0-rc.18",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"cypress": "^9.5.3",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"happy-dom": "^5.3.1",
|
||||
"postcss": "^8.4.12",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-import": "^14.1.0",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "^1.50.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"typescript": "~4.6.3",
|
||||
"vite": "^2.9.1",
|
||||
"vitest": "^0.15.1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<MainNavigationBar />
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import MainNavigationBar from '@/components/MainNavigationBar.vue';
|
||||
|
||||
log.debug('App created');
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
@import "../../../../server/vbv_lernwelt/static/css/tailwind.css";
|
||||
|
|
@ -1,7 +1,16 @@
|
|||
<script setup>
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import { reactive } from 'vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
|
||||
log.debug('MainNavigationBar.vue created');
|
||||
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
const state = reactive({showMenu: false});
|
||||
|
||||
function toggleNav() {
|
||||
|
|
@ -13,10 +22,14 @@ function calcNavigationMobileOpenClasses() {
|
|||
return state.showMenu ? ['fixed', 'w-full', 'h-screen'] : [];
|
||||
}
|
||||
|
||||
function menuActive(checkPath) {
|
||||
return route.path.startsWith(checkPath);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navigation bg-blue-900" :class="calcNavigationMobileOpenClasses()">
|
||||
<div v-if="appStore.showMainNavigationBar" class="navigation bg-blue-900" :class="calcNavigationMobileOpenClasses()">
|
||||
<nav
|
||||
class="
|
||||
px-8
|
||||
|
|
@ -27,15 +40,18 @@ function calcNavigationMobileOpenClasses() {
|
|||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<router-link
|
||||
to="/"
|
||||
class="
|
||||
font-bold
|
||||
to="/" class="flex">
|
||||
<it-icon-vbv class="h-8 w-16 -mt-3 -ml-3"/>
|
||||
<div class="
|
||||
text-white
|
||||
text-2xl
|
||||
hover:text-sky-500
|
||||
pr-10
|
||||
pl-3
|
||||
ml-1
|
||||
border-l border-white
|
||||
"
|
||||
>myVBV
|
||||
>myVBV
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
|
|
@ -48,18 +64,13 @@ function calcNavigationMobileOpenClasses() {
|
|||
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>
|
||||
<it-icon-menu class="h-8 w-10"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu open: "block", Menu closed: "hidden" -->
|
||||
<ul
|
||||
<div
|
||||
:class="state.showMenu ? 'flex' : 'hidden'"
|
||||
class="
|
||||
flex-auto
|
||||
|
|
@ -69,17 +80,62 @@ function calcNavigationMobileOpenClasses() {
|
|||
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>
|
||||
<router-link
|
||||
to="/dashboard"
|
||||
class="nav-item"
|
||||
:class="{'nav-item--active': menuActive('/dashboard')}"
|
||||
>
|
||||
Dashboard
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/shop"
|
||||
class="nav-item"
|
||||
:class="{'nav-item--active': menuActive('/shop')}"
|
||||
>
|
||||
Shop
|
||||
</router-link>
|
||||
<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 flex flex-row items-center">
|
||||
<it-icon-message class="w-8 h-8 mr-2 lg:w-6 lg:h-6 lg:mr-1"/>
|
||||
Netzwerk
|
||||
</li>
|
||||
<li class="text-white hover:text-sky-500 text-2xl font-bold lg:text-base lg:font-normal">Jan Baumgartner</li>
|
||||
</ul>
|
||||
<div class="hidden lg:list-item flex-auto"></div>
|
||||
<router-link
|
||||
to="/mediacenter"
|
||||
class="nav-item"
|
||||
:class="{'nav-item--active': menuActive('/mediacenter')}"
|
||||
>
|
||||
Mediathek
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/messages"
|
||||
class="nav-item flex flex-row items-center"
|
||||
>
|
||||
<it-icon-message class="w-8 h-8 mr-2"/>
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/profile"
|
||||
class="nav-item flex items-center"
|
||||
>
|
||||
<div v-if="userStore.loggedIn">
|
||||
<div v-if="userStore.avatar_url">
|
||||
<img class="inline-block h-8 w-8 rounded-full"
|
||||
:src="userStore.avatar_url"
|
||||
alt=""/>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ userStore.getFullName }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else><a class="" href="/login">Login</a></div>
|
||||
</router-link>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.nav-item {
|
||||
@apply text-white hover:text-sky-500 text-2xl font-bold lg:text-base lg:font-normal;
|
||||
}
|
||||
|
||||
.nav-item--active {
|
||||
@apply underline underline-offset-[21px] decoration-sky-500 decoration-4
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -47,8 +47,16 @@ const block = computed(() => {
|
|||
</button>
|
||||
</nav>
|
||||
|
||||
<div v-if="block.type === 'exercise'" class="h-screen">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="100%"
|
||||
scrolling="no"
|
||||
src="/media/web_based_trainings/rise_cmi5_test_export/scormcontent/index.html"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-5xl px-4 lg:px-8 py-4">
|
||||
<div v-else class="mx-auto max-w-5xl px-4 lg:px-8 py-4">
|
||||
<p>{{ block.value.description }}</p>
|
||||
|
||||
<div v-if="block.type === 'video'">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<svg viewBox="0 0 39 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M38.3507 28.111C38.3898 28.1686 38.4124 28.2358 38.4162 28.3053C38.4201 28.3748 38.4049 28.4441 38.3725 28.5057C38.34 28.5672 38.2914 28.6188 38.2319 28.655C38.1724 28.6911 38.1042 28.7104 38.0346 28.7108H36.3463C36.2839 28.7105 36.2224 28.6949 36.1673 28.6655C36.1122 28.636 36.0652 28.5936 36.0302 28.5419L32.0792 22.7066L28.1282 28.5419C28.0932 28.5936 28.0461 28.636 27.991 28.6654C27.9359 28.6949 27.8745 28.7104 27.812 28.7108H26.122C26.0523 28.7104 25.9841 28.6911 25.9246 28.655C25.8651 28.6188 25.8165 28.5672 25.7841 28.5057C25.7516 28.4441 25.7365 28.3748 25.7403 28.3053C25.7442 28.2358 25.7668 28.1686 25.8058 28.111L31.7648 19.312C31.8 19.26 31.8475 19.2175 31.9029 19.1881C31.9584 19.1586 32.0202 19.1432 32.083 19.1432C32.1458 19.1432 32.2076 19.1586 32.2631 19.1881C32.3186 19.2175 32.366 19.26 32.4012 19.312L38.3507 28.111Z"
|
||||
fill="#007AC3"/>
|
||||
<path
|
||||
d="M12.6181 28.111C12.6572 28.1686 12.6798 28.2358 12.6836 28.3053C12.6874 28.3748 12.6723 28.4441 12.6399 28.5057C12.6074 28.5672 12.5588 28.6188 12.4993 28.655C12.4398 28.6911 12.3716 28.7104 12.302 28.7108H10.6119C10.5495 28.7104 10.488 28.6949 10.4329 28.6654C10.3779 28.636 10.3308 28.5936 10.2958 28.5419L6.34521 22.7066L2.3942 28.5419C2.35919 28.5936 2.31211 28.636 2.25702 28.6654C2.20193 28.6949 2.1405 28.7104 2.07805 28.7108H0.383918C0.314304 28.7104 0.246102 28.6911 0.186595 28.655C0.127088 28.6188 0.0785123 28.5672 0.0460511 28.5057C0.01359 28.4441 -0.00153491 28.3748 0.00228956 28.3053C0.00611403 28.2358 0.0287451 28.1686 0.0677665 28.111L6.0268 19.312C6.06202 19.26 6.10943 19.2175 6.1649 19.1881C6.22037 19.1586 6.2822 19.1432 6.34498 19.1432C6.40777 19.1432 6.4696 19.1586 6.52506 19.1881C6.58053 19.2175 6.62795 19.26 6.66317 19.312L12.6181 28.111Z"
|
||||
fill="white"/>
|
||||
<path
|
||||
d="M0.0660575 7.0891C0.027036 7.03145 0.00440495 6.96429 0.000580477 6.89478C-0.003244 6.82527 0.0118844 6.75603 0.0443455 6.69444C0.0768067 6.63286 0.125382 6.58125 0.184889 6.54512C0.244396 6.50899 0.312599 6.48971 0.382213 6.48932H2.07047C2.13293 6.48967 2.19436 6.50523 2.24944 6.53466C2.30453 6.56409 2.35161 6.60651 2.38662 6.65824L6.33718 12.4935L10.2882 6.65824C10.3232 6.60651 10.3703 6.56409 10.4254 6.53466C10.4805 6.50523 10.5419 6.48967 10.6043 6.48932H12.2944C12.364 6.48971 12.4322 6.50899 12.4917 6.54512C12.5512 6.58125 12.5998 6.63286 12.6323 6.69444C12.6647 6.75603 12.6799 6.82527 12.676 6.89478C12.6722 6.96429 12.6496 7.03145 12.6106 7.0891L6.65966 15.8876C6.62451 15.9397 6.57711 15.9824 6.52164 16.0118C6.46616 16.0413 6.40429 16.0568 6.34147 16.0568C6.27865 16.0568 6.21678 16.0413 6.16131 16.0118C6.10583 15.9824 6.05844 15.9397 6.02328 15.8876L0.0660575 7.0891Z"
|
||||
fill="#007AC3"/>
|
||||
<path
|
||||
d="M25.7982 7.0891C25.7592 7.03145 25.7366 6.96429 25.7328 6.89478C25.7289 6.82527 25.7441 6.75603 25.7765 6.69444C25.809 6.63286 25.8576 6.58125 25.9171 6.54512C25.9766 6.50899 26.0448 6.48971 26.1144 6.48932H27.8026C27.8651 6.48963 27.9265 6.50517 27.9816 6.53461C28.0367 6.56405 28.0838 6.60648 28.1188 6.65824L32.0698 12.4935L36.0208 6.65824C36.0558 6.60651 36.1029 6.56409 36.158 6.53466C36.2131 6.50523 36.2745 6.48967 36.337 6.48932H38.027C38.0966 6.48971 38.1648 6.50899 38.2244 6.54512C38.2839 6.58125 38.3324 6.63286 38.3649 6.69444C38.3974 6.75603 38.4125 6.82527 38.4087 6.89478C38.4048 6.96429 38.3822 7.03145 38.3432 7.0891L32.3841 15.8881C32.349 15.9402 32.3016 15.9828 32.2461 16.0123C32.1906 16.0418 32.1288 16.0572 32.066 16.0572C32.0031 16.0572 31.9413 16.0418 31.8858 16.0123C31.8303 15.9828 31.7829 15.9402 31.7478 15.8881L25.7982 7.0891Z"
|
||||
fill="white"/>
|
||||
<path
|
||||
d="M24.2526 20.5202C23.1808 19.5633 21.7844 19.0514 20.3481 19.0889C17.1866 19.0889 15.1465 21.3851 15.1465 24.9395V34.1161C15.1466 34.218 15.1872 34.3156 15.2592 34.3876C15.3313 34.4596 15.429 34.5 15.5308 34.5H17.1035C17.2053 34.5 17.303 34.4596 17.3751 34.3876C17.4472 34.3156 17.4877 34.218 17.4878 34.1161V28.5739H22.0228C22.1247 28.5738 22.2223 28.5333 22.2943 28.4612C22.3663 28.3891 22.4067 28.2914 22.4067 28.1896V26.7895C22.4067 26.6876 22.3663 26.5899 22.2943 26.5179C22.2223 26.4458 22.1247 26.4052 22.0228 26.4051H17.4874V24.8722C17.4874 22.5431 18.5262 21.2591 20.4163 21.2591C21.3288 21.2362 22.2134 21.5752 22.8769 22.2021L23.0395 22.3471C23.0789 22.3821 23.125 22.4086 23.1751 22.4251C23.2252 22.4415 23.2782 22.4474 23.3307 22.4425C23.3831 22.4376 23.434 22.4219 23.4802 22.3965C23.5264 22.3711 23.5668 22.3364 23.5991 22.2947L24.4545 21.1859C24.5125 21.1105 24.5406 21.0163 24.5331 20.9214C24.5257 20.8265 24.4834 20.7378 24.4143 20.6724C24.3343 20.596 24.263 20.5283 24.2526 20.5202Z"
|
||||
fill="white"/>
|
||||
<path
|
||||
d="M19.2443 6.13249C19.2444 6.08179 19.2546 6.03161 19.2742 5.98484C19.2938 5.93808 19.3224 5.89566 19.3585 5.86001C19.3945 5.82437 19.4373 5.79622 19.4843 5.77717C19.5313 5.75813 19.5816 5.74857 19.6323 5.74904C19.7226 5.74904 19.7989 5.74904 19.8129 5.7522C21.1089 5.881 22.3057 6.50307 23.1558 7.4897C24.0059 8.47632 24.4442 9.75198 24.38 11.0527C24.2917 12.3636 23.7038 13.5907 22.7374 14.4808C21.7709 15.3709 20.4998 15.8562 19.186 15.8366H15.5277C15.4258 15.8365 15.3282 15.7959 15.2562 15.7238C15.1842 15.6518 15.1438 15.5541 15.1438 15.4522V0.884351C15.1438 0.782493 15.1842 0.6848 15.2562 0.612733C15.3282 0.540666 15.4258 0.50012 15.5277 0.5H17.0976C17.1996 0.5 17.2973 0.540494 17.3694 0.612574C17.4415 0.684654 17.482 0.782414 17.482 0.884351V13.2405C17.482 13.3425 17.5225 13.4402 17.5946 13.5123C17.6666 13.5844 17.7644 13.6249 17.8663 13.6249H19.2326C19.9527 13.6327 20.6501 13.3736 21.1905 12.8975C21.7308 12.4215 22.0758 11.7622 22.1588 11.0469C22.2259 10.2951 21.9924 9.5474 21.5092 8.96758C21.0261 8.38775 20.3328 8.0231 19.5812 7.95353C19.4866 7.94417 19.3988 7.89972 19.3353 7.82892C19.2718 7.75812 19.2371 7.66611 19.238 7.57099L19.2443 6.13249Z"
|
||||
fill="white"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -5,7 +5,7 @@ import {setupI18n} from './i18n'
|
|||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import '@/assets/styles/index.scss'
|
||||
import '../tailwind.css'
|
||||
|
||||
const i18n = setupI18n()
|
||||
const app = createApp(App)
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
import type {NavigationGuardWithThis, RouteLocationNormalized} from 'vue-router';
|
||||
import type {UserState} from '@/stores/user'
|
||||
import {useUserStore} from '@/stores/user'
|
||||
import type {Store} from 'pinia';
|
||||
|
||||
const cookieName = 'loginStatus'
|
||||
let userStore: Store | null = null
|
||||
|
||||
import {useUserStore} from '@/stores/user';
|
||||
|
||||
|
||||
export const updateLoggedIn: NavigationGuardWithThis<undefined> = (_to) => {
|
||||
const loggedIn = getCookieValue(cookieName) === 'true'
|
||||
const store = getUserStore()
|
||||
const loggedIn = getCookieValue('loginStatus') === 'true'
|
||||
const userStore = useUserStore()
|
||||
|
||||
store.$patch({ loggedIn })
|
||||
userStore.$patch({loggedIn});
|
||||
if (loggedIn && !userStore.email) {
|
||||
userStore.fetchUser();
|
||||
}
|
||||
}
|
||||
|
||||
export const redirectToLoginIfRequired: NavigationGuardWithThis<undefined> = (to, _from) => {
|
||||
const store = getUserStore()
|
||||
if(loginRequired(to) && !store.loggedIn) {
|
||||
return { name: 'home' }
|
||||
const userStore = useUserStore()
|
||||
if(loginRequired(to) && !userStore.loggedIn) {
|
||||
return `/login?next=${to.fullPath}`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,20 +28,6 @@ export const getCookieValue = (cookieName: string): string => {
|
|||
return cookieValue.pop() || '';
|
||||
}
|
||||
|
||||
// Pina is not ready when router is called the first time by app.use(), so we need to load it here
|
||||
const getUserStore = (): UserState & Store => {
|
||||
if (!userStore) {
|
||||
userStore = useUserStore()
|
||||
}
|
||||
return userStore as unknown as UserState & Store
|
||||
}
|
||||
|
||||
const loginRequired = (to: RouteLocationNormalized) => {
|
||||
return !to.meta?.public
|
||||
}
|
||||
|
||||
|
||||
|
||||
// export const unauthorizedAccess: NavigationGuardWithThis<undefined> = (to) => {
|
||||
// r loginRequired(to) && getCookieValue('loginStatus') !== 'true';
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue';
|
||||
import HomeView from '@/views/HomeView.vue';
|
||||
import LoginView from '@/views/LoginView.vue';
|
||||
import {redirectToLoginIfRequired, updateLoggedIn} from '@/router/guards';
|
||||
|
||||
const loginUrl = '/sso/login/'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
|
|
@ -18,15 +17,32 @@ const router = createRouter({
|
|||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: HomeView,
|
||||
beforeEnter(_to, _from) {
|
||||
window.location.href = loginUrl
|
||||
},
|
||||
component: LoginView,
|
||||
meta: {
|
||||
public: true
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: () => import('@/views/DashboardView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/shop',
|
||||
component: () => import('@/views/ShopView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/mediacenter',
|
||||
component: () => import('@/views/MediaView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/messages',
|
||||
component: () => import('@/views/MessagesView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
component: () => import('@/views/ProfileView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/learningpath/:learningPathSlug',
|
||||
component: () => import('../views/LearningPathView.vue'),
|
||||
props: true
|
||||
|
|
@ -36,10 +52,6 @@ const router = createRouter({
|
|||
component: () => import('../views/CircleView.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
component: () => import('../views/ProfileView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/styleguide',
|
||||
component: () => import('../views/StyelGuideView.vue'),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type {CircleChild, LearningContent, LearningSequence, LearningUnit} from '@/types';
|
||||
|
||||
|
||||
function createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
|
||||
function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
|
||||
return {
|
||||
id: 0,
|
||||
title: '',
|
||||
|
|
@ -35,7 +35,7 @@ export function parseLearningSequences (children: CircleChild[]): LearningSequen
|
|||
learningSequence = Object.assign(child, {learningUnits: []});
|
||||
|
||||
// initialize empty learning unit if there will not come a learning unit next
|
||||
learningUnit = createEmptyLearningUnit(learningSequence);
|
||||
learningUnit = _createEmptyLearningUnit(learningSequence);
|
||||
} else if (child.type === 'learnpath.LearningUnit') {
|
||||
if (!learningSequence) {
|
||||
throw new Error('LearningUnit found before LearningSequence');
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import axios from 'axios';
|
||||
|
||||
export function getUserData () {
|
||||
return axios({
|
||||
method: 'get',
|
||||
url: 'http://localhost:3000/api/me',
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import {defineStore} from 'pinia'
|
||||
|
||||
export type AppState = {
|
||||
showMainNavigationBar: boolean;
|
||||
}
|
||||
|
||||
export const useAppStore = defineStore({
|
||||
id: 'app',
|
||||
state: () => ({
|
||||
showMainNavigationBar: true,
|
||||
} as AppState),
|
||||
getters: {
|
||||
},
|
||||
actions: {
|
||||
}
|
||||
})
|
||||
|
|
@ -5,6 +5,7 @@ import {defineStore} from 'pinia'
|
|||
import type {Circle, CircleChild, CircleCompletion, LearningContent, LearningUnit, LearningUnitQuestion} from '@/types'
|
||||
import {itGet, itPost} from '@/fetchHelpers';
|
||||
import {parseLearningSequences} from '@/services/circle';
|
||||
import {useAppStore} from '@/stores/app';
|
||||
|
||||
export type CircleStoreState = {
|
||||
circleData: Circle;
|
||||
|
|
@ -80,19 +81,27 @@ export const useCircleStore = defineStore({
|
|||
},
|
||||
openLearningContent(learningContent: LearningContent) {
|
||||
this.currentLearningContent = learningContent;
|
||||
const appStore = useAppStore();
|
||||
appStore.showMainNavigationBar = false;
|
||||
this.page = 'LEARNING_CONTENT';
|
||||
},
|
||||
closeLearningContent() {
|
||||
this.currentLearningContent = undefined;
|
||||
const appStore = useAppStore();
|
||||
appStore.showMainNavigationBar = true;
|
||||
this.page = 'INDEX';
|
||||
},
|
||||
openSelfEvaluation(learningUnit: LearningUnit) {
|
||||
this.page = 'SELF_EVALUATION';
|
||||
const appStore = useAppStore();
|
||||
appStore.showMainNavigationBar = false;
|
||||
this.currentSelfEvaluation = learningUnit;
|
||||
},
|
||||
closeSelfEvaluation() {
|
||||
this.page = 'INDEX';
|
||||
this.currentSelfEvaluation = undefined;
|
||||
const appStore = useAppStore();
|
||||
appStore.showMainNavigationBar = true;
|
||||
this.page = 'INDEX';
|
||||
},
|
||||
calcSelfEvaluationStatus(learningUnit: LearningUnit) {
|
||||
if (learningUnit.children.length > 0) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import * as log from 'loglevel';
|
||||
|
||||
import {defineStore} from 'pinia'
|
||||
import {itGet, itPost} from '@/fetchHelpers';
|
||||
// typed state https://stackoverflow.com/questions/71012513/when-using-pinia-and-typescript-how-do-you-use-an-action-to-set-the-state
|
||||
|
||||
export type UserState = {
|
||||
first_name: string,
|
||||
last_name: string,
|
||||
email: string;
|
||||
username: string,
|
||||
avatar_url: string,
|
||||
loggedIn: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -10,16 +17,41 @@ export const useUserStore = defineStore({
|
|||
id: 'user',
|
||||
state: () => ({
|
||||
email: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
username: '',
|
||||
avatar_url: '',
|
||||
loggedIn: false
|
||||
} as UserState),
|
||||
getters: {
|
||||
getFullName(): string {
|
||||
return `${this.first_name} ${this.last_name}`.trim();
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setEmail (email: string) {
|
||||
this.email = email
|
||||
handleLogin(username: string, password: string, next='/') {
|
||||
if (username && password) {
|
||||
itPost('/core/login/', {
|
||||
username,
|
||||
password,
|
||||
}).then((data) => {
|
||||
this.$state = data;
|
||||
this.loggedIn = true;
|
||||
log.debug(`redirect to ${next}`);
|
||||
window.location.href = next;
|
||||
}).catch(() => {
|
||||
this.loggedIn = false;
|
||||
alert('Login failed');
|
||||
});
|
||||
}
|
||||
},
|
||||
setLoggedIn (isLoggedIn: boolean) {
|
||||
this.loggedIn = isLoggedIn
|
||||
fetchUser() {
|
||||
itGet('/api/core/me/').then((data) => {
|
||||
this.$state = data;
|
||||
this.loggedIn = true;
|
||||
}).catch(() => {
|
||||
this.loggedIn = false;
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import MainNavigationBar from '@/components/MainNavigationBar.vue';
|
||||
import LearningSequence from '@/components/circle/LearningSequence.vue';
|
||||
import CircleOverview from '@/components/circle/CircleOverview.vue';
|
||||
import LearningContent from '@/components/circle/LearningContent.vue';
|
||||
|
|
@ -11,6 +9,8 @@ import {useCircleStore} from '@/stores/circle';
|
|||
import SelfEvaluation from '@/components/circle/SelfEvaluation.vue';
|
||||
import CircleDiagram from '@/components/circle/CircleDiagram.vue';
|
||||
|
||||
log.debug('CircleView.vue created');
|
||||
|
||||
const props = defineProps<{
|
||||
circleSlug: string
|
||||
}>()
|
||||
|
|
@ -36,8 +36,6 @@ onMounted(async () => {
|
|||
<SelfEvaluation :key="circleStore.currentSelfEvaluation.translation_key"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<MainNavigationBar/>
|
||||
|
||||
<div class="circle">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:py-8">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
log.debug('DashboardView created');
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="px-8 py-8">
|
||||
<h1>Dashboard</h1>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -1,17 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import MainNavigationBar from '@/components/MainNavigationBar.vue';</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<MainNavigationBar/>
|
||||
|
||||
<main class="px-8 py-8">
|
||||
<h1>myVBV Start Page</h1>
|
||||
|
||||
<div class="mt-8 flex flex-col lg: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="/learningpath/versicherungsvermittlerin">Lernpfad "Versicherungsvermittlerin" (Login benötigt)</router-link>
|
||||
<router-link class="link text-xl" to="/circle/analyse">Circle "Analyse" (Login benötigt)</router-link>
|
||||
<a class="link text-xl" href="/login">Login</a>
|
||||
|
||||
<router-link class="link text-xl" to="/learningpath/versicherungsvermittlerin">Lernpfad "Versicherungsvermittlerin"</router-link>
|
||||
<router-link class="link text-xl" to="/circle/analyse">Circle "Analyse"</router-link>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import * as log from 'loglevel';
|
|||
import MainNavigationBar from '../components/MainNavigationBar.vue';
|
||||
import LearningPathDiagram from '../components/circle/LearningPathDiagram.vue';
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
|
||||
|
|
@ -65,9 +64,6 @@ export default {
|
|||
<template>
|
||||
<div class="bg-gray-300 h-screen" v-if="learningPathContents">
|
||||
|
||||
|
||||
<MainNavigationBar/>
|
||||
|
||||
<div class="learningpath flex flex-col">
|
||||
<div class="flex flex-col h-max">
|
||||
<div class="bg-white h-80 pt-8">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
import {reactive} from 'vue';
|
||||
import {useUserStore} from '@/stores/user';
|
||||
import {useRoute} from 'vue-router';
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
log.debug('LoginView.vue created');
|
||||
log.debug(route.query);
|
||||
|
||||
const state = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="px-8 py-8">
|
||||
<h1>Login</h1>
|
||||
|
||||
<form
|
||||
@submit.prevent="userStore.handleLogin(state.username, state.password, route.query.next)"
|
||||
>
|
||||
<div class="mt-8 mb-4">
|
||||
<label class="block mb-1" for="email">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
name="username"
|
||||
v-model="state.username"
|
||||
class="py-2 px-3 border border-gray-500 mt-1 block w-96"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block mb-1" for="password">Password</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
v-model="state.password"
|
||||
class="py-2 px-3 border border-gray-500 mt-1 block w-96"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
value="Login"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
log.debug('ShopView created');
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="px-8 py-8">
|
||||
<h1>Mediathek</h1>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
log.debug('MessagesView created');
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="px-8 py-8">
|
||||
<h1>Messages</h1>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -1,34 +1,15 @@
|
|||
<template>
|
||||
<div class="about">
|
||||
<h1>This is a profile page</h1>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getUserData } from '@/services/http'
|
||||
log.debug('ProfileView created');
|
||||
|
||||
export default {
|
||||
setup () {
|
||||
|
||||
onMounted(async () => {
|
||||
// const res = await getUserData();
|
||||
// users.value = res.data;
|
||||
// console.log(res);
|
||||
});
|
||||
|
||||
return {
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
<template>
|
||||
<main class="px-8 py-8">
|
||||
<h1>Profil</h1>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
log.debug('ShopView created');
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="px-8 py-8">
|
||||
<h1>Shop</h1>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import {reactive} from 'vue'
|
||||
import {Listbox, ListboxButton, ListboxOption, ListboxOptions} from '@headlessui/vue'
|
||||
import MainNavigationBar from '@/components/MainNavigationBar.vue';
|
||||
import ItCheckbox from '@/components/ui/ItCheckbox.vue';
|
||||
|
||||
|
||||
|
|
@ -34,8 +33,6 @@ function colorBgClass(color: string, value: number) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<MainNavigationBar/>
|
||||
|
||||
<main class="px-8 py-4">
|
||||
<h1>Style Guide</h1>
|
||||
|
||||
|
|
@ -168,6 +165,16 @@ function colorBgClass(color: string, value: number) {
|
|||
<it-icon-close class="w-6 h-6"/>
|
||||
</div>
|
||||
|
||||
<div class="inline-flex flex-col text-white bg-blue-900">
|
||||
vbv
|
||||
<it-icon-vbv class="w-24 h-24"/>
|
||||
</div>
|
||||
|
||||
<div class="inline-flex flex-col">
|
||||
vbv-pos
|
||||
<it-icon-vbv-pos class="w-24 h-24"/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ const colors = require('tailwindcss/colors')
|
|||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./client/index.html',
|
||||
'./client/src/**/*.{vue,js,ts,jsx,tsx}',
|
||||
'./server/vbv_lernwelt/**/*.{html,js}',
|
||||
'./index.html',
|
||||
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
||||
'../server/vbv_lernwelt/**/*.{html,js}',
|
||||
],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
|
|
@ -4,15 +4,9 @@ set -o errexit
|
|||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
python /app/manage.py collectstatic --noinput
|
||||
|
||||
# TODO remove after stabilisation
|
||||
python /app/manage.py reset_schema
|
||||
|
||||
python /app/manage.py collectstatic --noinput
|
||||
python /app/manage.py createcachetable
|
||||
python /app/manage.py migrate
|
||||
|
||||
# TODO remove after stabilisation
|
||||
python /app/manage.py create_default_users
|
||||
python /app/manage.py create_default_learning_path
|
||||
|
||||
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:80 --chdir=/app -k uvicorn.workers.UvicornWorker
|
||||
|
|
|
|||
|
|
@ -2,19 +2,16 @@
|
|||
"name": "vbv_lernwelt_cypress",
|
||||
"version": "1.0.0",
|
||||
"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",
|
||||
"build": "npm install --prefix client && npm run build --prefix client && npm run build:tailwind --prefix client",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"cypress:ci": "cypress run --config baseUrl=http://localhost:8001",
|
||||
"cypress:ci:open": "cypress open --config baseUrl=http://localhost:8001",
|
||||
"tailwind": "tailwindcss -i ./tailwind/input.css -o ./server/vbv_lernwelt/static/css/tailwind.css --watch"
|
||||
"cypress:ci:open": "cypress open --config baseUrl=http://localhost:8001"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"cypress": "^9.4.1",
|
||||
"tailwindcss": "^3.0.24"
|
||||
"cypress": "^9.4.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,9 @@ THIRD_PARTY_APPS = [
|
|||
'wagtail.search',
|
||||
'wagtail.admin',
|
||||
'wagtail',
|
||||
'wagtail.locales',
|
||||
# 'wagtail.locales',
|
||||
"wagtail_localize",
|
||||
"wagtail_localize.locales",
|
||||
'wagtail.api.v2',
|
||||
|
||||
'modelcluster',
|
||||
|
|
@ -127,7 +129,7 @@ AUTH_USER_MODEL = "core.User"
|
|||
|
||||
# FIXME make configurable!?
|
||||
# LOGIN_URL = "/sso/login/"
|
||||
LOGIN_URL = "/login/"
|
||||
LOGIN_URL = "/login"
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
|
||||
ALLOW_LOCAL_LOGIN = env.bool("IT_ALLOW_LOCAL_LOGIN", default=False)
|
||||
|
|
@ -168,7 +170,7 @@ MIDDLEWARE = [
|
|||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.common.BrokenLinkEmailsMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"django_htmx.middleware.HtmxMiddleware",
|
||||
"csp.middleware.CSPMiddleware",
|
||||
"vbv_lernwelt.core.middleware.auth.AuthenticationRequiredMiddleware",
|
||||
"vbv_lernwelt.core.middleware.security.SecurityRequestResponseLoggingMiddleware",
|
||||
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
||||
|
|
@ -448,6 +450,10 @@ REST_FRAMEWORK = {
|
|||
# django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup
|
||||
CORS_URLS_REGEX = r"^/api/.*$"
|
||||
|
||||
# django-csp
|
||||
CSP_DEFAULT_SRC = ("'self'", "'unsafe-inline'", 'ws://localhost:3000', 'localhost:8000', 'blob:', 'data:', 'http://*')
|
||||
CSP_FRAME_ANCESTORS = ("'self'",)
|
||||
|
||||
# By Default swagger ui is available only to admin user. You can change permission classs to change that
|
||||
# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings
|
||||
SPECTACULAR_SETTINGS = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import include, path, re_path
|
||||
|
|
@ -17,8 +16,7 @@ from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
|||
from vbv_lernwelt.core.views import (
|
||||
rate_limit_exceeded_view,
|
||||
permission_denied_view,
|
||||
check_rate_limit, cypress_reset_view, vue_home,
|
||||
)
|
||||
check_rate_limit, cypress_reset_view, vue_home, vue_login, me_user_view, )
|
||||
from .wagtail_api import wagtail_api_router
|
||||
|
||||
|
||||
|
|
@ -43,14 +41,16 @@ urlpatterns = [
|
|||
path('pages/', include(wagtail_urls)),
|
||||
path('learnpath/', include("vbv_lernwelt.learnpath.urls")),
|
||||
path('api/completion/', include("vbv_lernwelt.completion.urls")),
|
||||
re_path(r'api/core/me/$', me_user_view, name='me_user_view'),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
if settings.DEBUG:
|
||||
# Static file serving when using Gunicorn + Uvicorn for local web socket development
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
||||
if settings.ALLOW_LOCAL_LOGIN:
|
||||
urlpatterns += [path("login/", django_view_authentication_exempt(
|
||||
auth_views.LoginView.as_view(template_name="core/login.html"))),]
|
||||
urlpatterns += [
|
||||
re_path(r'core/login/$', django_view_authentication_exempt(vue_login), name='vue_login'),
|
||||
]
|
||||
|
||||
|
||||
# API URLS
|
||||
|
|
@ -67,7 +67,7 @@ urlpatterns += [
|
|||
|
||||
if settings.APP_ENVIRONMENT != 'production':
|
||||
urlpatterns += [
|
||||
re_path(r'cypressreset/$', cypress_reset_view, name='cypress_reset_view'),
|
||||
re_path(r'core/cypressreset/$', cypress_reset_view, name='cypress_reset_view'),
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ django==3.2.13
|
|||
# via
|
||||
# -r requirements.in
|
||||
# django-cors-headers
|
||||
# django-csp
|
||||
# django-debug-toolbar
|
||||
# django-extensions
|
||||
# django-filter
|
||||
|
|
@ -97,12 +98,15 @@ django==3.2.13
|
|||
# djangorestframework
|
||||
# drf-spectacular
|
||||
# wagtail
|
||||
# wagtail-localize
|
||||
django-click==2.3.0
|
||||
# via -r requirements.in
|
||||
django-cors-headers==3.11.0
|
||||
# via -r requirements.in
|
||||
django-coverage-plugin==2.0.2
|
||||
# via -r requirements-dev.in
|
||||
django-csp==3.7
|
||||
# via -r requirements.in
|
||||
django-debug-toolbar==3.2.4
|
||||
# via -r requirements-dev.in
|
||||
django-extensions==3.1.5
|
||||
|
|
@ -262,6 +266,8 @@ platformdirs==2.5.1
|
|||
# virtualenv
|
||||
pluggy==1.0.0
|
||||
# via pytest
|
||||
polib==1.1.1
|
||||
# via wagtail-localize
|
||||
portalocker==2.4.0
|
||||
# via concurrent-log-handler
|
||||
pre-commit==2.17.0
|
||||
|
|
@ -400,6 +406,7 @@ typing-extensions==4.1.1
|
|||
# django-stubs-ext
|
||||
# djangorestframework-stubs
|
||||
# mypy
|
||||
# wagtail-localize
|
||||
uritemplate==4.1.1
|
||||
# via
|
||||
# coreapi
|
||||
|
|
@ -414,12 +421,15 @@ uvloop==0.16.0
|
|||
# via uvicorn
|
||||
virtualenv==20.14.0
|
||||
# via pre-commit
|
||||
wagtail==3.0.0
|
||||
wagtail==3.0.1
|
||||
# via
|
||||
# -r requirements.in
|
||||
# wagtail-factories
|
||||
# wagtail-localize
|
||||
wagtail-factories==2.0.1
|
||||
# via -r requirements.in
|
||||
wagtail-localize==1.2.1
|
||||
# via -r requirements.in
|
||||
watchdog==2.1.7
|
||||
# via werkzeug
|
||||
watchgod==0.8.1
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ dj-database-url
|
|||
django-click
|
||||
django-ratelimit
|
||||
django-ipware
|
||||
django-csp
|
||||
|
||||
psycopg2-binary
|
||||
gunicorn
|
||||
|
|
@ -35,3 +36,4 @@ concurrent-log-handler
|
|||
|
||||
wagtail>=3,<4
|
||||
wagtail-factories
|
||||
wagtail-localize
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ django==3.2.13
|
|||
# via
|
||||
# -r requirements.in
|
||||
# django-cors-headers
|
||||
# django-csp
|
||||
# django-filter
|
||||
# django-htmx
|
||||
# django-model-utils
|
||||
|
|
@ -61,10 +62,13 @@ django==3.2.13
|
|||
# djangorestframework
|
||||
# drf-spectacular
|
||||
# wagtail
|
||||
# wagtail-localize
|
||||
django-click==2.3.0
|
||||
# via -r requirements.in
|
||||
django-cors-headers==3.11.0
|
||||
# via -r requirements.in
|
||||
django-csp==3.7
|
||||
# via -r requirements.in
|
||||
django-filter==21.1
|
||||
# via wagtail
|
||||
django-htmx==1.9.0
|
||||
|
|
@ -134,6 +138,8 @@ pillow==9.0.1
|
|||
# via
|
||||
# -r requirements.in
|
||||
# wagtail
|
||||
polib==1.1.1
|
||||
# via wagtail-localize
|
||||
portalocker==2.4.0
|
||||
# via concurrent-log-handler
|
||||
psycopg2-binary==2.9.3
|
||||
|
|
@ -192,6 +198,8 @@ telepath==0.2
|
|||
# via wagtail
|
||||
text-unidecode==1.3
|
||||
# via python-slugify
|
||||
typing-extensions==4.2.0
|
||||
# via wagtail-localize
|
||||
uritemplate==4.1.1
|
||||
# via drf-spectacular
|
||||
urllib3==1.26.9
|
||||
|
|
@ -202,12 +210,15 @@ uvicorn[standard]==0.17.6
|
|||
# via -r requirements.in
|
||||
uvloop==0.16.0
|
||||
# via uvicorn
|
||||
wagtail==3.0.0
|
||||
wagtail==3.0.1
|
||||
# via
|
||||
# -r requirements.in
|
||||
# wagtail-factories
|
||||
# wagtail-localize
|
||||
wagtail-factories==2.0.1
|
||||
# via -r requirements.in
|
||||
wagtail-localize==1.2.1
|
||||
# via -r requirements.in
|
||||
watchgod==0.8.1
|
||||
# via uvicorn
|
||||
webencodings==0.5.1
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class CompletionApiTestCase(APITestCase):
|
|||
|
||||
def setUp(self) -> None:
|
||||
self.user = User.objects.get(username='student')
|
||||
self.client.login(username='student', password='student')
|
||||
self.client.login(username='student', password='test')
|
||||
|
||||
def test_completeLearningContent_works(self):
|
||||
learning_content = LearningContent.objects.get(title='Einleitung Circle "Anlayse"')
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ def mark_circle_completion(request):
|
|||
page_key = request.data.get('page_key')
|
||||
completed = request.data.get('completed', True)
|
||||
|
||||
page = Page.objects.get(translation_key=page_key)
|
||||
page = Page.objects.get(translation_key=page_key, locale__language_code='de-CH')
|
||||
page_type = get_wagtail_type(page.specific)
|
||||
circle = Circle.objects.ancestor_of(page).first()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,25 +5,76 @@ from vbv_lernwelt.core.models import User
|
|||
|
||||
|
||||
def create_default_users(user_model=User, group_model=Group, default_password=None):
|
||||
if default_password is None:
|
||||
default_password = 'test'
|
||||
|
||||
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')
|
||||
student_group, created = group_model.objects.get_or_create(name='student_group')
|
||||
|
||||
admin_password = default_password
|
||||
if not admin_password:
|
||||
admin_password = 'admin'
|
||||
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.save()
|
||||
def _create_student_user(email, first_name, last_name, avatar_url=''):
|
||||
student_user, created = _get_or_create_user(
|
||||
user_model=user_model, username=email, password=default_password,
|
||||
)
|
||||
student_user.first_name = first_name
|
||||
student_user.last_name = last_name
|
||||
student_user.avatar_url = avatar_url
|
||||
student_user.groups.add(student_group)
|
||||
student_user.save()
|
||||
|
||||
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.save()
|
||||
def _create_admin_user(email, first_name, last_name, avatar_url=''):
|
||||
admin_user, created = _get_or_create_user(
|
||||
user_model=user_model, username=email, password=default_password
|
||||
)
|
||||
admin_user.is_superuser = True
|
||||
admin_user.is_staff = True
|
||||
admin_user.first_name = first_name
|
||||
admin_user.last_name = last_name
|
||||
admin_user.avatar_url = avatar_url
|
||||
admin_user.groups.add(admin_group)
|
||||
admin_user.save()
|
||||
|
||||
_create_admin_user(
|
||||
email='info@iterativ.ch',
|
||||
first_name='Info',
|
||||
last_name='Iterativ',
|
||||
avatar_url='/static/avatars/avatar_iterativ.png'
|
||||
)
|
||||
|
||||
_create_admin_user(
|
||||
email='admin',
|
||||
first_name='Peter',
|
||||
last_name='Adminson',
|
||||
avatar_url='/static/avatars/avatar_iterativ.png'
|
||||
)
|
||||
|
||||
_create_student_user(
|
||||
email='student',
|
||||
first_name='Student',
|
||||
last_name='Meier',
|
||||
avatar_url='/static/avatars/avatar_iterativ.png'
|
||||
)
|
||||
|
||||
_create_student_user(
|
||||
email='daniel.egger@iterativ.ch',
|
||||
first_name='Daniel',
|
||||
last_name='Egger',
|
||||
avatar_url='/static/avatars/avatar_iterativ.png'
|
||||
)
|
||||
|
||||
_create_student_user(
|
||||
email='axel.manderbach@lernetz.ch',
|
||||
first_name='Axel',
|
||||
last_name='Manderbach',
|
||||
avatar_url='/static/avatars/avatar_axel.jpg'
|
||||
)
|
||||
|
||||
_create_student_user(
|
||||
email='christoph.bosshard@vbv-afa.ch',
|
||||
first_name='Christoph',
|
||||
last_name='Bosshard',
|
||||
avatar_url='/static/avatars/avatar_christoph.jpg'
|
||||
)
|
||||
|
||||
|
||||
def _get_or_create_user(user_model, *args, **kwargs):
|
||||
|
|
@ -34,6 +85,10 @@ def _get_or_create_user(user_model, *args, **kwargs):
|
|||
user = user_model.objects.filter(username=username).first()
|
||||
|
||||
if not user:
|
||||
user = user_model.objects.create(username=username, password=make_password(password))
|
||||
user = user_model.objects.create(
|
||||
username=username,
|
||||
password=make_password(password),
|
||||
email=username,
|
||||
)
|
||||
created = True
|
||||
return user, created
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ def command(customer_language):
|
|||
print("cypress reset data")
|
||||
|
||||
delete_default_learning_path()
|
||||
create_default_learning_path()
|
||||
create_default_learning_path(skip_locales=False)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import djclick as click
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.db import transaction, connection
|
||||
|
||||
|
||||
|
|
@ -24,3 +25,7 @@ def command():
|
|||
print(user)
|
||||
|
||||
reset_schema(db_config_user=user)
|
||||
call_command('createcachetable')
|
||||
call_command('migrate')
|
||||
call_command('create_default_users')
|
||||
call_command('create_default_learning_path')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.2.13 on 2022-06-28 12:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='avatar_url',
|
||||
field=models.CharField(blank=True, default='', max_length=254),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(max_length=254, unique=True, verbose_name='email address'),
|
||||
),
|
||||
]
|
||||
|
|
@ -15,7 +15,7 @@ def create_users(apps, schema_editor):
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("core", "0001_initial"),
|
||||
("core", "0002_user_model"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
@ -10,6 +10,8 @@ class User(AbstractUser):
|
|||
"""
|
||||
# FIXME: look into it...
|
||||
# objects = UserManager()
|
||||
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
||||
email = models.EmailField('email address', unique=True)
|
||||
|
||||
|
||||
class SecurityRequestResponseLog(models.Model):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'id', 'first_name', 'last_name', 'email', 'username', 'avatar_url',
|
||||
]
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container mx-auto">
|
||||
|
||||
<div class="w-full min-h-screen bg-gray-50 flex flex-col sm:justify-center items-center pt-6 sm:pt-0">
|
||||
<div class="w-full sm:max-w-md p-5 mx-auto">
|
||||
<h2 class="mb-12 text-center text-5xl font-extrabold">Login</h2>
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<label class="block mb-1" for="email">Username</label>
|
||||
<input id="username" type="text" name="username" class="py-2 px-3 border border-gray-300 focus:border-red-300 focus:outline-none focus:ring focus:ring-red-200 focus:ring-opacity-50 rounded-md shadow-sm disabled:bg-gray-100 mt-1 block w-full"/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block mb-1" for="password">Password</label>
|
||||
<input id="password" type="password" name="password" class="py-2 px-3 border border-gray-300 focus:border-red-300 focus:outline-none focus:ring focus:ring-red-200 focus:ring-opacity-50 rounded-md shadow-sm disabled:bg-gray-100 mt-1 block w-full"/>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button
|
||||
class="w-full inline-flex items-center justify-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold capitalize text-white hover:bg-red-700 active:bg-red-700 focus:outline-none focus:border-red-700 focus:ring focus:ring-red-200 disabled:opacity-25 transition"
|
||||
data-cy="submit"
|
||||
type="submit">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -6,8 +6,6 @@ from rest_framework.throttling import UserRateThrottle
|
|||
from structlog.types import EventDict
|
||||
|
||||
|
||||
#from .models import User
|
||||
|
||||
def structlog_add_app_info(
|
||||
logger: logging.Logger, method_name: str, event_dict: EventDict
|
||||
) -> EventDict:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
# Create your views here.
|
||||
|
||||
import requests
|
||||
import structlog
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.core.management import call_command
|
||||
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
|
@ -11,8 +13,12 @@ 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 rest_framework.response import Response
|
||||
|
||||
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
||||
from vbv_lernwelt.core.serializers import UserSerializer
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@django_view_authentication_exempt
|
||||
|
|
@ -37,6 +43,32 @@ def vue_home(request):
|
|||
return HttpResponse(content)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@ensure_csrf_cookie
|
||||
def vue_login(request):
|
||||
try:
|
||||
username = request.data.get('username')
|
||||
password = request.data.get('password')
|
||||
if username and password:
|
||||
user = authenticate(request, username=username, password=password)
|
||||
if user:
|
||||
login(request, user)
|
||||
logger.debug('login successful', username=username, email=user.email, label='login')
|
||||
return Response(UserSerializer(user).data)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
logger.debug('login failed', username=username, label='login')
|
||||
return Response({'success': False}, status=401)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
def me_user_view(request):
|
||||
if request.user.is_authenticated:
|
||||
return Response(UserSerializer(request.user).data)
|
||||
return Response(status=403)
|
||||
|
||||
|
||||
def permission_denied_view(request, exception):
|
||||
return render(request, "403.html", status=403)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_def
|
|||
|
||||
@click.command()
|
||||
def command():
|
||||
create_default_learning_path()
|
||||
create_default_learning_path(skip_locales=False)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
import wagtail_factories
|
||||
from django.conf import settings
|
||||
from wagtail.models import Site, Page
|
||||
from django.core.management import call_command
|
||||
from wagtail.models import Site, Page, Locale
|
||||
from wagtail_localize.models import LocaleSynchronization
|
||||
|
||||
from vbv_lernwelt.core.admin import User
|
||||
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningContent
|
||||
from vbv_lernwelt.learnpath.models import LearningPath, Topic, Circle, LearningSequence, LearningContent, LearningUnit, \
|
||||
LearningUnitQuestion
|
||||
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
|
||||
LearningSequenceFactory, LearningContentFactory, VideoBlockFactory, PodcastBlockFactory, CompetenceBlockFactory, \
|
||||
ExerciseBlockFactory, DocumentBlockFactory, LearningUnitFactory, LearningUnitQuestionFactory
|
||||
|
||||
|
||||
def create_default_learning_path(user=None):
|
||||
def create_default_learning_path(user=None, skip_locales=True):
|
||||
if user is None:
|
||||
user = User.objects.get(username='admin')
|
||||
user = User.objects.get(username='info@iterativ.ch')
|
||||
|
||||
site = Site.objects.filter(is_default_site=True).first()
|
||||
|
||||
|
|
@ -22,6 +25,7 @@ def create_default_learning_path(user=None):
|
|||
site.port = 8000
|
||||
site.save()
|
||||
|
||||
|
||||
# create_default_competences()
|
||||
|
||||
lp = LearningPathFactory(title="Versicherungsvermittler/in", parent=site.root_page)
|
||||
|
|
@ -404,12 +408,29 @@ Fachspezialisten bei.
|
|||
# tp = TopicFactory.create(title="Prüfung", is_visible=False, learning_path=lp)
|
||||
# circle_7 = CircleFactory.create(title="Prüfungsvorbereitung", parent=lp, topic=tp)
|
||||
|
||||
# locales
|
||||
if not skip_locales:
|
||||
locale_de = Locale.objects.get(language_code='de-CH')
|
||||
locale_fr, _ = Locale.objects.get_or_create(language_code='fr-CH')
|
||||
LocaleSynchronization.objects.get_or_create(
|
||||
locale_id=locale_fr.id,
|
||||
sync_from_id=locale_de.id
|
||||
)
|
||||
locale_it, _ = Locale.objects.get_or_create(language_code='it-CH')
|
||||
LocaleSynchronization.objects.get_or_create(
|
||||
locale_id=locale_it.id,
|
||||
sync_from_id=locale_de.id
|
||||
)
|
||||
call_command('sync_locale_trees')
|
||||
|
||||
# all pages belong to 'admin' by default
|
||||
Page.objects.update(owner=user)
|
||||
|
||||
|
||||
def delete_default_learning_path():
|
||||
LearningContent.objects.all().delete()
|
||||
LearningUnitQuestion.objects.all().delete()
|
||||
LearningUnit.objects.all().delete()
|
||||
LearningSequence.objects.all().delete()
|
||||
Circle.objects.all().delete()
|
||||
Topic.objects.all().delete()
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class LearningContentFactory(wagtail_factories.PageFactory):
|
|||
|
||||
|
||||
class VideoBlockFactory(wagtail_factories.StructBlockFactory):
|
||||
url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
url = "https://www.youtube.com/embed/qhPIfxS2hvI"
|
||||
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:
|
||||
|
|
@ -65,7 +65,7 @@ class VideoBlockFactory(wagtail_factories.StructBlockFactory):
|
|||
|
||||
|
||||
class WebBasedTrainingBlockFactory(wagtail_factories.StructBlockFactory):
|
||||
url = "https://www.example.com"
|
||||
url = "/media/web_based_trainings/rise_cmi5_test_export/scormcontent/index.html"
|
||||
description = "Beispiel Rise Modul"
|
||||
|
||||
class Meta:
|
||||
|
|
@ -74,7 +74,7 @@ class WebBasedTrainingBlockFactory(wagtail_factories.StructBlockFactory):
|
|||
|
||||
class PodcastBlockFactory(wagtail_factories.StructBlockFactory):
|
||||
description = "Beispiel Podcast"
|
||||
url = "https://docs.wagtail.org/en/stable/topics/streamfield.html"
|
||||
url = "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/325190984&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true"
|
||||
|
||||
class Meta:
|
||||
model = PodcastBlock
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 450.709 418.11" enable-background="new 0 0 450.709 418.11" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M262.573,231.338c-8.74-7.379-17.496-10.559-28.81-10.559c-23.314,0-38.378,16.943-38.378,43.168v67.706
|
||||
c0,1.566,1.269,2.835,2.835,2.835h11.603c1.566,0,2.835-1.269,2.835-2.835v-40.89h33.462c1.566,0,2.835-1.269,2.835-2.835v-10.34
|
||||
c0-1.566-1.269-2.835-2.835-2.835h-33.462v-11.31c0-17.186,7.677-26.653,21.612-26.653c7.148,0,12.845,2.163,18.155,6.957
|
||||
c0.069,0.063,0.586,0.523,1.2,1.07c1.228,1.093,3.126,0.917,4.13-0.385l6.31-8.181c0.888-1.151,0.76-2.788-0.295-3.789
|
||||
C263.175,231.899,262.648,231.401,262.573,231.338z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M225.62,125.182c0.003-1.573,1.289-2.843,2.863-2.829c0.675,0.006,1.231,0.014,1.333,0.024
|
||||
c6.856,0.657,13.394,3.223,18.955,7.448c9.876,7.504,15.388,19.34,14.743,31.662c-1.041,19.793-17.869,35.296-38.312,35.296
|
||||
h-26.977c-1.566,0-2.835-1.269-2.835-2.835V86.457c0-1.566,1.269-2.835,2.835-2.835h11.585c1.566,0,2.835,1.269,2.835,2.835
|
||||
v91.169c0,1.566,1.269,2.835,2.835,2.835h10.081c11.139,0,20.622-8.355,21.591-19.019c1.042-11.523-7.505-21.809-19.019-22.824
|
||||
c-1.445-0.127-2.537-1.369-2.534-2.82L225.62,125.182z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#0069B0" d="M351.43,291.772"/>
|
||||
<path fill="#0079C3" d="M366.594,287.348c1.275,1.882-0.074,4.424-2.347,4.424h0.012H351.79c-0.941,0-1.82-0.467-2.347-1.245
|
||||
l-29.151-43.055l-29.151,43.055c-0.527,0.779-1.406,1.245-2.347,1.245h-12.47c-2.273,0-3.622-2.542-2.347-4.424l43.967-64.92
|
||||
c1.124-1.66,3.57-1.66,4.694,0L366.594,287.348z"/>
|
||||
<path d="M176.731,287.348c1.275,1.882-0.074,4.424-2.347,4.424h0.012h-12.469c-0.941,0-1.82-0.467-2.347-1.245l-29.151-43.055
|
||||
l-29.151,43.055c-0.527,0.779-1.406,1.245-2.347,1.245h-12.47c-2.273,0-3.622-2.542-2.347-4.424l43.967-64.92
|
||||
c1.124-1.66,3.57-1.66,4.694,0L176.731,287.348z"/>
|
||||
<path fill="#0079C3" d="M84.114,132.238c-1.275-1.882,0.074-4.424,2.347-4.424h-0.012h12.469c0.941,0,1.82,0.467,2.347,1.245
|
||||
l29.151,43.055l29.151-43.055c0.527-0.779,1.406-1.245,2.347-1.245h12.47c2.273,0,3.622,2.542,2.347,4.424l-43.967,64.92
|
||||
c-1.124,1.66-3.57,1.66-4.694,0L84.114,132.238z"/>
|
||||
<path d="M273.978,132.238c-1.275-1.882,0.074-4.424,2.347-4.424h-0.012h12.469c0.941,0,1.82,0.467,2.347,1.245l29.151,43.055
|
||||
l29.151-43.055c0.527-0.779,1.406-1.245,2.347-1.245h12.47c2.273,0,3.622,2.542,2.347,4.424l-43.967,64.92
|
||||
c-1.124,1.66-3.57,1.66-4.694,0L273.978,132.238z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 450.709 418.11" enable-background="new 0 0 450.709 418.11" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M262.573,231.338c-8.74-7.379-17.496-10.559-28.81-10.559c-23.314,0-38.378,16.943-38.378,43.168v67.706
|
||||
c0,1.566,1.269,2.835,2.835,2.835h11.603c1.566,0,2.835-1.269,2.835-2.835v-40.89h33.462c1.566,0,2.835-1.269,2.835-2.835v-10.34
|
||||
c0-1.566-1.269-2.835-2.835-2.835h-33.462v-11.31c0-17.186,7.677-26.653,21.612-26.653c7.148,0,12.845,2.163,18.155,6.957
|
||||
c0.069,0.063,0.586,0.523,1.2,1.07c1.228,1.093,3.126,0.917,4.13-0.385l6.31-8.181c0.888-1.151,0.76-2.788-0.295-3.789
|
||||
C263.175,231.899,262.648,231.401,262.573,231.338z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M225.62,125.182c0.003-1.573,1.289-2.843,2.863-2.829c0.675,0.006,1.231,0.014,1.333,0.024
|
||||
c6.856,0.657,13.394,3.223,18.955,7.448c9.876,7.504,15.388,19.34,14.743,31.662c-1.041,19.793-17.869,35.296-38.312,35.296
|
||||
h-26.977c-1.566,0-2.835-1.269-2.835-2.835V86.457c0-1.566,1.269-2.835,2.835-2.835h11.585c1.566,0,2.835,1.269,2.835,2.835
|
||||
v91.169c0,1.566,1.269,2.835,2.835,2.835h10.081c11.139,0,20.622-8.355,21.591-19.019c1.042-11.523-7.505-21.809-19.019-22.824
|
||||
c-1.445-0.127-2.537-1.369-2.534-2.82L225.62,125.182z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#0069B0" d="M351.43,291.772"/>
|
||||
<path fill="#0079C3" d="M366.594,287.348c1.275,1.882-0.074,4.424-2.347,4.424h0.012H351.79c-0.941,0-1.82-0.467-2.347-1.245
|
||||
l-29.151-43.055l-29.151,43.055c-0.527,0.779-1.406,1.245-2.347,1.245h-12.47c-2.273,0-3.622-2.542-2.347-4.424l43.967-64.92
|
||||
c1.124-1.66,3.57-1.66,4.694,0L366.594,287.348z"/>
|
||||
<path fill="#FFFFFF" d="M176.731,287.348c1.275,1.882-0.074,4.424-2.347,4.424h0.012h-12.469c-0.941,0-1.82-0.467-2.347-1.245
|
||||
l-29.151-43.055l-29.151,43.055c-0.527,0.779-1.406,1.245-2.347,1.245h-12.47c-2.273,0-3.622-2.542-2.347-4.424l43.967-64.92
|
||||
c1.124-1.66,3.57-1.66,4.694,0L176.731,287.348z"/>
|
||||
<path fill="#0079C3" d="M84.114,132.238c-1.275-1.882,0.074-4.424,2.347-4.424h-0.012h12.469c0.941,0,1.82,0.467,2.347,1.245
|
||||
l29.151,43.055l29.151-43.055c0.527-0.779,1.406-1.245,2.347-1.245h12.47c2.273,0,3.622,2.542,2.347,4.424l-43.967,64.92
|
||||
c-1.124,1.66-3.57,1.66-4.694,0L84.114,132.238z"/>
|
||||
<path fill="#FFFFFF" d="M273.978,132.238c-1.275-1.882,0.074-4.424,2.347-4.424h-0.012h12.469c0.941,0,1.82,0.467,2.347,1.245
|
||||
l29.151,43.055l29.151-43.055c0.527-0.779,1.406-1.245,2.347-1.245h12.47c2.273,0,3.622,2.542,2.347,4.424l-43.967,64.92
|
||||
c-1.124,1.66-3.57,1.66-4.694,0L273.978,132.238z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -5,9 +5,13 @@
|
|||
{% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
|
||||
|
||||
<div class="content">
|
||||
<form action="/cypressreset/" method="post">
|
||||
<form action="/core/cypressreset/" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="" name="">Testdaten zurück setzen</button>
|
||||
<button class="btn" name="">Testdaten zurück setzen</button>
|
||||
</form>
|
||||
<form action="/core/schemareset/" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="btn" name="">Datenbank zurück setzen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue