From c170c50c78ce5267ddbf6d130ede0378335beb4b Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 28 Sep 2020 18:50:22 +0100 Subject: [PATCH] Add view for converting aliases into regular pages --- docs/reference/hooks.rst | 25 +++++++ .../pages/confirm_convert_alias.html | 25 +++++++ .../wagtailadmin/pages/edit_alias.html | 4 +- .../admin/tests/pages/test_convert_alias.py | 69 +++++++++++++++++++ wagtail/admin/urls/pages.py | 6 +- wagtail/admin/views/pages/convert_alias.py | 65 +++++++++++++++++ wagtail/admin/wagtail_hooks.py | 9 +++ 7 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 wagtail/admin/templates/wagtailadmin/pages/confirm_convert_alias.html create mode 100644 wagtail/admin/tests/pages/test_convert_alias.py create mode 100644 wagtail/admin/views/pages/convert_alias.py diff --git a/docs/reference/hooks.rst b/docs/reference/hooks.rst index 26241d770e..6cd06c272b 100644 --- a/docs/reference/hooks.rst +++ b/docs/reference/hooks.rst @@ -614,6 +614,31 @@ Hooks for customising the way users are directed through the process of creating Uses the same behaviour as ``before_create_page``. + +.. _before_convert_alias_page: + +``before_convert_alias_page`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Called at the beginning of the ``convert_alias`` view, which is responsible for converting alias pages into normal Wagtail pages. + + The request and the page being converted are passed in as arguments to the hook. + + 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. + + +.. _after_convert_alias_page: + +``after_convert_alias_page`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Do something with a ``Page`` object after it has been converted from an alias. + + The request and the page that was just converted are passed in as arguments to the hook. + + 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. + + .. _register_page_action_menu_item: ``register_page_action_menu_item`` diff --git a/wagtail/admin/templates/wagtailadmin/pages/confirm_convert_alias.html b/wagtail/admin/templates/wagtailadmin/pages/confirm_convert_alias.html new file mode 100644 index 0000000000..8af778c9b3 --- /dev/null +++ b/wagtail/admin/templates/wagtailadmin/pages/confirm_convert_alias.html @@ -0,0 +1,25 @@ +{% extends "wagtailadmin/base.html" %} +{% load i18n wagtailadmin_tags %} +{% block titletag %}{% blocktrans with title=page.get_admin_display_title %}Convert alias {{ title }}{% endblocktrans %}{% endblock %} + +{% block content %} + {% trans "Convert alias" as del_str %} + {% include "wagtailadmin/shared/header.html" with title=del_str subtitle=page.get_admin_display_title icon="doc-empty-inverse" %} + +
+

+ {% trans 'Are you sure you want to convert this alias into a regular page?' %} +

+ +

+ {% trans 'This action cannot be undone.' %} +

+ +
+ {% csrf_token %} + + + {% trans "No, leave it as an alias" %} +
+
+{% endblock %} diff --git a/wagtail/admin/templates/wagtailadmin/pages/edit_alias.html b/wagtail/admin/templates/wagtailadmin/pages/edit_alias.html index 70ed882977..b35e2c0550 100644 --- a/wagtail/admin/templates/wagtailadmin/pages/edit_alias.html +++ b/wagtail/admin/templates/wagtailadmin/pages/edit_alias.html @@ -7,7 +7,9 @@

{% trans "Edit original page" %} - {% trans "Edit this page only (remove alias)" %} + + {% url 'wagtailadmin_pages:edit' page.id as this_url %} + {% trans "Convert this alias into a regular page" %}

