Skip to content

Commit

Permalink
fix: issues with filer image crash when trying to generate thumbnail (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
vinitkumar authored Jul 12, 2023
1 parent 4c2b9eb commit 45ef9ee
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 8 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
CHANGELOG
=========

unreleased
==================

* Fix a bug that creates a server error when requesting a thumbnail from an
invalid or missing file
* Fix a bug that on some systems webp images were not recognized
* Add missing css map files

3.0.0 (2023-07-05)
==================

Expand Down
13 changes: 9 additions & 4 deletions filer/admin/fileadmin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from django import forms
from django.contrib.admin.utils import unquote
from django.contrib.staticfiles.storage import staticfiles_storage
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import path, reverse
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _

from easy_thumbnails.exceptions import InvalidImageFormatError
from easy_thumbnails.files import get_thumbnailer
from easy_thumbnails.options import ThumbnailOptions

Expand Down Expand Up @@ -174,10 +176,13 @@ def icon_view(self, request, file_id: int, size: int) -> HttpResponse:
if not isinstance(file, BaseImage):
raise Http404()

thumbnailer = get_thumbnailer(file)
thumbnail_options = ThumbnailOptions({'size': (size, size), "crop": True})
thumbnail = thumbnailer.get_thumbnail(thumbnail_options, generate=True)
return HttpResponseRedirect(thumbnail.url)
try:
thumbnailer = get_thumbnailer(file)
thumbnail_options = ThumbnailOptions({'size': (size, size), "crop": True})
thumbnail = thumbnailer.get_thumbnail(thumbnail_options, generate=True)
return HttpResponseRedirect(thumbnail.url)
except InvalidImageFormatError:
return HttpResponseRedirect(staticfiles_storage.url('filer/icons/file-missing.svg'))


FileAdmin.fieldsets = FileAdmin.build_fieldsets()
13 changes: 12 additions & 1 deletion filer/apps.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import mimetypes

from django.apps import AppConfig
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import gettext_lazy as _
Expand All @@ -14,8 +16,14 @@ def register_optional_heif_supprt(self):

from .settings import IMAGE_EXTENSIONS, IMAGE_MIME_TYPES

# Register with easy_thumbnails
register_heif_opener()
IMAGE_EXTENSIONS += [".heic", ".heics", ".heif", ".heifs", ".hif"]
HEIF_EXTENSIONS = [".heic", ".heics", ".heif", ".heifs", ".hif"]
# Add extensions to python mimetypes which filer uses
for ext in HEIF_EXTENSIONS:
mimetypes.add_type("image/heic", ext)
# Mark them as images
IMAGE_EXTENSIONS += HEIF_EXTENSIONS
IMAGE_MIME_TYPES.append("heic")
except (ModuleNotFoundError, ImportError):
# No heif support installed
Expand Down Expand Up @@ -52,5 +60,8 @@ def resolve_validators(self):
self.FILE_VALIDATORS[mime_type] = functions

def ready(self):
# Make webp mime type known to python (needed for python < 3.11)
mimetypes.add_type("image/webp", ".webp")
#
self.resolve_validators()
self.register_optional_heif_supprt()
1 change: 1 addition & 0 deletions tests/requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# requirements from setup.py
Pillow
pillow-heif
django-app-helper>=3.3.1

# other requirements
Expand Down
42 changes: 41 additions & 1 deletion tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from filer.models.filemodels import File
from filer.models.foldermodels import Folder, FolderPermission
from filer.models.virtualitems import FolderRoot
from filer.settings import FILER_IMAGE_MODEL
from filer.settings import DEFERRED_THUMBNAIL_SIZES, FILER_IMAGE_MODEL
from filer.templatetags.filer_admin_tags import file_icon_url
from filer.thumbnail_processors import normalize_subject_location
from filer.utils.loader import load_model
Expand Down Expand Up @@ -254,9 +254,49 @@ class FilerImageAdminUrlsTests(TestCase):
def setUp(self):
self.superuser = create_superuser()
self.client.login(username='admin', password='secret')
self.img = create_image()
self.image_name = 'test_file.jpg'
self.filename = os.path.join(settings.FILE_UPLOAD_TEMP_DIR, self.image_name)
self.img.save(self.filename, 'JPEG')
with open(self.filename, 'rb') as upload:
self.file_object = Image.objects.create(file=django.core.files.File(upload, name=self.image_name))

def tearDown(self):
self.client.logout()
os.remove(self.filename)

def test_icon_view_sizes(self):
"""Tests if redirects are issued for accepted thumbnail sizes and 404 otherwise"""
test_set = tuple((size, 302) for size in DEFERRED_THUMBNAIL_SIZES)
test_set += (50, 404), (90, 404), (320, 404)
for size, expected_status in test_set:
url = reverse('admin:filer_file_fileicon', kwargs={
'file_id': self.file_object.pk,
'size': size,
})
response = self.client.get(url)
self.assertEqual(response.status_code, expected_status)
if response.status_code == 302: # redirect
# Redirects to a media file
self.assertIn("/media/", response["Location"])
# Does not redirect to a static file
self.assertNotIn("/static/", response["Location"])

def test_missing_file(self):
image = Image.objects.create(
owner=self.superuser,
original_filename="some-image.jpg",
)
url = reverse('admin:filer_file_fileicon', kwargs={
'file_id': image.pk,
'size': 80,
})
# Make file unaccessible

response = self.client.get(url)

self.assertEqual(response.status_code, 302)
self.assertIn("icons/file-missing.svg", response["Location"])


class FilerClipboardAdminUrlsTests(TestCase):
Expand Down
10 changes: 10 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import mimetypes
import os
from zipfile import ZipFile

from django.conf import settings
from django.core.files import File as DjangoFile
from django.test.testcases import TestCase

from filer.settings import IMAGE_EXTENSIONS
from filer.utils.loader import load_object
from filer.utils.zip import unzip
from tests.helpers import create_image
Expand Down Expand Up @@ -62,3 +64,11 @@ def tearDown(self):
def test_unzipping_works(self):
result = unzip(self.zipfilename)
self.assertEqual(result[0][0].name, self.file.name)


class MimeTypesTestCase(TestCase):
def test_mime_types_known(self):
"""Ensure that for all IMAGE_EXTENSIONS the mime types can be identified"""
for ext in IMAGE_EXTENSIONS:
self.assertIsNotNone(mimetypes.guess_type(f"file{ext}")[0],
f"Mime type for extension {ext} unknown")
4 changes: 2 additions & 2 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import os

import django.core
from django.apps import apps
from django.conf import settings
import django.core
from django.test import TestCase
from django.urls import reverse
from django.utils.crypto import get_random_string

from filer.models import File, Folder
from filer.validation import validate_upload, FileValidationError
from filer.validation import FileValidationError, validate_upload
from tests.helpers import create_superuser


Expand Down

0 comments on commit 45ef9ee

Please sign in to comment.