From 27e63b0d122a40f0bd7d19143c86e432f861fd6e Mon Sep 17 00:00:00 2001 From: imperosol Date: Thu, 23 Apr 2026 23:52:20 +0200 Subject: [PATCH] Automatically resize album thumbnail --- sas/forms.py | 29 ++++++++++++++++++++++++----- sas/models.py | 14 ++++---------- sas/templates/sas/macros.jinja | 8 ++++---- sas/views.py | 8 ++++---- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/sas/forms.py b/sas/forms.py index a478ce43..ff56b7a0 100644 --- a/sas/forms.py +++ b/sas/forms.py @@ -1,16 +1,22 @@ -from typing import Any +from pathlib import Path +from typing import TYPE_CHECKING, Any from django import forms from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +from PIL import Image from core.models import User +from core.utils import resize_image from core.views import MultipleImageField from core.views.forms import SelectDate from core.views.widgets.ajax_select import AutoCompleteSelectMultipleGroup from sas.models import Album, Picture, PictureModerationRequest from sas.widgets.ajax_select import AutoCompleteSelectAlbum +if TYPE_CHECKING: + from django.db.models.fields.files import FieldFile + class AlbumCreateForm(forms.ModelForm): class Meta: @@ -49,17 +55,30 @@ class AlbumEditForm(forms.ModelForm): class Meta: model = Album fields = ["name", "date", "file", "parent", "edit_groups"] - widgets = { - "edit_groups": AutoCompleteSelectMultipleGroup, - } + widgets = {"edit_groups": AutoCompleteSelectMultipleGroup, "date": SelectDate} name = forms.CharField(max_length=Album.NAME_MAX_LENGTH, label=_("file name")) - date = forms.DateField(label=_("Date"), widget=SelectDate, required=True) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) parent = forms.ModelChoiceField( Album.objects.all(), required=True, widget=AutoCompleteSelectAlbum ) + def clean_file(self): + # if a file was given in the form, resize it + f: FieldFile = self.cleaned_data["file"] + if self.errors or not f or "file" not in self.changed_data: + return f + f.file = resize_image(Image.open(f.file), 200, "WEBP") + return f + + def save(self, commit=True): # noqa: FBT002 + if self.instance.file: + self.instance.file.name = str(Path(self.instance.name) / "thumb.webp") + self.instance = super().save(commit=commit) + if not self.instance.file: + self.instance.generate_thumbnail() + return self.instance + class PictureModerationRequestForm(forms.ModelForm): """Form to create a PictureModerationRequest. diff --git a/sas/models.py b/sas/models.py index 64f6c15b..412526d2 100644 --- a/sas/models.py +++ b/sas/models.py @@ -110,7 +110,7 @@ class Picture(SasFile): def get_absolute_url(self): return reverse("sas:picture", kwargs={"picture_id": self.id}) - def generate_thumbnails(self, *, overwrite=False): + def generate_thumbnails(self): im = Image.open(BytesIO(self.file.read())) with contextlib.suppress(Exception): im = exif_auto_rotate(im) @@ -126,10 +126,6 @@ class Picture(SasFile): file = resize_image(im, max(im.size), extension, optimize=False) 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 = str(Path(self.name).with_suffix(".webp")) self.file = file self.file.name = self.name @@ -245,17 +241,15 @@ class Album(SasFile): return reverse("sas:album_preview", kwargs={"album_id": self.id}) def generate_thumbnail(self): - p = ( - self.children_pictures.order_by("?").first() - or self.children_albums.exclude(file=None) - .exclude(file="") + p = self.children_pictures.order_by("?").first() or ( + self.children_albums.exclude(Q(file=None) | Q(file="")) .order_by("?") .first() ) if p and p.file: image = resize_image(Image.open(BytesIO(p.file.read())), 200, "webp") self.file = image - self.file.name = f"{self.name}/thumb.webp" + self.file.name = str(Path(self.name) / "thumb.webp") self.save() diff --git a/sas/templates/sas/macros.jinja b/sas/templates/sas/macros.jinja index 36f76584..e0c21bce 100644 --- a/sas/templates/sas/macros.jinja +++ b/sas/templates/sas/macros.jinja @@ -2,19 +2,19 @@ {% if a.file %} {% set img = a.get_download_url() %} - {% set src = a.name %} + {% set alt = a.name %} {% elif a.children.filter(is_folder=False, is_moderated=True).exists() %} {% set picture = a.children.filter(is_folder=False).first().as_picture %} {% set img = picture.get_download_thumb_url() %} - {% set src = picture.name %} + {% set alt = picture.name %} {% else %} {% set img = static('core/img/sas.jpg') %} - {% set src = "sas.jpg" %} + {% set alt = "sas.jpg" %} {% endif %}
- {{ src }} + {{ alt }} {% if not a.is_moderated %}
 
{% trans %}To be moderated{% endtrans %}
diff --git a/sas/views.py b/sas/views.py index 160182e4..7e7f0ba2 100644 --- a/sas/views.py +++ b/sas/views.py @@ -16,6 +16,7 @@ from typing import Any from django.conf import settings from django.contrib.auth.mixins import PermissionRequiredMixin +from django.core.exceptions import PermissionDenied from django.db.models import Count, OuterRef, Subquery from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect @@ -152,10 +153,9 @@ class AlbumView(CanViewMixin, UseFragmentsMixin, DetailView): def post(self, request, *args, **kwargs): self.object = self.get_object() - if not self.object.file: - self.object.generate_thumbnail() - if request.user.can_edit(self.object): # Handle the copy-paste functions - FileView.handle_clipboard(request, self.object) + if not request.user.can_edit(self.object): + raise PermissionDenied + FileView.handle_clipboard(request, self.object) return HttpResponseRedirect(self.request.path) def get_fragment_data(self) -> dict[str, dict[str, Any]]: