From b98b882665c54301983942359870e9e0a1423fea Mon Sep 17 00:00:00 2001 From: relikd Date: Sun, 28 Jul 2024 22:41:04 +0200 Subject: [PATCH] ref: use thumbnail image instead of size limit --- backend/app/migrations/0001_initial.py | 2 +- backend/app/models/place.py | 6 +-- backend/common/form/img_with_preview.py | 50 ++++++++++--------------- backend/requirements.txt | 2 +- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/backend/app/migrations/0001_initial.py b/backend/app/migrations/0001_initial.py index 44f62f8..f854298 100644 --- a/backend/app/migrations/0001_initial.py +++ b/backend/app/migrations/0001_initial.py @@ -69,7 +69,7 @@ class Migration(migrations.Migration): ('sort', models.IntegerField(default=0, verbose_name='Sortierung')), ('isExtended', models.BooleanField(verbose_name='Bis 1.1.2025 verstecken')), ('title', models.CharField(max_length=100, verbose_name='Titel')), - ('image', common.form.img_with_preview.FileWithImagePreview(blank=True, null=True, upload_to=app.models.place.overwrite_img_upload, verbose_name='Bild')), + ('image', common.form.img_with_preview.ThumbnailImageField(blank=True, null=True, upload_to=app.models.place.overwrite_img_upload, verbose_name='Bild')), ('audio', common.form.audio_file.AudioFileField(blank=True, null=True, upload_to=app.models.place.overwrite_audio_upload, verbose_name='Audio')), ('location', map_location.fields.LocationField(blank=True, null=True, verbose_name='Position')), ('description', tinymce.models.HTMLField(verbose_name='Beschreibung')), diff --git a/backend/app/models/place.py b/backend/app/models/place.py index 8e763cf..3726a97 100644 --- a/backend/app/models/place.py +++ b/backend/app/models/place.py @@ -13,7 +13,7 @@ from map_location.fields import LocationField from app.models.category import Category from common.form.audio_file import AudioFileField -from common.form.img_with_preview import FileWithImagePreview +from common.form.img_with_preview import ThumbnailImageField def overwrite_img_upload(instance: 'Place', filename: str): @@ -38,8 +38,8 @@ class Place(models.Model): sort = models.IntegerField('Sortierung', default=0) isExtended = models.BooleanField('Bis 1.1.2025 verstecken') title = models.CharField('Titel', max_length=100) - image = FileWithImagePreview('Bild', blank=True, null=True, - upload_to=overwrite_img_upload) + image = ThumbnailImageField('Bild', blank=True, null=True, + upload_to=overwrite_img_upload) # type: ignore audio = AudioFileField('Audio', blank=True, null=True, upload_to=overwrite_audio_upload) location = LocationField('Position', blank=True, null=True, options={ diff --git a/backend/common/form/img_with_preview.py b/backend/common/form/img_with_preview.py index 19ac002..b70780c 100644 --- a/backend/common/form/img_with_preview.py +++ b/backend/common/form/img_with_preview.py @@ -1,13 +1,20 @@ -from django import forms from django.conf import settings -from django.contrib.admin.widgets import AdminFileWidget -from django.core.validators import FileExtensionValidator +from django.core.files.uploadedfile import UploadedFile from django.db import models -from django.forms import FileInput, widgets +from django.forms import widgets -from common.validators import MaxFilesizeValidator, readableToInt +from PIL import Image, ImageOps -MAX_UPLOAD_SIZE = '312 KB' + +def makeThumnail(data: UploadedFile, size: 'tuple[int, int]'): + with Image.open(data) as img: + ImageOps.exif_transpose(img, in_place=True) + img.thumbnail(size) + if img.mode != 'RGB': + img = img.convert('RGB') + data.seek(0) + data.truncate() + img.save(data, 'jpeg') class ImageFileWidget(widgets.ClearableFileInput): @@ -19,38 +26,19 @@ class ImageFileWidget(widgets.ClearableFileInput): return context -class ImgField(forms.FileField): - widget = ImageFileWidget - default_validators = [ - FileExtensionValidator(['jpg', 'jpeg', 'png']), - MaxFilesizeValidator(MAX_UPLOAD_SIZE), - ] - - def widget_attrs(self, widget): - attrs = super().widget_attrs(widget) - if isinstance(widget, FileInput) and 'accept' not in widget.attrs: - attrs.setdefault('accept', 'image/png,image/jpeg') # image/* - if isinstance(widget, ImageFileWidget): - attrs.update({ - 'data-upload-limit': readableToInt(MAX_UPLOAD_SIZE), - 'data-upload-limit-str': MAX_UPLOAD_SIZE, - 'onchange': 'validate_upload_limit(this)', - }) - return attrs - - -class FileWithImagePreview(models.FileField): # use ImageField to omit Pillow +class ThumbnailImageField(models.ImageField): __del_image_on_save = False def formfield(self, **kwargs): - if kwargs['widget'] is AdminFileWidget: - # Override admin widget. Defined by ImgField anyway - del kwargs['widget'] - return super().formfield(**{'form_class': ImgField, **kwargs}) + kwargs['widget'] = ImageFileWidget + return super().formfield(**kwargs) def save_form_data(self, instance, data): if data is False: self.__del_image_on_save = True + if isinstance(data, UploadedFile): + makeThumnail(data, (1200, 900)) # only on create + # on update: django.db.models.fields.files.ImageFieldFile super().save_form_data(instance, data) def pre_save(self, model_instance, add): diff --git a/backend/requirements.txt b/backend/requirements.txt index 72205bf..72e794a 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,4 +1,4 @@ -Django==4.2.13 +Django>=4.2,<5 Pillow==10.4.0 django_colorfield==0.11.0 django-tinymce==4.1.0