Browse Source

Add ability to set default configurable attributes to image tags

- Allow users to override the default attributes given to an image
- Update tests to account for new tags
- Add documentation for custom image attributes
- Recommend that loading=lazy & decoding=async be considered for performance in front-end sites
Jake Howard 2 years ago
parent
commit
5108b5f82a

+ 1 - 0
CHANGELOG.txt

@@ -7,6 +7,7 @@ Changelog
 
  * Add basic keyboard control and screen reader support for page listing re-ordering (Paarth Agarwal, Thomas van der Hoeven)
  * Add `PageQuerySet.private` method as an alias of `not_public` (Mehrdad Moradizadeh)
+ * Allow setting default attributes on image tags (Jake Howard)
  * Fix: Prevent `PageQuerySet.not_public` from returning all pages when no page restrictions exist (Mehrdad Moradizadeh)
 
 

+ 6 - 0
docs/advanced_topics/performance.md

@@ -83,3 +83,9 @@ TEMPLATES = [{
 To support high volumes of traffic with excellent response times, we recommend a caching proxy. Both [Varnish](https://varnish-cache.org/) and [Squid](http://www.squid-cache.org/) have been tested in production. Hosted proxies like [Cloudflare](https://www.cloudflare.com/) should also work well.
 
 Wagtail supports automatic cache invalidation for Varnish/Squid. See [](frontend_cache_purging) for more information.
+
+### Image attributes
+
+For some images, it may be beneficial to lazy load images, so the rest of the page can continue to load. It can be configured site-wide [](adding_default_attributes_to_images) or per-image [](image_tag_alt). For more details you can read about the [`loading='lazy'` attribute](https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes) and the [`'decoding='async'` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-decoding) or this [web.dev article on lazy loading images](https://web.dev/lazy-loading-images/).
+
+This optimisation is already handled for you for images in the admin site.

+ 1 - 1
docs/releases/4.1.md

@@ -16,10 +16,10 @@ Wagtail 4.1 is designated a Long Term Support (LTS) release. Long Term Support r
  * Add basic keyboard control and screen reader support for page listing re-ordering (Paarth Agarwal, Thomas van der Hoeven)
  * Add `PageQuerySet.private` method as an alias of `not_public` (Mehrdad Moradizadeh)
 
+ * Allow setting default attributes on image tags [](adding_default_attributes_to_images) (Jake Howard)
 
 ### Bug fixes
 
  * Prevent `PageQuerySet.not_public` from returning all pages when no page restrictions exist (Mehrdad Moradizadeh)
 
-
 ## Upgrade considerations

+ 33 - 2
docs/topics/images.md

@@ -161,7 +161,7 @@ Wagtail does not allow deforming or stretching images. Image dimension ratios wi
 
 Wagtail provides two shortcuts to give greater control over the `img` element:
 
-**1. Adding attributes to the {% image %} tag**
+### 1. Adding attributes to the {% image %} tag
 
 Extra attributes can be specified with the syntax `attribute="value"`:
 
@@ -171,7 +171,9 @@ Extra attributes can be specified with the syntax `attribute="value"`:
 
 You can set a more relevant `alt` attribute this way, overriding the one automatically generated from the title of the image. The `src`, `width`, and `height` attributes can also be overridden, if necessary.
 
-**2. Generating the image "as foo" to access individual properties**
+You can also add default attributes to all images (a default class or data attribute for example) - see [](adding_default_attributes_to_images).
+
+### 2. Generating the image "as foo" to access individual properties
 
 Wagtail can assign the image data to another variable using Django's `as` syntax:
 
@@ -228,6 +230,35 @@ Therefore, if you'd added the field `author` to your AbstractImage in the above
 
 (Due to the links in the database between renditions and their parent image, you _could_ access it as `{{ tmp_photo.image.author }}`, but that has reduced readability.)
 
+(adding_default_attributes_to_images)=
+
+## Adding default attributes to all images
+
+We can configure the `wagtail.images` application to specify additional attributes to add to images. This is done by setting up a custom `AppConfig` class within your project folder (i.e. the package containing the top-level settings and urls modules).
+
+To do this, create or update your existing `apps.py` file with the following:
+
+```python
+from wagtail.images.apps import WagtailImagesAppConfig
+
+
+class CustomImagesAppConfig(WagtailImagesAppConfig):
+    default_attrs = {"decoding": "async", "loading": "lazy"}
+```
+
+Then, replace `wagtail.images` in `settings.INSTALLED_APPS` with the path to `CustomUsersAppConfig`:
+
+```python
+INSTALLED_APPS = [
+    ...,
+    "myapplication.apps.CustomImagesAppConfig",
+    # "wagtail.images",
+    ...,
+]
+```
+
+Now, images created with `{% image %}` will additionally have `decoding="async" loading="lazy"` attributes. This also goes for images added to Rich Text and `ImageBlock` blocks.
+
 ## Alternative HTML tags
 
 The `as` keyword allows alternative HTML image tags (such as `<picture>` or `<amp-img>`) to be used.

+ 1 - 0
wagtail/images/apps.py

@@ -11,6 +11,7 @@ class WagtailImagesAppConfig(AppConfig):
     label = "wagtailimages"
     verbose_name = _("Wagtail images")
     default_auto_field = "django.db.models.AutoField"
+    default_attrs = {}
 
     def ready(self):
         register_signal_handlers()

+ 5 - 0
wagtail/images/models.py

@@ -7,6 +7,7 @@ from contextlib import contextmanager
 from io import BytesIO
 from typing import Union
 
+from django.apps import apps
 from django.conf import settings
 from django.core import checks
 from django.core.cache import InvalidCacheBackendError, caches
@@ -826,7 +827,11 @@ class AbstractRendition(ImageFileMixin, models.Model):
 
     def img_tag(self, extra_attributes={}):
         attrs = self.attrs_dict.copy()
+
+        attrs.update(apps.get_app_config("wagtailimages").default_attrs)
+
         attrs.update(extra_attributes)
+
         return mark_safe("<img{}>".format(flatatt(attrs)))
 
     def __html__(self):

+ 15 - 0
wagtail/images/tests/test_blocks.py

@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*
 import os
+import unittest.mock
 
+from django.apps import apps
 from django.conf import settings
 from django.core import serializers
 from django.test import TestCase
@@ -55,6 +57,19 @@ class TestImageChooserBlock(TestCase):
 
         self.assertHTMLEqual(html, expected_html)
 
+    def test_render_with_custom_default_attrs(self):
+        block = ImageChooserBlock()
+        with unittest.mock.patch.object(
+            apps.get_app_config("wagtailimages"),
+            "default_attrs",
+            new={"decoding": "async", "loading": "lazy"},
+        ):
+            html = block.render(self.bad_image)
+        self.assertHTMLEqual(
+            html,
+            '<img alt="missing image" src="/media/not-found" width="0" height="0" decoding="async" loading="lazy">',
+        )
+
     def test_render_missing(self):
         block = ImageChooserBlock()
         html = block.render(self.bad_image)

+ 15 - 0
wagtail/images/tests/test_jinja2.py

@@ -1,6 +1,8 @@
 import os
+import unittest.mock
 
 from django import template
+from django.apps import apps
 from django.conf import settings
 from django.core import serializers
 from django.template import engines
@@ -99,6 +101,19 @@ class TestImagesJinja(TestCase):
         with self.assertRaises(template.TemplateSyntaxError):
             self.render('{{ image(myimage, "fill-200×200") }}', {"myimage": self.image})
 
+    def test_custom_default_attrs(self):
+        with unittest.mock.patch.object(
+            apps.get_app_config("wagtailimages"),
+            "default_attrs",
+            new={"decoding": "async", "loading": "lazy"},
+        ):
+            self.assertHTMLEqual(
+                self.render(
+                    '{{ image(myimage, "width-200") }}', {"myimage": self.bad_image}
+                ),
+                '<img alt="missing image" src="/media/not-found" width="0" height="0" decoding="async" loading="lazy">',
+            )
+
     def test_chaining_filterspecs(self):
         self.assertHTMLEqual(
             self.render(