From b3e59b3829f3e84be3a64cf71b5a9c43e70a0451 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Sun, 1 Sep 2024 18:49:50 +0200 Subject: [PATCH 1/2] remove unused view `GET user//profile_upload` --- core/urls.py | 5 ----- core/views/user.py | 41 ++--------------------------------------- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/core/urls.py b/core/urls.py index 5a62ed65..32f81a2b 100644 --- a/core/urls.py +++ b/core/urls.py @@ -127,11 +127,6 @@ urlpatterns = [ UserUpdateProfileView.as_view(), name="user_edit", ), - path( - "user//profile_upload/", - UserUploadProfilePictView.as_view(), - name="user_profile_upload", - ), path("user//clubs/", UserClubView.as_view(), name="user_clubs"), path( "user//prefs/", diff --git a/core/views/user.py b/core/views/user.py index 27602d22..0c695e8c 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -31,7 +31,7 @@ from django.conf import settings from django.contrib.auth import login, views from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.mixins import LoginRequiredMixin -from django.core.exceptions import PermissionDenied, ValidationError +from django.core.exceptions import PermissionDenied from django.forms import CheckboxSelectMultiple from django.forms.models import modelform_factory from django.http import Http404, HttpResponse @@ -52,7 +52,7 @@ from django.views.generic.dates import MonthMixin, YearMixin from django.views.generic.edit import FormView, UpdateView from honeypot.decorators import check_honeypot -from core.models import Gift, Preferences, SithFile, User +from core.models import Gift, Preferences, User from core.views import ( CanEditMixin, CanEditPropMixin, @@ -561,43 +561,6 @@ class UserListView(ListView, CanEditPropMixin): template_name = "core/user_list.jinja" -class UserUploadProfilePictView(CanEditMixin, DetailView): - """Handle the upload of the profile picture taken with webcam in navigator.""" - - model = User - pk_url_kwarg = "user_id" - template_name = "core/user_edit.jinja" - - def post(self, request, *args, **kwargs): - from io import BytesIO - - from PIL import Image - - from core.utils import resize_image - - self.object = self.get_object() - if self.object.profile_pict: - raise ValidationError(_("User already has a profile picture")) - f = request.FILES["new_profile_pict"] - parent = SithFile.objects.filter(parent=None, name="profiles").first() - name = str(self.object.id) + "_profile.jpg" # Webcamejs uploads JPGs - im = Image.open(BytesIO(f.read())) - new_file = SithFile( - parent=parent, - name=name, - file=resize_image(im, 400, f.content_type.split("/")[-1]), - owner=self.object, - is_folder=False, - mime_type=f.content_type, - size=f.size, - ) - new_file.file.name = name - new_file.save() - self.object.profile_pict = new_file - self.object.save() - return redirect("core:user_edit", user_id=self.object.id) - - class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): """Edit a user's profile.""" From dd07c374d742675eabd21d4238d04b1139c971b1 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Sun, 1 Sep 2024 19:05:54 +0200 Subject: [PATCH 2/2] convert uploaded images to webp --- core/management/commands/populate.py | 24 ++++++++++++------------ core/templates/core/user_edit.jinja | 6 +++--- core/utils.py | 10 +++++++--- core/views/files.py | 3 ++- core/views/forms.py | 10 +++------- sas/models.py | 22 ++++++++++++++-------- 6 files changed, 41 insertions(+), 34 deletions(-) diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 8bd53c6f..77131c98 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -234,10 +234,10 @@ Welcome to the wiki page! skia_profile = SithFile( parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), owner=skia, is_folder=False, - mime_type="image/jpeg", + mime_type="image/webp", size=skia_profile_path.stat().st_size, ) skia_profile.file.name = name @@ -368,10 +368,10 @@ Welcome to the wiki page! richard_profile = SithFile( parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), owner=richard, is_folder=False, - mime_type="image/jpeg", + mime_type="image/webp", size=richard_profile_path.stat().st_size, ) richard_profile.file.name = name @@ -853,10 +853,10 @@ Welcome to the wiki page! sli_profile = SithFile( parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), owner=sli, is_folder=False, - mime_type="image/jpeg", + mime_type="image/webp", size=sli_profile_path.stat().st_size, ) sli_profile.file.name = name @@ -887,10 +887,10 @@ Welcome to the wiki page! krophil_profile = SithFile( parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), owner=krophil, is_folder=False, - mime_type="image/jpeg", + mime_type="image/webp", size=krophil_profile_path.stat().st_size, ) krophil_profile.file.name = name @@ -1217,13 +1217,13 @@ Welcome to the wiki page! parent=album, name=p.name, file=resize_image( - Image.open(BytesIO(p.read_bytes())), 1000, "JPEG" + Image.open(BytesIO(p.read_bytes())), 1000, "WEBP" ), owner=root, is_folder=False, is_in_sas=True, is_moderated=True, - mime_type="image/jpeg", + mime_type="image/webp", size=p.stat().st_size, ) pict.file.name = p.name @@ -1252,10 +1252,10 @@ Welcome to the wiki page! skia_profile = SithFile( parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), owner=skia, is_folder=False, - mime_type="image/jpeg", + mime_type="image/webp", size=skia_profile_path.stat().st_size, ) skia_profile.file.name = name diff --git a/core/templates/core/user_edit.jinja b/core/templates/core/user_edit.jinja index 37acfbe7..8cb8719b 100644 --- a/core/templates/core/user_edit.jinja +++ b/core/templates/core/user_edit.jinja @@ -164,7 +164,7 @@ /* Stop camera */ this.video.pause() this.video.srcObject.getTracks().forEach((track) => { - if (track.readyState == 'live') { + if (track.readyState === 'live') { track.stop(); } }); @@ -172,8 +172,8 @@ canvas.toBlob((blob) => { let file = new File( [blob], - "{% trans %}captured{% endtrans %}.png", - { type: "image/jpeg" }, + "{% trans %}captured{% endtrans %}.webp", + { type: "image/webp" }, ); let list = new DataTransfer(); diff --git a/core/utils.py b/core/utils.py index 5ed6cb1b..55e6afdd 100644 --- a/core/utils.py +++ b/core/utils.py @@ -102,13 +102,17 @@ def scale_dimension(width, height, long_edge): def resize_image(im, edge, img_format): (w, h) = im.size (width, height) = scale_dimension(w, h, long_edge=edge) + img_format = img_format.upper() content = BytesIO() # use the lanczos filter for antialiasing and discard the alpha channel - im = im.resize((width, height), Resampling.LANCZOS).convert("RGB") + im = im.resize((width, height), Resampling.LANCZOS) + if img_format == "JPEG": + # converting an image with an alpha channel to jpeg would cause a crash + im = im.convert("RGB") try: im.save( fp=content, - format=img_format.upper(), + format=img_format, quality=90, optimize=True, progressive=True, @@ -117,7 +121,7 @@ def resize_image(im, edge, img_format): PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1] im.save( fp=content, - format=img_format.upper(), + format=img_format, quality=90, optimize=True, progressive=True, diff --git a/core/views/files.py b/core/views/files.py index fca46c7e..3d1151d0 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -12,6 +12,7 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +import mimetypes from urllib.parse import quote, urljoin # This file contains all the views that concern the page model @@ -77,7 +78,7 @@ def send_file( raise Http404 with open(filepath, "rb") as filename: response.content = FileWrapper(filename) - response["Content-Type"] = f.mime_type + response["Content-Type"] = mimetypes.guess_type(filepath)[0] response["Last-Modified"] = http_date(f.date.timestamp()) response["Content-Length"] = filepath.stat().st_size return response diff --git a/core/views/forms.py b/core/views/forms.py index baf6b20e..8bf12912 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -239,10 +239,6 @@ class UserProfileForm(forms.ModelForm): "quote": forms.Textarea, } - def generate_name(self, field_name, f): - field_name = field_name[:-4] - return field_name + str(self.instance.id) + "." + f.content_type.split("/")[-1] - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -278,11 +274,11 @@ class UserProfileForm(forms.ModelForm): im = Image.open(BytesIO(f.read())) new_file = SithFile( parent=parent, - name=self.generate_name(field, f), - file=resize_image(im, 400, f.content_type.split("/")[-1]), + name=f"{field.removesuffix('_pict')}_{self.instance.id}.webp", + file=resize_image(im, 400, "webp"), owner=self.instance, is_folder=False, - mime_type=f.content_type, + mime_type="image/wepb", size=f.size, moderator=self.instance, is_moderated=True, diff --git a/sas/models.py b/sas/models.py index af1b30ff..fbe10223 100644 --- a/sas/models.py +++ b/sas/models.py @@ -110,19 +110,25 @@ class Picture(SasFile): im = exif_auto_rotate(im) except: pass - file = resize_image(im, max(im.size), self.mime_type.split("/")[-1]) - thumb = resize_image(im, 200, self.mime_type.split("/")[-1]) - compressed = resize_image(im, 1200, self.mime_type.split("/")[-1]) + # convert the compressed image and the thumbnail into webp + # The original image keeps its original type, because it's not + # meant to be shown on the website, but rather to keep the real image + # for less frequent cases (like downloading the pictures of an user) + extension = self.mime_type.split("/")[-1] + file = resize_image(im, max(im.size), extension) + thumb = resize_image(im, 200, "webp") + compressed = resize_image(im, 1200, "webp") if overwrite: self.file.delete() self.thumbnail.delete() self.compressed.delete() + new_extension_name = self.name.removesuffix(extension) + "webp" self.file = file self.file.name = self.name self.thumbnail = thumb - self.thumbnail.name = self.name + self.thumbnail.name = new_extension_name self.compressed = compressed - self.compressed.name = self.name + self.compressed.name = new_extension_name self.save() def rotate(self, degree): @@ -224,9 +230,9 @@ class Album(SasFile): .first() ) if p and p.file: - im = Image.open(BytesIO(p.file.read())) - self.file = resize_image(im, 200, "jpeg") - self.file.name = self.name + "/thumb.jpg" + image = resize_image(Image.open(BytesIO(p.file.read())), 200, "webp") + self.file = image + self.file.name = f"{self.name}/thumb.webp" self.save()