feat: add avatar upload

This commit is contained in:
Reto Aebersold 2024-01-09 17:21:06 +01:00
parent 1f24801e72
commit 28445cf1a5
13 changed files with 260 additions and 61 deletions

View File

@ -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>

View File

@ -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) {

View 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;
},
}, },
}); });

View File

@ -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'),

View File

@ -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})

View File

@ -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 = [

View File

@ -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="",
) )

View File

@ -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 = (

View File

@ -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)

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.20 on 2024-01-08 08:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("media_files", "0001_initial"),
("core", "0003_organisations"),
]
operations = [
migrations.RemoveField(
model_name="user",
name="avatar_url",
),
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",
),
),
]

View File

@ -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="")

View File

@ -0,0 +1,64 @@
from pathlib import Path
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"
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)

View File

@ -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
)