{% endblock %} diff --git a/wagtail/admin/tests/pages/test_convert_alias.py b/wagtail/admin/tests/pages/test_convert_alias.py new file mode 100644 index 0000000000..ee9d0a01a8 --- /dev/null +++ b/wagtail/admin/tests/pages/test_convert_alias.py @@ -0,0 +1,69 @@ +import json + +from django.contrib.auth.models import Permission +from django.test import TestCase +from django.urls import reverse + +from wagtail.core.models import Page, PageLogEntry +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils + + +class TestConvertAlias(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add child page + self.child_page = SimplePage(title="Hello world!", slug="hello-world", content="hello") + self.root_page.add_child(instance=self.child_page) + + # Add alias page + self.alias_page = self.child_page.create_alias(update_slug='alias-page') + + # Login + self.user = self.login() + + def test_convert_alias(self): + response = self.client.get(reverse('wagtailadmin_pages:convert_alias', args=[self.alias_page.id])) + self.assertEqual(response.status_code, 200) + + def test_convert_alias_not_alias(self): + response = self.client.get(reverse('wagtailadmin_pages:convert_alias', args=[self.child_page.id])) + self.assertEqual(response.status_code, 404) + + def test_convert_alias_bad_permission(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + response = self.client.get(reverse('wagtailadmin_pages:convert_alias', args=[self.alias_page.id])) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_post_convert_alias(self): + response = self.client.post(reverse('wagtailadmin_pages:convert_alias', args=[self.alias_page.id])) + + # User should be redirected to the edit view of the converted page + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.alias_page.id])) + + # Check the page was converted + self.alias_page.refresh_from_db() + self.assertIsNone(self.alias_page.alias_of) + + # Check that a revision was created + revision = self.alias_page.revisions.get() + self.assertEqual(revision.user, self.user) + self.assertEqual(self.alias_page.live_revision, revision) + + # Check audit log + log = PageLogEntry.objects.get(action='wagtail.convert_alias') + self.assertFalse(log.content_changed) + self.assertEqual(json.loads(log.data_json), {"page": {"id": self.alias_page.id, "title": self.alias_page.get_admin_display_title()}}) + self.assertEqual(log.page, self.alias_page.page_ptr) + self.assertEqual(log.revision, revision) + self.assertEqual(log.user, self.user) diff --git a/wagtail/admin/urls/pages.py b/wagtail/admin/urls/pages.py index c8ab1a719e..388b010288 100644 --- a/wagtail/admin/urls/pages.py +++ b/wagtail/admin/urls/pages.py @@ -2,9 +2,8 @@ from django.urls import path, re_path from wagtail.admin.views import page_privacy from wagtail.admin.views.pages import ( - copy, create, delete, edit, history, lock, moderation, move, ordering, preview, - revisions, search, unpublish, usage, workflow -) + convert_alias, copy, create, delete, edit, history, lock, moderation, move, ordering, preview, + revisions, search, unpublish, usage, workflow) app_name = 'wagtailadmin_pages' urlpatterns = [ @@ -19,6 +18,7 @@ urlpatterns = [ path('/add_subpage/', create.add_subpage, name='add_subpage'), path('/delete/', delete.delete, name='delete'), path('/unpublish/', unpublish.unpublish, name='unpublish'), + path('/convert_alias/', convert_alias.convert_alias, name='convert_alias'), path('search/', search.search, name='search'), diff --git a/wagtail/admin/views/pages/convert_alias.py b/wagtail/admin/views/pages/convert_alias.py new file mode 100644 index 0000000000..16f522d56a --- /dev/null +++ b/wagtail/admin/views/pages/convert_alias.py @@ -0,0 +1,65 @@ +from django.core.exceptions import PermissionDenied +from django.db import transaction +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 import hooks +from wagtail.core.models import Page, PageLogEntry + + +def convert_alias(request, page_id): + page = get_object_or_404(Page, id=page_id, alias_of_id__isnull=False).specific + if not page.permissions_for_user(request.user).can_edit(): + raise PermissionDenied + + with transaction.atomic(): + for fn in hooks.get_hooks('before_convert_alias_page'): + result = fn(request, page) + if hasattr(result, 'status_code'): + return result + + next_url = get_valid_next_url_from_request(request) + + if request.method == 'POST': + page.alias_of_id = None + page.save(update_fields=['alias_of_id'], clean=False) + + # Create an initial revision + revision = page.save_revision(user=request.user, changed=False, clean=False) + + if page.live: + page.live_revision = revision + page.save(update_fields=['live_revision'], clean=False) + + # Log + PageLogEntry.objects.log_action( + instance=page, + revision=revision, + action='wagtail.convert_alias', + user=request.user, + data={ + 'page': { + 'id': page.id, + 'title': page.get_admin_display_title() + }, + }, + ) + + messages.success(request, _("Page '{0}' has been converted into a regular page.").format(page.get_admin_display_title())) + + for fn in hooks.get_hooks('after_convert_alias_page'): + result = fn(request, page) + if hasattr(result, 'status_code'): + return result + + if next_url: + return redirect(next_url) + return redirect('wagtailadmin_pages:edit', page.id) + + return TemplateResponse(request, 'wagtailadmin/pages/confirm_convert_alias.html', { + 'page': page, + 'next': next_url, + }) diff --git a/wagtail/admin/wagtail_hooks.py b/wagtail/admin/wagtail_hooks.py index a5c30a4b23..0dce2d8d7f 100644 --- a/wagtail/admin/wagtail_hooks.py +++ b/wagtail/admin/wagtail_hooks.py @@ -849,6 +849,14 @@ def register_core_log_actions(actions): except KeyError: return _("Created an alias") + def convert_alias_message(data): + try: + return _("Converted the alias '%(title)s' into a regular page") % { + 'title': data['page']['title'], + } + except KeyError: + return _("Converted an alias into a regular page") + def move_message(data): try: return _("Moved from '%(old_parent)s' to '%(new_parent)s'") % { @@ -925,6 +933,7 @@ def register_core_log_actions(actions): actions.register_action('wagtail.revert', _('Revert'), revert_message) actions.register_action('wagtail.copy', _('Copy'), copy_message) actions.register_action('wagtail.create_alias', _('Create alias'), create_alias_message) + actions.register_action('wagtail.convert_alias', _('Convert alias into regular page'), convert_alias_message) actions.register_action('wagtail.move', _('Move'), move_message) actions.register_action('wagtail.publish.schedule', _("Schedule publication"), schedule_publish_message) actions.register_action('wagtail.schedule.cancel', _("Unschedule publication"), unschedule_publish_message)