Merge branch 'feature/bitbucket-unit-tests' into develop

This commit is contained in:
Daniel Egger 2022-08-26 18:20:03 +02:00
commit 5875f13143
60 changed files with 341 additions and 1026 deletions

View File

@ -9,7 +9,7 @@ pipelines:
services:
- postgres
caches:
- vbvpip
- pip
script:
- source ./env/bitbucket/prepare_for_test.sh
- pip install -r server/requirements/requirements-dev.txt
@ -27,14 +27,15 @@ pipelines:
- cypress/**/*.mp4
caches:
- node
- vbvpip
- pip
- cypress
script:
- export IT_SERVE_VUE=false
- export IT_ALLOW_LOCAL_LOGIN=true
- source ./env/bitbucket/prepare_for_test.sh
- python -m venv vbvvenv
- source vbvvenv/bin/activate
- pip install -r server/requirements/requirements-dev.txt
- npm install
- npm run build
- ./prepare_server_cypress.sh --start-background
- npm run cypress:ci
# - npm run build
@ -52,20 +53,20 @@ pipelines:
deployment: prod
trigger: manual
script:
- ./deploy.sh --commit "$BITBUCKET_COMMIT" --token "$DEPLOY_TOKEN" --url https://myservicecrm.swisscom.ch/deploy-iesc-bKVAkQguPDVi
- ./deploy.sh --commit "$BITBUCKET_COMMIT" --token "$DEPLOY_TOKEN" --url https://myservicecrm.swisscom.ch/deploy-iesc-xxx
custom:
deploy-preprod:
- step:
name: Deploy to PREPROD
deployment: preprod
script:
- ./deploy.sh --commit "$BITBUCKET_COMMIT" --token "$DEPLOY_TOKEN" --url https://preprod.myservicecrm.ch/deploy-iesc-bKVAkQguPDVi
- ./deploy.sh --commit "$BITBUCKET_COMMIT" --token "$DEPLOY_TOKEN" --url https://preprod.myservicecrm.ch/deploy-iesc-xxx
deploy-api:
- step:
name: Deploy to API
deployment: api
script:
- ./deploy.sh --commit "$BITBUCKET_COMMIT" --token "$DEPLOY_TOKEN" --url https://api.myservicecrm.ch/deploy-iesc-bKVAkQguPDVi
- ./deploy.sh --commit "$BITBUCKET_COMMIT" --token "$DEPLOY_TOKEN" --url https://api.myservicecrm.ch/deploy-iesc-xxx
definitions:
caches:

View File

