From 4aad1d83dfdb811d3a7d322bdd7f0a12510a259a Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Wed, 15 Jan 2025 12:05:59 +0100 Subject: [PATCH 01/12] feat: Re-introduce deleting languages of a page --- djangocms_versioning/cms_toolbars.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/djangocms_versioning/cms_toolbars.py b/djangocms_versioning/cms_toolbars.py index 777bc9e0..ee6f17b8 100644 --- a/djangocms_versioning/cms_toolbars.py +++ b/djangocms_versioning/cms_toolbars.py @@ -24,7 +24,7 @@ from django.utils.http import urlencode from django.utils.translation import gettext_lazy as _ -from djangocms_versioning.conf import LOCK_VERSIONS +from djangocms_versioning.conf import ALLOW_DELETING_VERSIONS, LOCK_VERSIONS from djangocms_versioning.constants import DRAFT, PUBLISHED from djangocms_versioning.helpers import ( get_latest_admin_viewable_content, @@ -385,6 +385,18 @@ def change_language_menu(self): ) add_plugins_menu.add_modal_item(name, url=url) + if remove and ALLOW_DELETING_VERSIONS: + remove_plugins_menu = language_menu.get_or_create_menu( + f'{LANGUAGE_MENU_IDENTIFIER}-del', _('Delete Translation') + ) + disabled = len(remove) == 1 + for code, name in remove: + pagecontent = self.page.get_admin_content(language=code) + if pagecontent: + translation_delete_url = admin_reverse('cms_pagecontent_delete', args=(pagecontent.pk,)) + url = add_url_parameters(translation_delete_url, language=code) + remove_plugins_menu.add_modal_item(name, url=url, disabled=disabled) + if copy: copy_plugins_menu = language_menu.get_or_create_menu( f"{LANGUAGE_MENU_IDENTIFIER}-copy", _("Copy all plugins") @@ -394,7 +406,7 @@ def change_language_menu(self): item_added = False for code, name in copy: # Get the Draft or Published PageContent. - page_content = self.get_page_content(language=code) + page_content = self.page.get_admin_content(language=code) if page_content: # Only offer to copy if content for source language exists page_copy_url = admin_reverse("cms_pagecontent_copy_language", args=(page_content.pk,)) copy_plugins_menu.add_ajax_item( From 7687000793fad89df0e3bb535ad5a0f3e94656b5 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Wed, 15 Jan 2025 12:09:34 +0100 Subject: [PATCH 02/12] Fix ruff issues --- djangocms_versioning/cms_toolbars.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/djangocms_versioning/cms_toolbars.py b/djangocms_versioning/cms_toolbars.py index ee6f17b8..13f8ff98 100644 --- a/djangocms_versioning/cms_toolbars.py +++ b/djangocms_versioning/cms_toolbars.py @@ -387,13 +387,13 @@ def change_language_menu(self): if remove and ALLOW_DELETING_VERSIONS: remove_plugins_menu = language_menu.get_or_create_menu( - f'{LANGUAGE_MENU_IDENTIFIER}-del', _('Delete Translation') + f"{LANGUAGE_MENU_IDENTIFIER}-del", _("Delete Translation") ) disabled = len(remove) == 1 for code, name in remove: pagecontent = self.page.get_admin_content(language=code) if pagecontent: - translation_delete_url = admin_reverse('cms_pagecontent_delete', args=(pagecontent.pk,)) + translation_delete_url = admin_reverse("cms_pagecontent_delete", args=(pagecontent.pk,)) url = add_url_parameters(translation_delete_url, language=code) remove_plugins_menu.add_modal_item(name, url=url, disabled=disabled) From debf6a327f3ba9730f36f1114a80b5d5e059ba89 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Wed, 15 Jan 2025 12:56:00 +0100 Subject: [PATCH 03/12] Add test for delete tranlsation menu --- tests/test_toolbars.py | 47 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/test_toolbars.py b/tests/test_toolbars.py index e4674560..69dc7909 100644 --- a/tests/test_toolbars.py +++ b/tests/test_toolbars.py @@ -1,3 +1,6 @@ +import re +from unittest.mock import patch + from cms.cms_toolbars import LANGUAGE_MENU_IDENTIFIER, PlaceholderToolbar from cms.test_utils.testcases import CMSTestCase from cms.toolbar.utils import get_object_edit_url, get_object_preview_url @@ -488,7 +491,7 @@ def _get_toolbar_item_by_name(self, menu, name): def test_change_language_menu_page_toolbar(self): """Check that patched PageToolbar.change_language_menu only provides - Add Translation links. + Add Translation links if DJANGOCMS_ALLOW_DELETING_VERSIONS is False. """ version = PageVersionFactory(content__language="en") PageContentWithVersionFactory(page=version.content.page, language="de") @@ -533,6 +536,48 @@ def test_change_language_menu_page_toolbar(self): lang_code = "fr" if "Française" in item.name else "it" self.assertIn(f"language={lang_code}", item.url) + def test_change_language_menu_page_toolbar_including_delete(self): + """Check that patched PageToolbar.change_language_menu also provides + Delete Translation links if DJANGOCMS_ALLOW_DELETING_VERSIONS is True. + """ + from djangocms_versioning import cms_toolbars + + with patch.object(cms_toolbars, "ALLOW_DELETING_VERSIONS", True): + version = PageVersionFactory(content__language="en") + PageContentWithVersionFactory(page=version.content.page, language="de") + PageContentWithVersionFactory(page=version.content.page, language="it") + page = version.content.page + page.update_languages(["en", "de", "it"]) + + request = self.get_page_request( + page=page, + path=get_object_edit_url(version.content), + user=self.get_superuser(), + ) + request.toolbar.set_object(version.content) + request.toolbar.populate() + request.toolbar.post_template_populate() + + language_menu = request.toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER) + # 3 out of 4 populated languages, Break, Add Translation menu, Copy all plugins + self.assertEqual(language_menu.get_item_count(), 7) + + language_menu_dict = { + menu.name: list(menu.items) + for key, menu in language_menu.menus.items() + } + self.assertIn("Add Translation", language_menu_dict.keys()) + self.assertIn("Copy all plugins", language_menu_dict.keys()) + self.assertIn("Delete Translation", language_menu_dict.keys()) + + pattern = r'\?language=([a-z]{2})' + for item in language_menu_dict["Delete Translation"]: + match = re.search(pattern, item.url) # Contains "?language=“? + self.assertTrue(bool(match)) + code = match.group(1) # Extract code + pk = page.get_admin_content(code).pk # get content object + self.assertIn(admin_reverse("cms_pagecontent_delete", args=(int(pk),)), item.url) # verify url + def test_change_language_menu_page_toolbar_language_selector_version_link(self): """ Ensure that the correct version is navigated to in the language selector. From 04227dab964c2a13f520dd57e4b8e0818bf6e810 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Wed, 15 Jan 2025 12:57:16 +0100 Subject: [PATCH 04/12] Single-quoted regex --- tests/test_toolbars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_toolbars.py b/tests/test_toolbars.py index 69dc7909..8a110da3 100644 --- a/tests/test_toolbars.py +++ b/tests/test_toolbars.py @@ -570,7 +570,7 @@ def test_change_language_menu_page_toolbar_including_delete(self): self.assertIn("Copy all plugins", language_menu_dict.keys()) self.assertIn("Delete Translation", language_menu_dict.keys()) - pattern = r'\?language=([a-z]{2})' + pattern = r"\?language=([a-z]{2})" for item in language_menu_dict["Delete Translation"]: match = re.search(pattern, item.url) # Contains "?language=“? self.assertTrue(bool(match)) From 9581840cc5859090d2a399ac2e1b151ba361b507 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 12:35:08 +0100 Subject: [PATCH 05/12] Only activate delete translation for django CMS version supporting it --- djangocms_versioning/cms_toolbars.py | 5 ++++- setup.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/djangocms_versioning/cms_toolbars.py b/djangocms_versioning/cms_toolbars.py index 13f8ff98..985d5f05 100644 --- a/djangocms_versioning/cms_toolbars.py +++ b/djangocms_versioning/cms_toolbars.py @@ -2,6 +2,7 @@ from copy import copy from typing import Optional +from cms import __version__ as cms_version from cms.cms_toolbars import ( ADD_PAGE_LANGUAGE_BREAK, LANGUAGE_MENU_IDENTIFIER, @@ -23,6 +24,7 @@ from django.urls import reverse from django.utils.http import urlencode from django.utils.translation import gettext_lazy as _ +from packaging import version from djangocms_versioning.conf import ALLOW_DELETING_VERSIONS, LOCK_VERSIONS from djangocms_versioning.constants import DRAFT, PUBLISHED @@ -33,6 +35,7 @@ from djangocms_versioning.models import Version VERSIONING_MENU_IDENTIFIER = "version" +CMS_SUPPORTS_DELETING_TRANSLATIONS = version.Version(cms_version) > version.Version("4.1.4") class VersioningToolbar(PlaceholderToolbar): @@ -385,7 +388,7 @@ def change_language_menu(self): ) add_plugins_menu.add_modal_item(name, url=url) - if remove and ALLOW_DELETING_VERSIONS: + if remove and ALLOW_DELETING_VERSIONS and CMS_SUPPORTS_DELETING_TRANSLATIONS: remove_plugins_menu = language_menu.get_or_create_menu( f"{LANGUAGE_MENU_IDENTIFIER}-del", _("Delete Translation") ) diff --git a/setup.py b/setup.py index d4c7ae5f..f8886579 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ "Django>=3.2", "django-cms>=4.1.1", "django-fsm<3" + "packaging", ] setup( From 2fa1841eff33aeb26f738d7c2d1f87e12d503ecf Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 16:39:36 +0100 Subject: [PATCH 06/12] fix: Redirect to different language if current translation is deleted --- djangocms_versioning/cms_toolbars.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/djangocms_versioning/cms_toolbars.py b/djangocms_versioning/cms_toolbars.py index 985d5f05..7245f162 100644 --- a/djangocms_versioning/cms_toolbars.py +++ b/djangocms_versioning/cms_toolbars.py @@ -9,6 +9,7 @@ PageToolbar, PlaceholderToolbar, ) +from cms.constants import REFRESH_PAGE from cms.models import PageContent from cms.toolbar.items import RIGHT, Break, ButtonList, TemplateItem from cms.toolbar.utils import get_object_preview_url @@ -35,7 +36,7 @@ from djangocms_versioning.models import Version VERSIONING_MENU_IDENTIFIER = "version" -CMS_SUPPORTS_DELETING_TRANSLATIONS = version.Version(cms_version) > version.Version("4.1.4") +CMS_SUPPORTS_DELETING_TRANSLATIONS = version.Version(cms_version) > version.Version("4.1.4") or True class VersioningToolbar(PlaceholderToolbar): @@ -398,7 +399,12 @@ def change_language_menu(self): if pagecontent: translation_delete_url = admin_reverse("cms_pagecontent_delete", args=(pagecontent.pk,)) url = add_url_parameters(translation_delete_url, language=code) - remove_plugins_menu.add_modal_item(name, url=url, disabled=disabled) + on_close = REFRESH_PAGE + if self.toolbar.get_object() == pagecontent and not disabled: + other_content = next((self.page.get_admin_content(lang)for lang in self.page.get_languages() + if lang != pagecontent.language and lang in languages), None) + on_close = get_object_preview_url(other_content) + remove_plugins_menu.add_modal_item(name, url=url, disabled=disabled, on_close=on_close) if copy: copy_plugins_menu = language_menu.get_or_create_menu( From 1c11746b655fa189a36b8637adee75a70d239556 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 16:41:35 +0100 Subject: [PATCH 07/12] fix setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f8886579..21af7063 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ INSTALL_REQUIREMENTS = [ "Django>=3.2", "django-cms>=4.1.1", - "django-fsm<3" + "django-fsm<3", "packaging", ] From a84fa6f4fa6eceed315557a568633fd649732d5f Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 18:37:20 +0100 Subject: [PATCH 08/12] Update djangocms_versioning/cms_toolbars.py --- djangocms_versioning/cms_toolbars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangocms_versioning/cms_toolbars.py b/djangocms_versioning/cms_toolbars.py index 7245f162..9b2f75fa 100644 --- a/djangocms_versioning/cms_toolbars.py +++ b/djangocms_versioning/cms_toolbars.py @@ -36,7 +36,7 @@ from djangocms_versioning.models import Version VERSIONING_MENU_IDENTIFIER = "version" -CMS_SUPPORTS_DELETING_TRANSLATIONS = version.Version(cms_version) > version.Version("4.1.4") or True +CMS_SUPPORTS_DELETING_TRANSLATIONS = version.Version(cms_version) > version.Version("4.1.4") class VersioningToolbar(PlaceholderToolbar): From 4474e44015a7ddc8451e8f3688cc465316bfa4d9 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 18:44:51 +0100 Subject: [PATCH 09/12] Update tests --- tests/test_toolbars.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_toolbars.py b/tests/test_toolbars.py index 8a110da3..aef4ce2b 100644 --- a/tests/test_toolbars.py +++ b/tests/test_toolbars.py @@ -1,12 +1,15 @@ import re +from unittest import skipIf from unittest.mock import patch +from cms import __version__ from cms.cms_toolbars import LANGUAGE_MENU_IDENTIFIER, PlaceholderToolbar from cms.test_utils.testcases import CMSTestCase from cms.toolbar.utils import get_object_edit_url, get_object_preview_url from cms.utils.urlutils import admin_reverse from django.contrib.auth.models import Permission from django.utils.text import slugify +from packaging.version import Version from djangocms_versioning.cms_config import VersioningCMSConfig from djangocms_versioning.cms_toolbars import VersioningPageToolbar @@ -30,6 +33,9 @@ ) +cms_version = Version(__version__) + + class VersioningToolbarTestCase(CMSTestCase): def _get_publish_url(self, version, versionable=PollsCMSConfig.versioning[0]): """Helper method to return the expected publish url @@ -536,6 +542,7 @@ def test_change_language_menu_page_toolbar(self): lang_code = "fr" if "Française" in item.name else "it" self.assertIn(f"language={lang_code}", item.url) + @skipIf(cms_version <= Version("4.1.4")) def test_change_language_menu_page_toolbar_including_delete(self): """Check that patched PageToolbar.change_language_menu also provides Delete Translation links if DJANGOCMS_ALLOW_DELETING_VERSIONS is True. @@ -578,6 +585,30 @@ def test_change_language_menu_page_toolbar_including_delete(self): pk = page.get_admin_content(code).pk # get content object self.assertIn(admin_reverse("cms_pagecontent_delete", args=(int(pk),)), item.url) # verify url + @skipIf(cms_version > Version("4.1.4")) + def test_change_language_menu_page_toolbar_excluding_delete(self): + from djangocms_versioning import cms_toolbars + + with patch.object(cms_toolbars, "ALLOW_DELETING_VERSIONS", True): + version = PageVersionFactory(content__language="en") + PageContentWithVersionFactory(page=version.content.page, language="de") + PageContentWithVersionFactory(page=version.content.page, language="it") + page = version.content.page + page.update_languages(["en", "de", "it"]) + + request = self.get_page_request( + page=page, + path=get_object_edit_url(version.content), + user=self.get_superuser(), + ) + request.toolbar.set_object(version.content) + request.toolbar.populate() + request.toolbar.post_template_populate() + + language_menu = request.toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER) + # 3 out of 4 populated languages, Break, Add Translation menu, Copy all plugins + self.assertEqual(language_menu.get_item_count(), 7) + def test_change_language_menu_page_toolbar_language_selector_version_link(self): """ Ensure that the correct version is navigated to in the language selector. From a4f57376ed98a6bebf79b34962d60eb23d7fbc8c Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 18:47:08 +0100 Subject: [PATCH 10/12] fix linting issue --- tests/test_toolbars.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_toolbars.py b/tests/test_toolbars.py index aef4ce2b..fee887fb 100644 --- a/tests/test_toolbars.py +++ b/tests/test_toolbars.py @@ -32,7 +32,6 @@ toolbar_button_exists, ) - cms_version = Version(__version__) From 2e52bd40036329e6cfcd1adaff52fe33f373a543 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 18:51:11 +0100 Subject: [PATCH 11/12] Add `skipIf` reason --- tests/test_toolbars.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_toolbars.py b/tests/test_toolbars.py index fee887fb..6e876050 100644 --- a/tests/test_toolbars.py +++ b/tests/test_toolbars.py @@ -541,7 +541,7 @@ def test_change_language_menu_page_toolbar(self): lang_code = "fr" if "Française" in item.name else "it" self.assertIn(f"language={lang_code}", item.url) - @skipIf(cms_version <= Version("4.1.4")) + @skipIf(cms_version <= Version("4.1.4"), "For CMS 4.1.5 and bove: Add delete translation menu") def test_change_language_menu_page_toolbar_including_delete(self): """Check that patched PageToolbar.change_language_menu also provides Delete Translation links if DJANGOCMS_ALLOW_DELETING_VERSIONS is True. @@ -584,7 +584,7 @@ def test_change_language_menu_page_toolbar_including_delete(self): pk = page.get_admin_content(code).pk # get content object self.assertIn(admin_reverse("cms_pagecontent_delete", args=(int(pk),)), item.url) # verify url - @skipIf(cms_version > Version("4.1.4")) + @skipIf(cms_version > Version("4.1.4"), "Only for CMS 4.1.4 and below: No delete translation menu") def test_change_language_menu_page_toolbar_excluding_delete(self): from djangocms_versioning import cms_toolbars From 1317dcea50dc1c590b34d50da7704b4194cf196a Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Thu, 16 Jan 2025 18:55:34 +0100 Subject: [PATCH 12/12] Fix test --- tests/test_toolbars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_toolbars.py b/tests/test_toolbars.py index 6e876050..f26fb924 100644 --- a/tests/test_toolbars.py +++ b/tests/test_toolbars.py @@ -606,7 +606,7 @@ def test_change_language_menu_page_toolbar_excluding_delete(self): language_menu = request.toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER) # 3 out of 4 populated languages, Break, Add Translation menu, Copy all plugins - self.assertEqual(language_menu.get_item_count(), 7) + self.assertEqual(language_menu.get_item_count(), 6) def test_change_language_menu_page_toolbar_language_selector_version_link(self): """