Merged in feature/VBV-599-avatar-upload (pull request #265)
Avatar Upload im onboarding Approved-by: Christian Cueni
This commit is contained in:
commit
67caf0074e
|
|
@ -6,6 +6,7 @@ import { useUserStore } from "@/stores/user";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { profileNextRoute, useEntities } from "@/services/onboarding";
|
import { profileNextRoute, useEntities } from "@/services/onboarding";
|
||||||
|
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -36,17 +37,23 @@ const validOrganisation = computed(() => {
|
||||||
return selectedOrganisation.value.id !== 0;
|
return selectedOrganisation.value.id !== 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
/* TODO: We do this later (not in the first release)
|
const avatarError = ref(false);
|
||||||
const {
|
const avatarLoading = ref(false);
|
||||||
upload: avatarUpload,
|
|
||||||
loading: avatarLoading,
|
|
||||||
error: avatarError,
|
|
||||||
fileInfo: avatarFileInfo,
|
|
||||||
} = useFileUpload();
|
|
||||||
|
|
||||||
watch(avatarFileInfo, (info) => {
|
async function avatarUpload(e: Event) {
|
||||||
console.log("fileInfo changed", info);
|
const { files } = e.target as HTMLInputElement;
|
||||||
})*/
|
if (!files?.length) return;
|
||||||
|
avatarLoading.value = true;
|
||||||
|
avatarError.value = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await user.setUserAvatar(files[0]);
|
||||||
|
} catch (e) {
|
||||||
|
avatarError.value = true;
|
||||||
|
} finally {
|
||||||
|
avatarLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(selectedOrganisation, async (organisation) => {
|
watch(selectedOrganisation, async (organisation) => {
|
||||||
await user.setUserOrganisation(organisation.id);
|
await user.setUserOrganisation(organisation.id);
|
||||||
|
|
@ -74,7 +81,6 @@ const nextRoute = computed(() => {
|
||||||
|
|
||||||
<ItDropdownSelect v-model="selectedOrganisation" :items="organisations" />
|
<ItDropdownSelect v-model="selectedOrganisation" :items="organisations" />
|
||||||
|
|
||||||
<!--- TODO: We do this later (not in the first release)
|
|
||||||
<div class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
|
<div class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3">{{ $t("a.Profilbild") }}</h3>
|
<h3 class="mb-3">{{ $t("a.Profilbild") }}</h3>
|
||||||
|
|
@ -103,7 +109,6 @@ const nextRoute = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
<AvatarImage :loading="avatarLoading" :image-url="user.avatar_url" />
|
<AvatarImage :loading="avatarLoading" :image-url="user.avatar_url" />
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
|
|
||||||
|
|
@ -24,27 +24,30 @@ export async function uploadFile(fileData: FileData, file: File) {
|
||||||
if (fileData.fields) {
|
if (fileData.fields) {
|
||||||
return s3Upload(fileData, file);
|
return s3Upload(fileData, file);
|
||||||
} else {
|
} else {
|
||||||
return directUpload(fileData, file);
|
return directUpload(fileData.url, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function directUpload(fileData: FileData, file: File) {
|
export function directUpload(url: string, file: File) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
|
|
||||||
const headers = {
|
const headers: HeadersInit = {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
} as HeadersInit;
|
};
|
||||||
|
|
||||||
|
const csrfToken = getCookieValue("csrftoken");
|
||||||
|
if (csrfToken) {
|
||||||
|
headers["X-CSRFToken"] = csrfToken;
|
||||||
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: formData,
|
body: formData,
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
|
||||||
options.headers["X-CSRFToken"] = getCookieValue("csrftoken");
|
|
||||||
|
|
||||||
return handleUpload(fileData.url, options);
|
return handleUpload(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function s3Upload(fileData: FileData, file: File) {
|
function s3Upload(fileData: FileData, file: File) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import log from "loglevel";
|
||||||
|
|
||||||
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
|
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
|
||||||
import { setI18nLanguage } from "@/i18nextWrapper";
|
import { setI18nLanguage } from "@/i18nextWrapper";
|
||||||
|
import { directUpload } from "@/services/files";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
|
@ -150,5 +151,9 @@ export const useUserStore = defineStore({
|
||||||
this.$state.organisation = organisation;
|
this.$state.organisation = organisation;
|
||||||
await itPost("/api/core/me/", { organisation }, { method: "PUT" });
|
await itPost("/api/core/me/", { organisation }, { method: "PUT" });
|
||||||
},
|
},
|
||||||
|
async setUserAvatar(file: File) {
|
||||||
|
const r = await directUpload("/api/core/avatar/", file);
|
||||||
|
this.$state.avatar_url = r.url;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from django_ratelimit.exceptions import Ratelimited
|
||||||
from graphene_django.views import GraphQLView
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
from vbv_lernwelt.api.directory import list_entities
|
from vbv_lernwelt.api.directory import list_entities
|
||||||
from vbv_lernwelt.api.user import get_cockpit_type, me_user_view
|
from vbv_lernwelt.api.user import get_cockpit_type, me_user_view, post_avatar
|
||||||
from vbv_lernwelt.assignment.views import request_assignment_completion_status
|
from vbv_lernwelt.assignment.views import request_assignment_completion_status
|
||||||
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.schema import schema
|
from vbv_lernwelt.core.schema import schema
|
||||||
|
|
@ -54,6 +54,7 @@ from vbv_lernwelt.importer.views import (
|
||||||
coursesessions_trainers_import,
|
coursesessions_trainers_import,
|
||||||
t2l_sync,
|
t2l_sync,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.media_files.views import user_image
|
||||||
from vbv_lernwelt.notify.views import email_notification_settings
|
from vbv_lernwelt.notify.views import email_notification_settings
|
||||||
from wagtail import urls as wagtail_urls
|
from wagtail import urls as wagtail_urls
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
|
|
@ -99,6 +100,7 @@ urlpatterns = [
|
||||||
# user management
|
# user management
|
||||||
path("sso/", include("vbv_lernwelt.sso.urls")),
|
path("sso/", include("vbv_lernwelt.sso.urls")),
|
||||||
re_path(r'api/core/me/$', me_user_view, name='me_user_view'),
|
re_path(r'api/core/me/$', me_user_view, name='me_user_view'),
|
||||||
|
re_path(r'api/core/avatar/$', post_avatar, name='post_avatar'),
|
||||||
re_path(r'api/core/entities/$', list_entities, name='list_entities'),
|
re_path(r'api/core/entities/$', list_entities, name='list_entities'),
|
||||||
|
|
||||||
re_path(r'api/core/login/$', django_view_authentication_exempt(vue_login),
|
re_path(r'api/core/login/$', django_view_authentication_exempt(vue_login),
|
||||||
|
|
@ -140,6 +142,8 @@ urlpatterns = [
|
||||||
name="request_assignment_completion_status"),
|
name="request_assignment_completion_status"),
|
||||||
|
|
||||||
# documents
|
# documents
|
||||||
|
path("api/core/userimage/<int:image_id>", user_image, name="user_image"),
|
||||||
|
|
||||||
# TODO: remfactor to files app
|
# TODO: remfactor to files app
|
||||||
path(r'api/core/document/start/', document_upload_start,
|
path(r'api/core/document/start/', document_upload_start,
|
||||||
name='file_upload_start'),
|
name='file_upload_start'),
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from vbv_lernwelt.core.serializers import UserSerializer
|
from vbv_lernwelt.core.serializers import UserSerializer
|
||||||
|
|
||||||
from vbv_lernwelt.course.models import Course, CourseSessionUser
|
from vbv_lernwelt.course.models import Course, CourseSessionUser
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||||
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET", "PUT"])
|
@api_view(["GET", "PUT"])
|
||||||
|
|
@ -59,3 +59,17 @@ def get_cockpit_type(request, course_id: int):
|
||||||
cockpit_type = None
|
cockpit_type = None
|
||||||
|
|
||||||
return Response({"type": cockpit_type})
|
return Response({"type": cockpit_type})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def post_avatar(request):
|
||||||
|
if "file" not in request.FILES:
|
||||||
|
return Response(status=400)
|
||||||
|
|
||||||
|
request.user.avatar = UserImage.objects.create(
|
||||||
|
file=request.FILES["file"],
|
||||||
|
)
|
||||||
|
request.user.save()
|
||||||
|
|
||||||
|
return Response({"url": request.user.avatar_url})
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class UserAdmin(auth_admin.UserAdmin):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
||||||
(_("Profile"), {"fields": ("organisation", "language")}),
|
(_("Profile"), {"fields": ("organisation", "language", "avatar")}),
|
||||||
(_("Additional data"), {"fields": ("additional_json_data",)}),
|
(_("Additional data"), {"fields": ("additional_json_data",)}),
|
||||||
)
|
)
|
||||||
list_display = [
|
list_display = [
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.contrib.auth.models import Group, Permission
|
from django.contrib.auth.models import Group, Permission
|
||||||
|
from django.core.files import File
|
||||||
from environs import Env
|
from environs import Env
|
||||||
|
|
||||||
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
|
||||||
env = Env()
|
env = Env()
|
||||||
env.read_env()
|
env.read_env()
|
||||||
|
|
||||||
|
|
@ -24,52 +28,54 @@ default_users = [
|
||||||
"email": "student",
|
"email": "student",
|
||||||
"first_name": "Student",
|
"first_name": "Student",
|
||||||
"last_name": "Meier",
|
"last_name": "Meier",
|
||||||
"avatar_url": "/static/avatars/avatar_iterativ.png",
|
"avatar_image": "avatar_iterativ.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"email": "daniel.egger@iterativ.ch",
|
"email": "daniel.egger@iterativ.ch",
|
||||||
"first_name": "Daniel",
|
"first_name": "Daniel",
|
||||||
"last_name": "Egger",
|
"last_name": "Egger",
|
||||||
"avatar_url": "/static/avatars/avatar_iterativ.png",
|
"avatar_image": "avatar_iterativ.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"email": "axel.manderbach@lernetz.ch",
|
"email": "axel.manderbach@lernetz.ch",
|
||||||
"first_name": "Axel",
|
"first_name": "Axel",
|
||||||
"last_name": "Manderbach",
|
"last_name": "Manderbach",
|
||||||
"avatar_url": "/static/avatars/avatar_axel.jpg",
|
"avatar_image": "avatar_axel.jpg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"email": "christoph.bosshard@vbv-afa.ch",
|
"email": "christoph.bosshard@vbv-afa.ch",
|
||||||
"first_name": "Christoph",
|
"first_name": "Christoph",
|
||||||
"last_name": "Bosshard",
|
"last_name": "Bosshard",
|
||||||
"avatar_url": "/static/avatars/avatar_christoph.png",
|
"avatar_image": "avatar_christoph.png",
|
||||||
"password": "myvbv1234",
|
"password": "myvbv1234",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"email": "alexandra.vangelista@lernetz.ch",
|
"email": "alexandra.vangelista@lernetz.ch",
|
||||||
"first_name": "Alexandra",
|
"first_name": "Alexandra",
|
||||||
"last_name": "Vangelista",
|
"last_name": "Vangelista",
|
||||||
"avatar_url": "/static/avatars/avatar_alexandra.png",
|
"avatar_image": "avatar_alexandra.png",
|
||||||
"password": "myvbv1234",
|
"password": "myvbv1234",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"email": "chantal.rosenberg@vbv-afa.ch",
|
"email": "chantal.rosenberg@vbv-afa.ch",
|
||||||
"first_name": "Chantal",
|
"first_name": "Chantal",
|
||||||
"last_name": "Rosenberg",
|
"last_name": "Rosenberg",
|
||||||
"avatar_url": "/static/avatars/avatar_chantal.png",
|
"avatar_image": "avatar_chantal.png",
|
||||||
"password": "myvbv1234",
|
"password": "myvbv1234",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"email": "bianca.muster@eiger-versicherungen.ch",
|
"email": "bianca.muster@eiger-versicherungen.ch",
|
||||||
"first_name": "Bianca",
|
"first_name": "Bianca",
|
||||||
"last_name": "Muster",
|
"last_name": "Muster",
|
||||||
"avatar_url": "/static/avatars/avatar_bianca.png",
|
"avatar_image": "avatar_bianca.png",
|
||||||
"password": "myvbv1234",
|
"password": "myvbv1234",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
AVATAR_DIR = settings.APPS_DIR / "static" / "avatars"
|
||||||
|
|
||||||
def create_default_users(default_password="test"):
|
|
||||||
|
def create_default_users(default_password="test", set_avatar=False):
|
||||||
admin_group, created = Group.objects.get_or_create(name="admin_group")
|
admin_group, created = Group.objects.get_or_create(name="admin_group")
|
||||||
_content_creator_grop, _created = Group.objects.get_or_create(
|
_content_creator_grop, _created = Group.objects.get_or_create(
|
||||||
name="content_creator_grop"
|
name="content_creator_grop"
|
||||||
|
|
@ -81,9 +87,9 @@ def create_default_users(default_password="test"):
|
||||||
email,
|
email,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
avatar_url,
|
|
||||||
language,
|
language,
|
||||||
password,
|
password,
|
||||||
|
avatar_image: str = None,
|
||||||
):
|
):
|
||||||
user, _ = User.objects.get_or_create(
|
user, _ = User.objects.get_or_create(
|
||||||
id=_id,
|
id=_id,
|
||||||
|
|
@ -92,25 +98,33 @@ def create_default_users(default_password="test"):
|
||||||
language=language,
|
language=language,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
avatar_url=avatar_url,
|
|
||||||
password=make_password(password),
|
password=make_password(password),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if avatar_image and set_avatar:
|
||||||
|
with open(AVATAR_DIR / avatar_image, "rb") as f:
|
||||||
|
image, _ = UserImage.objects.get_or_create(
|
||||||
|
file=File(f),
|
||||||
|
)
|
||||||
|
user.avatar = image
|
||||||
|
user.save()
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def _create_student_user(
|
def _create_student_user(
|
||||||
email,
|
email,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
avatar_url="",
|
|
||||||
password=default_password,
|
password=default_password,
|
||||||
language="de",
|
language="de",
|
||||||
|
avatar_image=None,
|
||||||
id=None,
|
id=None,
|
||||||
):
|
):
|
||||||
student_user = _create_user(
|
student_user = _create_user(
|
||||||
email=email,
|
email=email,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
avatar_url=avatar_url,
|
avatar_image=avatar_image,
|
||||||
language=language,
|
language=language,
|
||||||
password=password,
|
password=password,
|
||||||
_id=id,
|
_id=id,
|
||||||
|
|
@ -120,13 +134,18 @@ def create_default_users(default_password="test"):
|
||||||
student_user.save()
|
student_user.save()
|
||||||
|
|
||||||
def _create_admin_user(
|
def _create_admin_user(
|
||||||
email, first_name, last_name, avatar_url="", id=None, password=default_password
|
email,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
avatar_image=None,
|
||||||
|
id=None,
|
||||||
|
password=default_password,
|
||||||
):
|
):
|
||||||
admin_user = _create_user(
|
admin_user = _create_user(
|
||||||
email=email,
|
email=email,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
avatar_url=avatar_url,
|
avatar_image=avatar_image,
|
||||||
password=password,
|
password=password,
|
||||||
language="de",
|
language="de",
|
||||||
_id=id,
|
_id=id,
|
||||||
|
|
@ -145,7 +164,7 @@ def create_default_users(default_password="test"):
|
||||||
email=email,
|
email=email,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
avatar_url="",
|
avatar_image=None,
|
||||||
language="de",
|
language="de",
|
||||||
password=password,
|
password=password,
|
||||||
)
|
)
|
||||||
|
|
@ -158,7 +177,7 @@ def create_default_users(default_password="test"):
|
||||||
email="info@iterativ.ch",
|
email="info@iterativ.ch",
|
||||||
first_name="Info",
|
first_name="Info",
|
||||||
last_name="Iterativ",
|
last_name="Iterativ",
|
||||||
avatar_url="/static/avatars/avatar_iterativ.png",
|
avatar_image="avatar_iterativ.png",
|
||||||
password=env("IT_DEFAULT_ADMIN_PASSWORD", default_password),
|
password=env("IT_DEFAULT_ADMIN_PASSWORD", default_password),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -166,7 +185,7 @@ def create_default_users(default_password="test"):
|
||||||
email="admin",
|
email="admin",
|
||||||
first_name="Peter",
|
first_name="Peter",
|
||||||
last_name="Adminson",
|
last_name="Adminson",
|
||||||
avatar_url="/static/avatars/avatar_iterativ.png",
|
avatar_image="avatar_iterativ.png",
|
||||||
id=ADMIN_USER_ID,
|
id=ADMIN_USER_ID,
|
||||||
password=env("IT_DEFAULT_ADMIN_PASSWORD", default_password),
|
password=env("IT_DEFAULT_ADMIN_PASSWORD", default_password),
|
||||||
)
|
)
|
||||||
|
|
@ -183,7 +202,7 @@ def create_default_users(default_password="test"):
|
||||||
email="expert-vv.expert2@eiger-versicherungen.ch",
|
email="expert-vv.expert2@eiger-versicherungen.ch",
|
||||||
first_name="Christa",
|
first_name="Christa",
|
||||||
last_name="von Allmen",
|
last_name="von Allmen",
|
||||||
avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
|
avatar_image="uk1.patrizia.huggel.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="expert-vv.expert3@eiger-versicherungen.ch",
|
email="expert-vv.expert3@eiger-versicherungen.ch",
|
||||||
|
|
@ -199,53 +218,53 @@ def create_default_users(default_password="test"):
|
||||||
email="patrizia.huggel@eiger-versicherungen.ch",
|
email="patrizia.huggel@eiger-versicherungen.ch",
|
||||||
first_name="Patrizia",
|
first_name="Patrizia",
|
||||||
last_name="Huggel",
|
last_name="Huggel",
|
||||||
avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
|
avatar_image="uk1.patrizia.huggel.jpg",
|
||||||
password="myvbv1234",
|
password="myvbv1234",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="andreas.feuz@eiger-versicherungen.ch",
|
email="andreas.feuz@eiger-versicherungen.ch",
|
||||||
first_name="Andreas",
|
first_name="Andreas",
|
||||||
last_name="Feuz",
|
last_name="Feuz",
|
||||||
avatar_url="/static/avatars/uk1.daniel.tanaka.jpg",
|
avatar_image="uk1.daniel.tanaka.jpg",
|
||||||
password="myvbv1234",
|
password="myvbv1234",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="daniel.tanaka@eiger-versicherung.ch",
|
email="daniel.tanaka@eiger-versicherung.ch",
|
||||||
first_name="Daniel",
|
first_name="Daniel",
|
||||||
last_name="Tanaka",
|
last_name="Tanaka",
|
||||||
avatar_url="/static/avatars/uk1.daniel.tanaka.jpg",
|
avatar_image="uk1.daniel.tanaka.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="maria.spini@eiger-versicherung.ch",
|
email="maria.spini@eiger-versicherung.ch",
|
||||||
first_name="Maria",
|
first_name="Maria",
|
||||||
last_name="Spini",
|
last_name="Spini",
|
||||||
avatar_url="/static/avatars/uk1.maria.spini.jpg",
|
avatar_image="uk1.maria.spini.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="christian.koller@eiger-versicherung.ch",
|
email="christian.koller@eiger-versicherung.ch",
|
||||||
first_name="Christian",
|
first_name="Christian",
|
||||||
last_name="Koller",
|
last_name="Koller",
|
||||||
avatar_url="/static/avatars/uk1.christian.koller.jpg",
|
avatar_image="uk1.christian.koller.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="michael.meier@example.com",
|
email="michael.meier@example.com",
|
||||||
first_name="Michael",
|
first_name="Michael",
|
||||||
last_name="Meier",
|
last_name="Meier",
|
||||||
avatar_url="/static/avatars/uk1.michael.meier.jpg",
|
avatar_image="uk1.michael.meier.jpg",
|
||||||
password="myvbv1234",
|
password="myvbv1234",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="lina.egger@example.com",
|
email="lina.egger@example.com",
|
||||||
first_name="Lina",
|
first_name="Lina",
|
||||||
last_name="Egger",
|
last_name="Egger",
|
||||||
avatar_url="/static/avatars/uk1.lina.egger.jpg",
|
avatar_image="uk1.lina.egger.jpg",
|
||||||
password="myvbv1234",
|
password="myvbv1234",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="evelyn.schmid@example.com",
|
email="evelyn.schmid@example.com",
|
||||||
first_name="Evelyn",
|
first_name="Evelyn",
|
||||||
last_name="Schmid",
|
last_name="Schmid",
|
||||||
avatar_url="/static/avatars/uk1.evelyn.schmid.jpg",
|
avatar_image="uk1.evelyn.schmid.jpg",
|
||||||
)
|
)
|
||||||
|
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
|
|
@ -272,7 +291,7 @@ def create_default_users(default_password="test"):
|
||||||
email="luca.dupont@assurance.ch",
|
email="luca.dupont@assurance.ch",
|
||||||
first_name="Luca",
|
first_name="Luca",
|
||||||
last_name="Dupont",
|
last_name="Dupont",
|
||||||
avatar_url="/static/avatars/uk1.michael.meier.jpg",
|
avatar_image="uk1.michael.meier.jpg",
|
||||||
password="myafa1234",
|
password="myafa1234",
|
||||||
language="fr",
|
language="fr",
|
||||||
)
|
)
|
||||||
|
|
@ -280,7 +299,7 @@ def create_default_users(default_password="test"):
|
||||||
email="patrick.muster@eiger-versicherungen.ch",
|
email="patrick.muster@eiger-versicherungen.ch",
|
||||||
first_name="Patrick",
|
first_name="Patrick",
|
||||||
last_name="Muster",
|
last_name="Muster",
|
||||||
avatar_url="/static/avatars/uk1.michael.meier.jpg",
|
avatar_image="uk1.michael.meier.jpg",
|
||||||
password="myvbv1234",
|
password="myvbv1234",
|
||||||
language="de",
|
language="de",
|
||||||
)
|
)
|
||||||
|
|
@ -288,7 +307,7 @@ def create_default_users(default_password="test"):
|
||||||
email="geraldine.kolly@assurance.ch",
|
email="geraldine.kolly@assurance.ch",
|
||||||
first_name="Géraldine",
|
first_name="Géraldine",
|
||||||
last_name="Kolly",
|
last_name="Kolly",
|
||||||
avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
|
avatar_image="uk1.patrizia.huggel.jpg",
|
||||||
password="myafa1234",
|
password="myafa1234",
|
||||||
language="fr",
|
language="fr",
|
||||||
)
|
)
|
||||||
|
|
@ -299,35 +318,35 @@ def create_default_users(default_password="test"):
|
||||||
email="test-trainer1@example.com",
|
email="test-trainer1@example.com",
|
||||||
first_name="Test",
|
first_name="Test",
|
||||||
last_name="Trainer1",
|
last_name="Trainer1",
|
||||||
avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
|
avatar_image="uk1.patrizia.huggel.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
id=TEST_TRAINER2_USER_ID,
|
id=TEST_TRAINER2_USER_ID,
|
||||||
email="test-trainer2@example.com",
|
email="test-trainer2@example.com",
|
||||||
first_name="Test",
|
first_name="Test",
|
||||||
last_name="Trainer2",
|
last_name="Trainer2",
|
||||||
avatar_url="/static/avatars/uk1.christian.koller.jpg",
|
avatar_image="uk1.christian.koller.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
id=TEST_STUDENT1_USER_ID,
|
id=TEST_STUDENT1_USER_ID,
|
||||||
email="test-student1@example.com",
|
email="test-student1@example.com",
|
||||||
first_name="Test",
|
first_name="Test",
|
||||||
last_name="Student1",
|
last_name="Student1",
|
||||||
avatar_url="/static/avatars/uk1.michael.meier.jpg",
|
avatar_image="uk1.michael.meier.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
id=TEST_STUDENT2_USER_ID,
|
id=TEST_STUDENT2_USER_ID,
|
||||||
email="test-student2@example.com",
|
email="test-student2@example.com",
|
||||||
first_name="Test",
|
first_name="Test",
|
||||||
last_name="Student2",
|
last_name="Student2",
|
||||||
avatar_url="/static/avatars/uk1.lina.egger.jpg",
|
avatar_image="uk1.lina.egger.jpg",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
id=TEST_STUDENT3_USER_ID,
|
id=TEST_STUDENT3_USER_ID,
|
||||||
email="test-student3@example.com",
|
email="test-student3@example.com",
|
||||||
first_name="Test",
|
first_name="Test",
|
||||||
last_name="Student3",
|
last_name="Student3",
|
||||||
avatar_url="/static/avatars/uk1.christian.koller.jpg",
|
avatar_image="uk1.christian.koller.jpg",
|
||||||
)
|
)
|
||||||
_create_staff_user(
|
_create_staff_user(
|
||||||
email="matthias.wirth@vbv-afa.ch",
|
email="matthias.wirth@vbv-afa.ch",
|
||||||
|
|
@ -341,7 +360,6 @@ def create_default_users(default_password="test"):
|
||||||
last_name="Regionalleiter",
|
last_name="Regionalleiter",
|
||||||
password=default_password,
|
password=default_password,
|
||||||
language="de",
|
language="de",
|
||||||
avatar_url="",
|
|
||||||
)
|
)
|
||||||
_create_user(
|
_create_user(
|
||||||
_id=TEST_MENTOR1_USER_ID,
|
_id=TEST_MENTOR1_USER_ID,
|
||||||
|
|
@ -350,7 +368,6 @@ def create_default_users(default_password="test"):
|
||||||
last_name="Mentor",
|
last_name="Mentor",
|
||||||
password=default_password,
|
password=default_password,
|
||||||
language="de",
|
language="de",
|
||||||
avatar_url="",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from graphene import String
|
||||||
from graphene.types.generic import GenericScalar
|
from graphene.types.generic import GenericScalar
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
|
|
||||||
|
|
@ -7,6 +8,8 @@ from vbv_lernwelt.core.models import User
|
||||||
|
|
||||||
|
|
||||||
class UserObjectType(DjangoObjectType):
|
class UserObjectType(DjangoObjectType):
|
||||||
|
avatar_url = String(source="avatar_url")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = (
|
fields = (
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
@click.command()
|
@click.command()
|
||||||
def command():
|
def command():
|
||||||
print("Creating default users.")
|
print("Creating default users.")
|
||||||
create_default_users()
|
create_default_users(set_avatar=True)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.model_utils import migrate_avatars
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("media_files", "0001_initial"),
|
||||||
|
("core", "0003_organisations"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="avatar",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="Avatar image for the user",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="media_files.userimage",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_avatars),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="user",
|
||||||
|
name="avatar_url",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.files import File
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -144,3 +146,39 @@ def remove_organisations(apps=None, schema_editor=None):
|
||||||
Organisation.objects.filter(
|
Organisation.objects.filter(
|
||||||
organisation_id=org_id,
|
organisation_id=org_id,
|
||||||
).delete()
|
).delete()
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_avatars(apps=None, schema_editor=None):
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
if apps is None:
|
||||||
|
from vbv_lernwelt.core.models import User
|
||||||
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
else:
|
||||||
|
User = apps.get_model("core", "User")
|
||||||
|
UserImage = apps.get_model("media_files", "UserImage")
|
||||||
|
|
||||||
|
# Models created by Django migration don't contain methods of the parent model.
|
||||||
|
# We need to add them manually.
|
||||||
|
from wagtail.images.models import AbstractImage
|
||||||
|
|
||||||
|
UserImage.get_upload_to = AbstractImage.get_upload_to
|
||||||
|
|
||||||
|
avatar_dir = settings.APPS_DIR / "static" / "avatars"
|
||||||
|
|
||||||
|
for user in User.objects.all().exclude(
|
||||||
|
avatar_url="/static/avatars/myvbv-default-avatar.png"
|
||||||
|
):
|
||||||
|
if not user.avatar_url:
|
||||||
|
continue
|
||||||
|
|
||||||
|
avatar_file = user.avatar_url.split("/")[-1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(avatar_dir / avatar_file, "rb") as f:
|
||||||
|
image = UserImage.objects.create(
|
||||||
|
file=File(f),
|
||||||
|
)
|
||||||
|
user.avatar = image
|
||||||
|
user.save()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import uuid
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import JSONField
|
from django.db.models import JSONField
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
class Organisation(models.Model):
|
class Organisation(models.Model):
|
||||||
|
|
@ -34,9 +35,14 @@ class User(AbstractUser):
|
||||||
|
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
|
||||||
avatar_url = models.CharField(
|
avatar = models.ForeignKey(
|
||||||
max_length=254, blank=True, default="/static/avatars/myvbv-default-avatar.png"
|
"media_files.UserImage",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
help_text="Avatar image for the user",
|
||||||
)
|
)
|
||||||
|
|
||||||
email = models.EmailField("email address", unique=True)
|
email = models.EmailField("email address", unique=True)
|
||||||
sso_id = models.UUIDField(
|
sso_id = models.UUIDField(
|
||||||
"SSO subscriber ID", unique=True, null=True, blank=True, default=None
|
"SSO subscriber ID", unique=True, null=True, blank=True, default=None
|
||||||
|
|
@ -48,6 +54,16 @@ class User(AbstractUser):
|
||||||
Organisation, on_delete=models.SET_NULL, null=True, blank=True
|
Organisation, on_delete=models.SET_NULL, null=True, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def avatar_url(self):
|
||||||
|
if self.avatar:
|
||||||
|
filter_spec = "fill-400x400"
|
||||||
|
self.avatar.get_rendition(filter_spec)
|
||||||
|
url = reverse("user_image", kwargs={"image_id": self.avatar.id})
|
||||||
|
return f"{url}?filter={filter_spec}"
|
||||||
|
else:
|
||||||
|
return "/static/avatars/myvbv-default-avatar.png"
|
||||||
|
|
||||||
|
|
||||||
class SecurityRequestResponseLog(models.Model):
|
class SecurityRequestResponseLog(models.Model):
|
||||||
label = models.CharField(max_length=255, blank=True, default="")
|
label = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import skipIf
|
||||||
|
|
||||||
|
from django.core.files import File
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.creators.test_utils import create_user
|
||||||
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
|
||||||
|
TEST_IMAGE = Path(__file__).parent / "test_images" / "user1_profile.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
@skipIf(
|
||||||
|
os.environ.get("ENABLE_S3_STORAGE_UNIT_TESTS") is None,
|
||||||
|
"Only enable tests by setting ENABLE_S3_STORAGE_UNIT_TESTS=1",
|
||||||
|
)
|
||||||
|
class UserImageViewTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
self.user = create_user("supervisor")
|
||||||
|
|
||||||
|
with open(TEST_IMAGE, "rb") as f:
|
||||||
|
self.user_image, _ = UserImage.objects.get_or_create(
|
||||||
|
file=File(f, name=TEST_IMAGE.name),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_image(self):
|
||||||
|
# GIVEN
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
response = self._get_image(self.user_image.id, "fill-300x150")
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response["Content-Type"], "image/jpeg")
|
||||||
|
|
||||||
|
def test_invalid_id(self):
|
||||||
|
# GIVEN
|
||||||
|
image_id = 123456789
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
response = self._get_image(image_id)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_invalid_filter(self):
|
||||||
|
# GIVEN
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
filter_spec = "invalid-filter"
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
response = self._get_image(self.user_image.id, filter_spec)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(
|
||||||
|
response.content, f"Invalid filter spec: {filter_spec}".encode()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_image(self, image_id, filter_spec=None):
|
||||||
|
url = reverse("user_image", kwargs={"image_id": image_id})
|
||||||
|
if filter_spec:
|
||||||
|
url += f"?filter={filter_spec}"
|
||||||
|
|
||||||
|
return self.client.get(url)
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import imghdr
|
||||||
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpResponse, StreamingHttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from wagtail.images.exceptions import InvalidFilterSpecError
|
||||||
|
from wagtail.images.models import SourceImageIOError
|
||||||
|
|
||||||
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def user_image(request, image_id):
|
||||||
|
image = get_object_or_404(UserImage, id=image_id)
|
||||||
|
|
||||||
|
filter_spec = request.GET.get("filter", "original")
|
||||||
|
|
||||||
|
try:
|
||||||
|
rendition = image.get_rendition(filter_spec)
|
||||||
|
except SourceImageIOError:
|
||||||
|
return HttpResponse(
|
||||||
|
"Source image file not found", content_type="text/plain", status=410
|
||||||
|
)
|
||||||
|
except InvalidFilterSpecError:
|
||||||
|
return HttpResponse(
|
||||||
|
"Invalid filter spec: " + filter_spec,
|
||||||
|
content_type="text/plain",
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
rendition.file.open("rb")
|
||||||
|
image_format = imghdr.what(rendition.file)
|
||||||
|
return StreamingHttpResponse(
|
||||||
|
FileWrapper(rendition.file), content_type="image/" + image_format
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue