Skip to content

Commit

Permalink
Merge branch 'master' into mliebischer-support-svg-with-large-header
Browse files Browse the repository at this point in the history
  • Loading branch information
mliebischer committed Aug 22, 2023
2 parents 18f39bb + d99b035 commit 2035c6b
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 10 deletions.
36 changes: 36 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,42 @@ Changelog
.. towncrier release notes start
6.1.1 (2023-06-22)
------------------

Bug fixes:


- Return a 400 Bad Request response if the `@@images` view is published without a subpath. @davisagli (#144)


Tests


- Fix tests to work with various ``beautifulsoup4`` versions.
[maurits] (#867)


6.1.0 (2023-05-22)
------------------

New features:


- Move ``Zope2FileUploadStorable`` code from plone.app.z3cform to here to break a cyclic dependency.
[gforcada] (#3764)


6.0.2 (2023-05-08)
------------------

Bug fixes:


- Fix picture tag when original image is used instead of a scale.
[maurits] (#142)


6.0.1 (2023-03-14)
------------------

Expand Down
12 changes: 11 additions & 1 deletion plone/namedfile/picture.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,15 @@ def update_src_scale(self, src, scale):
src_scale = "/".join(parts[:-1]) + f"/{field_name}/{scale}"
src_scale
else:
src_scale = "/".join(parts[:-1]) + f"/{scale}"
# Usually the url has '@@images/fieldname/other_scale',
# and then we replace the other scale.
# But the url may use the original image, e.g. @@images/image.
# Then we want to keep the fieldname and return '.../image/scale'.
try:
full = len(parts) - parts.index("@@images") == 2
except ValueError:
full = False
if not full:
parts = parts[:-1]
src_scale = "/".join(parts) + f"/{scale}"
return src_scale
9 changes: 7 additions & 2 deletions plone/namedfile/scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from Products.CMFPlone.utils import safe_encode
from Products.Five import BrowserView
from xml.sax.saxutils import quoteattr
from zExceptions import BadRequest
from zExceptions import Unauthorized
from ZODB.blob import BlobFile
from ZODB.POSException import ConflictError
Expand All @@ -30,7 +31,7 @@
from zope.deprecation import deprecate
from zope.interface import alsoProvides
from zope.interface import implementer
from zope.publisher.interfaces import IPublishTraverse
from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.publisher.interfaces import NotFound
from zope.traversing.interfaces import ITraversable
from zope.traversing.interfaces import TraversalError
Expand Down Expand Up @@ -390,7 +391,7 @@ def __call__(
return value, format_, dimensions


@implementer(ITraversable, IPublishTraverse)
@implementer(ITraversable, IBrowserPublisher)
class ImageScaling(BrowserView):
"""view used for generating (and storing) image scales"""

Expand Down Expand Up @@ -434,6 +435,10 @@ def publishTraverse(self, request, name):
return scale_view
raise NotFound(self, name, self.request)

def browserDefault(self, request):
# There's nothing in the path after /@@images
raise BadRequest("Missing image scale path")

def traverse(self, name, furtherPath):
"""used for path traversal, i.e. in zope page templates"""
# validate access
Expand Down
11 changes: 11 additions & 0 deletions plone/namedfile/storages.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,14 @@ def store(self, pdata, blob):
fp = blob.open("w")
fp.write(bytes(pdata))
fp.close()


@implementer(IStorage)
class Zope2FileUploadStorable:
def store(self, data, blob):
data.seek(0)
with blob.open("w") as fp:
block = data.read(MAXCHUNKSIZE)
while block:
fp.write(block)
block = data.read(MAXCHUNKSIZE)
64 changes: 58 additions & 6 deletions plone/namedfile/tests/test_scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,9 +547,17 @@ def testGetPictureTagByName(self, mock_uuid_to_object):
http://nohost/item/@@images/image-800-....png 800w,
http://nohost/item/@@images/image-1000-....png 1000w,
http://nohost/item/@@images/image-1200-....png 1200w"/>
<img height="200" loading="lazy" src="http://nohost/item/@@images/image-600-....png" title="foo" width="200"/>
<img...src="http://nohost/item/@@images/image-600-....png".../>
</picture>"""
self.assertTrue(_ellipsis_match(expected, tag))
self.assertTrue(_ellipsis_match(expected, tag.strip()))

# The exact placement of the img tag attributes can differ, especially
# with different beautifulsoup versions.
# So check here that all attributes are present.
self.assertIn('height="200"', tag)
self.assertIn('loading="lazy"', tag)
self.assertIn('title="foo"', tag)
self.assertIn('width="200"', tag)

@patch.object(
plone.namedfile.scaling,
Expand Down Expand Up @@ -580,9 +588,18 @@ def testGetPictureTagWithAltAndTitle(self, mock_uuid_to_object):
{base}/@@images/image-800-....png 800w,
{base}/@@images/image-1000-....png 1000w,
{base}/@@images/image-1200-....png 1200w"/>
<img alt="Alternative text" height="200" loading="lazy" src="{base}/@@images/image-600-....png" title="Custom title" width="200"/>
<img...src="{base}/@@images/image-600-....png".../>
</picture>"""
self.assertTrue(_ellipsis_match(expected, tag))
self.assertTrue(_ellipsis_match(expected, tag.strip()))

# The exact placement of the img tag attributes can differ, especially
# with different beautifulsoup versions.
# So check here that all attributes are present.
self.assertIn('alt="Alternative text"', tag)
self.assertIn('height="200"', tag)
self.assertIn('loading="lazy"', tag)
self.assertIn('title="Custom title"', tag)
self.assertIn('width="200"', tag)

@patch.object(
plone.namedfile.scaling,
Expand All @@ -601,8 +618,15 @@ def testGetPictureTagWithoutAnyVariants(self, mock_uuid_to_object):
ImageScaling._sizes = patch_Img2PictureTag_allowed_scales()
mock_uuid_to_object.return_value = self.item
tag = self.scaling.picture("image", picture_variant="medium")
expected = """<img src="http://nohost/item/@@images/image-0-....png" title="foo" height="200" width="200" />"""
self.assertTrue(_ellipsis_match(expected, tag))
expected = """<img...src="http://nohost/item/@@images/image-0-....png".../>"""
self.assertTrue(_ellipsis_match(expected, tag.strip()))

# The exact placement of the img tag attributes can differ, especially
# with different beautifulsoup versions.
# So check here that all attributes are present.
self.assertIn('height="200"', tag)
self.assertIn('title="foo"', tag)
self.assertIn('width="200"', tag)

def testGetUnknownScale(self):
foo = self.scaling.scale("image", scale="foo?")
Expand Down Expand Up @@ -980,6 +1004,34 @@ def test_title(self):
)


class Img2PictureTagTests(unittest.TestCase):
"""Low level tests for Img2PictureTag."""

def _makeOne(self):
return plone.namedfile.picture.Img2PictureTag()

def test_update_src_scale(self):
update_src_scale = self._makeOne().update_src_scale
self.assertEqual(
update_src_scale("foo/fieldname/old", "new"),
"foo/fieldname/new"
)
self.assertEqual(
update_src_scale("@@images/fieldname/old", "mini"),
"@@images/fieldname/mini"
)
self.assertEqual(
update_src_scale("@@images/fieldname", "preview"),
"@@images/fieldname/preview"
)
self.assertEqual(
update_src_scale(
"photo.jpg/@@images/image-1200-4a03b0a8227d28737f5d9e3e481bdbd6.jpeg",
"teaser"),
"photo.jpg/@@images/image/teaser",
)


def test_suite():
from unittest import defaultTestLoader

Expand Down
6 changes: 6 additions & 0 deletions plone/namedfile/tests/test_scaling_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from plone.namedfile.testing import PLONE_NAMEDFILE_FUNCTIONAL_TESTING
from plone.namedfile.tests import getFile
from plone.testing.zope import Browser
from zExceptions import BadRequest
from zope.annotation import IAttributeAnnotatable
from zope.interface import implementer

Expand Down Expand Up @@ -215,6 +216,11 @@ def testSVGPublishThumbViaName(self):
self.assertEqual("image/svg+xml", self.browser.headers["content-type"])
self.assertEqual(self.browser.contents, data)

def testImagesViewWithNoSubpath(self):
transaction.commit()
with self.assertRaises(BadRequest):
self.browser.open(self.layer["app"].absolute_url() + "/item/@@images")


def test_suite():
from unittest import defaultTestLoader
Expand Down
6 changes: 6 additions & 0 deletions plone/namedfile/z3c-blobfile.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
factory=".storages.PDataStorable"
/>

<utility
factory=".storages.Zope2FileUploadStorable"
provides=".interfaces.IStorage"
name="ZPublisher.HTTPRequest.FileUpload"
/>

<adapter factory=".copy.BlobFileCopyHook" />

</configure>
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os


version = "6.0.1"
version = "6.1.2.dev0"

description = "File types and fields for images, files and blob files with " "filenames"
long_description = "\n\n".join(
Expand Down

0 comments on commit 2035c6b

Please sign in to comment.