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 {NavigationGuardWithThis, RouteLocationNormalized} from 'vue-router';
|
||||||
import type {UserState} from '@/stores/user'
|
import {useUserStore} from '@/stores/user';
|
||||||
import {useUserStore} from '@/stores/user'
|
|
||||||
import type {Store} from 'pinia';
|
|
||||||
|
|
||||||
const cookieName = 'loginStatus'
|
|
||||||
let userStore: Store | null = null
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const updateLoggedIn: NavigationGuardWithThis<undefined> = (_to) => {
|
export const updateLoggedIn: NavigationGuardWithThis<undefined> = (_to) => {
|
||||||
const loggedIn = getCookieValue(cookieName) === 'true'
|
const loggedIn = getCookieValue('loginStatus') === 'true'
|
||||||
const store = getUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
store.$patch({ loggedIn })
|
userStore.$patch({loggedIn});
|
||||||
|
if (loggedIn && !userStore.email) {
|
||||||
|
userStore.fetchUser();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const redirectToLoginIfRequired: NavigationGuardWithThis<undefined> = (to, _from) => {
|
export const redirectToLoginIfRequired: NavigationGuardWithThis<undefined> = (to, _from) => {
|
||||||
const store = getUserStore()
|
const userStore = useUserStore()
|
||||||
if(loginRequired(to) && !store.loggedIn) {
|
if(loginRequired(to) && !userStore.loggedIn) {
|
||||||
return { name: 'home' }
|
return `/login?next=${to.fullPath}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,20 +28,6 @@ export const getCookieValue = (cookieName: string): string => {
|
||||||
return cookieValue.pop() || '';
|
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) => {
|
const loginRequired = (to: RouteLocationNormalized) => {
|
||||||
return !to.meta?.public
|
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 {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';
|
import {redirectToLoginIfRequired, updateLoggedIn} from '@/router/guards';
|
||||||
|
|
||||||
const loginUrl = '/sso/login/'
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
|
|
@ -18,15 +17,12 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
component: HomeView,
|
component: LoginView,
|
||||||
beforeEnter(_to, _from) {
|
|
||||||
window.location.href = loginUrl
|
|
||||||
},
|
|
||||||
meta: {
|
meta: {
|
||||||
public: true
|
public: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/learningpath/:learningPathSlug',
|
path: '/learningpath/:learningPathSlug',
|
||||||
component: () => import('../views/LearningPathView.vue'),
|
component: () => import('../views/LearningPathView.vue'),
|
||||||
props: true
|
props: true
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type {CircleChild, LearningContent, LearningSequence, LearningUnit} from '@/types';
|
import type {CircleChild, LearningContent, LearningSequence, LearningUnit} from '@/types';
|
||||||
|
|
||||||
|
|
||||||
function createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
|
function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: 0,
|
||||||
title: '',
|
title: '',
|
||||||
|
|
@ -35,7 +35,7 @@ export function parseLearningSequences (children: CircleChild[]): LearningSequen
|
||||||
learningSequence = Object.assign(child, {learningUnits: []});
|
learningSequence = Object.assign(child, {learningUnits: []});
|
||||||
|
|
||||||
// initialize empty learning unit if there will not come a learning unit next
|
// 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') {
|
} else if (child.type === 'learnpath.LearningUnit') {
|
||||||
if (!learningSequence) {
|
if (!learningSequence) {
|
||||||
throw new Error('LearningUnit found before 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 {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
|
// 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 = {
|
export type UserState = {
|
||||||
|
first_name: string,
|
||||||
|
last_name: string,
|
||||||
email: string;
|
email: string;
|
||||||
|
username: string,
|
||||||
|
avatar_url: string,
|
||||||
loggedIn: boolean;
|
loggedIn: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -10,16 +17,38 @@ export const useUserStore = defineStore({
|
||||||
id: 'user',
|
id: 'user',
|
||||||
state: () => ({
|
state: () => ({
|
||||||
email: '',
|
email: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
username: '',
|
||||||
|
avatar_url: '',
|
||||||
loggedIn: false
|
loggedIn: false
|
||||||
} as UserState),
|
} as UserState),
|
||||||
getters: {
|
getters: {
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setEmail (email: string) {
|
handleLogin(username: string, password: string, next='/') {
|
||||||
this.email = email
|
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) {
|
fetchUser() {
|
||||||
this.loggedIn = isLoggedIn
|
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 SelfEvaluation from '@/components/circle/SelfEvaluation.vue';
|
||||||
import CircleDiagram from '@/components/circle/CircleDiagram.vue';
|
import CircleDiagram from '@/components/circle/CircleDiagram.vue';
|
||||||
|
|
||||||
|
log.debug('CircleView.vue created');
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
circleSlug: string
|
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">
|
<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>
|
<router-link class="link text-xl" to="/styleguide">Styelguide</router-link>
|
||||||
<a class="link text-xl" href="/login/">Login</a>
|
<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="/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>
|
<router-link class="link text-xl" to="/circle/analyse">Circle "Analyse" (Login benötigt)</router-link>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</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!?
|
# FIXME make configurable!?
|
||||||
# LOGIN_URL = "/sso/login/"
|
# LOGIN_URL = "/sso/login/"
|
||||||
LOGIN_URL = "/login/"
|
LOGIN_URL = "/login"
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
||||||
ALLOW_LOCAL_LOGIN = env.bool("IT_ALLOW_LOCAL_LOGIN", default=False)
|
ALLOW_LOCAL_LOGIN = env.bool("IT_ALLOW_LOCAL_LOGIN", default=False)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import views as auth_views
|
|
||||||
from django.contrib.auth.decorators import user_passes_test
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
from django.urls import include, path, re_path
|
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 (
|
from vbv_lernwelt.core.views import (
|
||||||
rate_limit_exceeded_view,
|
rate_limit_exceeded_view,
|
||||||
permission_denied_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
|
from .wagtail_api import wagtail_api_router
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -42,14 +41,16 @@ urlpatterns = [
|
||||||
path('pages/', include(wagtail_urls)),
|
path('pages/', include(wagtail_urls)),
|
||||||
path('learnpath/', include("vbv_lernwelt.learnpath.urls")),
|
path('learnpath/', include("vbv_lernwelt.learnpath.urls")),
|
||||||
path('api/completion/', include("vbv_lernwelt.completion.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)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# Static file serving when using Gunicorn + Uvicorn for local web socket development
|
# Static file serving when using Gunicorn + Uvicorn for local web socket development
|
||||||
urlpatterns += staticfiles_urlpatterns()
|
urlpatterns += staticfiles_urlpatterns()
|
||||||
|
|
||||||
if settings.ALLOW_LOCAL_LOGIN:
|
if settings.ALLOW_LOCAL_LOGIN:
|
||||||
urlpatterns += [path("login/", django_view_authentication_exempt(
|
urlpatterns += [
|
||||||
auth_views.LoginView.as_view(template_name="core/login.html"))),]
|
re_path(r'core/login/$', django_view_authentication_exempt(vue_login), name='vue_login'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# API URLS
|
# API URLS
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class CompletionApiTestCase(APITestCase):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.user = User.objects.get(username='student')
|
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):
|
def test_completeLearningContent_works(self):
|
||||||
learning_content = LearningContent.objects.get(title='Einleitung Circle "Anlayse"')
|
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):
|
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')
|
admin_group, created = group_model.objects.get_or_create(name='admin_group')
|
||||||
content_creator_grop, created = group_model.objects.get_or_create(name='content_creator_grop')
|
content_creator_grop, created = group_model.objects.get_or_create(name='content_creator_grop')
|
||||||
student_group, created = group_model.objects.get_or_create(name='student_group')
|
student_group, created = group_model.objects.get_or_create(name='student_group')
|
||||||
|
|
||||||
admin_password = default_password
|
def _create_student_user(email, first_name, last_name, avatar_url=''):
|
||||||
if not admin_password:
|
student_user, created = _get_or_create_user(
|
||||||
admin_password = 'admin'
|
user_model=user_model, username=email, password=default_password,
|
||||||
admin_user, created = _get_or_create_user(user_model=user_model, username='admin', password=admin_password)
|
)
|
||||||
admin_user.is_superuser = True
|
student_user.first_name = first_name
|
||||||
admin_user.is_staff = True
|
student_user.last_name = last_name
|
||||||
admin_user.groups.add(admin_group)
|
student_user.avatar_url = avatar_url
|
||||||
admin_user.save()
|
student_user.groups.add(student_group)
|
||||||
|
student_user.save()
|
||||||
|
|
||||||
student_user_password = default_password
|
def _create_admin_user(email, first_name, last_name, avatar_url=''):
|
||||||
if not student_user_password:
|
admin_user, created = _get_or_create_user(
|
||||||
student_user_password = 'student'
|
user_model=user_model, username=email, password=default_password
|
||||||
student_user, created = _get_or_create_user(user_model=user_model, username='student', password=student_user_password)
|
)
|
||||||
student_user.groups.add(student_group)
|
admin_user.is_superuser = True
|
||||||
student_user.save()
|
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):
|
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()
|
user = user_model.objects.filter(username=username).first()
|
||||||
|
|
||||||
if not user:
|
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
|
created = True
|
||||||
return user, created
|
return user, created
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,4 @@ def command(customer_language):
|
||||||
print("cypress reset data")
|
print("cypress reset data")
|
||||||
|
|
||||||
delete_default_learning_path()
|
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):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0001_initial"),
|
("core", "0002_user_model"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
@ -10,6 +10,8 @@ class User(AbstractUser):
|
||||||
"""
|
"""
|
||||||
# FIXME: look into it...
|
# FIXME: look into it...
|
||||||
# objects = UserManager()
|
# objects = UserManager()
|
||||||
|
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
||||||
|
email = models.EmailField('email address', unique=True)
|
||||||
|
|
||||||
|
|
||||||
class SecurityRequestResponseLog(models.Model):
|
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 structlog.types import EventDict
|
||||||
|
|
||||||
|
|
||||||
#from .models import User
|
|
||||||
|
|
||||||
def structlog_add_app_info(
|
def structlog_add_app_info(
|
||||||
logger: logging.Logger, method_name: str, event_dict: EventDict
|
logger: logging.Logger, method_name: str, event_dict: EventDict
|
||||||
) -> EventDict:
|
) -> EventDict:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import structlog
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
|
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
@ -11,8 +13,12 @@ from ratelimit.decorators import ratelimit
|
||||||
from rest_framework import authentication
|
from rest_framework import authentication
|
||||||
from rest_framework.decorators import api_view, authentication_classes, permission_classes
|
from rest_framework.decorators import api_view, authentication_classes, permission_classes
|
||||||
from rest_framework.permissions import IsAdminUser
|
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.middleware.auth import django_view_authentication_exempt
|
||||||
|
from vbv_lernwelt.core.serializers import UserSerializer
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@django_view_authentication_exempt
|
@django_view_authentication_exempt
|
||||||
|
|
@ -37,6 +43,32 @@ def vue_home(request):
|
||||||
return HttpResponse(content)
|
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):
|
def permission_denied_view(request, exception):
|
||||||
return render(request, "403.html", status=403)
|
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()
|
@click.command()
|
||||||
def 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
|
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:
|
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()
|
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)
|
# circle_7 = CircleFactory.create(title="Prüfungsvorbereitung", parent=lp, topic=tp)
|
||||||
|
|
||||||
# locales
|
# locales
|
||||||
locale_de = Locale.objects.get(language_code='de-CH')
|
if not skip_locales:
|
||||||
locale_fr, _ = Locale.objects.get_or_create(language_code='fr-CH')
|
locale_de = Locale.objects.get(language_code='de-CH')
|
||||||
LocaleSynchronization.objects.get_or_create(
|
locale_fr, _ = Locale.objects.get_or_create(language_code='fr-CH')
|
||||||
locale_id=locale_fr.id,
|
LocaleSynchronization.objects.get_or_create(
|
||||||
sync_from_id=locale_de.id
|
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_it, _ = Locale.objects.get_or_create(language_code='it-CH')
|
||||||
locale_id=locale_it.id,
|
LocaleSynchronization.objects.get_or_create(
|
||||||
sync_from_id=locale_de.id
|
locale_id=locale_it.id,
|
||||||
)
|
sync_from_id=locale_de.id
|
||||||
call_command('sync_locale_trees')
|
)
|
||||||
|
call_command('sync_locale_trees')
|
||||||
|
|
||||||
# all pages belong to 'admin' by default
|
# all pages belong to 'admin' by default
|
||||||
Page.objects.update(owner=user)
|
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