Browse Source

Explicitly add image/heic to 'accept' attribute on image fields

File upload dialogs (at least on Chrome / Mac) don't count heic as part of image/*, as it's not a web-safe format.
Matt Westcott 5 months ago
parent
commit
d02e09e00e
2 changed files with 38 additions and 2 deletions
  1. 18 2
      wagtail/images/fields.py
  2. 20 0
      wagtail/images/tests/test_admin_views.py

+ 18 - 2
wagtail/images/fields.py

@@ -6,6 +6,7 @@ from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.core.validators import FileExtensionValidator
 from django.forms.fields import FileField, ImageField
+from django.forms.widgets import FileInput
 from django.template.defaultfilters import filesizeformat
 from django.utils.translation import gettext_lazy as _
 
@@ -32,6 +33,8 @@ class WagtailImageField(ImageField):
     default_validators = [ImageFileExtensionValidator]
 
     def __init__(self, *args, **kwargs):
+        self.allowed_image_extensions = get_allowed_image_extensions()
+
         super().__init__(*args, **kwargs)
 
         # Get max upload size from settings
@@ -43,8 +46,6 @@ class WagtailImageField(ImageField):
         )
         self.max_upload_size_text = filesizeformat(self.max_upload_size)
 
-        self.allowed_image_extensions = get_allowed_image_extensions()
-
         self.supported_formats_text = ", ".join(self.allowed_image_extensions).upper()
 
         # Help text
@@ -181,3 +182,18 @@ class WagtailImageField(ImageField):
             self.check_image_pixel_size(f)
 
         return f
+
+    def widget_attrs(self, widget):
+        attrs = super().widget_attrs(widget)
+
+        if (
+            isinstance(widget, FileInput)
+            and "accept" not in widget.attrs
+            and attrs.get("accept") == "image/*"
+            and "heic" in self.allowed_image_extensions
+        ):
+            # File upload dialogs (at least on Chrome / Mac) don't count heic as part of image/*, as it's not a
+            # web-safe format, so add it explicitly
+            attrs["accept"] = "image/*, image/heic"
+
+        return attrs

+ 20 - 0
wagtail/images/tests/test_admin_views.py

@@ -1621,6 +1621,10 @@ class TestImageChooserView(WagtailTestUtils, TestCase):
         # draftail should NOT be a standard JS include on this page
         self.assertNotIn("wagtailadmin/js/draftail.js", response_json["html"])
 
+        # upload file field should have accept="image/*"
+        soup = self.get_soup(response_json["html"])
+        self.assertEqual(soup.select_one('input[type="file"]').get("accept"), "image/*")
+
     def test_simple_with_collection_nesting(self):
         root_collection = Collection.get_first_root_node()
         evil_plans = root_collection.add_child(name="Evil plans")
@@ -1630,6 +1634,22 @@ class TestImageChooserView(WagtailTestUtils, TestCase):
         # "Eviler Plans" should be prefixed with &#x21b3 (↳) and 4 non-breaking spaces.
         self.assertContains(response, "    &#x21b3 Eviler plans")
 
+    @override_settings(
+        WAGTAILIMAGES_EXTENSIONS=["gif", "jpg", "jpeg", "png", "webp", "avif", "heic"]
+    )
+    def test_upload_field_accepts_heic(self):
+        response = self.get()
+        self.assertEqual(response.status_code, 200)
+        response_json = json.loads(response.content.decode())
+        self.assertEqual(response_json["step"], "choose")
+        self.assertTemplateUsed(response, "wagtailimages/chooser/chooser.html")
+
+        # upload file field should have an explicit 'accept' case for image/heic
+        soup = self.get_soup(response_json["html"])
+        self.assertEqual(
+            soup.select_one('input[type="file"]').get("accept"), "image/*, image/heic"
+        )
+
     def test_choose_permissions(self):
         # Create group with access to admin and Chooser permission on one Collection, but not another.
         bakers_group = Group.objects.create(name="Bakers")