MAKENTNU/web

View on GitHub
src/util/formfields.py

Summary

Maintainability
A
1 hr
Test Coverage
import os
import sys
from io import BytesIO
 
from PIL import Image
from django import forms
from django.core.files import File
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
from django.db.models.fields.files import ImageFieldFile
 
from util.file_utils import file_contents_equal, filenames_equal
from util.logging_utils import get_request_logger
 
 
class CompressedImageField(forms.ImageField):
"""
An image form field that compresses images during cleaning, by reducing the quality of the image.
This only applies to JPEG images; images of all other formats will be left unchanged.
"""
 
Function `clean` has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.
def clean(self, data: InMemoryUploadedFile | bool | None, initial: ImageFieldFile = None):
cleaned_data: ImageFieldFile | InMemoryUploadedFile | TemporaryUploadedFile | bool | None = super().clean(data, initial=initial)
if data and cleaned_data:
try:
if initial and file_contents_equal(cleaned_data, initial):
if not filenames_equal(cleaned_data, initial):
# Return the cleaned data, so that the image will get the name of the uploaded file
return cleaned_data
# Don't change the field at all
# (unlike returning `False`, which would clear the image field (and delete the existing file through `django-cleanup`))
return None
except FileNotFoundError:
pass
 
try:
with Image.open(cleaned_data) as pillow_image:
if pillow_image.format == 'JPEG':
original_size = cleaned_data.size
 
Error: invalid syntax (, line 40)
match cleaned_data:
case InMemoryUploadedFile():
output = BytesIO()
self._save_reduced_image(pillow_image, output)
new_size = sys.getsizeof(output)
new_file = InMemoryUploadedFile(
output, cleaned_data.field_name, cleaned_data.name,
cleaned_data.content_type, new_size, cleaned_data.charset,
)
case TemporaryUploadedFile():
new_file = TemporaryUploadedFile(
cleaned_data.name, cleaned_data.content_type, 0, cleaned_data.charset, cleaned_data.content_type_extra,
)
self._save_reduced_image(pillow_image, new_file)
new_size = os.path.getsize(new_file.file.name)
case _:
raise forms.ValidationError(f"Unexpected type of uploaded file: {type(cleaned_data)}")
 
# Only use the reduced image if its size is actually smaller (for some image files, this wil not be the case)
if new_size < original_size:
return new_file
else:
new_file.close()
 
except IOError as e:
# Pillow (PIL) will throw an IOError if it cannot open the image, or does not support the given format
get_request_logger().exception(e)
 
return cleaned_data
 
@staticmethod
def _save_reduced_image(image: Image, file: BytesIO | File):
image.save(file, format='JPEG', quality=90)