From d406599d4c8b1ce5e5f4297d740329453aa1f073 Mon Sep 17 00:00:00 2001 From: rber474 Date: Fri, 23 Feb 2024 21:37:01 +0100 Subject: [PATCH 1/4] Fix #122 validating if image is supported by PIL showing a validation error if not --- .meta.toml | 2 +- news/122.bugfix | 4 ++ plone/app/users/browser/account.py | 29 +++++++++++++ plone/app/users/tests/test_portrait.py | 45 ++++++++++++++++++++ plone/app/users/tests/transparent_square.svg | 3 ++ 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 news/122.bugfix create mode 100644 plone/app/users/tests/test_portrait.py create mode 100644 plone/app/users/tests/transparent_square.svg diff --git a/.meta.toml b/.meta.toml index 0a061e9b..4f62c932 100644 --- a/.meta.toml +++ b/.meta.toml @@ -7,4 +7,4 @@ commit-id = "cfffba8c" [pyproject] codespell_ignores = "complet,exemple" -dependencies_ignores = "['Products.CMFPlone']" +dependencies_ignores = "['Products.CMFPlone', 'PIL']" diff --git a/news/122.bugfix b/news/122.bugfix new file mode 100644 index 00000000..367fd1c3 --- /dev/null +++ b/news/122.bugfix @@ -0,0 +1,4 @@ +Fix #122 validating if image is supported by PIL showing a validation error if not. +Include Pillow dependency in setup.py. +Fix ValueError: User could not be found in BaseTest setUp adding a transaction.commit(). +[rber474] \ No newline at end of file diff --git a/plone/app/users/browser/account.py b/plone/app/users/browser/account.py index bf02d69b..720e53f9 100644 --- a/plone/app/users/browser/account.py +++ b/plone/app/users/browser/account.py @@ -1,5 +1,7 @@ from AccessControl import Unauthorized from Acquisition import aq_inner +from PIL import Image +from PIL import UnidentifiedImageError from plone.app.layout.navigation.interfaces import INavigationRoot from plone.app.users.browser.interfaces import IAccountPanelForm from plone.app.users.browser.schemaeditor import getFromBaseSchema @@ -44,6 +46,11 @@ ), ) +MESSAGE_IMAGE_NOT_SUPPORTED = _( + "message_image_not_supported", + "The file you selected is not supported by Pillow. " "Please choose another.", +) + def getSchema(schema_interface, schema_adapter, form_name=None): request = getRequest() @@ -259,6 +266,23 @@ def validate_email(self, action, data): if err_str: notifyWidgetActionExecutionError(action, "email", err_str) + def validate_portrait(self, action, data): + """Portrait validation. + Checks if image is supported by Pillow. + SVG files are not yet supported. + """ + error_keys = [error.field.getName() for error in action.form.widgets.errors] + if "portrait" not in error_keys and data["portrait"] is not None: + portrait = data["portrait"].open() + try: + Image.open(portrait) + except UnidentifiedImageError: + notifyWidgetActionExecutionError( + action, "portrait", MESSAGE_IMAGE_NOT_SUPPORTED + ) + except Exception as exc: + raise exc + @button.buttonAndHandler(_("Save")) def handleSave(self, action): CheckAuthenticator(self.request) @@ -269,6 +293,11 @@ def handleSave(self, action): if "email" in data: self.validate_email(action, data) + # Validate portrait, upload image could be not supported + # by PIL what raises an exception when scaling image. + if "portrait" in data: + self.validate_portrait(action, data) + if action.form.widgets.errors: self.status = self.formErrorsMessage return diff --git a/plone/app/users/tests/test_portrait.py b/plone/app/users/tests/test_portrait.py new file mode 100644 index 00000000..fb158fa7 --- /dev/null +++ b/plone/app/users/tests/test_portrait.py @@ -0,0 +1,45 @@ +from pkg_resources import resource_stream +from plone.app.testing import SITE_OWNER_NAME +from plone.app.testing import SITE_OWNER_PASSWORD +from plone.app.users.tests.base import BaseTestCase + + +class TestPortrait(BaseTestCase): + def test_regression_supported_image_type_122(self): + # https://github.com/plone/plone.app.users/issues/122 + + self.browser.open("http://nohost/plone/") + self.browser.getLink("Log in").click() + self.browser.getControl("Login Name").value = SITE_OWNER_NAME + self.browser.getControl("Password").value = SITE_OWNER_PASSWORD + self.browser.getControl("Log in").click() + self.browser.open("http://nohost/plone/@@personal-information") + self.browser.getControl(name="form.widgets.email").value = "test@test.com" + portrait_file = resource_stream("plone.app.users.tests", "onepixel.jpg") + self.browser.getControl(name="form.widgets.portrait").add_file( + portrait_file, "image/jpg", "onepixel.# jpg" + ) + self.browser.getControl("Save").click() + self.assertIn("Changes saved.", self.browser.contents) + + def test_not_supported_image_type_122(self): + # https://github.com/plone/plone.app.users/issues/122 + + self.browser.open("http://nohost/plone/") + self.browser.getLink("Log in").click() + self.browser.getControl("Login Name").value = SITE_OWNER_NAME + self.browser.getControl("Password").value = SITE_OWNER_PASSWORD + self.browser.getControl("Log in").click() + self.browser.open("http://nohost/plone/@@personal-information") + self.browser.getControl(name="form.widgets.email").value = "test@test.com" + portrait_file = resource_stream( + "plone.app.users.tests", "transparent_square.svg" + ) + self.browser.getControl(name="form.widgets.portrait").add_file( + portrait_file, "image/svg+xml", "onepixel.# jpg" + ) + self.browser.getControl("Save").click() + self.assertIn( + "The file you selected is not supported by Pillow. Please choose another.", + self.browser.contents, + ) diff --git a/plone/app/users/tests/transparent_square.svg b/plone/app/users/tests/transparent_square.svg new file mode 100644 index 00000000..0b11a65e --- /dev/null +++ b/plone/app/users/tests/transparent_square.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 48418981e354340cca5b166a8601be4433f6c837 Mon Sep 17 00:00:00 2001 From: rber474 Date: Fri, 23 Feb 2024 21:37:18 +0100 Subject: [PATCH 2/4] Include Pillow dependency in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ddd88c08..99505993 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ extras_require=extras_require, install_requires=[ "Acquisition", + "Pillow", "Products.GenericSetup", "Products.PlonePAS >= 5.0.1", "Products.statusmessages", From 35e130784da5f1c05a50de570354dbbf24688976 Mon Sep 17 00:00:00 2001 From: rber474 Date: Fri, 23 Feb 2024 21:37:32 +0100 Subject: [PATCH 3/4] Fix ValueError: User could not be found in BaseTest setUp adding a transaction.commit() --- plone/app/users/tests/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plone/app/users/tests/base.py b/plone/app/users/tests/base.py index b059d13f..b572beba 100644 --- a/plone/app/users/tests/base.py +++ b/plone/app/users/tests/base.py @@ -24,6 +24,7 @@ from zope.component import getSiteManager from zope.component import getUtility +import transaction import unittest @@ -42,6 +43,7 @@ def setUp(self): self.browser = Browser(self.layer["app"]) self.request = self.layer["request"] + transaction.commit() def tearDown(self): login(self.portal, "admin") From 0863c451ba724613acb1f957e47fdf16a4e374bd Mon Sep 17 00:00:00 2001 From: rber474 Date: Fri, 23 Feb 2024 21:57:19 +0100 Subject: [PATCH 4/4] Fix dependencychecker for Pillow --- .meta.toml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.meta.toml b/.meta.toml index 4f62c932..0a061e9b 100644 --- a/.meta.toml +++ b/.meta.toml @@ -7,4 +7,4 @@ commit-id = "cfffba8c" [pyproject] codespell_ignores = "complet,exemple" -dependencies_ignores = "['Products.CMFPlone', 'PIL']" +dependencies_ignores = "['Products.CMFPlone']" diff --git a/pyproject.toml b/pyproject.toml index 62234f63..9d361c9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,6 +117,7 @@ Zope = [ ] python-dateutil = ['dateutil'] ignore-packages = ['Products.CMFPlone'] +Pillow = ['PIL'] ## # Add extra configuration options in .meta.toml: