diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 82005895e4..e7b2d15b4e 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -27,6 +27,7 @@ Changelog * Fix: Add separators when displaying multiple error messages on a StructBlock (Kyle Bayliss) * Fix: Specify `verbose_name` on `TranslatableMixin.locale` so that it is translated when used as a label (Romein van Buren) * Fix: Disallow null characters in API filter values (Jochen Wersdörfer) + * Fix: Fix image preview when Willow optimizers are enabled (Alex Tomkins) * Docs: Remove duplicate section on frontend caching proxies from performance page (Jake Howard) * Docs: Document `restriction_type` field on PageViewRestriction (Shlomo Markowitz) * Docs: Document Wagtail's bug bounty policy (Jake Howard) diff --git a/docs/releases/6.2.md b/docs/releases/6.2.md index 4abb4c6fcb..67579056ab 100644 --- a/docs/releases/6.2.md +++ b/docs/releases/6.2.md @@ -41,6 +41,7 @@ depth: 1 * Add separators when displaying multiple error messages on a StructBlock (Kyle Bayliss) * Specify `verbose_name` on `TranslatableMixin.locale` so that it is translated when used as a label (Romein van Buren) * Disallow null characters in API filter values (Jochen Wersdörfer) + * Fix image preview when Willow optimizers are enabled (Alex Tomkins) ### Documentation diff --git a/wagtail/images/tests/test_admin_views.py b/wagtail/images/tests/test_admin_views.py index 47ca175b13..e89dd091bd 100644 --- a/wagtail/images/tests/test_admin_views.py +++ b/wagtail/images/tests/test_admin_views.py @@ -1,6 +1,7 @@ import datetime import json import urllib +from unittest.mock import patch from django.conf import settings from django.contrib.auth.models import Group, Permission @@ -14,6 +15,8 @@ from django.utils.encoding import force_str from django.utils.html import escape, escapejs from django.utils.http import RFC3986_SUBDELIMS, urlencode from django.utils.safestring import mark_safe +from willow.optimizers.base import OptimizerBase +from willow.registry import registry from wagtail.admin.admin_url_finder import AdminURLFinder from wagtail.images import get_image_model @@ -3535,6 +3538,35 @@ class TestPreviewView(WagtailTestUtils, TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response["Content-Type"], "image/png") + def test_preview_with_optimizer(self): + """ + Test that preview works with optimizers + + Willow optimizers require + """ + + class DummyOptimizer(OptimizerBase): + library_name = "dummy" + image_format = "png" + + @classmethod + def check_library(cls): + return True + + @classmethod + def process(cls, file_path: str): + pass + + # Get the image + with patch.object(registry, "_registered_optimizers", [DummyOptimizer]): + response = self.client.get( + reverse("wagtailimages:preview", args=(self.image.id, "fill-800x600")) + ) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "image/png") + def test_get_invalid_filter_spec(self): """ Test that an invalid filter spec returns a 400 response diff --git a/wagtail/images/views/images.py b/wagtail/images/views/images.py index 1187df09c2..c818a847d4 100644 --- a/wagtail/images/views/images.py +++ b/wagtail/images/views/images.py @@ -1,8 +1,9 @@ import os +from tempfile import SpooledTemporaryFile from django.conf import settings from django.core.exceptions import PermissionDenied -from django.http import HttpResponse, JsonResponse +from django.http import FileResponse, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.urls import reverse @@ -291,8 +292,11 @@ def preview(request, image_id, filter_spec): image = get_object_or_404(get_image_model(), id=image_id) try: - response = HttpResponse() - image = Filter(spec=filter_spec).run(image, response) + # Temporary image needs to be an instance that Willow can run optimizers on + temp_image = SpooledTemporaryFile(max_size=settings.FILE_UPLOAD_MAX_MEMORY_SIZE) + image = Filter(spec=filter_spec).run(image, temp_image) + temp_image.seek(0) + response = FileResponse(temp_image) response["Content-Type"] = "image/" + image.format_name return response except InvalidFilterSpecError: