From 0b6abcfd7db31c3c7804431d733df625c8fda3ac Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Wed, 4 Jun 2025 16:04:23 +0200 Subject: [PATCH 1/3] feat: backport changes in PR #170 --- plone/namedfile/scaling.py | 66 +++++++++++++++ plone/namedfile/tests/test_scaling.py | 112 ++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) diff --git a/plone/namedfile/scaling.py b/plone/namedfile/scaling.py index 8f2c9c4b..1450e370 100644 --- a/plone/namedfile/scaling.py +++ b/plone/namedfile/scaling.py @@ -740,6 +740,72 @@ def picture( fieldname=fieldname, ).prettify() + def srcset( + self, + fieldname=None, + scale_in_src="huge", + sizes="", + alt=_marker, + css_class=None, + title=_marker, + **kwargs, + ): + if fieldname is None: + try: + primary = IPrimaryFieldInfo(self.context, None) + except TypeError: + return + if primary is None: + return # 404 + fieldname = primary.fieldname + + original_width, original_height = self.getImageSize(fieldname) + + storage = getMultiAdapter( + (self.context, functools.partial(self.modified, fieldname)), + IImageScaleStorage, + ) + + srcset_urls = [] + for width, height in self.available_sizes.values(): + if width <= original_width: + scale = storage.pre_scale( + fieldname=fieldname, width=width, height=height, mode="scale" + ) + extension = scale["mimetype"].split("/")[-1].lower() + srcset_urls.append( + f'{self.context.absolute_url()}/@@images/{scale["uid"]}.{extension} {scale["width"]}w' + ) + attributes = {} + if title is _marker: + attributes["title"] = self.context.Title() + elif title: + attributes["title"] = title + if alt is _marker: + attributes["alt"] = self.context.Title() + else: + attributes["alt"] = alt + + if css_class is not None: + attributes["class"] = css_class + + attributes.update(**kwargs) + + attributes["sizes"] = sizes + + srcset_string = ", ".join(srcset_urls) + attributes["srcset"] = srcset_string + + if scale_in_src not in self.available_sizes: + for key, (width, height) in self.available_sizes.items(): + if width <= original_width: + scale_in_src = key + break + + scale = self.scale(fieldname=fieldname, scale=scale_in_src) + attributes["src"] = scale.url + + return _image_tag_from_values(*attributes.items()) class NavigationRootScaling(ImageScaling): @lazy_property diff --git a/plone/namedfile/tests/test_scaling.py b/plone/namedfile/tests/test_scaling.py index c23846d6..7b4ee4c6 100644 --- a/plone/namedfile/tests/test_scaling.py +++ b/plone/namedfile/tests/test_scaling.py @@ -814,6 +814,118 @@ def testOversizedHighPixelDensityScale(self): self.assertEqual(len(foo.srcset), 1) self.assertEqual(foo.srcset[0]["scale"], 2) + def testImgSrcSet(self): + """test rendered srcset values""" + self.scaling.available_sizes = { + "huge": (1600, 65536), + "great": (1200, 65536), + "larger": (1000, 65536), + "large": (800, 65536), + "teaser": (600, 65536), + "preview": (400, 65536), + "mini": (200, 65536), + "thumb": (128, 128), + "tile": (64, 64), + "icon": (32, 32), + "listing": (16, 16), + } + tag = self.scaling.srcset("image", sizes="50vw") + base = self.item.absolute_url() + expected = f"""foo""" + self.assertTrue(_ellipsis_match(expected, tag.strip())) + + def testImgSrcSetCustomSrc(self): + """test that we can select a custom scale in the src attribute""" + self.scaling.available_sizes = { + "huge": (1600, 65536), + "great": (1200, 65536), + "larger": (1000, 65536), + "large": (800, 65536), + "teaser": (600, 65536), + "preview": (400, 65536), + "mini": (200, 65536), + "thumb": (128, 128), + "tile": (64, 64), + "icon": (32, 32), + "listing": (16, 16), + } + tag = self.scaling.srcset("image", sizes="50vw", scale_in_src="mini") + base = self.item.absolute_url() + expected = f"""foo""" + self.assertTrue(_ellipsis_match(expected, tag.strip())) + + def testImgSrcSetInexistentScale(self): + """test that when requesting an inexistent scale for the src attribute + we provide the biggest scale we can produce + """ + self.scaling.available_sizes = { + "huge": (1600, 65536), + "great": (1200, 65536), + "larger": (1000, 65536), + "large": (800, 65536), + "teaser": (600, 65536), + "preview": (400, 65536), + "mini": (200, 65536), + "thumb": (128, 128), + "tile": (64, 64), + "icon": (32, 32), + "listing": (16, 16), + } + tag = self.scaling.srcset( + "image", sizes="50vw", scale_in_src="inexistent-scale-name" + ) + base = self.item.absolute_url() + expected = f"""foo""" + self.assertTrue(_ellipsis_match(expected, tag.strip())) + + def testImgSrcSetCustomTitle(self): + """test passing a custom title to the srcset method""" + self.scaling.available_sizes = { + "huge": (1600, 65536), + "great": (1200, 65536), + "larger": (1000, 65536), + "large": (800, 65536), + "teaser": (600, 65536), + "preview": (400, 65536), + "mini": (200, 65536), + "thumb": (128, 128), + "tile": (64, 64), + "icon": (32, 32), + "listing": (16, 16), + } + tag = self.scaling.srcset("image", sizes="50vw", title="My Custom Title") + base = self.item.absolute_url() + expected = f"""foo""" + self.assertTrue(_ellipsis_match(expected, tag.strip())) + + def testImgSrcSetAdditionalAttributes(self): + """test that additional parameters are output as is, like alt, loading, ...""" + self.scaling.available_sizes = { + "huge": (1600, 65536), + "great": (1200, 65536), + "larger": (1000, 65536), + "large": (800, 65536), + "teaser": (600, 65536), + "preview": (400, 65536), + "mini": (200, 65536), + "thumb": (128, 128), + "tile": (64, 64), + "icon": (32, 32), + "listing": (16, 16), + } + tag = self.scaling.srcset( + "image", + sizes="50vw", + alt="This image shows nothing", + css_class="my-personal-class", + title="My Custom Title", + loading="lazy", + ) + base = self.item.absolute_url() + + expected = f"""This image shows nothing""" + self.assertTrue(_ellipsis_match(expected, tag.strip())) + class ImageTraverseTests(unittest.TestCase): From 840a82f402bf1500fd90b51178bee3ad71e2ddc1 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Wed, 4 Jun 2025 16:06:17 +0200 Subject: [PATCH 2/3] add rendering example to test.pt --- plone/namedfile/test.pt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plone/namedfile/test.pt b/plone/namedfile/test.pt index a1ad17d1..92a9aa93 100644 --- a/plone/namedfile/test.pt +++ b/plone/namedfile/test.pt @@ -26,6 +26,10 @@ contain, picture tags, stored scales, + img tag with srcset, clear

@@ -138,6 +142,27 @@

+ +
+

img with srcset attributes

+

+srcset allows the browser to select the correct image, depending on the space the image has on a page. +

+

+To do so, the @@images view provides a srcset method, that will output the full srcset of this image, using all available image scales. It has as required parameter the value of the sizes attribute that the user of this method has to provide and will be output as is in the generated HTML. +

+

+ +

+

+ + + +

+
+

Stored scales

From 295706dadc8a186bec121a5667a0bb9a4c7db3d0 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Wed, 4 Jun 2025 16:06:42 +0200 Subject: [PATCH 3/3] changelog --- news/182.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/182.feature diff --git a/news/182.feature b/news/182.feature new file mode 100644 index 00000000..6465efa7 --- /dev/null +++ b/news/182.feature @@ -0,0 +1 @@ +Add `srcset` method @erral