diff --git a/client/src/pages/onboarding/AccountProfile.vue b/client/src/pages/onboarding/AccountProfile.vue
index 1eb9bb78..31b982b8 100644
--- a/client/src/pages/onboarding/AccountProfile.vue
+++ b/client/src/pages/onboarding/AccountProfile.vue
@@ -6,6 +6,7 @@ import { useUserStore } from "@/stores/user";
import { useRoute } from "vue-router";
import { useTranslation } from "i18next-vue";
import { profileNextRoute, useEntities } from "@/services/onboarding";
+import AvatarImage from "@/components/ui/AvatarImage.vue";
const { t } = useTranslation();
@@ -36,17 +37,23 @@ const validOrganisation = computed(() => {
return selectedOrganisation.value.id !== 0;
});
-/* TODO: We do this later (not in the first release)
-const {
- upload: avatarUpload,
- loading: avatarLoading,
- error: avatarError,
- fileInfo: avatarFileInfo,
-} = useFileUpload();
+const avatarError = ref(false);
+const avatarLoading = ref(false);
-watch(avatarFileInfo, (info) => {
- console.log("fileInfo changed", info);
-})*/
+async function avatarUpload(e: Event) {
+ 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) => {
await user.setUserOrganisation(organisation.id);
@@ -74,7 +81,6 @@ const nextRoute = computed(() => {
-
diff --git a/client/src/services/files.ts b/client/src/services/files.ts
index 37d18cfd..169461ff 100644
--- a/client/src/services/files.ts
+++ b/client/src/services/files.ts
@@ -24,27 +24,30 @@ export async function uploadFile(fileData: FileData, file: File) {
if (fileData.fields) {
return s3Upload(fileData, file);
} 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();
formData.append("file", file);
- const headers = {
+ const headers: HeadersInit = {
Accept: "application/json",
- } as HeadersInit;
+ };
+
+ const csrfToken = getCookieValue("csrftoken");
+ if (csrfToken) {
+ headers["X-CSRFToken"] = csrfToken;
+ }
const options = {
method: "POST",
headers: headers,
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) {
diff --git a/client/src/stores/user.ts b/client/src/stores/user.ts
index 1c7c41c4..1243cb19 100644
--- a/client/src/stores/user.ts
+++ b/client/src/stores/user.ts
@@ -2,6 +2,7 @@ import log from "loglevel";
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
import { setI18nLanguage } from "@/i18nextWrapper";
+import { directUpload } from "@/services/files";
import dayjs from "dayjs";
import { defineStore } from "pinia";
@@ -150,5 +151,9 @@ export const useUserStore = defineStore({
this.$state.organisation = organisation;
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;
+ },
},
});
diff --git a/server/config/urls.py b/server/config/urls.py
index f7d27476..6cd5c4ad 100644
--- a/server/config/urls.py
+++ b/server/config/urls.py
@@ -12,7 +12,7 @@ from django_ratelimit.exceptions import Ratelimited
from graphene_django.views import GraphQLView
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.core.middleware.auth import django_view_authentication_exempt
from vbv_lernwelt.core.schema import schema
@@ -54,6 +54,7 @@ from vbv_lernwelt.importer.views import (
coursesessions_trainers_import,
t2l_sync,
)
+from vbv_lernwelt.media_files.views import user_image
from vbv_lernwelt.notify.views import email_notification_settings
from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls
@@ -99,6 +100,7 @@ urlpatterns = [
# user management
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/avatar/$', post_avatar, name='post_avatar'),
re_path(r'api/core/entities/$', list_entities, name='list_entities'),
re_path(r'api/core/login/$', django_view_authentication_exempt(vue_login),
@@ -140,6 +142,8 @@ urlpatterns = [
name="request_assignment_completion_status"),
# documents
+ path("api/core/userimage/", user_image, name="user_image"),
+
# TODO: remfactor to files app
path(r'api/core/document/start/', document_upload_start,
name='file_upload_start'),
diff --git a/server/vbv_lernwelt/api/user.py b/server/vbv_lernwelt/api/user.py
index 2ff2b3d3..b984ba23 100644
--- a/server/vbv_lernwelt/api/user.py
+++ b/server/vbv_lernwelt/api/user.py
@@ -5,10 +5,10 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from vbv_lernwelt.core.serializers import UserSerializer
-
from vbv_lernwelt.course.models import Course, CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.learning_mentor.models import LearningMentor
+from vbv_lernwelt.media_files.models import UserImage
@api_view(["GET", "PUT"])
@@ -59,3 +59,17 @@ def get_cockpit_type(request, course_id: int):
cockpit_type = None
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})
diff --git a/server/vbv_lernwelt/core/admin.py b/server/vbv_lernwelt/core/admin.py
index 7171b3bb..2fb286cc 100644
--- a/server/vbv_lernwelt/core/admin.py
+++ b/server/vbv_lernwelt/core/admin.py
@@ -43,7 +43,7 @@ class UserAdmin(auth_admin.UserAdmin):
},
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
- (_("Profile"), {"fields": ("organisation", "language")}),
+ (_("Profile"), {"fields": ("organisation", "language", "avatar")}),
(_("Additional data"), {"fields": ("additional_json_data",)}),
)
list_display = [
diff --git a/server/vbv_lernwelt/core/create_default_users.py b/server/vbv_lernwelt/core/create_default_users.py
index aef8516d..a401ba6c 100644
--- a/server/vbv_lernwelt/core/create_default_users.py
+++ b/server/vbv_lernwelt/core/create_default_users.py
@@ -1,7 +1,11 @@
+from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import Group, Permission
+from django.core.files import File
from environs import Env
+from vbv_lernwelt.media_files.models import UserImage
+
env = Env()
env.read_env()
@@ -24,52 +28,54 @@ default_users = [
"email": "student",
"first_name": "Student",
"last_name": "Meier",
- "avatar_url": "/static/avatars/avatar_iterativ.png",
+ "avatar_image": "avatar_iterativ.png",
},
{
"email": "daniel.egger@iterativ.ch",
"first_name": "Daniel",
"last_name": "Egger",
- "avatar_url": "/static/avatars/avatar_iterativ.png",
+ "avatar_image": "avatar_iterativ.png",
},
{
"email": "axel.manderbach@lernetz.ch",
"first_name": "Axel",
"last_name": "Manderbach",
- "avatar_url": "/static/avatars/avatar_axel.jpg",
+ "avatar_image": "avatar_axel.jpg",
},
{
"email": "christoph.bosshard@vbv-afa.ch",
"first_name": "Christoph",
"last_name": "Bosshard",
- "avatar_url": "/static/avatars/avatar_christoph.png",
+ "avatar_image": "avatar_christoph.png",
"password": "myvbv1234",
},
{
"email": "alexandra.vangelista@lernetz.ch",
"first_name": "Alexandra",
"last_name": "Vangelista",
- "avatar_url": "/static/avatars/avatar_alexandra.png",
+ "avatar_image": "avatar_alexandra.png",
"password": "myvbv1234",
},
{
"email": "chantal.rosenberg@vbv-afa.ch",
"first_name": "Chantal",
"last_name": "Rosenberg",
- "avatar_url": "/static/avatars/avatar_chantal.png",
+ "avatar_image": "avatar_chantal.png",
"password": "myvbv1234",
},
{
"email": "bianca.muster@eiger-versicherungen.ch",
"first_name": "Bianca",
"last_name": "Muster",
- "avatar_url": "/static/avatars/avatar_bianca.png",
+ "avatar_image": "avatar_bianca.png",
"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")
_content_creator_grop, _created = Group.objects.get_or_create(
name="content_creator_grop"
@@ -81,9 +87,9 @@ def create_default_users(default_password="test"):
email,
first_name,
last_name,
- avatar_url,
language,
password,
+ avatar_image: str = None,
):
user, _ = User.objects.get_or_create(
id=_id,
@@ -92,25 +98,33 @@ def create_default_users(default_password="test"):
language=language,
first_name=first_name,
last_name=last_name,
- avatar_url=avatar_url,
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
def _create_student_user(
email,
first_name,
last_name,
- avatar_url="",
password=default_password,
language="de",
+ avatar_image=None,
id=None,
):
student_user = _create_user(
email=email,
first_name=first_name,
last_name=last_name,
- avatar_url=avatar_url,
+ avatar_image=avatar_image,
language=language,
password=password,
_id=id,
@@ -120,13 +134,18 @@ def create_default_users(default_password="test"):
student_user.save()
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(
email=email,
first_name=first_name,
last_name=last_name,
- avatar_url=avatar_url,
+ avatar_image=avatar_image,
password=password,
language="de",
_id=id,
@@ -145,7 +164,7 @@ def create_default_users(default_password="test"):
email=email,
first_name=first_name,
last_name=last_name,
- avatar_url="",
+ avatar_image=None,
language="de",
password=password,
)
@@ -158,7 +177,7 @@ def create_default_users(default_password="test"):
email="info@iterativ.ch",
first_name="Info",
last_name="Iterativ",
- avatar_url="/static/avatars/avatar_iterativ.png",
+ avatar_image="avatar_iterativ.png",
password=env("IT_DEFAULT_ADMIN_PASSWORD", default_password),
)
@@ -166,7 +185,7 @@ def create_default_users(default_password="test"):
email="admin",
first_name="Peter",
last_name="Adminson",
- avatar_url="/static/avatars/avatar_iterativ.png",
+ avatar_image="avatar_iterativ.png",
id=ADMIN_USER_ID,
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",
first_name="Christa",
last_name="von Allmen",
- avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
+ avatar_image="uk1.patrizia.huggel.jpg",
)
_create_student_user(
email="expert-vv.expert3@eiger-versicherungen.ch",
@@ -199,53 +218,53 @@ def create_default_users(default_password="test"):
email="patrizia.huggel@eiger-versicherungen.ch",
first_name="Patrizia",
last_name="Huggel",
- avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
+ avatar_image="uk1.patrizia.huggel.jpg",
password="myvbv1234",
)
_create_student_user(
email="andreas.feuz@eiger-versicherungen.ch",
first_name="Andreas",
last_name="Feuz",
- avatar_url="/static/avatars/uk1.daniel.tanaka.jpg",
+ avatar_image="uk1.daniel.tanaka.jpg",
password="myvbv1234",
)
_create_student_user(
email="daniel.tanaka@eiger-versicherung.ch",
first_name="Daniel",
last_name="Tanaka",
- avatar_url="/static/avatars/uk1.daniel.tanaka.jpg",
+ avatar_image="uk1.daniel.tanaka.jpg",
)
_create_student_user(
email="maria.spini@eiger-versicherung.ch",
first_name="Maria",
last_name="Spini",
- avatar_url="/static/avatars/uk1.maria.spini.jpg",
+ avatar_image="uk1.maria.spini.jpg",
)
_create_student_user(
email="christian.koller@eiger-versicherung.ch",
first_name="Christian",
last_name="Koller",
- avatar_url="/static/avatars/uk1.christian.koller.jpg",
+ avatar_image="uk1.christian.koller.jpg",
)
_create_student_user(
email="michael.meier@example.com",
first_name="Michael",
last_name="Meier",
- avatar_url="/static/avatars/uk1.michael.meier.jpg",
+ avatar_image="uk1.michael.meier.jpg",
password="myvbv1234",
)
_create_student_user(
email="lina.egger@example.com",
first_name="Lina",
last_name="Egger",
- avatar_url="/static/avatars/uk1.lina.egger.jpg",
+ avatar_image="uk1.lina.egger.jpg",
password="myvbv1234",
)
_create_student_user(
email="evelyn.schmid@example.com",
first_name="Evelyn",
last_name="Schmid",
- avatar_url="/static/avatars/uk1.evelyn.schmid.jpg",
+ avatar_image="uk1.evelyn.schmid.jpg",
)
_create_student_user(
@@ -272,7 +291,7 @@ def create_default_users(default_password="test"):
email="luca.dupont@assurance.ch",
first_name="Luca",
last_name="Dupont",
- avatar_url="/static/avatars/uk1.michael.meier.jpg",
+ avatar_image="uk1.michael.meier.jpg",
password="myafa1234",
language="fr",
)
@@ -280,7 +299,7 @@ def create_default_users(default_password="test"):
email="patrick.muster@eiger-versicherungen.ch",
first_name="Patrick",
last_name="Muster",
- avatar_url="/static/avatars/uk1.michael.meier.jpg",
+ avatar_image="uk1.michael.meier.jpg",
password="myvbv1234",
language="de",
)
@@ -288,7 +307,7 @@ def create_default_users(default_password="test"):
email="geraldine.kolly@assurance.ch",
first_name="Géraldine",
last_name="Kolly",
- avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
+ avatar_image="uk1.patrizia.huggel.jpg",
password="myafa1234",
language="fr",
)
@@ -299,35 +318,35 @@ def create_default_users(default_password="test"):
email="test-trainer1@example.com",
first_name="Test",
last_name="Trainer1",
- avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
+ avatar_image="uk1.patrizia.huggel.jpg",
)
_create_student_user(
id=TEST_TRAINER2_USER_ID,
email="test-trainer2@example.com",
first_name="Test",
last_name="Trainer2",
- avatar_url="/static/avatars/uk1.christian.koller.jpg",
+ avatar_image="uk1.christian.koller.jpg",
)
_create_student_user(
id=TEST_STUDENT1_USER_ID,
email="test-student1@example.com",
first_name="Test",
last_name="Student1",
- avatar_url="/static/avatars/uk1.michael.meier.jpg",
+ avatar_image="uk1.michael.meier.jpg",
)
_create_student_user(
id=TEST_STUDENT2_USER_ID,
email="test-student2@example.com",
first_name="Test",
last_name="Student2",
- avatar_url="/static/avatars/uk1.lina.egger.jpg",
+ avatar_image="uk1.lina.egger.jpg",
)
_create_student_user(
id=TEST_STUDENT3_USER_ID,
email="test-student3@example.com",
first_name="Test",
last_name="Student3",
- avatar_url="/static/avatars/uk1.christian.koller.jpg",
+ avatar_image="uk1.christian.koller.jpg",
)
_create_staff_user(
email="matthias.wirth@vbv-afa.ch",
@@ -341,7 +360,6 @@ def create_default_users(default_password="test"):
last_name="Regionalleiter",
password=default_password,
language="de",
- avatar_url="",
)
_create_user(
_id=TEST_MENTOR1_USER_ID,
@@ -350,7 +368,6 @@ def create_default_users(default_password="test"):
last_name="Mentor",
password=default_password,
language="de",
- avatar_url="",
)
diff --git a/server/vbv_lernwelt/core/graphql/types.py b/server/vbv_lernwelt/core/graphql/types.py
index 961275d6..25a22b53 100644
--- a/server/vbv_lernwelt/core/graphql/types.py
+++ b/server/vbv_lernwelt/core/graphql/types.py
@@ -1,5 +1,6 @@
import json
+from graphene import String
from graphene.types.generic import GenericScalar
from graphene_django import DjangoObjectType
@@ -7,6 +8,8 @@ from vbv_lernwelt.core.models import User
class UserObjectType(DjangoObjectType):
+ avatar_url = String(source="avatar_url")
+
class Meta:
model = User
fields = (
diff --git a/server/vbv_lernwelt/core/management/commands/create_default_users.py b/server/vbv_lernwelt/core/management/commands/create_default_users.py
index 2053c336..b5daf874 100644
--- a/server/vbv_lernwelt/core/management/commands/create_default_users.py
+++ b/server/vbv_lernwelt/core/management/commands/create_default_users.py
@@ -6,4 +6,4 @@ from vbv_lernwelt.core.create_default_users import create_default_users
@click.command()
def command():
print("Creating default users.")
- create_default_users()
+ create_default_users(set_avatar=True)
diff --git a/server/vbv_lernwelt/core/migrations/0004_auto_20240108_0943.py b/server/vbv_lernwelt/core/migrations/0004_auto_20240108_0943.py
new file mode 100644
index 00000000..f56c3a82
--- /dev/null
+++ b/server/vbv_lernwelt/core/migrations/0004_auto_20240108_0943.py
@@ -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",
+ ),
+ ),
+ ]
diff --git a/server/vbv_lernwelt/core/models.py b/server/vbv_lernwelt/core/models.py
index 4895d2d3..d8a9bb2c 100644
--- a/server/vbv_lernwelt/core/models.py
+++ b/server/vbv_lernwelt/core/models.py
@@ -3,6 +3,7 @@ import uuid
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models import JSONField
+from django.urls import reverse
class Organisation(models.Model):
@@ -34,9 +35,14 @@ class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
- avatar_url = models.CharField(
- max_length=254, blank=True, default="/static/avatars/myvbv-default-avatar.png"
+ avatar = models.ForeignKey(
+ "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)
sso_id = models.UUIDField(
"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
)
+ @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):
label = models.CharField(max_length=255, blank=True, default="")
diff --git a/server/vbv_lernwelt/media_files/tests/test_user_images.py b/server/vbv_lernwelt/media_files/tests/test_user_images.py
new file mode 100644
index 00000000..adb796b6
--- /dev/null
+++ b/server/vbv_lernwelt/media_files/tests/test_user_images.py
@@ -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)
diff --git a/server/vbv_lernwelt/media_files/views.py b/server/vbv_lernwelt/media_files/views.py
new file mode 100644
index 00000000..96d624a4
--- /dev/null
+++ b/server/vbv_lernwelt/media_files/views.py
@@ -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
+ )