@ -1,11 +1,11 @@
<script setup lang="ts">
import * as log from 'loglevel';
import { onMounted, reactive} from 'vue';
import { useUserStore } from '@/stores/user';
import { useLearningPathStore } from '@/stores/learningPath';
import { useRoute, useRouter } from 'vue-router';
import { useAppStore } from '@/stores/app';
import {onMounted, reactive} from 'vue';
import {useUserStore} from '@/stores/user';
import {useLearningPathStore} from '@/stores/learningPath';
import {useRoute, useRouter} from 'vue-router';
import {useAppStore} from '@/stores/app';
import IconLogout from "@/components/icons/IconLogout.vue";
import IconSettings from "@/components/icons/IconSettings.vue";
import ItDropdown from "@/components/ui/ItDropdown.vue";
@ -146,6 +146,7 @@ const profileDropdownData = [
v-if="userStore.loggedIn"
to="/messages"
class="nav-item flex flex-row items-center"
data-cy="messages-link"
>
<it-icon-message class="w-8 h-8 mr-6"/>
</router-link>
@ -213,6 +214,7 @@ const profileDropdownData = [
<router-link
to="/messages"
class="nav-item flex flex-row items-center"
data-cy="messages-link"
>
<it-icon-message class="w-8 h-8 mr-6"/>
</router-link>

View File

@ -1,13 +1,13 @@
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import {setupI18n} from './i18n'
// import {setupI18n} from './i18n'
import App from './App.vue'
import router from './router'
import '../tailwind.css'
const i18n = setupI18n()
// const i18n = setupI18n()
const app = createApp(App)
// todo: define lang setup
@ -15,6 +15,6 @@ const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(i18n)
// app.use(i18n)
app.mount('#app')

View File

@ -10,7 +10,7 @@ const userStore = useUserStore();
<template>
<main class="px-8 py-8 lg:px-12 lg:py-12 bg-gray-200">
<h1>Willkommen, {{userStore.first_name}}</h1>
<h1 data-cy="welcome-message">Willkommen, {{userStore.first_name}}</h1>
<h2 class="mt-12">Deine Kurse</h2>

View File

@ -48,7 +48,7 @@ onMounted(async () => {
<LearningPathDiagram class="max-w-[1680px] w-full" identifier="mainVisualization" v-bind:vertical="false"></LearningPathDiagram>
</div>
<h1 class="m-12">{{ learningPathStore.learningPath.title }}</h1>
<h1 data-cy="learning-path-title" class="m-12">{{ learningPathStore.learningPath.title }}</h1>
<div
class="bg-white m-12 p-8 flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-gray-500 justify-start">

View File

@ -48,6 +48,7 @@ const userStore = useUserStore();
<div>
<input
data-cy="login-button"
type="submit"
value="Login"
class="btn-primary"

24
cypress.config.js Normal file
View File

@ -0,0 +1,24 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
watchForFileChanges: false,
video: false,
retries: {
runMode: 1,
openMode: 0,
},
reporter: 'junit',
reporterOptions: {
mochaFile: 'cypress/test-reports/cypress-results-[hash].xml',
toConsole: true,
},
e2e: {
// experimentalSessionAndOrigin: true,
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://localhost:8001',
},
})

View File

@ -1,14 +0,0 @@
{
"baseUrl": "http://localhost:8001",
"watchForFileChanges": false,
"video": false,
"retries": {
"runMode": 1,
"openMode": 0
},
"reporter": "junit",
"reporterOptions": {
"mochaFile": "cypress/test-reports/cypress-results-[hash].xml",
"toConsole": true
}
}

7
cypress/e2e/helpers.js Normal file
View File

@ -0,0 +1,7 @@
export const login = (username, password) => {
cy.request({
method: 'POST',
url: '/core/login/',
body: { username, password },
})
}

47
cypress/e2e/login.cy.js Normal file
View File

@ -0,0 +1,47 @@
import { login } from "./helpers";
describe("login", () => {
beforeEach(() => {
cy.manageCommand("cypress_reset");
});
it("can login to app with username/password", () => {
cy.visit("/");
cy.get("#username").type("admin");
cy.get("#password").type("test");
cy.get('[data-cy="login-button"]').click();
cy.request("/api/core/me").its("status").should("eq", 200);
cy.get('[data-cy="welcome-message"]').should(
"contain",
"Willkommen, Peter"
);
});
it("can login with helper function", () => {
login("admin", "test");
cy.visit("/");
cy.request("/api/core/me").its("status").should("eq", 200);
cy.get('[data-cy="welcome-message"]').should(
"contain",
"Willkommen, Peter"
);
});
it("login will redirect to requestet page", () => {
cy.visit("/learningpath/versicherungsvermittlerin");
cy.get("h1").should("contain", "Login");
cy.get("#username").type("admin");
cy.get("#password").type("test");
cy.get('[data-cy="login-button"]').click();
cy.get('[data-cy="learning-path-title"]').should(
"contain",
"Versicherungsvermittler"
);
});
});

View File

@ -1,148 +0,0 @@
/// <reference types="cypress" />
// Welcome to Cypress!
//
// This spec file contains a variety of sample tests
// for a todo list app that are designed to demonstrate
// the power of writing tests in Cypress.
//
// To learn more about how Cypress works and
// what makes it such an awesome testing tool,
// please read our getting started guide:
// https://on.cypress.io/introduction-to-cypress
describe('example to-do app', () => {
beforeEach(() => {
cy.manageCommand('cypress_reset');
cy.visit('/todo/');
cy.get("#username").type("cypress@example.com");
cy.get("#password").type("test");
cy.get('[data-cy="submit"]').click();
})
it.skip('can access simple todo page', () => {
cy.get('[data-cy="simple-list-title"]').should('contain', 'Todos');
});
// it('displays two todo items by default', () => {
// // We use the `cy.get()` command to get all elements that match the selector.
// // Then, we use `should` to assert that there are two matched items,
// // which are the two default items.
// cy.get('.todo-list li').should('have.length', 2)
//
// // We can go even further and check that the default todos each contain
// // the correct text. We use the `first` and `last` functions
// // to get just the first and last matched elements individually,
// // and then perform an assertion with `should`.
// cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
// cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
// })
// it('can add new todo items', () => {
// // We'll store our item text in a variable so we can reuse it
// const newItem = 'Feed the cat'
//
// // Let's get the input element and use the `type` command to
// // input our new list item. After typing the content of our item,
// // we need to type the enter key as well in order to submit the input.
// // This input has a data-test attribute so we'll use that to select the
// // element in accordance with best practices:
// // https://on.cypress.io/selecting-elements
// cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)
//
// // Now that we've typed our new item, let's check that it actually was added to the list.
// // Since it's the newest item, it should exist as the last element in the list.
// // In addition, with the two default items, we should have a total of 3 elements in the list.
// // Since assertions yield the element that was asserted on,
// // we can chain both of these assertions together into a single statement.
// cy.get('.todo-list li')
// .should('have.length', 3)
// .last()
// .should('have.text', newItem)
// })
// it('can check off an item as completed', () => {
// // In addition to using the `get` command to get an element by selector,
// // we can also use the `contains` command to get an element by its contents.
// // However, this will yield the <label>, which is lowest-level element that contains the text.
// // In order to check the item, we'll find the <input> element for this <label>
// // by traversing up the dom to the parent element. From there, we can `find`
// // the child checkbox <input> element and use the `check` command to check it.
// cy.contains('Pay electric bill')
// .parent()
// .find('input[type=checkbox]')
// .check()
//
// // Now that we've checked the button, we can go ahead and make sure
// // that the list element is now marked as completed.
// // Again we'll use `contains` to find the <label> element and then use the `parents` command
// // to traverse multiple levels up the dom until we find the corresponding <li> element.
// // Once we get that element, we can assert that it has the completed class.
// cy.contains('Pay electric bill')
// .parents('li')
// .should('have.class', 'completed')
// })
//
// context('with a checked task', () => {
// beforeEach(() => {
// // We'll take the command we used above to check off an element
// // Since we want to perform multiple tests that start with checking
// // one element, we put it in the beforeEach hook
// // so that it runs at the start of every test.
// cy.contains('Pay electric bill')
// .parent()
// .find('input[type=checkbox]')
// .check()
// })
//
// it('can filter for uncompleted tasks', () => {
// // We'll click on the "active" button in order to
// // display only incomplete items
// cy.contains('Active').click()
//
// // After filtering, we can assert that there is only the one
// // incomplete item in the list.
// cy.get('.todo-list li')
// .should('have.length', 1)
// .first()
// .should('have.text', 'Walk the dog')
//
// // For good measure, let's also assert that the task we checked off
// // does not exist on the page.
// cy.contains('Pay electric bill').should('not.exist')
// })
//
// it('can filter for completed tasks', () => {
// // We can perform similar steps as the test above to ensure
// // that only completed tasks are shown
// cy.contains('Completed').click()
//
// cy.get('.todo-list li')
// .should('have.length', 1)
// .first()
// .should('have.text', 'Pay electric bill')
//
// cy.contains('Walk the dog').should('not.exist')
// })
//
// it('can delete all completed tasks', () => {
// // First, let's click the "Clear completed" button
// // `contains` is actually serving two purposes here.
// // First, it's ensuring that the button exists within the dom.
// // This button only appears when at least one task is checked
// // so this command is implicitly verifying that it does exist.
// // Second, it selects the button so we can click it.
// cy.contains('Clear completed').click()
//
// // Then we can make sure that there is only one element
// // in the list and our element does not exist
// cy.get('.todo-list li')
// .should('have.length', 1)
// .should('not.have.text', 'Pay electric bill')
//
// // Finally, make sure that the clear button no longer exists.
// cy.contains('Clear completed').should('not.exist')
// })
// })
})

View File

@ -53,9 +53,16 @@
const _ = Cypress._;
Cypress.Commands.add('manageCommand', (command, preCommand = '') => {
const execCommand = `${preCommand} python3 server/manage.py ${command} --settings=config.settings.test_cypress`;
const execCommand = `${preCommand} python server/manage.py ${command} --settings=config.settings.test_cypress`;
console.log(execCommand);
return cy.exec(execCommand);
return cy.exec(execCommand, { failOnNonZeroExit: false }).then(result => {
if(result.code) {
throw new Error(`Execution of "${command}" failed
Exit code: ${result.code}
Stdout:\n${result.stdout}
Stderr:\n${result.stderr}`);
}
});
});
Cypress.Commands.add('manageShellCommand', (command) => {

View File

@ -3,7 +3,7 @@
# push new version to Docker Hub
# > docker push iterativ/vbv-lernwelt-bitbucket
# run locally with directory mounted
# > docker run -v $(dirname "$(pwd)"):/src -it iterativ/vbv-lernwelt-bitbucket /bin/bash
# > docker run -v "$(pwd)":/src -it iterativ/vbv-lernwelt-bitbucket /bin/bash
FROM python:3.10-bullseye
MAINTAINER Daniel Egger <daniel.egger@iterativ.ch>

View File

@ -5,13 +5,9 @@
"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"
"cypress:ci": "cypress run"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.2",
"cypress": "^9.4.1"
"cypress": "^10.6.0"
}
}

View File

@ -103,7 +103,6 @@ THIRD_PARTY_APPS = [
LOCAL_APPS = [
"vbv_lernwelt.core",
"vbv_lernwelt.simpletodo",
"vbv_lernwelt.sso",
"vbv_lernwelt.learnpath",
"vbv_lernwelt.completion",

View File

@ -4,7 +4,6 @@ import os
os.environ['IT_APP_ENVIRONMENT'] = 'development'
from .base import * # noqa
from .base import env
# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
TEST_RUNNER = "django.test.runner.DiscoverRunner"

View File

@ -1,4 +1,7 @@
# pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position
import os
os.environ['IT_APP_ENVIRONMENT'] = 'development'
from .base import * # noqa

View File

@ -34,7 +34,6 @@ urlpatterns = [
path('admin/raise_error/', user_passes_test(lambda u: u.is_superuser, login_url='/login/')(raise_example_error), ),
path(settings.ADMIN_URL, admin.site.urls),
path("checkratelimit/", check_rate_limit),
path("todo/", include("vbv_lernwelt.simpletodo.urls")),
path("sso/", include("vbv_lernwelt.sso.urls")),
path('cms/', include(wagtailadmin_urls)),
path('documents/', include(wagtaildocs_urls)),
@ -42,18 +41,14 @@ urlpatterns = [
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'),
re_path(r'core/login/$', django_view_authentication_exempt(vue_login), name='vue_login'),
re_path(r'core/logout/$', vue_logout, name='vue_logout'),
] + 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 += [
re_path(r'core/login/$', django_view_authentication_exempt(vue_login), name='vue_login'),
]
# API URLS
urlpatterns += [
# API base url
@ -113,4 +108,3 @@ if settings.DEBUG:
# serve everything else via the vue app
urlpatterns += [re_path(r'^.*$', vue_home, name='home')]

View File

@ -14,8 +14,8 @@ class CircleCompletion(models.Model):
# Page can either be a LearningContent or a LearningUnitQuestion for now
page_key = models.UUIDField()
page_type = models.CharField(max_length=255, default='', blank=True)
circle_key = models.UUIDField()
learning_path_key = models.UUIDField()
circle_key = models.UUIDField(blank=True, default='')
learning_path_key = models.UUIDField(blank=True, default='')
completed = models.BooleanField(default=False)
json_data = models.JSONField(default=dict, blank=True)

View File

@ -4,25 +4,21 @@ from rest_framework.test import APITestCase
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.tests.helpers import create_locales_for_wagtail
from vbv_lernwelt.learnpath.models import LearningContent
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path
from vbv_lernwelt.learnpath.tests.test_create_default_learning_path import create_locales_for_wagtail
from vbv_lernwelt.learnpath.tests.create_simple_test_learning_path import create_simple_test_learning_path
class CompletionApiTestCase(APITestCase):
@classmethod
def setUpClass(cls) -> None:
super(CompletionApiTestCase, cls).setUpClass()
def setUp(self) -> None:
create_locales_for_wagtail()
create_default_users()
create_default_learning_path()
def setUp(self) -> None:
create_simple_test_learning_path()
self.user = User.objects.get(username='student')
self.client.login(username='student', password='test')
def test_completeLearningContent_works(self):
learning_content = LearningContent.objects.get(title='Einleitung Circle "Anlayse"')
learning_content = LearningContent.objects.get(title='Einleitung Circle "Unit-Test Circle"')
learning_content_key = str(learning_content.translation_key)
circle_key = str(learning_content.get_parent().translation_key)

View File

@ -1,13 +1,16 @@
import djclick as click
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path, \
from vbv_lernwelt.learnpath.create_default_learning_path import create_default_learning_path, \
delete_default_learning_path
@click.command()
@click.option("--customer_language", default="de")
def command(customer_language):
@click.option('--reset-learning-path', default=False)
def command(reset_learning_path):
print("cypress reset data")
delete_default_learning_path()
create_default_learning_path(skip_locales=True)
if reset_learning_path:
delete_default_learning_path()
create_default_learning_path(skip_locales=True)

View File

@ -0,0 +1,7 @@
from django.conf import settings
from wagtail.models import Locale
def create_locales_for_wagtail():
for language in settings.WAGTAIL_CONTENT_LANGUAGES:
Locale.objects.get_or_create(language_code=language[0])

View File

@ -1,16 +0,0 @@
from django.test import TestCase
from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.tests.factories import UserFactory
from vbv_lernwelt.simpletodo.models import SimpleList
class TestUserCreation(TestCase):
def test_create_user(self):
User(last_name='Sepp').save()
def test_simple(self):
# create_default_learning_path()
self.user = UserFactory()
SimpleList.objects.get_or_create(title='Default', user=self.user)
self.assertTrue(True)

View File

@ -48,20 +48,23 @@ def vue_home(request):
@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)
if settings.ALLOW_LOCAL_LOGIN:
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)
logger.debug('login failed', username=username, label='login')
return Response({'success': False}, status=401)
else:
return Response({'success': False, 'message': 'ALLOW_LOCAL_LOGIN=false'}, status=403)
@api_view(['GET'])

View File

@ -1,8 +1,9 @@
import djclick as click
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path
from vbv_lernwelt.learnpath.create_default_learning_path import create_default_learning_path
@click.command()
def command():
create_default_learning_path(skip_locales=True)
# create_simple_test_learning_path(skip_locales=True)

View File

@ -1,6 +1,6 @@
import djclick as click
from vbv_lernwelt.learnpath.tests.create_default_learning_path import delete_default_learning_path
from vbv_lernwelt.learnpath.create_default_learning_path import delete_default_learning_path
@click.command()

View File

@ -1,13 +1,12 @@
# Create your models here.
from django.db import models
from django.utils.text import slugify
from wagtail import blocks
from wagtail.admin.panels import FieldPanel, StreamFieldPanel
from wagtail.blocks import StreamBlock
from wagtail.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
from wagtail.models import Page, Orderable
from vbv_lernwelt.learnpath.models_competences import *
from vbv_lernwelt.learnpath.models_learning_unit_content import WebBasedTrainingBlock, VideoBlock, PodcastBlock, \
CompetenceBlock, ExerciseBlock, DocumentBlock, KnowledgeBlock
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class

View File

@ -1,66 +0,0 @@
from django.db import models
from wagtail.models import Page, Orderable
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import FieldPanel, StreamFieldPanel, InlinePanel
class CompetencePage(Page):
"""This is the page where the competences and Fullfillment criterias are manged
For one Learning Path"""
content_panels = Page.content_panels + [
InlinePanel('competences', label="Competences"),
]
subpage_types = ['learnpath.Circle']
parent_page_types = ['learnpath.LearningPath']
class Meta:
verbose_name = "Learning Path"
def __str__(self):
return f"{self.title}"
class Competence(Orderable):
""" In VBV Terms this is a "Handlungskompetenz"""
category_short = models.CharField(max_length=3, default='')
name = models.CharField(max_length=2048)
competence_page = ParentalKey('learnpath.CompetencePage',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='competences',
)
def get_short_info(self):
return f"{self.category_short}{self.sort_order}"
def __str__(self):
return f"{self.get_short_info()}: {self.name}"
class Meta:
verbose_name = "Competence"
class FullfillmentCriteria(Orderable):
""" VBV Term Leistungskriterium"""
name = models.CharField(max_length=2048)
competence = models.ForeignKey(Competence, on_delete=models.CASCADE, null=True)
def get_short_info(self):
return f"{self.competence.get_short_info()}.{self.sort_order}"
def __str__(self):
return f"{self.get_short_info()}: {self.name}"
class Meta:
verbose_name = "Fullfillment Criteria"

View File

@ -1,78 +0,0 @@
{
"competences": [
{
"name": "Weiterempfehlung für Neukunden generieren",
"category_short": "A",
"fullfillment_criteria": [
{
"name": "bestehende Kunden so zu beraten, dass sie von diesen weiterempfohlen werden"
},
{
"name": "geeignete Personen wie z.B. Garagisten, Architekten, Treuhänder auf die Vermittlung/Zusammenarbeit anzusprechen"
},
{
"name": "verschiedene Datenquellen wie Internet, Telefonbuch, Handelszeitung, Baugesuche etc. gezielt für die Gewinnung von Neukunden zu benützen"
},
{
"name": "ein beliebiges Gespräch resp. einen bestehenden Kontakt in die Richtung «Versicherung» zu lenken"
},
{
"name": "das Thema Risiko und Sicherheit in einem Gespräch gezielt und auf die Situation des jeweiligen Gesprächspartners bezogen einfliessen zu lassen"
},
{
"name": "im täglichen Kontakt potentielle Kundinnen und Kunden zu erkennen"
}
]
},
{
"name": "Kundengespräche vereinbaren",
"category_short": "A",
"fullfillment_criteria": [
{
"name": "je nach (Neu-) Kunde Form und Ort für das Gespräch festzulegen"
},
{
"name": "sich intern und extern die nötigen Informationen über den (Neu-) Kunde zu beschaffen"
},
{
"name": "die Terminierung auf ein bestimmtes Thema wie z.B. Rechtsschutz, Vorsorge, Krankenversicherung etc. auszurichten"
},
{
"name": "für das zu führende Gespräch eine Agenda zu erstellen"
},
{
"name": "für das zu führende Gespräch geeignete Hilfsmittel und Unterlagen zusammenzustellen"
}, {
"name": "eine Kaltakquise durchzuführen und auf mögliche Einwände reagieren zu können"
}
]
},
{
"name": "Auftritt in den sozialen Medien zeitgemäss halten",
"category_short": "A",
"fullfillment_criteria": [ {
"name": "in Zusammenarbeit mit den IT-Spezialisten und der Marketingabteilung die Inhalte für den zu realisierenden Medienauftritt zielgruppengerecht festzulegen"
},
{
"name": "für die verschiedenen Kundensegmente die passenden sozialen Medien zu definieren"
},
{
"name": "die Inhalte compliant zu halten"
}
]
},
{
"name": "Kundendaten erfassen",
"category_short": "A",
"fullfillment_criteria": []
},
{
"name": "Wünsche, Ziele und Bedürfnisse der Kunden im Gespräch ermitteln",
"category_short": "B",
"fullfillment_criteria": []
}
]
}

View File

@ -1,30 +0,0 @@
import factory
import wagtail_factories
from vbv_lernwelt.learnpath.models_competences import Competence, FullfillmentCriteria, CompetencePage
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory
class CompetencePageFactory(wagtail_factories.PageFactory):
# learning_path = factory.SubFactory(LearningPathFactory)
class Meta:
model = CompetencePage
class CompetenceFactory(factory.django.DjangoModelFactory):
category_short = 'A'
name = "Weiterempfehung für neukunden generieren"
competence_page = factory.SubFactory(CompetencePageFactory)
class Meta:
model = Competence
class FullfilmentCriteriaFactory(factory.django.DjangoModelFactory):
name = 'Bestehende Kunden so zu beraten, dass sie von diesen weiterempfohlen werden'
competence = factory.SubFactory(CompetenceFactory)
class Meta:
model = FullfillmentCriteria

View File

@ -1,23 +0,0 @@
import json
import os.path
from vbv_lernwelt.learnpath.tests.competences_factories import CompetenceFactory, FullfilmentCriteriaFactory
competences_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'competences.json')
def create_default_competences(competences_json=competences_file):
with open(competences_json) as f:
competences_json = json.load(f)
for index, compentence in enumerate(competences_json['competences']):
competence_model = CompetenceFactory(name=compentence['name'], category_short=compentence['category_short'], sort_order=index)
print(competence_model)
for criteria_index, criteria in enumerate(compentence['fullfillment_criteria']):
criteria_model = FullfilmentCriteriaFactory(name=criteria['name'], competence=competence_model, sort_order=criteria_index)
print(criteria_model)

View File

@ -0,0 +1,149 @@
import wagtail_factories
from django.conf import settings
from wagtail.models import Site, Page
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFactory, TopicFactory, CircleFactory, \
LearningSequenceFactory, LearningContentFactory, VideoBlockFactory, PodcastBlockFactory, CompetenceBlockFactory, \
ExerciseBlockFactory, LearningUnitFactory, LearningUnitQuestionFactory
def create_circle(title, learning_path):
return CircleFactory(
title=title,
parent=learning_path,
description="Unit-Test Circle",
job_situations=[
('job_situation', 'Absicherung der Familie'),
('job_situation', 'Reisen'),
],
goals=[
('goal', '... die heutige Versicherungssituation von Privat- oder Geschäftskunden einzuschätzen.'),
('goal', '... deinem Kunden seine optimale Lösung aufzuzeigen'),
],
experts=[
('person', {'last_name': 'Huggel', 'first_name': 'Patrizia', 'email': 'patrizia.huggel@example.com'}),
]
)
def create_circle_children(circle, title):
LearningSequenceFactory(title='Starten', parent=circle, icon='it-icon-ls-start')
LearningContentFactory(
title=f'Einleitung Circle "{title}"',
parent=circle,
minutes=15,
contents=[('video', VideoBlockFactory(
url='https://www.youtube.com/embed/qhPIfxS2hvI',
description='In dieser Circle zeigt dir ein Fachexperte anhand von Kundensituationen, wie du erfolgreich'
'den Kundenbedarf ermitteln, analysieren, priorisieren und anschliessend zusammenfassen kannst.'
))]
)
LearningSequenceFactory(title='Beobachten', parent=circle, icon='it-icon-ls-watch')
lu = LearningUnitFactory(
title='Absicherung der Familie',
parent=circle,
)
LearningUnitQuestionFactory(
title="Ich bin in der Lage, mit geeigneten Fragestellungen die Deckung von Versicherungen zu erfassen.",
parent=lu
)
LearningUnitQuestionFactory(
title="Zweite passende Frage zu 'Absicherung der Familie'",
parent=lu
)
LearningContentFactory(
title='Ermittlung des Kundenbedarfs',
parent=circle,
minutes=30,
contents=[('podcast', PodcastBlockFactory(
description='Die Ermittlung des Kundenbedarfs muss in einem eingehenden Gespräch herausgefunden werden. Höre dazu auch diesen Podcast an.',
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',
))]
)
LearningContentFactory(
title='Kundenbedürfnisse erkennen',
parent=circle,
minutes=30,
contents=[('competence', CompetenceBlockFactory())]
)
LearningContentFactory(
title='Was braucht eine Familie?',
parent=circle,
minutes=60,
contents=[('exercise', ExerciseBlockFactory(url='/media/web_based_trainings/story-01-a-01-patrizia-marco-sichern-sich-ab-einstieg/scormcontent/index.html'
))]
)
lu = LearningUnitFactory(title='Reisen', parent=circle)
LearningUnitQuestionFactory(
title='Passende Frage zu "Reisen"',
parent=lu
)
LearningContentFactory(
title='Reiseversicherung',
parent=circle,
minutes=240,
contents=[('competence', CompetenceBlockFactory())]
)
LearningContentFactory(
title='Sorgenfrei reisen',
parent=circle,
minutes=120,
contents=[('exercise', ExerciseBlockFactory(
url='/media/web_based_trainings/story-06-a-01-emma-und-ayla-campen-durch-amerika-einstieg/scormcontent/index.html'))]
)
LearningSequenceFactory(title='Beenden', parent=circle, icon='it-icon-ls-end')
LearningContentFactory(
title='Kompetenzprofil anschauen',
parent=circle,
minutes=30,
contents=[('document', CompetenceBlockFactory())]
)
LearningContentFactory(
title='Circle "Analyse" abschliessen',
parent=circle,
minutes=30,
contents=[('document', CompetenceBlockFactory())]
)
def create_simple_test_learning_path(user=None, skip_locales=True):
if user is None:
user = User.objects.get(username='info@iterativ.ch')
site = Site.objects.filter(is_default_site=True).first()
if not site:
site = wagtail_factories.SiteFactory(is_default_site=True)
if settings.APP_ENVIRONMENT == 'development':
site.port = 8000
site.save()
lp = LearningPathFactory(title="Unit-Test Lernpfad", parent=site.root_page)
TopicFactory(title="Unit-Test Topic", is_visible=False, parent=lp)
circle_analyse = create_circle('Unit-Test Circle', lp)
create_circle_children(circle_analyse, 'Unit-Test Circle')
# 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)

View File

@ -1,20 +0,0 @@
GET http://localhost:8000/graphql/
Accept: application/json
###
{
page(id: 8) {
children {
__typename
id
title
children {
__typename
id
title
}
}
}
}

View File

@ -1,17 +0,0 @@
{
page(id: 8) {
children {
__typename
id
title
children {
__typename
id
title
}
}
}
}

View File

@ -2,44 +2,30 @@ from rest_framework.test import APITestCase
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.learnpath.models import LearningPath, Circle
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path
from vbv_lernwelt.learnpath.tests.test_create_default_learning_path import create_locales_for_wagtail
from vbv_lernwelt.core.tests.helpers import create_locales_for_wagtail
from vbv_lernwelt.learnpath.models import LearningPath
from vbv_lernwelt.learnpath.tests.create_simple_test_learning_path import create_simple_test_learning_path
class TestRetrieveLearingPathContents(APITestCase):
@classmethod
def setUpClass(cls) -> None:
super(TestRetrieveLearingPathContents, cls).setUpClass()
def setUp(self) -> None:
create_locales_for_wagtail()
create_default_users()
create_default_learning_path()
create_simple_test_learning_path()
def setUp(self) -> None:
qs = LearningPath.objects.filter(title="Versicherungsvermittler/in")
self.credentials = {
'username': 'admin',
'password': 'admin'}
self.user = User.objects.get(username='student')
self.client.login(username='student', password='test')
user = User.objects.get(username='admin')
self.client.force_authenticate(user=user)
self.client.post('/login/', self.credentials, follow=True)
def test_get_circle(self):
circle = Circle.objects.get(slug='analyse')
response = self.client.get(f'/wagtailapi/v2/pages/{circle.id}/')
def test_get_learnpathPage(self):
learning_path = LearningPath.objects.get(slug='unit-test-lernpfad')
response = self.client.get('/learnpath/api/page/unit-test-lernpfad/')
print(response)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['title'], 'Analyse')
data = response.json()
# print(data)
def test_get_circle_has_learning_sequences(self):
circle = Circle.objects.get(slug='analyse')
response = self.client.get(f'/wagtailapi/v2/pages/{circle.id}/')
self.assertTrue(len(response.data['learning_sequences']) > 2)
self.assertEqual(response.data['learning_sequences'][0]['title'], 'Starten')
def test_get_circle_has_learning_sequences_learningpackages(self):
circle = Circle.objects.get(slug='analyse')
response = self.client.get(f'/wagtailapi/v2/pages/{circle.id}/')
self.assertTrue(len(response.data['learning_sequences'][0]['learnging_packages']) > 1)
self.assertTrue(response.data['learning_sequences'][0]['learnging_packages'][0]['title'])
self.assertEqual(learning_path.title, data['title'])
# topic and circle
self.assertEqual(2, len(data['children']))
# circle "unit-test-circle" contents
self.assertEqual(13, len(data['children'][1]['children']))

View File

@ -1,18 +0,0 @@
from django.test import TestCase
from vbv_lernwelt.learnpath.models_competences import Competence, FullfillmentCriteria
from vbv_lernwelt.learnpath.tests.competences_factories import CompetencePageFactory, CompetenceFactory, \
FullfilmentCriteriaFactory
class TestCompetencesFactories(TestCase):
def test_create_competences_page(self):
CompetencePageFactory()
def test_create_competence(self):
CompetenceFactory(name='Boogie Woogie')
self.assertEqual(Competence.objects.filter(name='Boogie Woogie').count(), 1)
def test_create_fullfillment_criteria(self):
FullfilmentCriteriaFactory(name='shuffle like ...')
self.assertEqual(FullfillmentCriteria.objects.filter(name='shuffle like ...').count(), 1)

View File

@ -1,12 +0,0 @@
from django.test import TestCase
from vbv_lernwelt.learnpath.tests.create_default_competences import create_default_competences
class TestCreateDefaultCompetences(TestCase):
def test_create_default_competeneces(self):
create_default_competences()

View File

@ -1,23 +0,0 @@
from django.conf import settings
from django.test import TestCase
from wagtail.models import Locale
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.learnpath.models import LearningPath
from vbv_lernwelt.learnpath.tests.create_default_learning_path import create_default_learning_path
class TestCreateDefaultLearningPaths(TestCase):
def setUp(self) -> None:
create_default_users()
create_locales_for_wagtail()
def test_create_learning_path(self):
create_default_learning_path()
qs = LearningPath.objects.filter(title="Versicherungsvermittler/in")
self.assertTrue(qs.exists())
def create_locales_for_wagtail():
for language in settings.WAGTAIL_CONTENT_LANGUAGES:
Locale.objects.get_or_create(language_code=language[0])

View File

@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
#
# Iterativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2015 Iterativ GmbH. All rights reserved.
#
# Created on 2022-03-29
# @author: lorenz.padberg@iterativ.ch

View File

@ -2,6 +2,7 @@
import glob
from pathlib import Path
import structlog
from django.conf import settings
from django.shortcuts import render
from django.views.decorators.cache import cache_page
@ -11,13 +12,19 @@ from wagtail.models import Page
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
logger = structlog.get_logger(__name__)
@api_view(['GET'])
@cache_page(60 * 60 * 8, cache="learning_path_cache")
def page_api_view(request, slug):
page = Page.objects.get(slug=slug, locale__language_code='de-CH')
serializer = page.specific.get_serializer_class()(page.specific)
return Response(serializer.data)
try:
page = Page.objects.get(slug=slug, locale__language_code='de-CH')
serializer = page.specific.get_serializer_class()(page.specific)
return Response(serializer.data)
except Exception as e:
logger.error(e)
return Response({"error": str(e)}, status=404)
@django_view_authentication_exempt

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,9 +1,8 @@
from django.conf import settings
from django.test import TestCase
from wagtail.core.models import Collection
from wagtail.models import Locale
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.core.tests.helpers import create_locales_for_wagtail
from vbv_lernwelt.media_library.create_default_documents import create_default_collections, create_default_documents
from vbv_lernwelt.media_library.models import LibraryDocument
@ -24,8 +23,3 @@ class TestCreateDefaultDocuments(TestCase):
create_default_documents()
qs = LibraryDocument.objects.all()
self.assertTrue(qs.exists())
def create_locales_for_wagtail():
for language in settings.WAGTAIL_CONTENT_LANGUAGES:
Locale.objects.get_or_create(language_code=language[0])

View File

@ -1,39 +0,0 @@
# Register your models here.
from django.contrib import admin
from .models import SimpleTask, SimpleList
@admin.register(SimpleList)
class SimpleListAdmin(admin.ModelAdmin):
list_display = [
"title",
"user",
"created",
]
list_filter = [
"user",
]
search_fields = [
"title",
]
@admin.register(SimpleTask)
class SimpleTaskAdmin(admin.ModelAdmin):
date_hierarchy = "deadline"
list_display = [
"title",
"deadline",
"created",
"list",
"done",
]
list_filter = [
"list",
"done",
]
search_fields = [
"title",
"text",
]

View File

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

View File

@ -1,105 +0,0 @@
# Generated by Django 3.2.12 on 2022-02-03 20:37
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="SimpleList",
fields=[
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("title", models.CharField(max_length=255)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="SimpleTask",
fields=[
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("title", models.CharField(max_length=255)),
("text", models.TextField(blank=True, default="")),
("done", models.BooleanField(default=False)),
("deadline", models.DateTimeField(blank=True, null=True)),
(
"list",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="simpletodo.simplelist",
),
),
],
options={
"abstract": False,
},
),
]

View File

@ -1,24 +0,0 @@
import uuid
from django.conf import settings
from django.db import models
from model_utils.models import TimeStampedModel
class SimpleList(TimeStampedModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=255)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return f"{self.title} ({self.user})"
class SimpleTask(TimeStampedModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=255)
text = models.TextField(blank=True, default="")
done = models.BooleanField(default=False)
deadline = models.DateTimeField(blank=True, null=True)
list = models.ForeignKey(SimpleList, on_delete=models.CASCADE)

View File

@ -1,41 +0,0 @@
import structlog
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer
from vbv_lernwelt.simpletodo.models import SimpleTask, SimpleList
logger = structlog.get_logger(__name__)
class SimpleTaskSerializer(ModelSerializer):
list_title = serializers.CharField(max_length=100)
class Meta:
model = SimpleTask
fields = [
"id",
"title",
"text",
"done",
"deadline",
"list_title",
]
def create(self, validated_data):
user = validated_data.pop("user", None)
if user is None:
raise serializers.ValidationError("User is required")
list_title = validated_data.pop("list_title")
simple_list, _ = SimpleList.objects.get_or_create(title=list_title, user=user)
validated_data["list"] = simple_list
logger.debug(
"Creating task",
label="simpletodo",
dt={"s1": 3, "s2": 4},
title=validated_data.get("title"),
list_title=list_title,
)
return super().create(validated_data)

View File

@ -1,17 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="container mx-auto">
{% for list in simple_lists %}
{% include "simpletodo/partials/simple_list.html" with list=list%}
{% endfor %}
</div>
<it-icon-ls-start class="text-orange-500"></it-icon-ls-start>
<it-icon-arrow-up></it-icon-arrow-up>
<it-icon-arrow-down></it-icon-arrow-down>
{% endblock %}

View File

@ -1,21 +0,0 @@
<form class="flex mt-4" action="/todo/api/tasks/" method="POST">
{% csrf_token %}
<input class="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-gray-darker"
type="text"
name="title"
maxlength="100"
required
placeholder="Add Todo"
>
<input type="hidden" name="list_title" value="{{ list.title }}">
<input
type="submit"
value="Add"
hx-post="/todo/api/tasks/"
hx-trigger="submit"
hx-target="#parent-div"
hx-swap="outerHTML"
class="flex-no-shrink p-2 border-2 rounded text-blue-500 border-blue-500 hover:text-white hover:bg-blue-500"
>
</form>

View File

@ -1,18 +0,0 @@
<div class="h-100 w-full flex items-center justify-center bg-blue-100 font-sans">
<div class="bg-white rounded shadow p-6 m-4 w-full lg:w-3/5 md:w-3/4">
<div class="mb-4">
<h2
class="text-gray-darkest"
data-cy="simple-list-title"
>
{{ list.title }}
</h2>
{% include "simpletodo/partials/add_task_form.html" with task=task %}
</div>
{% for task in list.simpletask_set.all %}
{% include "simpletodo/partials/task.html" with task=task %}
{% endfor %}
</div>
</div>

View File

@ -1,35 +0,0 @@
<div class="task">
<div class="flex mb-4 items-center">
{% if task.done %}
<p class="flex-auto text-blue-500 line-through">
{% else %}
<p class="flex-auto text-blue-900">
{% endif %}
{{ task.title }}
</p>
<button
hx-post="/todo/api/tasks/{{ task.id }}/toggle_done/"
hx-swap="outerHTML swap:0.5s"
hx-target="closest .task"
{% if task.done %}
class="flex-no-shrink p-2 ml-4 mr-2 border-2 rounded hover:text-white text-gray-500 border-gray-500 hover:bg-gray-500"
{% else %}
class="flex-no-shrink p-2 ml-4 mr-2 border-2 rounded hover:text-white text-green-500 border-green-500 hover:bg-green-500"
{% endif %}
>
{% if task.done %}
Not Done
{% else %}
Done
{% endif %}
</button>
<button
hx-delete="/todo/api/tasks/{{ task.id }}/"
hx-swap="outerHTML swap:0.5s"
hx-target="closest .task"
class="flex-no-shrink p-2 ml-2 border-2 rounded text-red-500 border-red-500 hover:text-white hover:bg-red-500">
Remove
</button>
</div>
</div>

View File

@ -1,26 +0,0 @@
from django.test import TestCase
from vbv_lernwelt.core.tests.factories import UserFactory
from vbv_lernwelt.simpletodo.models import SimpleTask
from vbv_lernwelt.simpletodo.serializers import SimpleTaskSerializer
class SimpleTaskSerializerTestCase(TestCase):
def setUp(self) -> None:
self.user = UserFactory()
def test_serializer(self):
serializer = SimpleTaskSerializer(
data={
"title": "Test",
"list_title": "Todos",
}
)
serializer.is_valid(raise_exception=True)
serializer.save(user=self.user)
task = SimpleTask.objects.first()
self.assertEqual(task.title, "Test")
self.assertEqual(task.list.title, "Todos")

View File

@ -1,13 +0,0 @@
from django.conf.urls import url, include
from django.urls import path
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r"tasks", views.SimpleTaskViewSet, basename="tasks")
urlpatterns = [
path("", views.index, name="index"),
url(r"^api/", include(router.urls)),
]

