diff --git a/djangocms_versioning/cms_toolbars.py b/djangocms_versioning/cms_toolbars.py index 777bc9e0..9b2f75fa 100644 --- a/djangocms_versioning/cms_toolbars.py +++ b/djangocms_versioning/cms_toolbars.py @@ -2,12 +2,14 @@ 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, 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 @@ -23,8 +25,9 @@ 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 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, @@ -33,6 +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") class VersioningToolbar(PlaceholderToolbar): @@ -385,6 +389,23 @@ def change_language_menu(self): ) add_plugins_menu.add_modal_item(name, url=url) + 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") + ) + 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) + 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( f"{LANGUAGE_MENU_IDENTIFIER}-copy", _("Copy all plugins") @@ -394,7 +415,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( diff --git a/setup.py b/setup.py index d4c7ae5f..21af7063 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,8 @@ INSTALL_REQUIREMENTS = [ "Django>=3.2", "django-cms>=4.1.1", - "django-fsm<3" + "django-fsm<3", + "packaging", ] setup( diff --git a/tests/test_toolbars.py b/tests/test_toolbars.py index e4674560..f26fb924 100644 --- a/tests/test_toolbars.py +++ b/tests/test_toolbars.py @@ -1,9 +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 @@ -26,6 +32,8 @@ toolbar_button_exists, ) +cms_version = Version(__version__) + class VersioningToolbarTestCase(CMSTestCase): def _get_publish_url(self, version, versionable=PollsCMSConfig.versioning[0]): @@ -488,7 +496,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 +541,73 @@ 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"), "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. + """ + 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 + + @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 + + 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(), 6) + def test_change_language_menu_page_toolbar_language_selector_version_link(self): """ Ensure that the correct version is navigated to in the language selector.