from django.contrib.auth.models import AnonymousUser, Group from django.contrib.sites.models import Site from django.test import override_settings from cms.api import add_plugin, create_page from cms.cache.permissions import get_permission_cache, set_permission_cache from cms.constants import PAGE_TYPES_ID from cms.models import ( ACCESS_CHILDREN, ACCESS_PAGE, ACCESS_PAGE_AND_DESCENDANTS, CMSPlugin, Page, PageContent, PagePermission, ) from cms.test_utils.testcases import CMSTestCase from cms.utils import page_permissions @override_settings(CMS_PERMISSION=True, CMS_PUBLIC_FOR="all") class CopyPermissionsTests(CMSTestCase): def setUp(self): super().setUp() self.admin = self.get_superuser() self.actor = self.get_staff_user_with_no_permissions() for codename in ("add_page", "change_page", "add_text", "change_text"): self.add_permission(self.actor, codename) self.target = create_page("target", "nav_playground.html", "en") self.target_permission = self.add_page_permission( self.actor, self.target, can_add=True, can_change=True, grant_on=ACCESS_PAGE ) self.source = create_page("source", "nav_playground.html", "en") self.secret = create_page("secret", "nav_playground.html", "en", parent=self.source) self.add_page_permission(self.admin, self.secret, can_view=True, grant_on=ACCESS_PAGE) self.marker = "CONFIDENTIAL-COPY-REGRESSION" add_plugin(self.secret.get_placeholders("en").get(slot="body"), "TextPlugin", "en", body=self.marker) def copy(self, copy_permissions, source=None, position=0): with self.login_user_context(self.actor): response = self.client.post( self.get_admin_url(Page, "copy_page", (source or self.source).pk), {"position": position, "target": self.target.pk, "copy_permissions": copy_permissions}, ) # Permission checks after the POST should use a fresh request's user. self.actor = type(self.actor).objects.get(pk=self.actor.pk) return response def assertCopyDenied(self, copy_permissions): counts = (Page.objects.count(), CMSPlugin.objects.count(), PagePermission.objects.count()) response = self.copy(copy_permissions) self.assertEqual(response.json()["status"], 403) self.assertEqual(counts, (Page.objects.count(), CMSPlugin.objects.count(), PagePermission.objects.count())) def test_unreadable_descendant_requires_copy_permissions(self): self.assertTrue(page_permissions.user_can_view_page(self.actor, self.source)) self.assertFalse(page_permissions.user_can_view_page(self.actor, self.secret)) self.assertFalse(page_permissions.user_can_change_page(self.actor, self.secret)) self.assertEqual(self.client.get(self.secret.get_absolute_url()).status_code, 404) self.assertCopyDenied("") def test_unreadable_descendant_can_be_copied_with_permissions(self): response = self.copy("on") self.assertEqual(response.status_code, 200) copied = Page.objects.get(pk=response.json()["id"]).get_child_pages().get() self.assertTrue(copied.has_view_restrictions(copied.site)) self.assertFalse(page_permissions.user_can_view_page(self.actor, copied)) self.assertEqual(self.client.get(copied.get_absolute_url()).status_code, 404) with self.login_user_context(self.admin): self.assertContains(self.client.get(copied.get_absolute_url()), self.marker) def test_destination_change_grant_cannot_expose_unreadable_descendant(self): self.target_permission.grant_on = ACCESS_PAGE_AND_DESCENDANTS self.target_permission.save() self.assertCopyDenied("on") def test_destination_view_grant_cannot_expose_unreadable_descendant(self): self.add_page_permission(self.actor, self.target, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) self.assertCopyDenied("on") def test_destination_group_grant_cannot_expose_unreadable_descendant(self): group = Group.objects.create(name="Destination viewers") self.actor.groups.add(group) self.add_page_permission(None, self.target, group=group, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) self.assertCopyDenied("on") @override_settings( CMS_LANGUAGES={ 1: [{"code": "en", "name": "English"}], 2: [{"code": "en", "name": "English"}], } ) def test_destination_site_global_grant_cannot_expose_unreadable_descendant(self): site = Site.objects.create(domain="destination.example", name="Destination") self.target = create_page("other-site", "nav_playground.html", "en", site=site) permission = self.add_global_permission(self.actor, can_add=True, can_change=True) permission.sites.set([site]) self.assertFalse(page_permissions.user_can_view_page(self.actor, self.secret)) with self.settings(SITE_ID=site.pk): self.assertCopyDenied("on") def test_unreadable_grandchild_requires_copy_permissions(self): self.secret.pagepermission_set.all().delete() grandchild = create_page("grandchild", "nav_playground.html", "en", parent=self.secret) self.add_page_permission(self.admin, grandchild, can_view=True, grant_on=ACCESS_PAGE) self.assertTrue(page_permissions.user_can_view_page(self.actor, self.secret)) self.assertCopyDenied("") def test_destination_immediate_children_grant_does_not_expose_descendant(self): self.add_page_permission(self.actor, self.target, can_view=True, grant_on=ACCESS_CHILDREN) response = self.copy("on") self.assertEqual(response.status_code, 200) copied = Page.objects.get(pk=response.json()["id"]).get_child_pages().get() self.assertFalse(page_permissions.user_can_view_page(self.actor, copied)) def test_copy_permissions_preserves_root_restriction(self): self.add_page_permission(self.actor, self.source, can_view=True, grant_on=ACCESS_PAGE) response = self.copy("on") self.assertEqual(response.status_code, 200) copied = Page.objects.get(pk=response.json()["id"]) self.assertTrue(copied.pagepermission_set.filter(user=self.actor, can_view=True).exists()) self.assertEqual(self.client.get(copied.get_absolute_url()).status_code, 404) def test_copy_permissions_invalidates_permission_caches(self): self.add_page_permission(self.actor, self.source, can_change=True, grant_on=ACCESS_PAGE) self.assertTrue(page_permissions.user_can_change_page(self.actor, self.source)) copied = self.source.copy( self.source.site, parent_page=self.target, permissions=True, user=self.actor, ) self.assertTrue(page_permissions.user_can_change_page(self.actor, copied)) def test_copy_permissions_invalidates_another_users_cached_actions(self): recipient = self._create_user("recipient", is_staff=True, is_superuser=False) self.add_permission(recipient, "change_page") self.add_page_permission(recipient, self.source, can_change=True, grant_on=ACCESS_PAGE) self.assertTrue(page_permissions.user_can_change_page(recipient, self.source)) copied = self.source.copy( self.source.site, parent_page=self.target, permissions=True, user=self.actor, ) self.assertTrue(page_permissions.user_can_change_page(recipient, copied)) def test_copy_permissions_invalidates_cache_again_on_commit(self): self.add_page_permission(self.actor, self.source, can_change=True, grant_on=ACCESS_PAGE) with self.captureOnCommitCallbacks(execute=True) as callbacks: self.source.copy( self.source.site, parent_page=self.target, permissions=True, user=self.actor, ) # Simulate a concurrent request caching pre-commit permission rows. set_permission_cache(self.actor, self.source.site, "change_page", []) self.assertEqual(get_permission_cache(self.actor, self.source.site, "change_page"), []) self.assertEqual(len(callbacks), 1) self.assertIsNone(get_permission_cache(self.actor, self.source.site, "change_page")) def test_readable_descendants_can_be_copied_without_permissions(self): self.add_page_permission(self.actor, self.secret, can_view=True, grant_on=ACCESS_PAGE) response = self.copy("") self.assertEqual(response.status_code, 200) copied = Page.objects.get(pk=response.json()["id"]).get_child_pages().get() self.assertFalse(copied.has_view_restrictions(copied.site)) self.assertContains(self.client.get(copied.get_absolute_url()), self.marker) def test_inherited_restrictions_survive_outside_source_subtree(self): ancestor = create_page("ancestor", "nav_playground.html", "en") source = create_page("nested-source", "nav_playground.html", "en", parent=ancestor) child = create_page("nested-child", "nav_playground.html", "en", parent=source) self.add_page_permission(self.admin, ancestor, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) self.add_page_permission(self.actor, ancestor, can_view=True, grant_on=ACCESS_CHILDREN) self.assertTrue(page_permissions.user_can_view_page(self.actor, source)) self.assertFalse(page_permissions.user_can_view_page(self.actor, child)) response = self.copy("on", source=source) self.assertEqual(response.status_code, 200) copied = Page.objects.get(pk=response.json()["id"]) copied_child = copied.get_child_pages().get() self.assertTrue(page_permissions.user_can_view_page(self.actor, copied)) self.assertFalse(page_permissions.user_can_view_page(self.actor, copied_child)) for page in (copied, copied_child): self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), page)) self.assertTrue(PagePermission.objects.for_page(page).filter(user=self.admin, can_view=True).exists()) self.assertFalse(page.pagepermission_set.filter(can_change=True).exists()) added_child = create_page("later-child", "nav_playground.html", "en", parent=copied_child) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), added_child)) self.assertFalse(page_permissions.user_can_view_page(self.actor, added_child)) def test_unreadable_root_is_still_rejected(self): response = self.copy("on", source=self.secret) self.assertEqual(response.json()["status"], 403) @override_settings(CMS_PERMISSION=True, CMS_PUBLIC_FOR="all") class MovePermissionsTests(CMSTestCase): """A moved subtree leaves the ancestors that restricted it behind.""" def setUp(self): super().setUp() self.admin = self.get_superuser() self.actor = self.get_staff_user_with_no_permissions() for codename in ("add_page", "change_page", "add_text", "change_text"): self.add_permission(self.actor, codename) self.target = create_page("target", "nav_playground.html", "en") self.target_permission = self.add_page_permission( self.actor, self.target, can_add=True, can_change=True, grant_on=ACCESS_PAGE ) self.ancestor = create_page("ancestor", "nav_playground.html", "en") self.add_page_permission(self.admin, self.ancestor, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) self.source = create_page("source", "nav_playground.html", "en", parent=self.ancestor) self.secret = create_page("secret", "nav_playground.html", "en", parent=self.source) self.marker = "CONFIDENTIAL-MOVE-REGRESSION" add_plugin(self.secret.get_placeholders("en").get(slot="body"), "TextPlugin", "en", body=self.marker) def move(self, page=None, target=None, position=0): page = page or self.source with self.login_user_context(self.actor): response = self.client.post( self.get_admin_url(Page, "move_page", page.pk), {"id": page.pk, "position": position, "target": (target or self.target).pk}, ) # Permission checks after the POST should use a fresh request's user. self.actor = type(self.actor).objects.get(pk=self.actor.pk) return response def grant_source(self, grant_on=ACCESS_PAGE_AND_DESCENDANTS): return self.add_page_permission( self.actor, self.source, can_change=True, can_move_page=True, grant_on=grant_on ) def test_move_preserves_inherited_restriction(self): """The editor of a restricted branch cannot publish it by moving it.""" self.grant_source() self.assertTrue(page_permissions.user_can_view_page(self.actor, self.secret)) response = self.move() self.assertEqual(response.json().get("status", 200), 200) source = Page.objects.get(pk=self.source.pk) secret = Page.objects.get(pk=self.secret.pk) self.assertEqual(source.parent_id, self.target.pk) for page in (source, secret): self.assertTrue(page.has_view_restrictions(page.site)) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), page)) self.assertEqual(self.client.get(page.get_absolute_url()).status_code, 404) with self.login_user_context(self.admin): self.assertContains(self.client.get(secret.get_absolute_url()), self.marker) def test_move_grants_no_editing_privileges(self): """Only ``can_view`` is materialized, never the ancestor's other flags.""" self.add_page_permission(self.admin, self.ancestor, can_change=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) self.grant_source() self.move() source = Page.objects.get(pk=self.source.pk) self.assertTrue(source.pagepermission_set.filter(user=self.admin, can_view=True).exists()) # The actor's own grant stays; the ancestor's can_change is not imported. self.assertFalse(source.pagepermission_set.filter(user=self.admin, can_change=True).exists()) def test_move_keeps_unreadable_descendant_hidden(self): """The mover may not read ``secret``; the move must not change that.""" self.grant_source(grant_on=ACCESS_PAGE) self.assertTrue(page_permissions.user_can_view_page(self.actor, self.source)) self.assertFalse(page_permissions.user_can_view_page(self.actor, self.secret)) response = self.move() self.assertEqual(response.json().get("status", 200), 200) secret = Page.objects.get(pk=self.secret.pk) self.assertFalse(page_permissions.user_can_view_page(self.actor, secret)) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), secret)) self.assertEqual(self.client.get(secret.get_absolute_url()).status_code, 404) def test_destination_change_grant_cannot_expose_unreadable_descendant(self): """A destination grant would widen access, so the move is refused.""" self.grant_source(grant_on=ACCESS_PAGE) self.target_permission.grant_on = ACCESS_PAGE_AND_DESCENDANTS self.target_permission.save() response = self.move() self.assertEqual(response.json()["status"], 403) self.assertEqual(Page.objects.get(pk=self.source.pk).parent_id, self.ancestor.pk) def test_destination_view_grant_cannot_expose_unreadable_descendant(self): self.grant_source(grant_on=ACCESS_PAGE) self.add_page_permission(self.actor, self.target, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) response = self.move() self.assertEqual(response.json()["status"], 403) self.assertEqual(Page.objects.get(pk=self.source.pk).parent_id, self.ancestor.pk) def test_move_inside_restricted_branch_adds_no_permissions(self): """Reordering inside the branch keeps inheritance, so nothing is copied.""" self.grant_source() self.add_page_permission(self.actor, self.ancestor, can_add=True, can_change=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) count = PagePermission.objects.count() response = self.move(target=self.ancestor) self.assertEqual(response.json().get("status", 200), 200) source = Page.objects.get(pk=self.source.pk) self.assertEqual(PagePermission.objects.count(), count) self.assertTrue(source.has_view_restrictions(source.site)) def test_move_into_restricted_branch_adds_no_permissions(self): """An unrestricted page moved into a restricted branch inherits, no rows.""" public = create_page("public", "nav_playground.html", "en") self.add_page_permission(self.actor, public, can_change=True, can_move_page=True, grant_on=ACCESS_PAGE) self.add_page_permission(self.actor, self.ancestor, can_add=True, can_change=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) count = PagePermission.objects.count() response = self.move(page=public, target=self.ancestor) self.assertEqual(response.json().get("status", 200), 200) public = Page.objects.get(pk=public.pk) self.assertEqual(PagePermission.objects.count(), count) self.assertTrue(public.has_view_restrictions(public.site)) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), public)) def test_move_of_unrestricted_page_adds_no_permissions(self): public = create_page("public", "nav_playground.html", "en") self.add_page_permission(self.actor, public, can_change=True, can_move_page=True, grant_on=ACCESS_PAGE) count = PagePermission.objects.count() response = self.move(page=public) self.assertEqual(response.json().get("status", 200), 200) public = Page.objects.get(pk=public.pk) self.assertEqual(PagePermission.objects.count(), count) self.assertFalse(public.has_view_restrictions(public.site)) self.assertEqual(self.client.get(public.get_absolute_url()).status_code, 200) @override_settings(CMS_PERMISSION=True, CMS_PUBLIC_FOR="all") class DuplicatePermissionsTests(CMSTestCase): """``Duplicate`` copies a single page; its restrictions come along.""" def setUp(self): super().setUp() self.admin = self.get_superuser() self.actor = self.get_staff_user_with_no_permissions() for codename in ("add_page", "change_page", "add_text", "change_text"): self.add_permission(self.actor, codename) self.target = create_page("target", "nav_playground.html", "en") self.add_page_permission(self.actor, self.target, can_add=True, can_change=True, grant_on=ACCESS_PAGE) self.marker = "CONFIDENTIAL-DUPLICATE-REGRESSION" def duplicate(self, source): content = source.get_admin_content("en") endpoint = self.get_admin_url(PageContent, "duplicate", content.pk) with self.login_user_context(self.actor): response = self.client.post( f"{endpoint}?parent_page={self.target.pk}", { "title": "duplicate", "slug": "duplicate", "template": "nav_playground.html", "language": "en", "source": source.pk, "parent_page": self.target.pk, "_save": 1, }, ) self.actor = type(self.actor).objects.get(pk=self.actor.pk) return response, self.target.get_child_pages().first() def test_duplicate_preserves_own_restriction(self): source = create_page("source", "nav_playground.html", "en") add_plugin(source.get_placeholders("en").get(slot="body"), "TextPlugin", "en", body=self.marker) self.add_page_permission(self.admin, source, can_view=True, grant_on=ACCESS_PAGE) self.add_page_permission(self.actor, source, can_view=True, grant_on=ACCESS_PAGE) self.assertTrue(page_permissions.user_can_view_page(self.actor, source)) response, copy = self.duplicate(source) self.assertEqual(response.status_code, 302) self.assertIsNotNone(copy) self.assertTrue(copy.has_view_restrictions(copy.site)) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), copy)) self.assertEqual(self.client.get(copy.get_absolute_url()).status_code, 404) self.assertFalse(copy.pagepermission_set.filter(can_change=True).exists()) with self.login_user_context(self.admin): self.assertContains(self.client.get(copy.get_absolute_url()), self.marker) def test_duplicate_preserves_inherited_restriction(self): ancestor = create_page("ancestor", "nav_playground.html", "en") self.add_page_permission(self.admin, ancestor, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) self.add_page_permission(self.actor, ancestor, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS) source = create_page("source", "nav_playground.html", "en", parent=ancestor) add_plugin(source.get_placeholders("en").get(slot="body"), "TextPlugin", "en", body=self.marker) response, copy = self.duplicate(source) self.assertEqual(response.status_code, 302) self.assertTrue(copy.has_view_restrictions(copy.site)) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), copy)) self.assertEqual(self.client.get(copy.get_absolute_url()).status_code, 404) # A page added below the copy stays protected, as it would below the source. child = create_page("child", "nav_playground.html", "en", parent=copy) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), child)) def test_duplicate_of_unrestricted_page_adds_no_permissions(self): source = create_page("source", "nav_playground.html", "en") add_plugin(source.get_placeholders("en").get(slot="body"), "TextPlugin", "en", body=self.marker) count = PagePermission.objects.count() response, copy = self.duplicate(source) self.assertEqual(response.status_code, 302) self.assertEqual(PagePermission.objects.count(), count) self.assertFalse(copy.has_view_restrictions(copy.site)) self.assertContains(self.client.get(copy.get_absolute_url()), self.marker) @override_settings(CMS_PERMISSION=True, CMS_PUBLIC_FOR="all") class PageTypeSourcePermissionsTests(CMSTestCase): """``Add page`` can copy a page type; its restrictions come along. Page types can no longer be created through the UI, but rows migrated from django CMS 3.x are still offered as ``source`` on the add form. """ def setUp(self): super().setUp() self.admin = self.get_superuser() self.actor = self.get_staff_user_with_no_permissions() for codename in ("add_page", "change_page", "add_text", "change_text"): self.add_permission(self.actor, codename) self.target = create_page("target", "nav_playground.html", "en") self.add_page_permission(self.actor, self.target, can_add=True, can_change=True, grant_on=ACCESS_PAGE) self.marker = "CONFIDENTIAL-PAGE-TYPE-REGRESSION" def create_page_type(self, title, site=None): root = create_page("Page Types", "nav_playground.html", "en", site=site, reverse_id=PAGE_TYPES_ID) page_type = create_page(title, "nav_playground.html", "en", site=site, parent=root) Page.objects.filter(pk__in=(root.pk, page_type.pk)).update(is_page_type=True) add_plugin(page_type.get_placeholders("en").get(slot="body"), "TextPlugin", "en", body=self.marker) return Page.objects.get(pk=page_type.pk) def add_from_source(self, source): endpoint = self.get_admin_url(PageContent, "add") with self.login_user_context(self.actor): response = self.client.post( f"{endpoint}?parent_page={self.target.pk}", { "title": "from-page-type", "slug": "from-page-type", "template": "nav_playground.html", "language": "en", "source": source.pk, "parent_page": self.target.pk, "_save": 1, }, ) self.actor = type(self.actor).objects.get(pk=self.actor.pk) return response, self.target.get_child_pages().first() def assertAddDenied(self, source): counts = (Page.objects.count(), CMSPlugin.objects.count()) response, copy = self.add_from_source(source) self.assertEqual(response.status_code, 200) self.assertIsNone(copy) self.assertEqual(counts, (Page.objects.count(), CMSPlugin.objects.count())) def test_rejects_page_type_user_cannot_view(self): source = self.create_page_type("secret-type") self.add_page_permission(self.admin, source, can_view=True, grant_on=ACCESS_PAGE) self.assertFalse(page_permissions.user_can_view_page(self.actor, source)) self.assertAddDenied(source) @override_settings( CMS_LANGUAGES={ 1: [{"code": "en", "name": "English"}], 2: [{"code": "en", "name": "English"}], } ) def test_rejects_page_type_from_another_site(self): site = Site.objects.create(domain="other.example", name="Other") self.assertAddDenied(self.create_page_type("other-site-type", site=site)) def test_rejects_page_type_root(self): source = self.create_page_type("visible-type") self.assertAddDenied(source.parent) def test_readable_page_type_can_be_used(self): source = self.create_page_type("visible-type") self.assertTrue(page_permissions.user_can_view_page(self.actor, source)) response, copy = self.add_from_source(source) self.assertEqual(response.status_code, 302) self.assertIsNotNone(copy) self.assertFalse(copy.is_page_type) self.assertContains(self.client.get(copy.get_absolute_url()), self.marker) def test_restricted_page_type_keeps_its_restriction(self): source = self.create_page_type("restricted-type") self.add_page_permission(self.admin, source, can_view=True, grant_on=ACCESS_PAGE) self.add_page_permission(self.actor, source, can_view=True, grant_on=ACCESS_PAGE) self.assertTrue(page_permissions.user_can_view_page(self.actor, source)) response, copy = self.add_from_source(source) self.assertEqual(response.status_code, 302) self.assertTrue(copy.has_view_restrictions(copy.site)) self.assertFalse(page_permissions.user_can_view_page(AnonymousUser(), copy)) self.assertEqual(self.client.get(copy.get_absolute_url()).status_code, 404) def test_rejects_source_when_adding_translation(self): source = self.create_page_type("restricted-type") self.add_page_permission(self.admin, source, can_view=True, grant_on=ACCESS_PAGE) self.add_page_permission(self.actor, source, can_view=True, grant_on=ACCESS_PAGE) destination = create_page("destination", "nav_playground.html", "de") self.add_page_permission(self.actor, destination, can_change=True, grant_on=ACCESS_PAGE) self.assertTrue(page_permissions.user_can_view_page(self.actor, source)) self.assertTrue(page_permissions.user_can_view_page(AnonymousUser(), destination)) endpoints = ( self.get_admin_url(PageContent, "add"), self.get_admin_url(PageContent, "duplicate", source.get_content_obj("en").pk), ) data = { "cms_page": destination.pk, "source": source.pk, "language": "en", "title": "translation", "slug": "translation", "_save": 1, } models = (Page, PageContent, CMSPlugin, PagePermission) counts = tuple(model.objects.count() for model in models) with self.login_user_context(self.actor): for endpoint in endpoints: with self.subTest(endpoint=endpoint): response = self.client.post( f"{endpoint}?cms_page={destination.pk}&language=en&parent_page={self.target.pk}", data, ) self.assertEqual(response.status_code, 200) self.assertIn("source", response.context["adminform"].form.errors) self.assertFalse(destination.pagecontent_set.filter(language="en").exists()) self.assertEqual(counts, tuple(model.objects.count() for model in models)) # Adding a translation without a source remains supported. data.pop("source") response = self.client.post( f"{endpoints[0]}?cms_page={destination.pk}&language=en&parent_page={self.target.pk}", data, ) self.assertEqual(response.status_code, 302) self.assertNotContains(self.client.get(destination.get_absolute_url("en")), self.marker)