From 6919923367150b1de6b9a882751379695aab7cb3 Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Sun, 21 Sep 2025 00:08:09 +0530 Subject: [PATCH 1/5] feat(recyclebin): enhance delete actions with recycle bin support and dynamic success messages --- src/plone/app/content/browser/actions.py | 38 +++++++++++++++++-- .../app/content/browser/contents/delete.py | 32 +++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/plone/app/content/browser/actions.py b/src/plone/app/content/browser/actions.py index 6f16e535..8c95172f 100644 --- a/src/plone/app/content/browser/actions.py +++ b/src/plone/app/content/browser/actions.py @@ -6,6 +6,7 @@ from plone.base.utils import get_user_friendly_types from plone.base.utils import safe_text from plone.locking.interfaces import ILockable +from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile @@ -19,6 +20,8 @@ from zope import schema from zope.component import getMultiAdapter from zope.component import queryMultiAdapter +from zope.component import queryUtility +from zope.component.hooks import getSite from zope.container.interfaces import INameChooser from zope.event import notify from zope.interface import Interface @@ -85,9 +88,38 @@ def handle_delete(self, action): # unlock object as it is locked by current user ILockable(self.context).unlock() parent.manage_delObjects(self.context.getId()) - IStatusMessage(self.request).add( - _("${title} has been deleted.", mapping={"title": title}) - ) + + # Check if recycle bin is enabled and show appropriate message + try: + recyclebin_enabled_view = getMultiAdapter( + (getSite(), self.request), name="recyclebin-enabled" + ) + recycling_enabled = recyclebin_enabled_view() + except Exception: + recycling_enabled = False + + if recycling_enabled: + # Get retention period from registry (default to 30 days if not found) + registry = queryUtility(IRegistry) + retention_period = 30 # default + if registry is not None: + try: + retention_period = registry.get( + "plone-recyclebin.retention_period", 30 + ) + except Exception: + retention_period = 30 + + IStatusMessage(self.request).add( + _( + "${title} has been moved to the recycle bin. It can be restored by administrators and will be permanently deleted after ${days} days.", + mapping={"title": title, "days": retention_period}, + ) + ) + else: + IStatusMessage(self.request).add( + _("${title} has been deleted.", mapping={"title": title}) + ) else: IStatusMessage(self.request).add( _('"${title}" has already been deleted', mapping={"title": title}) diff --git a/src/plone/app/content/browser/contents/delete.py b/src/plone/app/content/browser/contents/delete.py index 8eb6018e..d8f9683e 100644 --- a/src/plone/app/content/browser/contents/delete.py +++ b/src/plone/app/content/browser/contents/delete.py @@ -4,9 +4,11 @@ from plone.app.content.interfaces import IStructureAction from plone.base import PloneMessageFactory as _ from plone.locking.interfaces import ILockable +from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from zope.component import getMultiAdapter +from zope.component import queryUtility from zope.component.hooks import getSite from zope.i18n import translate from zope.interface import implementer @@ -43,9 +45,37 @@ def get_options(self): class DeleteActionView(ContentsBaseAction): required_obj_permission = delete_objects - success_msg = _("Successfully delete items") failure_msg = _("Failed to delete items") + @property + def success_msg(self): + """Dynamic success message that includes recycle bin information.""" + # Check if recycle bin is enabled + try: + recyclebin_enabled_view = getMultiAdapter( + (getSite(), self.request), name="recyclebin-enabled" + ) + recycling_enabled = recyclebin_enabled_view() + except Exception: + recycling_enabled = False + + if not recycling_enabled: + return _("Successfully deleted items") + + # Get retention period from registry (default to 30 days if not found) + registry = queryUtility(IRegistry) + retention_period = 30 # default + if registry is not None: + try: + retention_period = registry.get("plone-recyclebin.retention_period", 30) + except Exception: + retention_period = 30 + + return _( + "Successfully moved items to recycle bin. Items can be restored by administrators and will be permanently deleted after ${days} days.", + mapping={"days": retention_period}, + ) + def __call__(self): if self.request.form.get("render") == "yes": confirm_view = getMultiAdapter( From a66acc9dd5f900dec2286a2678a9eb7637e8d6ea Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Mon, 22 Sep 2025 09:04:27 +0530 Subject: [PATCH 2/5] fix: update registry key for recycle bin retention period in delete actions --- src/plone/app/content/browser/actions.py | 2 +- src/plone/app/content/browser/contents/delete.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plone/app/content/browser/actions.py b/src/plone/app/content/browser/actions.py index 8c95172f..8e4d483a 100644 --- a/src/plone/app/content/browser/actions.py +++ b/src/plone/app/content/browser/actions.py @@ -105,7 +105,7 @@ def handle_delete(self, action): if registry is not None: try: retention_period = registry.get( - "plone-recyclebin.retention_period", 30 + "recyclebin-controlpanel.retention_period", 30 ) except Exception: retention_period = 30 diff --git a/src/plone/app/content/browser/contents/delete.py b/src/plone/app/content/browser/contents/delete.py index d8f9683e..9865d09a 100644 --- a/src/plone/app/content/browser/contents/delete.py +++ b/src/plone/app/content/browser/contents/delete.py @@ -67,7 +67,7 @@ def success_msg(self): retention_period = 30 # default if registry is not None: try: - retention_period = registry.get("plone-recyclebin.retention_period", 30) + retention_period = registry.get("recyclebin-controlpanel.retention_period", 30) except Exception: retention_period = 30 From 962ef053d16dac4fb32be9b177d49867de20ef6d Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Mon, 22 Sep 2025 09:14:09 +0530 Subject: [PATCH 3/5] feat(recyclebin): refactor delete actions to utilize centralized recycle bin messaging --- src/plone/app/content/browser/actions.py | 31 +++++----------- .../app/content/browser/contents/delete.py | 25 ++++--------- src/plone/app/content/utils.py | 35 +++++++++++++++++++ 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/plone/app/content/browser/actions.py b/src/plone/app/content/browser/actions.py index 8e4d483a..6d12d982 100644 --- a/src/plone/app/content/browser/actions.py +++ b/src/plone/app/content/browser/actions.py @@ -2,7 +2,9 @@ from Acquisition import aq_inner from Acquisition import aq_parent from OFS.CopySupport import CopyError +from plone.app.content.utils import get_recycle_bin_message from plone.base import PloneMessageFactory as _ +from plone.base.interfaces.recyclebin import IRecycleBin from plone.base.utils import get_user_friendly_types from plone.base.utils import safe_text from plone.locking.interfaces import ILockable @@ -21,7 +23,6 @@ from zope.component import getMultiAdapter from zope.component import queryMultiAdapter from zope.component import queryUtility -from zope.component.hooks import getSite from zope.container.interfaces import INameChooser from zope.event import notify from zope.interface import Interface @@ -90,32 +91,16 @@ def handle_delete(self, action): parent.manage_delObjects(self.context.getId()) # Check if recycle bin is enabled and show appropriate message - try: - recyclebin_enabled_view = getMultiAdapter( - (getSite(), self.request), name="recyclebin-enabled" - ) - recycling_enabled = recyclebin_enabled_view() - except Exception: - recycling_enabled = False + recycle_bin = queryUtility(IRecycleBin) + recycling_enabled = recycle_bin.is_enabled() if recycle_bin else False if recycling_enabled: - # Get retention period from registry (default to 30 days if not found) + # Get retention period from registry registry = queryUtility(IRegistry) - retention_period = 30 # default - if registry is not None: - try: - retention_period = registry.get( - "recyclebin-controlpanel.retention_period", 30 - ) - except Exception: - retention_period = 30 + retention_period = registry["recyclebin-controlpanel.retention_period"] - IStatusMessage(self.request).add( - _( - "${title} has been moved to the recycle bin. It can be restored by administrators and will be permanently deleted after ${days} days.", - mapping={"title": title, "days": retention_period}, - ) - ) + message = get_recycle_bin_message(title=title, retention_period=retention_period) + IStatusMessage(self.request).add(message) else: IStatusMessage(self.request).add( _("${title} has been deleted.", mapping={"title": title}) diff --git a/src/plone/app/content/browser/contents/delete.py b/src/plone/app/content/browser/contents/delete.py index 9865d09a..28a44a3a 100644 --- a/src/plone/app/content/browser/contents/delete.py +++ b/src/plone/app/content/browser/contents/delete.py @@ -1,8 +1,10 @@ from AccessControl import Unauthorized from AccessControl.Permissions import delete_objects from plone.app.content.browser.contents import ContentsBaseAction +from plone.app.content.utils import get_recycle_bin_message from plone.app.content.interfaces import IStructureAction from plone.base import PloneMessageFactory as _ +from plone.base.interfaces.recyclebin import IRecycleBin from plone.locking.interfaces import ILockable from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName @@ -51,30 +53,17 @@ class DeleteActionView(ContentsBaseAction): def success_msg(self): """Dynamic success message that includes recycle bin information.""" # Check if recycle bin is enabled - try: - recyclebin_enabled_view = getMultiAdapter( - (getSite(), self.request), name="recyclebin-enabled" - ) - recycling_enabled = recyclebin_enabled_view() - except Exception: - recycling_enabled = False + recycle_bin = queryUtility(IRecycleBin) + recycling_enabled = recycle_bin.is_enabled() if recycle_bin else False if not recycling_enabled: return _("Successfully deleted items") - # Get retention period from registry (default to 30 days if not found) + # Get retention period from registry registry = queryUtility(IRegistry) - retention_period = 30 # default - if registry is not None: - try: - retention_period = registry.get("recyclebin-controlpanel.retention_period", 30) - except Exception: - retention_period = 30 + retention_period = registry["recyclebin-controlpanel.retention_period"] - return _( - "Successfully moved items to recycle bin. Items can be restored by administrators and will be permanently deleted after ${days} days.", - mapping={"days": retention_period}, - ) + return get_recycle_bin_message(retention_period=retention_period) def __call__(self): if self.request.form.get("render") == "yes": diff --git a/src/plone/app/content/utils.py b/src/plone/app/content/utils.py index a6131bba..baad070c 100644 --- a/src/plone/app/content/utils.py +++ b/src/plone/app/content/utils.py @@ -40,3 +40,38 @@ def json_dumps(data): # can eventually provide custom handling here if we want json_loads = simplejson.loads + + +def get_recycle_bin_message(title=None, retention_period=0): + """Generate appropriate message for recycled items based on retention period. + + Args: + title: The title of the deleted item (optional, for single item messages) + retention_period: Number of days to retain items (0 = indefinite) + + Returns: + Translated message string + """ + from plone.base import PloneMessageFactory as _ + + if title: + # Single item message + if retention_period == 0: + return _( + "${title} has been moved to the recycle bin. It can be restored by administrators.", + mapping={"title": title}, + ) + else: + return _( + "${title} has been moved to the recycle bin. It can be restored by administrators and will be permanently deleted after ${days} days.", + mapping={"title": title, "days": retention_period}, + ) + else: + # Multiple items message + if retention_period == 0: + return _("Successfully moved items to recycle bin. Items can be restored by administrators.") + else: + return _( + "Successfully moved items to recycle bin. Items can be restored by administrators and will be permanently deleted after ${days} days.", + mapping={"days": retention_period}, + ) From 22bdbf3209c36f64238f6a039bb5ebfa90b2f12a Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Mon, 22 Sep 2025 09:16:58 +0530 Subject: [PATCH 4/5] lint --- src/plone/app/content/browser/actions.py | 4 +++- src/plone/app/content/browser/contents/delete.py | 2 +- src/plone/app/content/utils.py | 10 ++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/plone/app/content/browser/actions.py b/src/plone/app/content/browser/actions.py index 6d12d982..737598f2 100644 --- a/src/plone/app/content/browser/actions.py +++ b/src/plone/app/content/browser/actions.py @@ -99,7 +99,9 @@ def handle_delete(self, action): registry = queryUtility(IRegistry) retention_period = registry["recyclebin-controlpanel.retention_period"] - message = get_recycle_bin_message(title=title, retention_period=retention_period) + message = get_recycle_bin_message( + title=title, retention_period=retention_period + ) IStatusMessage(self.request).add(message) else: IStatusMessage(self.request).add( diff --git a/src/plone/app/content/browser/contents/delete.py b/src/plone/app/content/browser/contents/delete.py index 28a44a3a..9be44321 100644 --- a/src/plone/app/content/browser/contents/delete.py +++ b/src/plone/app/content/browser/contents/delete.py @@ -1,8 +1,8 @@ from AccessControl import Unauthorized from AccessControl.Permissions import delete_objects from plone.app.content.browser.contents import ContentsBaseAction -from plone.app.content.utils import get_recycle_bin_message from plone.app.content.interfaces import IStructureAction +from plone.app.content.utils import get_recycle_bin_message from plone.base import PloneMessageFactory as _ from plone.base.interfaces.recyclebin import IRecycleBin from plone.locking.interfaces import ILockable diff --git a/src/plone/app/content/utils.py b/src/plone/app/content/utils.py index baad070c..f5dd43e4 100644 --- a/src/plone/app/content/utils.py +++ b/src/plone/app/content/utils.py @@ -44,16 +44,16 @@ def json_dumps(data): def get_recycle_bin_message(title=None, retention_period=0): """Generate appropriate message for recycled items based on retention period. - + Args: title: The title of the deleted item (optional, for single item messages) retention_period: Number of days to retain items (0 = indefinite) - + Returns: Translated message string """ from plone.base import PloneMessageFactory as _ - + if title: # Single item message if retention_period == 0: @@ -69,7 +69,9 @@ def get_recycle_bin_message(title=None, retention_period=0): else: # Multiple items message if retention_period == 0: - return _("Successfully moved items to recycle bin. Items can be restored by administrators.") + return _( + "Successfully moved items to recycle bin. Items can be restored by administrators." + ) else: return _( "Successfully moved items to recycle bin. Items can be restored by administrators and will be permanently deleted after ${days} days.", From 6a83a04539c43fb7ed673fb87316cc0c4b1c449d Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Wed, 1 Oct 2025 09:20:38 +0530 Subject: [PATCH 5/5] feat: refactor deletion messaging to utilize centralized success message handling --- src/plone/app/content/browser/actions.py | 25 +++-------------- .../app/content/browser/contents/delete.py | 18 ++---------- src/plone/app/content/utils.py | 28 ++++++++++++++++--- 3 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/plone/app/content/browser/actions.py b/src/plone/app/content/browser/actions.py index 737598f2..9e3d8fa7 100644 --- a/src/plone/app/content/browser/actions.py +++ b/src/plone/app/content/browser/actions.py @@ -2,13 +2,11 @@ from Acquisition import aq_inner from Acquisition import aq_parent from OFS.CopySupport import CopyError -from plone.app.content.utils import get_recycle_bin_message +from plone.app.content.utils import get_deleted_success_message from plone.base import PloneMessageFactory as _ -from plone.base.interfaces.recyclebin import IRecycleBin from plone.base.utils import get_user_friendly_types from plone.base.utils import safe_text from plone.locking.interfaces import ILockable -from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile @@ -22,7 +20,6 @@ from zope import schema from zope.component import getMultiAdapter from zope.component import queryMultiAdapter -from zope.component import queryUtility from zope.container.interfaces import INameChooser from zope.event import notify from zope.interface import Interface @@ -90,23 +87,9 @@ def handle_delete(self, action): ILockable(self.context).unlock() parent.manage_delObjects(self.context.getId()) - # Check if recycle bin is enabled and show appropriate message - recycle_bin = queryUtility(IRecycleBin) - recycling_enabled = recycle_bin.is_enabled() if recycle_bin else False - - if recycling_enabled: - # Get retention period from registry - registry = queryUtility(IRegistry) - retention_period = registry["recyclebin-controlpanel.retention_period"] - - message = get_recycle_bin_message( - title=title, retention_period=retention_period - ) - IStatusMessage(self.request).add(message) - else: - IStatusMessage(self.request).add( - _("${title} has been deleted.", mapping={"title": title}) - ) + # Show appropriate message (automatically handles recycle bin checks) + message = get_deleted_success_message(title=title) + IStatusMessage(self.request).add(message) else: IStatusMessage(self.request).add( _('"${title}" has already been deleted', mapping={"title": title}) diff --git a/src/plone/app/content/browser/contents/delete.py b/src/plone/app/content/browser/contents/delete.py index 9be44321..f60a44af 100644 --- a/src/plone/app/content/browser/contents/delete.py +++ b/src/plone/app/content/browser/contents/delete.py @@ -2,15 +2,12 @@ from AccessControl.Permissions import delete_objects from plone.app.content.browser.contents import ContentsBaseAction from plone.app.content.interfaces import IStructureAction -from plone.app.content.utils import get_recycle_bin_message +from plone.app.content.utils import get_deleted_success_message from plone.base import PloneMessageFactory as _ -from plone.base.interfaces.recyclebin import IRecycleBin from plone.locking.interfaces import ILockable -from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from zope.component import getMultiAdapter -from zope.component import queryUtility from zope.component.hooks import getSite from zope.i18n import translate from zope.interface import implementer @@ -52,18 +49,7 @@ class DeleteActionView(ContentsBaseAction): @property def success_msg(self): """Dynamic success message that includes recycle bin information.""" - # Check if recycle bin is enabled - recycle_bin = queryUtility(IRecycleBin) - recycling_enabled = recycle_bin.is_enabled() if recycle_bin else False - - if not recycling_enabled: - return _("Successfully deleted items") - - # Get retention period from registry - registry = queryUtility(IRegistry) - retention_period = registry["recyclebin-controlpanel.retention_period"] - - return get_recycle_bin_message(retention_period=retention_period) + return get_deleted_success_message() def __call__(self): if self.request.form.get("render") == "yes": diff --git a/src/plone/app/content/utils.py b/src/plone/app/content/utils.py index f5dd43e4..e93c115e 100644 --- a/src/plone/app/content/utils.py +++ b/src/plone/app/content/utils.py @@ -1,6 +1,10 @@ from DateTime import DateTime from persistent.list import PersistentList from persistent.mapping import PersistentMapping +from plone.base import PloneMessageFactory as _ +from plone.base.interfaces.recyclebin import IRecycleBin +from plone.registry.interfaces import IRegistry +from zope.component import queryUtility import datetime import Missing @@ -42,17 +46,33 @@ def json_dumps(data): json_loads = simplejson.loads -def get_recycle_bin_message(title=None, retention_period=0): - """Generate appropriate message for recycled items based on retention period. +def get_deleted_success_message(title=None): + """Generate appropriate success message for deleted items. + + Automatically checks if recycle bin is enabled and gets retention period + from registry to avoid code duplication. Args: title: The title of the deleted item (optional, for single item messages) - retention_period: Number of days to retain items (0 = indefinite) Returns: Translated message string """ - from plone.base import PloneMessageFactory as _ + + # Check if recycle bin is enabled + recycle_bin = queryUtility(IRecycleBin) + recycling_enabled = recycle_bin.is_enabled() if recycle_bin else False + + if not recycling_enabled: + # Recycle bin is disabled, show regular deletion message + if title: + return _("${title} has been deleted.", mapping={"title": title}) + else: + return _("Successfully deleted items") + + # Recycle bin is enabled, get retention period and show recycle message + registry = queryUtility(IRegistry) + retention_period = registry["recyclebin-controlpanel.retention_period"] if title: # Single item message