VBV-76: Refactor user and login handling
This commit is contained in:
parent
4b02991f0d
commit
2af7439b97
|
|
@ -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 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,12 @@ const router = createRouter({
|
|||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: HomeView,
|
||||
beforeEnter(_to, _from) {
|
||||
window.location.href = loginUrl
|
||||
},
|
||||
component: LoginView,
|
||||
meta: {
|
||||
public: true
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
path: '/learningpath/:learningPathSlug',
|
||||
component: () => import('../views/LearningPathView.vue'),
|
||||
props: true
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
|
|
@ -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,38 @@ export const useUserStore = defineStore({
|
|||
id: 'user',
|
||||
state: () => ({
|
||||
email: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
username: '',
|
||||
avatar_url: '',
|
||||
loggedIn: false
|
||||
} as UserState),
|
||||
getters: {
|
||||
},
|
||||
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;
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,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
|
||||
}>()
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import MainNavigationBar from '@/components/MainNavigationBar.vue';</script>
|
|||
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import MainNavigationBar from '@/components/MainNavigationBar.vue';
|
||||
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>
|
||||
<MainNavigationBar/>
|
||||
|
||||
<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>
|
||||
|
|
@ -129,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)
|
||||
|
|
|
|||
|
|
@ -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,7 +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
|
||||
|
||||
|
||||
|
|
@ -42,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
|
||||
|
|
|
|||
|
|
@ -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"')
|
||||
|
|
|
|||
|
|
@ -5,25 +5,75 @@ 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.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.png'
|
||||
)
|
||||
|
||||
_create_student_user(
|
||||
email='christoph.bosshard@vbv-afa.ch',
|
||||
first_name='Christoph',
|
||||
last_name='Bosshard',
|
||||
avatar_url='/static/avatars/avatar_christoph.png'
|
||||
)
|
||||
|
||||
|
||||
def _get_or_create_user(user_model, *args, **kwargs):
|
||||
|
|
@ -34,6 +84,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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import LearningPathFac
|
|||
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()
|
||||
|
||||
|
|
@ -409,18 +409,19 @@ Fachspezialisten bei.
|
|||
# circle_7 = CircleFactory.create(title="Prüfungsvorbereitung", parent=lp, topic=tp)
|
||||
|
||||
# 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')
|
||||
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)
|
||||
|
|
|
|||
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 |
Loading…
Reference in New Issue