From 84ca9bce0ba65936dcdef70c55076c8e197ae56a Mon Sep 17 00:00:00 2001 From: Dan Braghis Date: Fri, 25 Mar 2022 14:51:28 +0000 Subject: [PATCH 1/3] Implement construct_translated_pages_to_cascade_actions fup hook --- wagtail_localize/wagtail_hooks.py | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/wagtail_localize/wagtail_hooks.py b/wagtail_localize/wagtail_hooks.py index 5c6645813..e1fbe0edd 100644 --- a/wagtail_localize/wagtail_hooks.py +++ b/wagtail_localize/wagtail_hooks.py @@ -1,3 +1,4 @@ +from typing import List from urllib.parse import urlencode from django.contrib.admin.utils import quote @@ -466,3 +467,45 @@ def convert_to_alias_message(data): def register_icons(icons): # icon id "wagtail-localize-convert" (which translates to `.icon-wagtail-localize-convert`) return icons + ["wagtail_localize/icons/wagtail-localize-convert.svg"] + + +if WAGTAIL_VERSION >= (4, 0): + from .models import LocaleSynchronization + + @hooks.register("construct_translated_pages_to_cascade_actions") + def construct_synced_page_tree_list(pages: List[Page], action: str): + + locale_sync_map = {} + for page in pages: + # TODO: what about locale C follows B which follows A, when we come in from A? + if page.locale_id not in locale_sync_map: + locale_sync_map[page.locale_id] = list( + LocaleSynchronization.objects.filter( + sync_from=page.locale_id + ).values_list("locale", flat=True) + ) + + page_list = {} + if action == "unpublish": + for page in pages: + if not locale_sync_map[page.locale_id]: + # no locales sync from this page locale + continue + + page_list[page] = Page.objects.translation_of( + page, inclusive=False + ).filter( + alias_of__isnull=True, locale__in=locale_sync_map[page.locale_id] + ) + elif action in ["move", "delete"]: + for page in pages: + if not locale_sync_map[page.locale_id]: + # no locales sync from this page locale + continue + + # only include those translations or aliases from locales that sync from this locale + page_list[page] = Page.objects.translation_of( + page, inclusive=False + ).filter(locale__in=locale_sync_map[page.locale_id]) + + return page_list From dfb3665f69a75a2bc005bc250e8752db699cc18d Mon Sep 17 00:00:00 2001 From: Dan Braghis Date: Mon, 4 Apr 2022 22:49:54 +0100 Subject: [PATCH 2/3] Add tests --- wagtail_localize/tests/test_synctree.py | 79 ++++++++++++++++++++++++- wagtail_localize/wagtail_hooks.py | 1 - 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/wagtail_localize/tests/test_synctree.py b/wagtail_localize/tests/test_synctree.py index 442fc733e..cdb51b5d3 100644 --- a/wagtail_localize/tests/test_synctree.py +++ b/wagtail_localize/tests/test_synctree.py @@ -1,6 +1,9 @@ +import unittest + from django.contrib.contenttypes.models import ContentType from django.test import TestCase from django.urls import reverse +from wagtail import VERSION as WAGTAIL_VERSION from wagtail.core.models import Locale, Page from wagtail.tests.utils import WagtailTestUtils @@ -9,6 +12,12 @@ from wagtail_localize.test.models import TestHomePage, TestPage +try: + from wagtail import hooks +except ImportError: + from wagtail.core import hooks + + class TestPageIndex(TestCase): def setUp(self): self.en_locale = Locale.objects.get(language_code="en") @@ -93,7 +102,7 @@ def test_from_database(self): self.assertEqual(canadaonlypage_entry.aliased_locales, []) -class TestSignalsAndHooks(TestCase, WagtailTestUtils): +class SyncTreeTestsSetupBase(TestCase): def setUp(self): self.en_locale = Locale.objects.get(language_code="en") self.fr_locale = Locale.objects.create(language_code="fr") @@ -126,6 +135,11 @@ def setUp(self): ) ) + +class TestSignalsAndHooks(SyncTreeTestsSetupBase, WagtailTestUtils): + def setUp(self): + super().setUp() + LocaleSynchronization.objects.create( locale=self.fr_locale, sync_from=self.en_locale, @@ -263,3 +277,66 @@ def test_create_homepage_in_sync_source_locale(self): self.assertTrue(new_en_homepage.has_translation(self.fr_locale)) self.assertTrue(new_en_homepage.has_translation(self.fr_ca_locale)) self.assertTrue(new_en_homepage.has_translation(self.es_locale)) + + +@unittest.skipUnless( + WAGTAIL_VERSION >= (4, 0), + "construct_translated_pages_to_cascade_actions was added starting with Wagtail 3.0", +) +class TestConstructSyncedPageTreeListHook(SyncTreeTestsSetupBase): + def _get_hook_function(self): + the_hooks = hooks.get_hooks("construct_translated_pages_to_cascade_actions") + return the_hooks[0] + + def setup_locale_synchronisation(self, locale, sync_from_locale): + LocaleSynchronization.objects.create( + locale=locale, + sync_from=sync_from_locale, + ) + + def test_hook(self): + the_hooks = hooks.get_hooks("construct_translated_pages_to_cascade_actions") + self.assertEqual(len(the_hooks), 1) + + def test_hook_returns_nothing_without_locale_synchronisation(self): + hook = self._get_hook_function() + for action in ["unpublish", "delete", "move"]: + with self.subTest( + f"Calling construct_translated_pages_to_cascade_actions with {action}" + ): + results = hook([self.en_aboutpage], action) + self.assertDictEqual(results, {}) + + def test_hook_returns_relevant_pages_from_synced_locale_on_unpublish_action(self): + self.setup_locale_synchronisation(self.fr_locale, self.en_locale) + hook = self._get_hook_function() + results = hook([self.en_aboutpage], "unpublish") + self.assertIsNotNone(results.get(self.en_aboutpage)) + self.assertQuerysetEqual( + results[self.en_aboutpage], Page.objects.filter(pk=self.fr_aboutpage.pk) + ) + + # unpublish should not include alias pages as they follow the parent + self.fr_aboutpage.alias_of = self.en_aboutpage + self.fr_aboutpage.save() + results = hook([self.en_aboutpage], "unpublish") + self.assertIsNotNone(results.get(self.en_aboutpage)) + self.assertQuerysetEqual(results[self.en_aboutpage], Page.objects.none()) + + def test_hook_returns_relevant_pages_from_synced_locale_on_move_action(self): + self.setup_locale_synchronisation(self.fr_locale, self.en_locale) + hook = self._get_hook_function() + results = hook([self.en_aboutpage], "move") + self.assertIsNotNone(results.get(self.en_aboutpage)) + self.assertQuerysetEqual( + results[self.en_aboutpage], Page.objects.filter(pk=self.fr_aboutpage.pk) + ) + + def test_hook_returns_relevant_pages_from_synced_locale_on_delete_action(self): + self.setup_locale_synchronisation(self.fr_locale, self.en_locale) + hook = self._get_hook_function() + results = hook([self.en_aboutpage], "move") + self.assertIsNotNone(results.get(self.en_aboutpage)) + self.assertQuerysetEqual( + results[self.en_aboutpage], Page.objects.filter(pk=self.fr_aboutpage.pk) + ) diff --git a/wagtail_localize/wagtail_hooks.py b/wagtail_localize/wagtail_hooks.py index e1fbe0edd..6564f0bff 100644 --- a/wagtail_localize/wagtail_hooks.py +++ b/wagtail_localize/wagtail_hooks.py @@ -474,7 +474,6 @@ def register_icons(icons): @hooks.register("construct_translated_pages_to_cascade_actions") def construct_synced_page_tree_list(pages: List[Page], action: str): - locale_sync_map = {} for page in pages: # TODO: what about locale C follows B which follows A, when we come in from A? From 23b2d720543f9e2271e1907d4904786632834227 Mon Sep 17 00:00:00 2001 From: Dan Braghis Date: Fri, 20 May 2022 10:33:54 +0100 Subject: [PATCH 3/3] Add setting to cascade actions currently: delete, move, unpublish --- wagtail_localize/tests/test_synctree.py | 25 ++++++++++++++++++++++++- wagtail_localize/wagtail_hooks.py | 7 ++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/wagtail_localize/tests/test_synctree.py b/wagtail_localize/tests/test_synctree.py index cdb51b5d3..20b2fcbf6 100644 --- a/wagtail_localize/tests/test_synctree.py +++ b/wagtail_localize/tests/test_synctree.py @@ -1,7 +1,7 @@ import unittest from django.contrib.contenttypes.models import ContentType -from django.test import TestCase +from django.test import TestCase, override_settings from django.urls import reverse from wagtail import VERSION as WAGTAIL_VERSION from wagtail.core.models import Locale, Page @@ -307,6 +307,27 @@ def test_hook_returns_nothing_without_locale_synchronisation(self): results = hook([self.en_aboutpage], action) self.assertDictEqual(results, {}) + def test_hook_returns_nothing_without_explicit_setting(self): + self.setup_locale_synchronisation(self.fr_locale, self.en_locale) + hook = self._get_hook_function() + for action in ["unpublish", "delete", "move"]: + with self.subTest( + f"Calling construct_translated_pages_to_cascade_actions with {action} " + f"without WAGTAILLOCALIZE_CASCADE_PAGE_ACTIONS set" + ): + results = hook([self.en_aboutpage], action) + self.assertDictEqual(results, {}) + + with override_settings(WAGTAILLOCALIZE_CASCADE_PAGE_ACTIONS=False): + for action in ["unpublish", "delete", "move"]: + with self.subTest( + f"Calling construct_translated_pages_to_cascade_actions with {action} " + f"and WAGTAILLOCALIZE_CASCADE_PAGE_ACTIONS=False" + ): + results = hook([self.en_aboutpage], action) + self.assertDictEqual(results, {}) + + @override_settings(WAGTAILLOCALIZE_CASCADE_PAGE_ACTIONS=True) def test_hook_returns_relevant_pages_from_synced_locale_on_unpublish_action(self): self.setup_locale_synchronisation(self.fr_locale, self.en_locale) hook = self._get_hook_function() @@ -323,6 +344,7 @@ def test_hook_returns_relevant_pages_from_synced_locale_on_unpublish_action(self self.assertIsNotNone(results.get(self.en_aboutpage)) self.assertQuerysetEqual(results[self.en_aboutpage], Page.objects.none()) + @override_settings(WAGTAILLOCALIZE_CASCADE_PAGE_ACTIONS=True) def test_hook_returns_relevant_pages_from_synced_locale_on_move_action(self): self.setup_locale_synchronisation(self.fr_locale, self.en_locale) hook = self._get_hook_function() @@ -332,6 +354,7 @@ def test_hook_returns_relevant_pages_from_synced_locale_on_move_action(self): results[self.en_aboutpage], Page.objects.filter(pk=self.fr_aboutpage.pk) ) + @override_settings(WAGTAILLOCALIZE_CASCADE_PAGE_ACTIONS=True) def test_hook_returns_relevant_pages_from_synced_locale_on_delete_action(self): self.setup_locale_synchronisation(self.fr_locale, self.en_locale) hook = self._get_hook_function() diff --git a/wagtail_localize/wagtail_hooks.py b/wagtail_localize/wagtail_hooks.py index 6564f0bff..ddc11c976 100644 --- a/wagtail_localize/wagtail_hooks.py +++ b/wagtail_localize/wagtail_hooks.py @@ -470,10 +470,15 @@ def register_icons(icons): if WAGTAIL_VERSION >= (4, 0): + from django.conf import settings + from .models import LocaleSynchronization @hooks.register("construct_translated_pages_to_cascade_actions") - def construct_synced_page_tree_list(pages: List[Page], action: str): + def construct_synced_page_tree_list(pages: List[Page], action: str) -> dict: + if not getattr(settings, "WAGTAILLOCALIZE_CASCADE_PAGE_ACTIONS", False): + return {} + locale_sync_map = {} for page in pages: # TODO: what about locale C follows B which follows A, when we come in from A?