Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/182.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `srcset` method @erral
66 changes: 66 additions & 0 deletions plone/namedfile/scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions plone/namedfile/test.pt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<a href="#contain">contain</a>,
<a href="#picture">picture tags</a>,
<a href="#stored">stored scales</a>,
<a href="#srcset"
i18n="name"
i18n:translate=""
>img tag with srcset</a>,
<a href="#clear">clear</a>
</p>
<section class="section section-main">
Expand Down Expand Up @@ -138,6 +142,27 @@
</p>
</section>


<section class="section"
id="srcset"
>
<h2 i18n:translate="">img with srcset attributes</h2>
<p i18n:translate="">
srcset allows the browser to select the correct image, depending on the space the image has on a page.
</p>
<p i18n:translate="msg_images_test_srcset">
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.
</p>
<p>
<img tal:replace="structure python:images.srcset('image', sizes='(min-width: 1400px) 550px, 90vw')" />
</p>
<p>
<code>
<img tal:replace="python:images.srcset('image', sizes='(min-width: 1400px) 550px, 90vw')" />
</code>
</p>
</section>

<section class="section" id="stored">
<h3>Stored scales</h3>
<div class="accordion" id="accordion-stored">
Expand Down
112 changes: 112 additions & 0 deletions plone/namedfile/tests/test_scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""<img title="foo" alt="foo" sizes="50vw" srcset="{base}/@@images/image-200-....png 200w, {base}/@@images/image-128-....png 128w, {base}/@@images/image-64-....png 64w, {base}/@@images/image-32-....png 32w, {base}/@@images/image-16-....png 16w" src="{base}/@@images/image-1600-....png".../>"""
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"""<img title="foo" alt="foo" sizes="50vw" srcset="{base}/@@images/image-200-....png 200w, {base}/@@images/image-128-....png 128w, {base}/@@images/image-64-....png 64w, {base}/@@images/image-32-....png 32w, {base}/@@images/image-16-....png 16w" src="{base}/@@images/image-200-....png".../>"""
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"""<img title="foo" alt="foo" sizes="50vw" srcset="{base}/@@images/image-200-....png 200w, {base}/@@images/image-128-....png 128w, {base}/@@images/image-64-....png 64w, {base}/@@images/image-32-....png 32w, {base}/@@images/image-16-....png 16w" src="{base}/@@images/image-200-....png".../>"""
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"""<img title="My Custom Title" alt="foo" sizes="50vw" srcset="{base}/@@images/image-200-....png 200w, {base}/@@images/image-128-....png 128w, {base}/@@images/image-64-....png 64w, {base}/@@images/image-32-....png 32w, {base}/@@images/image-16-....png 16w" src="{base}/@@images/image-1600-....png".../>"""
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"""<img title="My Custom Title" alt="This image shows nothing" class="my-personal-class" loading="lazy" sizes="50vw" srcset="{base}/@@images/image-200-....png 200w, {base}/@@images/image-128-....png 128w, {base}/@@images/image-64-....png 64w, {base}/@@images/image-32-....png 32w, {base}/@@images/image-16-....png 16w" src="{base}/@@images/image-1600-....png".../>"""
self.assertTrue(_ellipsis_match(expected, tag.strip()))


class ImageTraverseTests(unittest.TestCase):

Expand Down
Loading