From 9771ab2664e5e83714bad38abd51936d8da11a56 Mon Sep 17 00:00:00 2001 From: MrTango Date: Tue, 11 Feb 2025 15:43:44 +0100 Subject: [PATCH 1/8] remove unsued updateSharingInfo view --- plone/app/workflow/browser/configure.zcml | 8 -------- plone/app/workflow/browser/macro_wrapper.pt | 11 ----------- plone/app/workflow/browser/sharing.py | 10 ---------- 3 files changed, 29 deletions(-) delete mode 100644 plone/app/workflow/browser/macro_wrapper.pt diff --git a/plone/app/workflow/browser/configure.zcml b/plone/app/workflow/browser/configure.zcml index 572ff92..6e46819 100644 --- a/plone/app/workflow/browser/configure.zcml +++ b/plone/app/workflow/browser/configure.zcml @@ -10,12 +10,4 @@ permission="plone.DelegateRoles" /> - - diff --git a/plone/app/workflow/browser/macro_wrapper.pt b/plone/app/workflow/browser/macro_wrapper.pt deleted file mode 100644 index ff8e2fb..0000000 --- a/plone/app/workflow/browser/macro_wrapper.pt +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/plone/app/workflow/browser/sharing.py b/plone/app/workflow/browser/sharing.py index 4eda1cf..6c24d9c 100644 --- a/plone/app/workflow/browser/sharing.py +++ b/plone/app/workflow/browser/sharing.py @@ -50,7 +50,6 @@ class SharingView(BrowserView): # Actions template = ViewPageTemplateFile("sharing.pt") - macro_wrapper = ViewPageTemplateFile("macro_wrapper.pt") STICKY = STICKY @@ -654,12 +653,3 @@ def update_role_settings(self, new_settings, reindex=True): self.context.reindexObjectSecurity() return changed - - def updateSharingInfo(self, search_term=""): - self.handle_form() - the_id = "user-group-sharing" - macro = self.template.macros[the_id] - res = self.macro_wrapper(the_macro=macro, instance=self.context, view=self) - messages = self.context.restrictedTraverse("global_statusmessage")() - self.request.response.setHeader("Content-type", "application/json") - return json.dumps({"body": res, "messages": messages}) From ea814e60b24be11a7bfc99b2ff39564487252019 Mon Sep 17 00:00:00 2001 From: MrTango Date: Wed, 12 Feb 2025 10:42:50 +0100 Subject: [PATCH 2/8] remove unused import --- plone/app/workflow/browser/sharing.pt | 292 -------------------------- plone/app/workflow/browser/sharing.py | 2 - 2 files changed, 294 deletions(-) delete mode 100644 plone/app/workflow/browser/sharing.pt diff --git a/plone/app/workflow/browser/sharing.pt b/plone/app/workflow/browser/sharing.pt deleted file mode 100644 index 57d752b..0000000 --- a/plone/app/workflow/browser/sharing.pt +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - - - -
-
- - -

- Sharing for - title -

