diff --git a/filer/management/commands/filer_check.py b/filer/management/commands/filer_check.py index 3a2de61b3..3eb77d12b 100644 --- a/filer/management/commands/filer_check.py +++ b/filer/management/commands/filer_check.py @@ -4,6 +4,8 @@ from django.core.management.base import BaseCommand from django.utils.module_loading import import_string +from PIL import UnidentifiedImageError + from filer import settings as filer_settings @@ -41,6 +43,13 @@ def add_arguments(self, parser): default=False, help="Delete references in database if files are missing in media folder.", ) + parser.add_argument( + '--image-dimensions', + action='store_true', + dest='image_dimensions', + default=False, + help="Look for images without dimensions set, set them accordingly.", + ) parser.add_argument( '--noinput', '--no-input', @@ -72,6 +81,8 @@ def handle(self, *args, **options): self.stdout.write("Aborted: Delete orphaned files from storage.") return self.verify_storages(options) + if options['image_dimensions']: + self.image_dimensions(options) def verify_references(self, options): from filer.models.filemodels import File @@ -112,3 +123,41 @@ def walk(prefix): filer_public = filer_settings.FILER_STORAGES['public']['main'] storage = import_string(filer_public['ENGINE'])() walk(filer_public['UPLOAD_TO_PREFIX']) + + def image_dimensions(self, options): + from django.db.models import Q + + import easy_thumbnails + from easy_thumbnails.VIL import Image as VILImage + + from filer.models.imagemodels import Image + from filer.utils.compatibility import PILImage + + no_dimensions = Image.objects.filter( + Q(_width=0) | Q(_width__isnull=True) + ) + self.stdout.write(f"trying to set dimensions on {no_dimensions.count()} files") + for image in no_dimensions: + if image.file_ptr: + file_holder = image.file_ptr + else: + file_holder = image + try: + imgfile = file_holder.file + imgfile.seek(0) + except (FileNotFoundError): + pass + else: + if image.file.name.lower().endswith('.svg'): + with VILImage.load(imgfile) as vil_image: + # invalid svg doesnt throw errors + image._width, image._height = vil_image.size + else: + try: + with PILImage.open(imgfile) as pil_image: + image._width, image._height = pil_image.size + image._transparent = easy_thumbnails.utils.is_transparent(pil_image) + except UnidentifiedImageError: + continue + image.save() + return diff --git a/tests/test_filer_check.py b/tests/test_filer_check.py index 01b951ca0..f18bd389c 100644 --- a/tests/test_filer_check.py +++ b/tests/test_filer_check.py @@ -1,6 +1,6 @@ import os import shutil -from io import StringIO +from io import BytesIO, StringIO from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command @@ -9,10 +9,21 @@ from filer import settings as filer_settings from filer.models.filemodels import File +from filer.models.imagemodels import Image from tests.helpers import create_image class FilerCheckTestCase(TestCase): + + svg_file_string = """ + + + + {} + """ + def setUp(self): # ensure that filer_public directory is empty from previous tests storage = import_string(filer_settings.FILER_STORAGES['public']['main']['ENGINE'])() @@ -67,3 +78,81 @@ def test_delete_orphans(self): call_command('filer_check', delete_orphans=True, interactive=False, verbosity=0) self.assertFalse(os.path.exists(orphan_file)) + + def test_image_dimensions_corrupted_file(self): + original_filename = 'testimage.jpg' + file_obj = SimpleUploadedFile( + name=original_filename, + # corrupted! + content=create_image().tobytes(), + content_type='image/jpeg') + self.filer_image = Image.objects.create( + file=file_obj, + original_filename=original_filename) + + self.filer_image._width = 0 + self.filer_image.save() + call_command('filer_check', image_dimensions=True) + + def test_image_dimensions_file_not_found(self): + self.filer_image = Image.objects.create( + file="123.jpg", + original_filename="123.jpg") + call_command('filer_check', image_dimensions=True) + self.filer_image.refresh_from_db() + + def test_image_dimensions(self): + + original_filename = 'testimage.jpg' + with BytesIO() as jpg: + create_image().save(jpg, format='JPEG') + jpg.seek(0) + file_obj = SimpleUploadedFile( + name=original_filename, + content=jpg.read(), + content_type='image/jpeg') + self.filer_image = Image.objects.create( + file=file_obj, + original_filename=original_filename) + + self.filer_image._width = 0 + self.filer_image.save() + + call_command('filer_check', image_dimensions=True) + self.filer_image.refresh_from_db() + self.assertGreater(self.filer_image._width, 0) + + def test_image_dimensions_invalid_svg(self): + + original_filename = 'test.svg' + svg_file = bytes("" + self.svg_file_string, "utf-8") + file_obj = SimpleUploadedFile( + name=original_filename, + content=svg_file, + content_type='image/svg+xml') + self.filer_image = Image.objects.create( + file=file_obj, + original_filename=original_filename) + + self.filer_image._width = 0 + self.filer_image.save() + call_command('filer_check', image_dimensions=True) + + def test_image_dimensions_svg(self): + + original_filename = 'test.svg' + svg_file = bytes(self.svg_file_string, "utf-8") + file_obj = SimpleUploadedFile( + name=original_filename, + content=svg_file, + content_type='image/svg+xml') + self.filer_image = Image.objects.create( + file=file_obj, + original_filename=original_filename) + + self.filer_image._width = 0 + self.filer_image.save() + + call_command('filer_check', image_dimensions=True) + self.filer_image.refresh_from_db() + self.assertGreater(self.filer_image._width, 0)