From 2a4ac39f73ea7e06d3035e02e2832c21e2794895 Mon Sep 17 00:00:00 2001 From: Slavisa Karalic <slavisa.karalic@gmail.com> Date: Tue, 12 Jan 2016 10:04:43 +0100 Subject: [PATCH] Do recursive check and more verbose reporting --- CHANGES.rst | 5 +- .../browser/delete_confirmation_info.pt | 97 ++++++++++++------- plone/app/linkintegrity/browser/info.py | 35 +++++-- .../linkintegrity/tests/test_functional.py | 10 ++ 4 files changed, 101 insertions(+), 46 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b48995e..bf9dfc9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,7 +10,10 @@ Breaking changes: New features: -- *add item here* +- Information about contents within a selected folder for deletion. + This information contains number of deleted objects, + number of subfolders and number of published objects. + [karalics] Bug fixes: diff --git a/plone/app/linkintegrity/browser/delete_confirmation_info.pt b/plone/app/linkintegrity/browser/delete_confirmation_info.pt index 4f1fe50..e2cf7fb 100644 --- a/plone/app/linkintegrity/browser/delete_confirmation_info.pt +++ b/plone/app/linkintegrity/browser/delete_confirmation_info.pt @@ -1,53 +1,80 @@ <tal:block - tal:define="breaches python:view.breaches" - tal:condition="breaches" + tal:define="breaches python:view.breaches;" i18n:domain="plone"> - <h2 i18n:translate="linkintegrity_breaches_title">Potential link breakage</h2> + + <h2 tal:condition="breaches" i18n:translate="linkintegrity_breaches_title">Potential link breakage</h2> <div id="content-core"> - <p i18n:translate="linkintegrity_instructions"> + + <p tal:condition="breaches" i18n:translate="linkintegrity_instructions"> By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that you remove these references first. </p> <div tal:define="token context/@@authenticator/token"> - <article tal:repeat="breach breaches" class="breach-container"> - - <tal:target tal:define="target breach/target"> - <header><a href="${target/url}" tal:content="target/title" /></header> - <p> - <span i18n:translate="linkintegrity_is_referenced"> - This <span i18n:name="portal_type" tal:content="target/type_title" /> - is referenced by the following items: - </span> - </p> - </tal:target> - - <ul> - <li tal:repeat="source python:breach['sources']" class="breach-item"> - <tal:visible condition="source/accessible"> - <a tal:attributes="href source/url" tal:content="source/title" /> - [<a tal:attributes="href string:${source/url}/edit?_authenticator=${token}" - i18n:translate="linkintegrity_edit_in_new_window" - target="_blank">Edit in new window</a>] - </tal:visible> - <tal:private condition="not: source/accessible" - i18n:translate="linkintegrity_item_not_accessible"> - The item is not accessible. - </tal:private> - </li> - </ul> - - </article> - - <br /> + <article tal:repeat="breach breaches" class="breach-container"> + + <tal:target tal:define="target breach/target"> + <header><a href="${target/url}" tal:content="target/title" /></header> + <p> + <span i18n:translate="linkintegrity_is_referenced"> + This <span i18n:name="portal_type" tal:content="target/type_title" /> + is referenced by the following items: + </span> + </p> + </tal:target> + + <ul> + <li tal:repeat="source python:breach['sources']" class="breach-item"> + <tal:visible condition="source/accessible"> + <a tal:attributes="href source/url" tal:content="source/title" /> + [<a tal:attributes="href string:${source/url}/edit?_authenticator=${token}" + i18n:translate="linkintegrity_edit_in_new_window" + target="_blank">Edit in new window</a>] + </tal:visible> + <tal:private condition="not: source/accessible" + i18n:translate="linkintegrity_item_not_accessible"> + The item is not accessible. + </tal:private> + </li> + </ul> + + </article> + + <div tal:define="breach_count view/breach_count" tal:condition="breach_count"> + + <h2 i18n:translate="deleting_overview" >Deleting overview</h2> + <p> + <span tal:define="refs python:len(breach_count)" + i18n:translate="selected_folders_with_content"> + Number of selected, non-empty folders: <strong><span tal:replace="refs" i18n:name="refs" /></strong> + </span> + </p> + <ul> + <li tal:repeat="content python:breach_count"> + <span i18n:translate="deleting_contents"> Following content within + <strong><span tal:replace="content" i18n:name="content" /></strong> will also be deleted: + </span><br> + <ul> + <li tal:define="objects python:['Objects in all', 'Folders', 'Published objects'] " + tal:repeat="item python:range(3) " + i18n:translate="" > + <span tal:replace="python: breach_count[content][item]" /> + <span i18n:translate="" tal:replace="python: objects[item]" /> + </li> + </ul> + </li> + </ul> + + + + </div> <p i18n:translate="linkintegrity_delete_anyway"> Would you like to delete it anyway? </p> - </div> </div> </tal:block> diff --git a/plone/app/linkintegrity/browser/info.py b/plone/app/linkintegrity/browser/info.py index 7363028..eb10069 100644 --- a/plone/app/linkintegrity/browser/info.py +++ b/plone/app/linkintegrity/browser/info.py @@ -16,6 +16,7 @@ class DeleteConfirmationInfo(BrowserView): template = ViewPageTemplateFile('delete_confirmation_info.pt') + breach_count = {} def __init__(self, context, request): self.linkintegrity_enabled = linkintegrity_enabled() @@ -41,26 +42,38 @@ def get_breaches(self, items=None): or their children (if a object is a folder) will be ignored. """ if items is None: - items = [self.context] catalog = getToolByName(self.context, 'portal_catalog') results = [] uids_to_ignore = [] + uids_visited = set() + self.breach_count = {} for obj in items: obj_path = '/'.join(obj.getPhysicalPath()) brains_to_delete = catalog(path={'query': obj_path}) # add the current items uid and all its childrens uids to the # list of uids that are ignored uids_to_ignore.extend([i.UID for i in brains_to_delete]) - for breach in self.get_breaches_for_item(obj): - add_breach = False - for source in breach['sources']: - # Only add the breach if one the sources is not in the - # list of items that are to be deleted. - if source['uid'] not in uids_to_ignore: - add_breach = True - if add_breach: - results.append(breach) + for brain_to_delete in brains_to_delete: + obj_to_delete = brain_to_delete.getObject() + for breach in self.get_breaches_for_item(obj): + add_breach = False + for source in breach['sources']: + # Only add the breach if one the sources is not in the + # list of items that are to be deleted. + if source['uid'] not in uids_to_ignore and \ + source['uid'] not in uids_visited: + add_breach = True + uids_visited.add(source['uid']) + break + if add_breach: + results.append(breach) + if IFolder.providedBy(obj): + count = len(catalog(path={'query': obj_path})) + count_dirs = len(catalog(path={'query': obj_path}, is_folderish=True)) + count_public = len(catalog(path={'query': obj_path}, review_state='published')) + if count: + self.breach_count[obj_path]=[count, count_dirs, count_public] # Cleanup: Some breaches where added before it was known # that their source will be deleted too. @@ -76,6 +89,7 @@ def get_breaches(self, items=None): results.remove(result) return results + def get_breaches_for_item(self, obj=None): """Get breaches for one object and its children. @@ -153,3 +167,4 @@ def get_portal_type_title(self, obj): def is_accessible(self, obj): return _checkPermission(AccessContentsInformation, obj) + diff --git a/plone/app/linkintegrity/tests/test_functional.py b/plone/app/linkintegrity/tests/test_functional.py index a1fbf36..b13c1a8 100644 --- a/plone/app/linkintegrity/tests/test_functional.py +++ b/plone/app/linkintegrity/tests/test_functional.py @@ -352,6 +352,16 @@ def test_removal_via_zmi(self): self.browser.getControl('Delete').click() self.assertNotIn('doc2', self.portal.objectIds()) + def test_warn_about_content(self): + folder1 = self.portal.folder1 + doc1 = self.portal.folder1.doc1 + self.browser.open('{0:s}/delete_confirmation?_authenticator={1:s}'.format( + folder1.absolute_url(), self._get_token(folder1))) + self.assertIn('Number of selected', self.browser.contents) + self.assertIn('2 Objects in all', self.browser.contents) + self.assertIn('1 Folders', self.browser.contents) + self.assertIn('0 Published objects', self.browser.contents) + class FunctionalReferenceDXTestCase(DXBaseTestCase, ReferenceTestCase): """Functional reference testcase for dx content types"""