- -
- You can control who can view and edit your item using the list below. -
- -
- -
-
- - - -
- - -
-
-
- - -
- - - - - - - - - - - - - - - - - -
Name
- - - - - - - - - - - (${entry/id}) - - - - - - - - - - - - - - -
-
- -
-
- - - - By default, permissions from the container of this item are inherited. - If you disable this, only the explicitly defined sharing permissions will - be valid. - In the overview, the symbol - - indicates an inherited value. Similarly, the symbol - - indicates a global role, which is managed by the site administrator. - -
-
- - - -
-
-
-
-
- - - - diff --git a/plone/app/workflow/browser/sharing.py b/plone/app/workflow/browser/sharing.py index 6c24d9c..33cfb4d 100644 --- a/plone/app/workflow/browser/sharing.py +++ b/plone/app/workflow/browser/sharing.py @@ -21,8 +21,6 @@ from zope.event import notify from zope.i18n import translate -import json - AUTH_GROUP = "AuthenticatedUsers" STICKY = (AUTH_GROUP,) From d24f0fa9b1d3529d813b702dcf4c554e6bd84938 Mon Sep 17 00:00:00 2001 From: MrTango Date: Thu, 13 Feb 2025 11:15:31 +0100 Subject: [PATCH 3/8] facture out ui related code to plone.app.layout --- plone/app/workflow/browser/sharing.py | 75 +++------------------------ 1 file changed, 7 insertions(+), 68 deletions(-) diff --git a/plone/app/workflow/browser/sharing.py b/plone/app/workflow/browser/sharing.py index 33cfb4d..3ebb9df 100644 --- a/plone/app/workflow/browser/sharing.py +++ b/plone/app/workflow/browser/sharing.py @@ -3,7 +3,6 @@ from Acquisition import aq_parent from itertools import chain from plone.app.workflow import PloneMessageFactory as _ -from plone.app.workflow.events import LocalrolesModifiedEvent from plone.app.workflow.interfaces import ISharingPageRole from plone.base.utils import safe_text from plone.i18n.normalizer.interfaces import IIDNormalizer @@ -12,13 +11,9 @@ from Products.CMFCore import permissions from Products.CMFCore.utils import getToolByName from Products.Five.browser import BrowserView -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile -from Products.statusmessages.interfaces import IStatusMessage -from zExceptions import Forbidden from zope.component import getMultiAdapter from zope.component import getUtilitiesFor from zope.component import getUtility -from zope.event import notify from zope.i18n import translate @@ -46,76 +41,20 @@ def merge_search_results(results, key): class SharingView(BrowserView): # Actions - - template = ViewPageTemplateFile("sharing.pt") + template = None STICKY = STICKY def __call__(self): """Perform the update and redirect if necessary, or render the page""" - postback = self.handle_form() - if postback: - return self.template() - else: - context_state = self.context.restrictedTraverse("@@plone_context_state") - url = context_state.view_url() - self.request.response.redirect(url) - - def handle_form(self): - """ - We split this out so we can reuse this for ajax. - Will return a boolean if it was a post or not - """ - postback = True - - form = self.request.form - submitted = form.get("form.submitted", False) - save_button = form.get("form.button.Save", None) is not None - cancel_button = form.get("form.button.Cancel", None) is not None - if submitted and save_button and not cancel_button: - if not self.request.get("REQUEST_METHOD", "GET") == "POST": - raise Forbidden - - authenticator = self.context.restrictedTraverse("@@authenticator", None) - if not authenticator.verify(): - raise Forbidden - - # Update the acquire-roles setting - if self.can_edit_inherit(): - inherit = bool(form.get("inherit", False)) - reindex = self.update_inherit(inherit, reindex=False) - else: - reindex = False - - # Update settings for users and groups - entries = form.get("entries", []) - roles = [r["id"] for r in self.roles()] - settings = [] - for entry in entries: - settings.append( - dict( - id=entry["id"], - type=entry["type"], - roles=[r for r in roles if entry.get("role_%s" % r, False)], - ) - ) - if settings: - reindex = self.update_role_settings(settings, reindex=False) or reindex - if reindex: - self.context.reindexObjectSecurity() - notify(LocalrolesModifiedEvent(self.context, self.request)) - IStatusMessage(self.request).addStatusMessage( - _("Changes saved."), type="info" + if self.template is None: + raise ValueError( + "You are using the base SharingView view in plone.app.workflow," + " for classic UI, override the SharingView from plone.app.layout" + " by subclassing your BrowserLayer of" + " plone.app.layout.interfaces.IBrowserLayer." ) - # Other buttons return to the sharing page - if cancel_button: - postback = False - - return postback - - # View - @memoize def roles(self): """Get a list of roles that can be managed. From 9ccf5f3588418146d7fd48f518c74ec8fe5f4e4d Mon Sep 17 00:00:00 2001 From: MrTango Date: Thu, 13 Feb 2025 20:06:38 +0100 Subject: [PATCH 4/8] fix erros message, fix tests --- plone/app/workflow/browser/sharing.py | 2 +- plone/app/workflow/tests/sharingpage.rst | 259 ------------------ plone/app/workflow/tests/test_functional.py | 1 - plone/app/workflow/tests/test_sharing_view.py | 33 --- 4 files changed, 1 insertion(+), 294 deletions(-) delete mode 100644 plone/app/workflow/tests/sharingpage.rst diff --git a/plone/app/workflow/browser/sharing.py b/plone/app/workflow/browser/sharing.py index 3ebb9df..f492fbe 100644 --- a/plone/app/workflow/browser/sharing.py +++ b/plone/app/workflow/browser/sharing.py @@ -52,7 +52,7 @@ def __call__(self): "You are using the base SharingView view in plone.app.workflow," " for classic UI, override the SharingView from plone.app.layout" " by subclassing your BrowserLayer of" - " plone.app.layout.interfaces.IBrowserLayer." + " plone.app.layout.interfaces.IPloneAppLayoutLayer." ) @memoize diff --git a/plone/app/workflow/tests/sharingpage.rst b/plone/app/workflow/tests/sharingpage.rst deleted file mode 100644 index 2ee61a4..0000000 --- a/plone/app/workflow/tests/sharingpage.rst +++ /dev/null @@ -1,259 +0,0 @@ -====================================== -Functional Testing of the Sharing Page -====================================== - -The test setup has already ensured that we have a number of users available. -Let's also create some - - >>> from plone.testing.zope import Browser - - >>> app = layer['app'] - >>> portal = layer['portal'] - >>> request = layer['request'] - - >>> portal_url = portal.absolute_url() - >>> folder_url = '%s/folder1' % portal_url - - >>> browser = Browser(app) - >>> browser.handleErrors = False - - -Anonymous users ---------------- - -When we log out, we cannot see the "Sharing" tab at all. - - >>> browser.open('%s/logout' % portal_url) - >>> browser.open(folder_url) - -We're not forced to log in and we can view the item. Thus, we have view -permission. - - >>> 'Please log in' not in browser.contents - True - -We shouldn't see the Sharing tab - - >>> browser.getLink('Sharing') - Traceback (most recent call last): - ... - zope.testbrowser.browser.LinkNotFoundError - -Manager -------- - -A Manager should be able to delegate all the accessible roles. Let's also -take this opportunity to delegate some roles so that we can check what the -other roles can do. - - >>> browser.open('%s/logout' % portal_url) - >>> browser.open('%s/login_form' % portal_url) - >>> browser.getControl(name='__ac_name').value = 'manager' - >>> browser.getControl(name='__ac_password').value = 'secret' - >>> browser.getControl('Log in').click() - - >>> browser.open(folder_url) - >>> browser.getLink('Sharing').click() - - >>> "Can add" in browser.contents - True - >>> "Can edit" in browser.contents - True - >>> "Can manage" in browser.contents - False - >>> "Can own" in browser.contents - False - >>> "Can subscribe" in browser.contents - False - >>> "Can view" in browser.contents - True - >>> "Can review" in browser.contents - True - - >>> browser.getControl(name='search_term').value = "delegate_contributor" - >>> browser.getControl(name='form.button.Search').click() - >>> 'Changes saved' not in browser.contents - True - >>> browser.getControl(name='entries.role_Contributor:records').getControl(value='True',index=-1).click() - >>> request.form['form.button.Save'] = 1 - >>> browser.getControl("Save").click() - >>> 'Changes saved' in browser.contents - True - - >>> browser.getControl(name='search_term').value = "delegate_editor" - >>> browser.getControl(name='form.button.Search').click() - >>> browser.getControl(name='entries.role_Editor:records').getControl(value='True',index=-1).click() - >>> request.form['form.button.Save'] = 1 - >>> browser.getControl("Save").click() - >>> 'Changes saved' in browser.contents - True - - >>> #browser.getControl(name='search_term').value = "delegate_manager" - >>> #browser.getControl(name='form.button.Search').click() - >>> #browser.getControl(name='entries.role_Manager:records').getControl(value='True',index=-1).click() - >>> #request = self.portal.REQUEST - >>> #request.form['form.button.Save'] = 1 - >>> #browser.getControl("Save").click() - >>> #'Changes saved' in browser.contents - - >>> browser.getControl(name='search_term').value = "delegate_reader" - >>> browser.getControl(name='form.button.Search').click() - >>> browser.getControl(name='entries.role_Reader:records').getControl(value='True',index=-1).click() - >>> request.form['form.button.Save'] = 1 - >>> browser.getControl("Save").click() - >>> 'Changes saved' in browser.contents - True - - >>> browser.getControl(name='search_term').value = "delegate_reviewer" - >>> browser.getControl(name='form.button.Search').click() - >>> browser.getControl(name='entries.role_Reviewer:records').getControl(value='True',index=-1).click() - >>> request.form['form.button.Save'] = 1 - >>> browser.getControl("Save").click() - >>> 'Changes saved' in browser.contents - True - -Owner ------ - -The owner should be able to delegate Reader, Editor and Contributor. - - >>> browser.open('%s/logout' % portal_url) - >>> browser.open('%s/login_form' % portal_url) - >>> browser.getControl(name='__ac_name').value = 'member' - >>> browser.getControl(name='__ac_password').value = 'secret' - >>> browser.getControl('Log in').click() - - >>> browser.open(folder_url) - >>> browser.getLink('Sharing').click() - - >>> "Can add" in browser.contents - True - >>> "Can edit" in browser.contents - True - >>> "Can manage" in browser.contents - False - >>> "Can own" in browser.contents - False - >>> "Can subscribe" in browser.contents - False - >>> "Can view" in browser.contents - True - >>> "Can review" in browser.contents - False - -Delegated Reader ----------------- - -A delegated reader should only be able to view the page, not even get to the -Sharing tab. - - >>> browser.open('%s/logout' % portal_url) - >>> browser.open('%s/login_form' % portal_url) - >>> browser.getControl(name='__ac_name').value = 'delegate_reader' - >>> browser.getControl(name='__ac_password').value = 'secret' - >>> browser.getControl('Log in').click() - - >>> browser.open(folder_url) - >>> browser.getLink('Sharing') - Traceback (most recent call last): - ... - zope.testbrowser.browser.LinkNotFoundError - -Delegated Editor ----------------- - -A delegated Editor can give other people "view" and "edit" rights. - - >>> browser.open('%s/logout' % portal_url) - >>> browser.open('%s/login_form' % portal_url) - >>> browser.getControl(name='__ac_name').value = 'delegate_editor' - >>> browser.getControl(name='__ac_password').value = 'secret' - >>> browser.getControl('Log in').click() - - >>> browser.open(folder_url) - >>> browser.getLink('Sharing').click() - - >>> "Can add" in browser.contents - False - >>> "Can edit" in browser.contents - True - >>> "Can view" in browser.contents - True - >>> "Can review" in browser.contents - False - - -Delegated Contributor ---------------------- - -A delegated Contributor cannot assign any further rights. - - >>> browser.open('%s/logout' % portal_url) - >>> browser.open('%s/login_form' % portal_url) - >>> browser.getControl(name='__ac_name').value = 'delegate_contributor' - >>> browser.getControl(name='__ac_password').value = 'secret' - >>> browser.getControl('Log in').click() - - >>> browser.open(folder_url) - >>> browser.getLink('Sharing').click() - Traceback (most recent call last): - ... - zope.testbrowser.browser.LinkNotFoundError - -Delegated Reviewer ------------------- - -A delegated Reviewer can assign "view" and "review" rights. - - >>> browser.open('%s/logout' % portal_url) - >>> browser.open('%s/login_form' % portal_url) - >>> browser.getControl(name='__ac_name').value = 'delegate_reviewer' - >>> browser.getControl(name='__ac_password').value = 'secret' - >>> browser.getControl('Log in').click() - - >>> browser.open(folder_url) - >>> browser.getLink('Sharing').click() - - >>> "Can add" in browser.contents - False - >>> "Can edit" in browser.contents - False - >>> "Can manage" in browser.contents - False - >>> "Can own" in browser.contents - False - >>> "Can subscribe" in browser.contents - False - >>> "Can view" in browser.contents - True - >>> "Can review" in browser.contents - True - -#Delegated Manager -#----------------- -# -#A delegated Manager can assign all rights. -# -# >>> browser.open('%s/logout' % portal_url) -# >>> browser.open('%s/login_form' % portal_url) -# >>> browser.getControl(name='__ac_name').value = 'delegate_manager' -# >>> browser.getControl(name='__ac_password').value = 'secret' -# >>> browser.getControl('Log in').click() -# -# >>> browser.open(folder_url) -# >>> browser.getLink('Sharing').click() -# -# >>> "Can add" in browser.contents -# True -# >>> "Can edit" in browser.contents -# True -# >>> "Can manage" in browser.contents -# False -# >>> "Can own" in browser.contents -# False -# >>> "Can subscribe" in browser.contents -# False -# >>> "Can view" in browser.contents -# True -# >>> "Can review" in browser.contents -# True diff --git a/plone/app/workflow/tests/test_functional.py b/plone/app/workflow/tests/test_functional.py index 51c350a..cd65b22 100644 --- a/plone/app/workflow/tests/test_functional.py +++ b/plone/app/workflow/tests/test_functional.py @@ -13,7 +13,6 @@ doctests = ( "onestateworkflow.rst", - "sharingpage.rst", ) diff --git a/plone/app/workflow/tests/test_sharing_view.py b/plone/app/workflow/tests/test_sharing_view.py index df11b1b..3315b3d 100644 --- a/plone/app/workflow/tests/test_sharing_view.py +++ b/plone/app/workflow/tests/test_sharing_view.py @@ -120,39 +120,6 @@ def test_search_for_group_by_title(self): msg="Didn't find testuser when I searched by group title.", ) - def test_group_name_links_to_prefs_for_admin(self): - """Make sure that for admins group name links to group prefs""" - self.request.form["search_term"] = "testgroup" - view = getMultiAdapter((self.portal, self.request), name="sharing") - self.assertIn( - '', - view(), - msg="Group name was not linked to group prefs.", - ) - - def test_group_name_links_not_include_authusers(self): - """Make sure that for admins group name links to group prefs""" - self.request.form["search_term"] = "testgroup" - view = getMultiAdapter((self.portal, self.request), name="sharing") - self.assertNotIn( - '', - view(), - msg="AuthenticatedUsers was linked to group prefs.", - ) - - def test_group_name_doesnt_link_to_prefs_for_reviewer(self): - """Make sure that for admins group name links to group prefs""" - login(self.portal, "testreviewer") - self.request.form["search_term"] = "testgroup" - view = getMultiAdapter((self.portal, self.request), name="sharing") - self.assertNotIn( - '', - view(), - msg="Group name link was unexpectedly shown to reviewer.", - ) - def test_local_manager_removes_inheritance(self): """When a user that inherits the right to remove inheritance do it, its roles are locally set on content From 9f720a6b9e20afe83782012c2991905c3f85b29c Mon Sep 17 00:00:00 2001 From: MrTango Date: Thu, 13 Feb 2025 20:41:07 +0100 Subject: [PATCH 5/8] fix linting --- plone/app/workflow/tests/test_functional.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plone/app/workflow/tests/test_functional.py b/plone/app/workflow/tests/test_functional.py index cd65b22..d94e4e6 100644 --- a/plone/app/workflow/tests/test_functional.py +++ b/plone/app/workflow/tests/test_functional.py @@ -11,9 +11,7 @@ import unittest -doctests = ( - "onestateworkflow.rst", -) +doctests = ("onestateworkflow.rst",) def setup(doctest): From 4640d7ba3c03f036d4d2704eaaa6fac7d16a4362 Mon Sep 17 00:00:00 2001 From: MrTango Date: Thu, 13 Feb 2025 20:54:50 +0100 Subject: [PATCH 6/8] add dependency Products.statusmessages --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 696200b..a1bfabf 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ "Products.statusmessages", "Products.DCWorkflow", "Products.GenericSetup", + "Products.statusmessages", "Zope", ], ) From 9525a8d0b9a8c5d3cd8620a91b7ae1f107bfb902 Mon Sep 17 00:00:00 2001 From: MrTango Date: Thu, 13 Feb 2025 20:56:49 +0100 Subject: [PATCH 7/8] fix dependencies --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index a1bfabf..71a6357 100644 --- a/setup.py +++ b/setup.py @@ -56,10 +56,8 @@ "plone.base", "plone.i18n", "plone.memoize", - "Products.statusmessages", "Products.DCWorkflow", "Products.GenericSetup", - "Products.statusmessages", "Zope", ], ) From 61cc06b758fe716e5df006bd54688bdfd4d10f19 Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 31 May 2026 12:16:43 +0200 Subject: [PATCH 8/8] Add changelog entry for moving sharing UI to plone.app.layout --- news/3953.breaking | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 news/3953.breaking diff --git a/news/3953.breaking b/news/3953.breaking new file mode 100644 index 0000000..642d630 --- /dev/null +++ b/news/3953.breaking @@ -0,0 +1,7 @@ +Move the classic UI sharing page template and form handling to ``plone.app.layout``. +The ``SharingView`` in ``plone.app.workflow`` now provides only the data/API methods; +the base ``__call__`` raises a ``ValueError`` directing integrators to use the +``SharingView`` override registered for ``plone.app.layout.interfaces.IPloneAppLayoutLayer``. +Classic UI installations must depend on a ``plone.app.layout`` version that ships this override. +The ``updateSharingInfo`` view and the ``Products.statusmessages`` dependency are removed. +[MrTango]