kopia lustrzana https://github.com/wagtail/wagtail
Sync tree: cascade unpublish, move and delete (#7984)
* Add construct_synced_page_tree_list hook and use in page unpublish view * Implement construct_synced_page_tree_list in simple_translation but only when sync page tree is enabled * Add hook documentation * Add construct_synced_page_tree_list hook tests (#8058) * Move translated and alias pages when WAGTAIL_I18N_ENABLED and WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE are enabled Co-Authored-By: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> * Delete corresponding translations when WAGTAIL_I18N_ENABLED and WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE are true Co-Authored-By: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> * Rename the hook to be more specific * Update singular string version in confirm_move.html * Update test test_translation_count_in_context Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> Co-authored-by: Karl Hobley <karl@kaed.uk>pull/8375/head
rodzic
84c414e758
commit
4cc10322a1
|
@ -792,6 +792,21 @@ Hooks for customising the way users are directed through the process of creating
|
|||
The function does not have to return anything, but if an object with a ``status_code`` property is returned, Wagtail will use it as a response object and skip the rest of the view.
|
||||
|
||||
|
||||
.. _construct_translated_pages_to_cascade_actions:
|
||||
|
||||
``construct_translated_pages_to_cascade_actions``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Return additional pages to process in a synced tree setup.
|
||||
|
||||
This hook is only triggered on unpublishing a page when ``WAGTAIL_I18N_ENABLED = True``.
|
||||
|
||||
The list of pages and the action are passed in as arguments to the hook.
|
||||
|
||||
The function should return a dictionary with the page from the pages list as key, and a list of additional pages to perform the action on.
|
||||
We recommend they are non-aliased, direct translations of the pages from the function argument.
|
||||
|
||||
|
||||
.. _register_page_action_menu_item:
|
||||
|
||||
``register_page_action_menu_item``
|
||||
|
|
|
@ -9,12 +9,60 @@
|
|||
<div class="nice-padding">
|
||||
<p>
|
||||
{% trans 'Are you sure you want to delete this page?' %}
|
||||
|
||||
{% if descendant_count %}
|
||||
{% blocktrans trimmed count counter=descendant_count %}
|
||||
This will also delete one more subpage.
|
||||
Deleting this page will also delete {{ descendant_count }} child page.
|
||||
{% plural %}
|
||||
This will also delete {{ descendant_count }} more subpages.
|
||||
Deleting this page will also delete {{ descendant_count }} more child pages.
|
||||
{% endblocktrans %}
|
||||
|
||||
{% if translation_count %} {# has translations #}
|
||||
{% if translation_descendant_count %} {# has translations with descendants #}
|
||||
{% if translation_count == 1 %}
|
||||
{% blocktrans trimmed count counter=translation_descendant_count %}
|
||||
It will also delete 1 translation and its combined {{ translation_descendant_count }} translated child page.
|
||||
{% plural %}
|
||||
It will also delete 1 translation and its combined {{ translation_descendant_count }} translated child pages.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed count counter=translation_descendant_count %}
|
||||
It will also delete {{ translation_count }} translations and their combined {{ translation_descendant_count }} translated child page.
|
||||
{% plural %}
|
||||
It will also delete {{ translation_count }} translations and their combined {{ translation_descendant_count }} translated child pages.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed count counter=translation_count %}
|
||||
It will also delete {{ translation_count }} translation.
|
||||
{% plural %}
|
||||
It will also delete {{ translation_count }} translations.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% elif translation_count %} {# no descendants #}
|
||||
{% if translation_descendant_count %} {# has translations with descendants #}
|
||||
{% if translation_count == 1 %}
|
||||
{% blocktrans trimmed count counter=translation_descendant_count %}
|
||||
Deleting this page will also delete 1 translation and its combined {{ translation_descendant_count }} translated child page.
|
||||
{% plural %}
|
||||
Deleting this page will also delete 1 translation and its combined {{ translation_descendant_count }} translated child pages.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed count counter=translation_descendant_count %}
|
||||
Deleting this page will also delete {{ translation_count }} translations and their combined {{ translation_descendant_count }} translated child page.
|
||||
{% plural %}
|
||||
Deleting this page will also delete {{ translation_count }} translations and their combined {{ translation_descendant_count }} translated child pages.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed count counter=translation_count %}
|
||||
Deleting this page will also delete {{ translation_count }} translation of this page.
|
||||
{% plural %}
|
||||
This will also delete {{ descendant_count }} more child pages.
|
||||
Deleting this page will also delete {{ translation_count }} translations of this page.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
|
|
|
@ -6,11 +6,20 @@
|
|||
{% include "wagtailadmin/shared/header.html" with title=move_str subtitle=page_to_move.get_admin_display_title icon="doc-empty-inverse" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
{% if page_to_move.is_leaf %}
|
||||
<p>{% blocktrans trimmed with title=destination.get_admin_display_title %}Are you sure you want to move this page into '{{ title }}'?{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<p>{% blocktrans trimmed with title=destination.get_admin_display_title %}Are you sure you want to move this page and all of its children into '{{ title }}'?{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
{% if page_to_move.is_leaf %}
|
||||
{% blocktrans trimmed with title=destination.get_admin_display_title %}Are you sure you want to move this page into '{{ title }}'?{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed with title=destination.get_admin_display_title %}Are you sure you want to move this page and all of its children into '{{ title }}'?{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% if translations_to_move_count %}
|
||||
{% blocktrans trimmed count counter=translations_to_move_count %}
|
||||
This will also move one translation of this page and its child pages
|
||||
{% plural %}
|
||||
This will also move {{ translations_to_move_count }} translations of this page and their child pages
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<form action="{% url 'wagtailadmin_pages:move_confirm' page_to_move.id destination.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -6,22 +6,50 @@
|
|||
{% include "wagtailadmin/shared/header.html" with title=unpublish_str subtitle=page.get_admin_display_title icon="doc-empty-inverse" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<p>{% trans "Are you sure you want to unpublish this page?" %}</p>
|
||||
<p>
|
||||
{% trans "Are you sure you want to unpublish this page?" %}
|
||||
{% if translation_count %}
|
||||
{% blocktrans trimmed with translation_count=translation_count count counter=translation_count %}
|
||||
This will also unpublish one translation of the page.
|
||||
{% plural %}
|
||||
This will also unpublish all {{ translation_count }} translations of the page.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<form action="{% url 'wagtailadmin_pages:unpublish' page.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<ul class="fields">
|
||||
{% if live_descendant_count > 0 %}
|
||||
{% if live_descendant_count > 0 or translation_descendant_count > 0 %}
|
||||
<li>
|
||||
<div class="field boolean_field checkbox_input">
|
||||
<div class="field-content">
|
||||
<div class="input">
|
||||
<input id="id_include_descendants" name="include_descendants" type="checkbox">
|
||||
<label for="id_include_descendants" class="plain-checkbox-label">{% blocktrans trimmed count counter=live_descendant_count %}
|
||||
This page has one subpage. Unpublish this too
|
||||
{% plural %}
|
||||
This page has {{ live_descendant_count }} subpages. Unpublish these too
|
||||
{% endblocktrans %}</label>
|
||||
<label for="id_include_descendants" class="plain-checkbox-label">
|
||||
{% if translation_descendant_count %}
|
||||
{% if translation_descendant_count == 1 %}
|
||||
{% blocktrans trimmed count counter=live_descendant_count %}
|
||||
This page has one subpage and its translations have a combined one translated child page. Unpublish these too
|
||||
{% plural %}
|
||||
This page has {{ live_descendant_count }} child pages and its translations have a combined one translated child page. Unpublish these too
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed count counter=live_descendant_count %}
|
||||
This page has one child page and its translations have a combined {{ translation_descendant_count }} translated child pages. Unpublish these too
|
||||
{% plural %}
|
||||
This page has {{ live_descendant_count }} child pages and its translations have a combined {{ translation_descendant_count }} translated child pages. Unpublish these too
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed count counter=live_descendant_count %}
|
||||
This page has one subpage. Unpublish this too
|
||||
{% plural %}
|
||||
This page has {{ live_descendant_count }} subpages. Unpublish these too
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
@ -24,12 +25,35 @@ def delete(request, page_id):
|
|||
|
||||
next_url = get_valid_next_url_from_request(request)
|
||||
|
||||
pages_to_delete = {page}
|
||||
|
||||
# The `construct_translated_pages_to_cascade_actions` hook returns translation and
|
||||
# alias pages when the action is set to "delete"
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
fn_pages = fn([page], "delete")
|
||||
if fn_pages and isinstance(fn_pages, dict):
|
||||
for additional_pages in fn_pages.values():
|
||||
pages_to_delete.update(additional_pages)
|
||||
|
||||
pages_to_delete = list(pages_to_delete)
|
||||
|
||||
if request.method == "POST":
|
||||
parent_id = page.get_parent().id
|
||||
# Delete the source page.
|
||||
action = DeletePageAction(page, user=request.user)
|
||||
# Permission checks are done above, so skip them in execute.
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
# Delete translation and alias pages if they have the same parent page.
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
parent_page_translations = page.get_parent().get_translations()
|
||||
for page_or_alias in pages_to_delete:
|
||||
if page_or_alias.get_parent() in parent_page_translations:
|
||||
action = DeletePageAction(page_or_alias, user=request.user)
|
||||
# Permission checks are done above, so skip them in execute.
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
messages.success(
|
||||
request, _("Page '{0}' deleted.").format(page.get_admin_display_title())
|
||||
)
|
||||
|
@ -50,5 +74,21 @@ def delete(request, page_id):
|
|||
"page": page,
|
||||
"descendant_count": page.get_descendant_count(),
|
||||
"next": next_url,
|
||||
# note that while pages_to_delete may contain a mix of translated pages
|
||||
# and aliases, we count the "translations" only, as aliases are similar
|
||||
# to symlinks, so they should just follow the source
|
||||
"translation_count": len(
|
||||
[
|
||||
translation.id
|
||||
for translation in pages_to_delete
|
||||
if not translation.alias_of_id and translation.id != page.id
|
||||
]
|
||||
),
|
||||
"translation_descendant_count": sum(
|
||||
[
|
||||
translation.get_descendants().filter(alias_of__isnull=True).count()
|
||||
for translation in pages_to_delete
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
@ -74,14 +75,42 @@ def move_confirm(request, page_to_move_id, destination_id):
|
|||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
pages_to_move = {page_to_move}
|
||||
|
||||
# The `construct_translated_pages_to_cascade_actions` hook returns translation and
|
||||
# alias pages when the action is set to "move"
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
fn_pages = fn([page_to_move], "move")
|
||||
if fn_pages and isinstance(fn_pages, dict):
|
||||
for additional_pages in fn_pages.values():
|
||||
pages_to_move.update(additional_pages)
|
||||
|
||||
pages_to_move = list(pages_to_move)
|
||||
|
||||
if request.method == "POST":
|
||||
# any invalid moves *should* be caught by the permission check in the action class,
|
||||
# so don't bother to catch InvalidMoveToDescendant
|
||||
# any invalid moves *should* be caught by the permission check in the action
|
||||
# class, so don't bother to catch InvalidMoveToDescendant
|
||||
action = MovePageAction(
|
||||
page_to_move, destination, pos="last-child", user=request.user
|
||||
)
|
||||
action.execute()
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
# Move translation and alias pages if they have the same parent page.
|
||||
parent_page_translations = page_to_move.get_parent().get_translations()
|
||||
for translation in pages_to_move:
|
||||
if translation.get_parent() in parent_page_translations:
|
||||
# Move the translated or alias page to it's translated or
|
||||
# alias "destination" page.
|
||||
action = MovePageAction(
|
||||
translation,
|
||||
destination.get_translation(translation.locale),
|
||||
pos="last-child",
|
||||
user=request.user,
|
||||
)
|
||||
action.execute()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
_("Page '{0}' moved.").format(page_to_move.get_admin_display_title()),
|
||||
|
@ -106,5 +135,12 @@ def move_confirm(request, page_to_move_id, destination_id):
|
|||
{
|
||||
"page_to_move": page_to_move,
|
||||
"destination": destination,
|
||||
"translations_to_move_count": len(
|
||||
[
|
||||
translation.id
|
||||
for translation in pages_to_move
|
||||
if not translation.alias_of_id and translation.id != page_to_move.id
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
|
@ -20,6 +21,17 @@ def unpublish(request, page_id):
|
|||
|
||||
next_url = get_valid_next_url_from_request(request)
|
||||
|
||||
pages_to_unpublish = {page}
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
fn_pages = fn([page], "unpublish")
|
||||
if fn_pages and isinstance(fn_pages, dict):
|
||||
for additional_pages in fn_pages.values():
|
||||
pages_to_unpublish.update(additional_pages)
|
||||
|
||||
pages_to_unpublish = list(pages_to_unpublish)
|
||||
|
||||
if request.method == "POST":
|
||||
include_descendants = request.POST.get("include_descendants", False)
|
||||
|
||||
|
@ -28,10 +40,11 @@ def unpublish(request, page_id):
|
|||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
action = UnpublishPageAction(
|
||||
page, user=request.user, include_descendants=include_descendants
|
||||
)
|
||||
action.execute(skip_permission_checks=True)
|
||||
for page in pages_to_unpublish:
|
||||
action = UnpublishPageAction(
|
||||
page, user=request.user, include_descendants=include_descendants
|
||||
)
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
for fn in hooks.get_hooks("after_unpublish_page"):
|
||||
result = fn(request, page)
|
||||
|
@ -59,5 +72,12 @@ def unpublish(request, page_id):
|
|||
"page": page,
|
||||
"next": next_url,
|
||||
"live_descendant_count": page.get_descendants().live().count(),
|
||||
"translation_count": len(pages_to_unpublish[1:]),
|
||||
"translation_descendant_count": sum(
|
||||
[
|
||||
p.get_descendants().filter(alias_of__isnull=True).live().count()
|
||||
for p in pages_to_unpublish[1:]
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.actions.create_alias import CreatePageAliasAction
|
||||
from wagtail.actions.move_page import MovePageAction
|
||||
from wagtail.admin import widgets as wagtailadmin_widgets
|
||||
from wagtail.contrib.simple_translation.wagtail_hooks import (
|
||||
page_listing_more_buttons,
|
||||
|
@ -102,3 +105,456 @@ class TestWagtailHooksButtons(Utils):
|
|||
list(page_listing_more_buttons(blog_page, page_perms))[0],
|
||||
wagtailadmin_widgets.Button,
|
||||
)
|
||||
|
||||
|
||||
class TestConstructSyncedPageTreeListHook(Utils):
|
||||
def unpublish_hook(self, pages, action):
|
||||
self.assertEqual(action, "unpublish")
|
||||
self.assertIsInstance(pages, list)
|
||||
|
||||
def missing_hook_action(self, pages, action):
|
||||
self.assertEqual(action, "")
|
||||
self.assertIsInstance(pages, list)
|
||||
|
||||
def test_double_registered_hook(self):
|
||||
# We should have two implementations of `construct_translated_pages_to_cascade_actions`
|
||||
# One in simple_translation.wagtail_hooks and the other will be
|
||||
# registered as a temporary hook.
|
||||
with hooks.register_temporarily(
|
||||
"construct_translated_pages_to_cascade_actions", self.unpublish_hook
|
||||
):
|
||||
defined_hooks = hooks.get_hooks(
|
||||
"construct_translated_pages_to_cascade_actions"
|
||||
)
|
||||
self.assertEqual(len(defined_hooks), 2)
|
||||
|
||||
@override_settings(WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=True)
|
||||
def test_page_tree_sync_on(self):
|
||||
with hooks.register_temporarily(
|
||||
"construct_translated_pages_to_cascade_actions", self.unpublish_hook
|
||||
):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
response = fn([self.en_homepage], "unpublish")
|
||||
if response:
|
||||
self.assertIsInstance(response, dict)
|
||||
self.assertEqual(len(response.items()), 1)
|
||||
|
||||
@override_settings(WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=False)
|
||||
def test_page_tree_sync_off(self):
|
||||
with hooks.register_temporarily(
|
||||
"construct_translated_pages_to_cascade_actions", self.unpublish_hook
|
||||
):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
response = fn([self.en_homepage], "unpublish")
|
||||
self.assertIsNone(response)
|
||||
|
||||
@override_settings(WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=True)
|
||||
def test_missing_hook_action(self):
|
||||
with hooks.register_temporarily(
|
||||
"construct_translated_pages_to_cascade_actions", self.missing_hook_action
|
||||
):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
response = fn([self.en_homepage], "")
|
||||
if response is not None:
|
||||
self.assertIsInstance(response, dict)
|
||||
|
||||
@override_settings(
|
||||
WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=True, WAGTAIL_I18N_ENABLED=True
|
||||
)
|
||||
def test_other_l10n_pages_were_unpublished(self):
|
||||
# Login to access the admin
|
||||
self.login()
|
||||
|
||||
# Make sur the French homepage is published/live
|
||||
self.fr_homepage.live = True
|
||||
self.fr_homepage.save()
|
||||
self.assertTrue(self.en_homepage.live)
|
||||
self.assertTrue(self.fr_homepage.live)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("wagtailadmin_pages:unpublish", args=(self.en_homepage.id,)),
|
||||
{"include_descendants": False},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Refresh objects from the database
|
||||
self.en_homepage.refresh_from_db()
|
||||
self.fr_homepage.refresh_from_db()
|
||||
|
||||
# Test that both the English and French homepages are unpublished
|
||||
self.assertFalse(self.en_homepage.live)
|
||||
self.assertFalse(self.fr_homepage.live)
|
||||
|
||||
|
||||
class TestMovingTranslatedPages(Utils):
|
||||
@override_settings(
|
||||
WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=True, WAGTAIL_I18N_ENABLED=True
|
||||
)
|
||||
def test_move_translated_pages(self):
|
||||
self.login()
|
||||
|
||||
# BlogIndex needs translated pages before child pages can be translated
|
||||
self.fr_blog_index = self.en_blog_index.copy_for_translation(self.fr_locale)
|
||||
self.de_blog_index = self.en_blog_index.copy_for_translation(self.de_locale)
|
||||
|
||||
# Create blog_post copies for translation
|
||||
self.fr_blog_post = self.en_blog_post.copy_for_translation(self.fr_locale)
|
||||
self.de_blog_post = self.en_blog_post.copy_for_translation(self.de_locale)
|
||||
|
||||
# Confirm location of English blog post page before it is moved
|
||||
# Should be living at /blog/blog-post/ right now. But will eventually
|
||||
# exist at /blog-post/
|
||||
self.assertEqual(self.en_blog_post.get_parent().id, self.en_blog_index.id)
|
||||
|
||||
# Check if fr and de blog post parent ids are in the translated list
|
||||
# This is to make sure the fr blog_post is situated under /fr/blog/
|
||||
# (same concept with /de/).
|
||||
# We'll check these after the move to ensure they exist under /fr/ without
|
||||
# the /blog/ parent page.
|
||||
original_translated_parent_ids = [
|
||||
p.id for p in self.en_blog_index.get_translations()
|
||||
]
|
||||
self.assertIn(self.fr_blog_post.get_parent().id, original_translated_parent_ids)
|
||||
self.assertIn(self.de_blog_post.get_parent().id, original_translated_parent_ids)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wagtailadmin_pages:move_confirm",
|
||||
args=(
|
||||
self.en_blog_post.id,
|
||||
self.en_homepage.id,
|
||||
),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.fr_blog_post.refresh_from_db()
|
||||
self.de_blog_post.refresh_from_db()
|
||||
|
||||
# Check if the new pages exist under their respective translated homepages
|
||||
home_page_translation_ids = [p.id for p in self.en_homepage.get_translations()]
|
||||
self.assertIn(
|
||||
self.fr_blog_post.get_parent(update=True).id, home_page_translation_ids
|
||||
)
|
||||
self.assertIn(
|
||||
self.de_blog_post.get_parent(update=True).id, home_page_translation_ids
|
||||
)
|
||||
|
||||
@override_settings(WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=False)
|
||||
def test_unmovable_translation_pages(self):
|
||||
"""
|
||||
Test that moving a page with WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE
|
||||
disabled doesn't apply to its translations.
|
||||
"""
|
||||
self.login()
|
||||
|
||||
# BlogIndex needs translated pages before child pages can be translated
|
||||
self.fr_blog_index = self.en_blog_index.copy_for_translation(self.fr_locale)
|
||||
self.de_blog_index = self.en_blog_index.copy_for_translation(self.de_locale)
|
||||
|
||||
# Create blog_post copies for translation
|
||||
self.fr_blog_post = self.en_blog_post.copy_for_translation(self.fr_locale)
|
||||
self.de_blog_post = self.en_blog_post.copy_for_translation(self.de_locale)
|
||||
|
||||
# Confirm location of English blog post page before it is moved
|
||||
# Should be living at /blog/blog-post/ right now. But will eventually
|
||||
# exist at /blog-post/
|
||||
self.assertEqual(self.en_blog_post.get_parent().id, self.en_blog_index.id)
|
||||
|
||||
# Confirm the fr and de blog post pages are under the blog index page
|
||||
# We'll confirm these have not moved after ther POST request.
|
||||
original_translated_parent_ids = [
|
||||
p.id for p in self.en_blog_index.get_translations()
|
||||
]
|
||||
self.assertIn(self.fr_blog_post.get_parent().id, original_translated_parent_ids)
|
||||
self.assertIn(self.de_blog_post.get_parent().id, original_translated_parent_ids)
|
||||
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wagtailadmin_pages:move_confirm",
|
||||
args=(
|
||||
self.en_blog_post.id,
|
||||
self.en_homepage.id,
|
||||
),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.en_blog_post.refresh_from_db()
|
||||
self.fr_blog_post.refresh_from_db()
|
||||
self.de_blog_post.refresh_from_db()
|
||||
|
||||
# Check that the en_blog_post page has moved directly under the home page.
|
||||
self.assertEqual(
|
||||
self.en_blog_post.get_parent(update=True).id, self.en_homepage.id
|
||||
)
|
||||
|
||||
# Check if the fr and de pages exist under their original parent page (/blog/)
|
||||
self.assertIn(
|
||||
self.fr_blog_post.get_parent(update=True).id, original_translated_parent_ids
|
||||
)
|
||||
self.assertIn(
|
||||
self.de_blog_post.get_parent(update=True).id, original_translated_parent_ids
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=True, WAGTAIL_I18N_ENABLED=True
|
||||
)
|
||||
def test_translation_count_in_context(self):
|
||||
"""Test translation count is correct in the confirm_move.html template."""
|
||||
self.login()
|
||||
|
||||
# BlogIndex needs translated pages before child pages can be translated
|
||||
self.fr_blog_index = self.en_blog_index.copy_for_translation(self.fr_locale)
|
||||
self.de_blog_index = self.en_blog_index.copy_for_translation(self.de_locale)
|
||||
|
||||
# create translation in FR tree
|
||||
self.fr_blog_post = self.en_blog_post.copy_for_translation(self.fr_locale)
|
||||
# create alias in DE tree
|
||||
self.de_blog_post = self.en_blog_post.copy_for_translation(
|
||||
self.de_locale, alias=True
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"wagtailadmin_pages:move_confirm",
|
||||
args=(
|
||||
self.en_blog_post.id,
|
||||
self.en_homepage.id,
|
||||
),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context["translations_to_move_count"], 1)
|
||||
self.assertIn(
|
||||
"This will also move one translation of this page and its child pages",
|
||||
response.content.decode("utf-8"),
|
||||
)
|
||||
|
||||
|
||||
@override_settings(
|
||||
WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE=True, WAGTAIL_I18N_ENABLED=True
|
||||
)
|
||||
class TestDeletingTranslatedPages(Utils):
|
||||
def delete_hook(self, pages, action):
|
||||
self.assertEqual(action, "delete")
|
||||
self.assertIsInstance(pages, list)
|
||||
|
||||
def test_construct_translated_pages_to_cascade_actions_when_deleting(self):
|
||||
with hooks.register_temporarily(
|
||||
"construct_translated_pages_to_cascade_actions", self.delete_hook
|
||||
):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
response = fn([self.en_homepage], "delete")
|
||||
if response is not None:
|
||||
self.assertIsInstance(response, dict)
|
||||
self.assertEqual(len(response.items()), 1)
|
||||
|
||||
def test_delete_translated_pages(self):
|
||||
# Login to the Wagtail admin with a superuser account
|
||||
self.login()
|
||||
|
||||
# BlogIndex needs translated pages before child pages can be translated
|
||||
self.fr_blog_index = self.en_blog_index.copy_for_translation(self.fr_locale)
|
||||
# Create a copy of the en_blog_post object as a translated page
|
||||
self.fr_blog_post = self.en_blog_post.copy_for_translation(self.fr_locale)
|
||||
|
||||
# 1. Delete the en_blog_post by making a POST request to /delete/
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wagtailadmin_pages:delete",
|
||||
args=(self.en_blog_post.id,),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# 2. Confirm fr_blog_post is deleted
|
||||
self.assertIsNone(Page.objects.filter(pk=self.fr_blog_post.id).first())
|
||||
|
||||
def test_delete_confirmation_template(self):
|
||||
"""Test the context info is correct in the confirm_delete.html template."""
|
||||
self.login()
|
||||
|
||||
# BlogIndex needs translated pages before child pages can be translated
|
||||
self.fr_blog_index = self.en_blog_index.copy_for_translation(self.fr_locale)
|
||||
# Create a copy of the en_blog_post object as a translated page
|
||||
self.fr_blog_post = self.en_blog_post.copy_for_translation(self.fr_locale)
|
||||
|
||||
# Create an alias page to test the `translations_to_move_count`
|
||||
# in the template context
|
||||
new_page = CreatePageAliasAction(
|
||||
self.en_blog_post,
|
||||
recursive=False,
|
||||
parent=self.en_blog_index,
|
||||
update_slug="alias-page-slug",
|
||||
user=None,
|
||||
)
|
||||
new_page.execute(skip_permission_checks=True)
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"wagtailadmin_pages:delete",
|
||||
args=(self.en_blog_post.id,),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context["translation_count"], 1)
|
||||
self.assertEqual(response.context["translation_descendant_count"], 0)
|
||||
self.assertIn(
|
||||
"Deleting this page will also delete 1 translation of this page.",
|
||||
response.content.decode("utf-8"),
|
||||
)
|
||||
|
||||
def test_deleting_page_with_divergent_translation_tree(self):
|
||||
self.login()
|
||||
|
||||
# New parent to eventually hold the fr_blog_post object.
|
||||
self.en_new_parent = TestPage(title="Test Parent", slug="test-parent")
|
||||
self.en_homepage.add_child(instance=self.en_new_parent)
|
||||
|
||||
# Copy the /blog/ and French /blog-post/ pages.
|
||||
self.fr_blog_index = self.en_blog_index.copy_for_translation(self.fr_locale)
|
||||
self.fr_blog_post = self.en_blog_post.copy_for_translation(self.fr_locale)
|
||||
# Copy the en new parent to be a french page
|
||||
self.fr_new_parent = self.en_new_parent.copy_for_translation(self.fr_locale)
|
||||
|
||||
# Manually move the fr_blog_post to live under fr_new_parent
|
||||
# Because this does not go through the POST request in pages/move.py
|
||||
# this action will create a diverged tree scnenario where en_blog_post
|
||||
# and fr_blog_post don't mirror their original positions in the tree.
|
||||
action = MovePageAction(
|
||||
self.fr_blog_post,
|
||||
self.fr_new_parent,
|
||||
pos="last-child",
|
||||
user=None,
|
||||
)
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
self.fr_blog_post.refresh_from_db()
|
||||
self.en_blog_post.refresh_from_db()
|
||||
|
||||
# Confirm fr_blog_post parent id is the fr_new_parent id.
|
||||
# Confirm en_blog_post parent id is the en_blog_index id
|
||||
self.assertEqual(
|
||||
self.fr_blog_post.get_parent(update=True).id, self.fr_new_parent.id
|
||||
)
|
||||
self.assertEqual(
|
||||
self.en_blog_post.get_parent(update=True).id, self.en_blog_index.id
|
||||
)
|
||||
|
||||
# Make a post request to move the en_blog_post to live under en_homepage
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wagtailadmin_pages:delete",
|
||||
args=(self.en_blog_post.id,),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Confirm that the en_blog_post object no longer exists.
|
||||
self.assertFalse(Page.objects.filter(pk=self.en_blog_post.id).exists())
|
||||
# Confirm that the fr_blog_post object stll exists, because it was moved
|
||||
self.assertTrue(Page.objects.filter(pk=self.fr_blog_post.id).exists())
|
||||
|
||||
# Confirm the fr_blog_post parent id matches the new_parent_page id
|
||||
# This confirms is hasn't moved and hasn't been deleted.
|
||||
# to a different location in the tree.
|
||||
self.fr_blog_post.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.fr_blog_post.get_parent(update=True).id, self.fr_new_parent.id
|
||||
)
|
||||
|
||||
def test_alias_pages_when_deleting_source_page(self):
|
||||
"""
|
||||
When deleting a page that has an alias page in the same tree, the alias page
|
||||
should continue to exist while the original page should be deleted
|
||||
while using the `construct_translated_pages_to_cascade_actions` hook is active.
|
||||
"""
|
||||
self.login()
|
||||
|
||||
# Test the source page exists in the right tree location
|
||||
self.assertEqual(self.en_blog_post.get_parent().id, self.en_blog_index.id)
|
||||
|
||||
# Create an alias page from en_blog_post
|
||||
action = CreatePageAliasAction(
|
||||
self.en_blog_post,
|
||||
recursive=False,
|
||||
parent=self.en_blog_index,
|
||||
update_slug="sample-slug",
|
||||
user=None,
|
||||
)
|
||||
new_page = action.execute(skip_permission_checks=True)
|
||||
# Make sure the alias page is an alias of the en_blog_post
|
||||
# and exists under the same parent page.
|
||||
self.assertEqual(new_page.get_parent().id, self.en_blog_index.id)
|
||||
# Test alias of source page
|
||||
self.assertEqual(new_page.alias_of_id, self.en_blog_post.id)
|
||||
|
||||
# Delete the en_blog_post page and make sure the alias page is kept in tact.
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wagtailadmin_pages:delete",
|
||||
args=(self.en_blog_post.id,),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(Page.objects.filter(pk=self.en_blog_post.id).exists())
|
||||
self.assertTrue(Page.objects.filter(pk=new_page.id).exists())
|
||||
|
||||
def test_translation_alias_pages_when_deleting_source_page(self):
|
||||
"""
|
||||
When deleting a page that has an alias page, the alias page
|
||||
should be deleted while using the `construct_translated_pages_to_cascade_actions`
|
||||
hook is active.
|
||||
"""
|
||||
self.login()
|
||||
|
||||
# BlogIndex needs translated pages before child pages can be translated
|
||||
self.fr_blog_index = self.en_blog_index.copy_for_translation(self.fr_locale)
|
||||
# Create a copy of the en_blog_post object as a translated alias page
|
||||
self.fr_blog_post = self.en_blog_post.copy_for_translation(
|
||||
self.fr_locale, alias=True
|
||||
)
|
||||
self.assertEqual(self.fr_blog_post.alias_of_id, self.en_blog_post.id)
|
||||
self.assertEqual(self.fr_blog_post.get_parent().id, self.fr_blog_index.id)
|
||||
|
||||
# Test that the fr_blog_post alias_id is in the list of translations, is a
|
||||
# proper alias of en_blog_post, and is using the french locale (fr).
|
||||
# Also check that the page is in the correct language tree
|
||||
translation_ids = [p.id for p in self.fr_blog_post.get_translations()]
|
||||
self.assertIn(self.fr_blog_post.alias_of_id, translation_ids)
|
||||
self.assertEqual(self.fr_blog_post.alias_of_id, self.en_blog_post.id)
|
||||
self.assertEqual(self.fr_blog_post.locale.language_code, "fr")
|
||||
|
||||
# Test the source is in the source tree root (source HomePage)
|
||||
# Test that the translated alias is in the translated root (fr HomePage)
|
||||
en_root = Page.objects.filter(depth__gt=1, locale=self.en_locale).first()
|
||||
fr_root = Page.objects.filter(depth__gt=1, locale=self.fr_locale).first()
|
||||
self.assertIn(self.en_blog_post, en_root.get_descendants().specific())
|
||||
self.assertIn(self.fr_blog_post, fr_root.get_descendants().specific())
|
||||
|
||||
# Delete the en_blog_post page and make sure the alias page is kept in tact.
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"wagtailadmin_pages:delete",
|
||||
args=(self.en_blog_post.id,),
|
||||
),
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(Page.objects.filter(pk=self.en_blog_post.id).exists())
|
||||
self.assertFalse(Page.objects.filter(pk=self.fr_blog_post.id).exists())
|
||||
|
||||
# The source page continues to exist in the source tree root (HomePage)
|
||||
self.assertNotIn(self.en_blog_post, en_root.get_descendants().specific())
|
||||
# The alias should no longer be in the translated tree root (fr HomePage)
|
||||
self.assertNotIn(self.fr_blog_post, fr_root.get_descendants().specific())
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.utils import quote
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.urls import include, path, reverse
|
||||
|
@ -5,7 +8,7 @@ from django.utils.translation import gettext as _
|
|||
|
||||
from wagtail import hooks
|
||||
from wagtail.admin import widgets as wagtailadmin_widgets
|
||||
from wagtail.models import Locale, TranslatableMixin
|
||||
from wagtail.models import Locale, Page, TranslatableMixin
|
||||
from wagtail.snippets.widgets import SnippetListingButton
|
||||
|
||||
from .views import SubmitPageTranslationView, SubmitSnippetTranslationView
|
||||
|
@ -89,3 +92,23 @@ def register_snippet_listing_buttons(snippet, user, next_url=None):
|
|||
},
|
||||
priority=100,
|
||||
)
|
||||
|
||||
|
||||
@hooks.register("construct_translated_pages_to_cascade_actions")
|
||||
def construct_translated_pages_to_cascade_actions(pages: List[Page], action: str):
|
||||
if not getattr(settings, "WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE", False):
|
||||
return
|
||||
|
||||
page_list = {}
|
||||
if action == "unpublish":
|
||||
# Only return non-alias translations of the page
|
||||
for page in pages:
|
||||
page_list[page] = Page.objects.translation_of(page, inclusive=False).filter(
|
||||
alias_of__isnull=True
|
||||
)
|
||||
elif action == "move" or action == "delete":
|
||||
# Return all translations or aliases in other trees
|
||||
for page in pages:
|
||||
page_list[page] = Page.objects.translation_of(page, inclusive=False)
|
||||
|
||||
return page_list
|
||||
|
|
Ładowanie…
Reference in New Issue