Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert to alias #515

Merged
merged 10 commits into from
Feb 17, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ const EditorFooter: FunctionComponent<EditorProps> = ({
</form>
];

if (links.convertToAliasUrl) {
actions.push(
<form method="GET" action={links.convertToAliasUrl}>
<input
type="hidden"
name="csrfmiddlewaretoken"
value={csrfToken}
/>
zerolab marked this conversation as resolved.
Show resolved Hide resolved
<input type="hidden" name="next" value={window.location.href} />

<button
type="submit"
className="button action-secondary"
aria-label={gettext('Convert to alias page')}
>
<Icon name="wagtail-localize-convert" />
{gettext('Convert to alias page')}
</button>
</form>
);
}

if (perms.canDelete) {
actions.push(
<a className="button action-secondary" href={links.deleteUrl}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export interface EditorProps {
unlockUrl: string;
deleteUrl: string;
stopTranslationUrl: string;
convertToAliasUrl: string;
};
previewModes: PreviewMode[];
machineTranslator: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n wagtailadmin_tags %}
{% block titletag %}{% blocktrans with title=page.get_admin_display_title %}Convert to alias {{ title }}{% endblocktrans %}{% endblock %}

{% block content %}
{% trans "Convert to alias" as del_str %}
{% include "wagtailadmin/shared/header.html" with title=del_str subtitle=page.get_admin_display_title icon="doc-empty-inverse" %}

<div class="nice-padding">
<p>
{% trans 'Are you sure you want to convert this page into an alias?' %}
zerolab marked this conversation as resolved.
Show resolved Hide resolved
</p>

<form action="{% url 'wagtail_localize:convert_to_alias' page.id %}" method="POST">
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}">
<button type="submit" class="button serious">{% trans 'Yes, convert it' %}</button>
<a href="{% if next %}{{ next }}{% else %}{% url 'wagtailadmin_explore' page.get_parent.id %}{% endif %}" class="button button-secondary">{% trans "No, leave it as a page" %}</a>
</form>
</div>
{% endblock %}
2 changes: 2 additions & 0 deletions wagtail_localize/templates/wagtail_localize/icons/convert.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
187 changes: 187 additions & 0 deletions wagtail_localize/tests/test_convert_to_alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
from django.test import TestCase
from django.urls import reverse
from wagtail.core.models import Locale, Page, PageLogEntry
from wagtail.tests.utils import WagtailTestUtils

from wagtail_localize.models import LocaleSynchronization
from wagtail_localize.test.models import TestPage
from wagtail_localize.wagtail_hooks import ConvertToAliasPageActionMenuItem


class ConvertToAliasTestData(WagtailTestUtils):
def setUp(self):
self.en_locale = Locale.objects.get(language_code="en")
self.fr_locale = Locale.objects.create(language_code="fr")

self.home_page = Page.objects.get(depth=2)
self.page = self.home_page.add_child(
instance=TestPage(
title="The title",
slug="the-page",
)
)

LocaleSynchronization.objects.create(
locale=self.fr_locale,
sync_from=self.en_locale,
)
self.fr_page = self.page.get_translation(self.fr_locale)

self.login()


class ConvertToAliasTest(ConvertToAliasTestData, TestCase):
def _page_action_is_shown(self, page, view="edit"):
menu_item = ConvertToAliasPageActionMenuItem()
context = {"view": view, "page": page}

return menu_item._is_shown(context)

def test_page_action_not_available_on_non_translation_pages(self):
self.assertFalse(self._page_action_is_shown(self.page))

def test_page_action_not_available_on_alias_pages(self):
# as created, the FR page is just an alias.
self.assertFalse(self._page_action_is_shown(self.fr_page))
alias_page = self.page.create_alias(
parent=self.home_page,
update_slug="the-page-alias",
)
self.assertFalse(self._page_action_is_shown(alias_page))

def test_page_action_not_available_when_source_page_deleted(self):
self.page.delete()
self.assertFalse(self._page_action_is_shown(self.fr_page))

def test_page_action_available(self):
self.client.post(
reverse(
"wagtail_localize:submit_page_translation",
args=[self.page.id],
),
{"locales": [self.fr_locale.id]},
)
self.fr_page.refresh_from_db()
self.assertTrue(self._page_action_is_shown(self.fr_page))

def test_convert_url_in_edit_context(self):
self.client.post(
reverse(
"wagtail_localize:submit_page_translation",
args=[self.page.id],
),
{"locales": [self.fr_locale.id]},
)
response = self.client.get(
reverse("wagtailadmin_pages:edit", args=[self.fr_page.id])
)

self.assertIn("convertToAliasUrl", response.context["props"])
self.assertIn(
reverse("wagtail_localize:convert_to_alias", args=[self.fr_page.id]),
response.context["props"],
)

def test_convert_url_not_in_edit_context_when_source_deleted(self):
self.client.post(
reverse(
"wagtail_localize:submit_page_translation",
args=[self.page.id],
),
{"locales": [self.fr_locale.id]},
)
self.page.delete()
response = self.client.get(
reverse("wagtailadmin_pages:edit", args=[self.fr_page.id])
)
self.assertNotContains(
response,
reverse("wagtail_localize:convert_to_alias", args=[self.fr_page.id]),
)


class ConvertToAliasViewTest(ConvertToAliasTestData, TestCase):
def setUp(self):
super().setUp()
# submit for translation, thus no longer an alias
self.client.post(
reverse(
"wagtail_localize:submit_page_translation",
args=[self.page.id],
),
{"locales": [self.fr_locale.id]},
)
self.fr_page.refresh_from_db()
self.convert_url = reverse(
"wagtail_localize:convert_to_alias", args=[self.fr_page.id]
)

def test_convert_to_alias_action_redirect_to_own_view(self):
response = self.client.post(
reverse("wagtailadmin_pages:edit", args=[self.fr_page.id]),
{"localize-convert-to-alias": "Convert to alias page"},
)
self.assertRedirects(response, self.convert_url)

def test_convert_view_404_with_invalid_page_id(self):
response = self.client.get(
reverse("wagtail_localize:convert_to_alias", args=[100000]), follow=True
)
self.assertEqual(response.status_code, 404)

def test_convert_view_404s_for_alias_pages(self):
alias_page = self.page.create_alias(
parent=self.home_page,
update_slug="the-page-alias",
)
response = self.client.get(
reverse("wagtail_localize:convert_to_alias", args=[alias_page.id]),
follow=True,
)
self.assertEqual(response.status_code, 404)

def test_convert_view_404s_for_non_translation_pages(self):
response = self.client.get(
reverse("wagtail_localize:convert_to_alias", args=[self.page.id]),
follow=True,
)
self.assertEqual(response.status_code, 404)

def test_convert_view_shows_confirmation_page_on_get(self):
response = self.client.get(self.convert_url)
self.assertTemplateUsed("wagtail_localize/admin/confirm_convert_to_alias.html")
self.assertEqual(response.context["page"].id, self.fr_page.id)

def test_convert(self):
self.assertIsNone(self.fr_page.alias_of_id)
response = self.client.post(self.convert_url)
self.assertRedirects(
response, reverse("wagtailadmin_pages:edit", args=[self.fr_page.id])
)
self.fr_page.refresh_from_db()
self.assertEqual(self.fr_page.alias_of_id, self.page.id)

def test_convert_adds_entry_to_audit_log(self):
self.assertFalse(
PageLogEntry.objects.filter(
page=self.fr_page.id, action="wagtail_localize.convert_to_alias"
).exists()
)

self.client.post(self.convert_url)
self.assertTrue(
PageLogEntry.objects.filter(
page=self.fr_page.id, action="wagtail_localize.convert_to_alias"
).exists()
)

def test_convert_redirect_with_valid_next_url(self):
home = reverse("wagtailadmin_home")
response = self.client.post(self.convert_url + f"?next={home}")
self.assertRedirects(response, home)

def test_convert_redirect_with_invalid_next_url(self):
response = self.client.post(self.convert_url + "?next=https://example.com")
self.assertRedirects(
response, reverse("wagtailadmin_pages:edit", args=[self.fr_page.id])
)
75 changes: 75 additions & 0 deletions wagtail_localize/views/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.utils.translation import gettext as _
from wagtail.admin import messages
from wagtail.admin.views.pages.utils import get_valid_next_url_from_request
from wagtail.core.models import Page, PageLogEntry

from wagtail_localize.models import TranslationSource


def convert_to_alias(request, page_id):
page = get_object_or_404(Page, id=page_id, alias_of_id__isnull=True)
if not page.permissions_for_user(request.user).can_edit():
kaedroho marked this conversation as resolved.
Show resolved Hide resolved
raise PermissionDenied

try:
# Attempt to get the source page id, if it exists
source_page = Page.objects.get(
~Q(pk=page.pk),
translation_key=page.translation_key,
locale_id=TranslationSource.objects.get(
object_id=page.translation_key,
specific_content_type=page.content_type_id,
).locale_id,
)
except (Page.DoesNotExist, TranslationSource.DoesNotExist):
raise Http404

with transaction.atomic():
next_url = get_valid_next_url_from_request(request)

if request.method == "POST":
page.alias_of_id = source_page.id
page.save(update_fields=["alias_of_id"], clean=False)

PageLogEntry.objects.log_action(
instance=page,
revision=page.get_latest_revision(),
action="wagtail_localize.convert_to_alias",
user=request.user,
data={
"page": {
"id": page.id,
"title": page.get_admin_display_title(),
zerolab marked this conversation as resolved.
Show resolved Hide resolved
},
"source": {
"id": source_page.id,
"title": source_page.get_admin_display_title(),
},
},
)

messages.success(
request,
_("Page '{}' has been converted into an alias.").format(
page.get_admin_display_title()
zerolab marked this conversation as resolved.
Show resolved Hide resolved
),
)

if next_url:
return redirect(next_url)
return redirect("wagtailadmin_pages:edit", page.id)

return TemplateResponse(
request,
"wagtail_localize/admin/confirm_convert_to_alias.html",
{
"page": page,
"next": next_url,
},
)
25 changes: 25 additions & 0 deletions wagtail_localize/views/edit_translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
StringSegment,
StringTranslation,
Translation,
TranslationSource,
)
from wagtail_localize.segments import StringSegmentValue

Expand Down Expand Up @@ -773,6 +774,25 @@ def get_translation_progress(segment, locale):
).format(model_name=capfirst(source_instance._meta.verbose_name)),
)

if isinstance(instance, Page):
try:
# Check that there is a parent page.
add_convert_to_alias_url = (
Page.objects.filter(
translation_key=instance.translation_key,
locale_id=TranslationSource.objects.get(
object_id=instance.translation_key,
specific_content_type=instance.content_type_id,
).locale_id,
)
.exclude(pk=instance.pk)
.exists()
)
except (TranslationSource.DoesNotExist, IndexError):
add_convert_to_alias_url = False
else:
add_convert_to_alias_url = False

return render(
request,
"wagtail_localize/admin/edit_translation.html",
Expand Down Expand Up @@ -873,6 +893,11 @@ def get_translation_progress(segment, locale):
"stopTranslationUrl": reverse(
"wagtail_localize:stop_translation", args=[translation.id]
),
"convertToAliasUrl": reverse(
"wagtail_localize:convert_to_alias", args=[instance.id]
)
if add_convert_to_alias_url
else None,
},
"previewModes": [
{
Expand Down
Loading