View File

@ -1,92 +0,0 @@
from django.http import HttpResponse
from django.shortcuts import redirect, render
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer
from rest_framework.response import Response
from vbv_lernwelt.simpletodo.models import SimpleList, SimpleTask
from vbv_lernwelt.simpletodo.serializers import SimpleTaskSerializer
def index(request):
simple_lists = SimpleList.objects.filter(user=request.user)
if simple_lists.count() == 0:
simple_lists = [SimpleList.objects.create(user=request.user, title="Todos")]
return render(request, "simpletodo/index.html", {"simple_lists": simple_lists})
class SimpleTaskViewSet(viewsets.ModelViewSet):
serializer_class = SimpleTaskSerializer
renderer_classes = [TemplateHTMLRenderer, JSONRenderer]
def get_queryset(self):
user = self.request.user
return SimpleTask.objects.filter(list__user=user)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if request.accepted_renderer.format == "html":
serializer.is_valid(raise_exception=True)
else:
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user)
if request.accepted_renderer.format == "html":
return redirect("/todo/")
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.delete()
if request.htmx:
return HttpResponse(status=200, content="")
return Response(status=status.HTTP_204_NO_CONTENT)
@action(
detail=True,
methods=[
"post",
],
)
def toggle_done(self, request, pk=None):
task = self.get_object()
task.done = not task.done
task.save()
if request.htmx:
return render(request, "simpletodo/partials/task.html", {"task": task})
return Response(self.get_serializer(task), status=status.HTTP_200_OK)
#
# def get_category_from_request(self, request):
# cat_name = request.query_params.get('cat_name')
# category_obj = None
#
# if cat_name:
# category_obj = VideoCategory.objects.filter(category__iexact=cat_name).first()
# if not category_obj:
# category_obj = VideoCategory.objects.first()
#
# return category_obj
#
# @action(detail=False, methods=['get'])
# def form(self, request):
# category_obj = self.get_category_from_request(request)
# return Response(template_name='videos/partials/video_form.html', data={'category': category_obj})
#
# @action(detail=False, methods=['get'])
# def cancel(self, request):
# category_obj = self.get_category_from_request(request)
# return Response(template_name='videos/partials/show_add_form.html', data={'category': category_obj})

View File

@ -1,4 +1,8 @@
server/requirements/
env_secrets/
env/docker_local.env
server/vbv_lernwelt/media/
server/vbv_lernwelt/simpletodo/
supabase.md
scripts/supabase/init.sql
.envs/