mirror of
https://github.com/wagtail/wagtail.git
synced 2024-12-01 11:41:20 +01:00
Add Page methods can_move_to
and can_create_at
These two tests are used when checking if a new page type can be created under a page instance, or if an existing page type can move under a page instance.
This commit is contained in:
parent
c1c77e4e70
commit
0071d85aa0
@ -114,41 +114,47 @@ In addition to the model fields provided, ``Page`` has many properties and metho
|
||||
.. automethod:: get_siblings
|
||||
|
||||
.. attribute:: search_fields
|
||||
|
||||
|
||||
A list of fields to be indexed by the search engine. See Search docs :ref:`wagtailsearch_indexing_fields`
|
||||
|
||||
.. attribute:: subpage_types
|
||||
|
||||
A whitelist of page models which can be created as children of this page type e.g a ``BlogIndex`` page might allow ``BlogPage``, but not ``JobPage`` e.g
|
||||
A whitelist of page models which can be created as children of this page type. For example, a ``BlogIndex`` page might allow a ``BlogPage`` as a child, but not a ``JobPage``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class BlogIndex(Page):
|
||||
subpage_types = ['mysite.BlogPage', 'mysite.BlogArchivePage']
|
||||
|
||||
The creation of child pages can be blocked altogether for a given page by setting it's subpage_types attribute to an empty array e.g
|
||||
|
||||
|
||||
The creation of child pages can be blocked altogether for a given page by setting it's subpage_types attribute to an empty array:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class BlogPage(Page):
|
||||
subpage_types = []
|
||||
|
||||
|
||||
.. attribute:: parent_page_types
|
||||
|
||||
A whitelist of page models which are allowed as parent page types e.g a ``BlogPage`` may only allow itself to be created below the ``BlogIndex`` page e.g
|
||||
A whitelist of page models which are allowed as parent page types. For example, a ``BlogPage`` may only allow itself to be created below the ``BlogIndex`` page:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class BlogPage(Page):
|
||||
parent_page_types = ['mysite.BlogIndexPage']
|
||||
|
||||
Pages can block themselves from being created at all by setting parent_page_types to an empty array (this is useful for creating unique pages that should only be created once) e.g
|
||||
|
||||
|
||||
Pages can block themselves from being created at all by setting parent_page_types to an empty array (this is useful for creating unique pages that should only be created once):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class HiddenPage(Page):
|
||||
parent_page_types = []
|
||||
|
||||
.. automethod:: can_exist_under
|
||||
|
||||
.. automethod:: can_create_at
|
||||
|
||||
.. automethod:: can_move_to
|
||||
|
||||
.. attribute:: password_required_template
|
||||
|
||||
Defines which template file should be used to render the login form for Protected pages using this model. This overrides the default, defined using ``PASSWORD_REQUIRED_TEMPLATE`` in your settings. See :ref:`private_pages`
|
||||
|
25
wagtail/tests/testapp/migrations/0018_singletonpage.py
Normal file
25
wagtail/tests/testapp/migrations/0018_singletonpage.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailcore', '0020_add_index_on_page_first_published_at'),
|
||||
('tests', '0017_businessnowherepage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SingletonPage',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('wagtailcore.page',),
|
||||
),
|
||||
]
|
@ -360,7 +360,7 @@ register_snippet(Advert)
|
||||
|
||||
class StandardIndex(Page):
|
||||
""" Index for the site """
|
||||
pass
|
||||
parent_page_types = [Page]
|
||||
|
||||
|
||||
# A custom panel setup where all Promote fields are placed in the Content tab instead;
|
||||
@ -420,6 +420,12 @@ TaggedPage.content_panels = [
|
||||
FieldPanel('tags'),
|
||||
]
|
||||
|
||||
class SingletonPage(Page):
|
||||
@classmethod
|
||||
def can_create_at(cls, parent):
|
||||
# You can only create one of these!
|
||||
return super(SingletonPage, cls).can_create_at(parent) \
|
||||
and not cls.objects.exists()
|
||||
|
||||
class PageChooserModel(models.Model):
|
||||
page = models.ForeignKey('wagtailcore.Page', help_text='help text')
|
||||
|
@ -67,7 +67,8 @@ def add_subpage(request, parent_page_id):
|
||||
|
||||
page_types = [
|
||||
(model.get_verbose_name(), model._meta.app_label, model._meta.model_name)
|
||||
for model in parent_page.creatable_subpage_models()
|
||||
for model in type(parent_page).creatable_subpage_models()
|
||||
if model.can_create_at(parent_page)
|
||||
]
|
||||
# sort by lower-cased version of verbose name
|
||||
page_types.sort(key=lambda page_type: page_type[0].lower())
|
||||
|
@ -750,6 +750,31 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
|
||||
if page_model.is_creatable
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def can_exist_under(cls, parent):
|
||||
"""
|
||||
Checks if this page type can exist as a subpage under a parent page
|
||||
instance.
|
||||
|
||||
See also: :func:`Page.can_create_at` and :func:`Page.can_move_to`
|
||||
"""
|
||||
return cls in parent.specific_class.allowed_subpage_models()
|
||||
|
||||
@classmethod
|
||||
def can_create_at(cls, parent):
|
||||
"""
|
||||
Checks if this page type can be created as a subpage under a parent
|
||||
page instance.
|
||||
"""
|
||||
return cls.is_creatable and cls.can_exist_under(parent)
|
||||
|
||||
def can_move_to(self, parent):
|
||||
"""
|
||||
Checks if this page instance can be moved to be a subpage of a parent
|
||||
page instance.
|
||||
"""
|
||||
return self.can_exist_under(parent)
|
||||
|
||||
@classmethod
|
||||
def get_verbose_name(cls):
|
||||
"""
|
||||
@ -1388,7 +1413,7 @@ class PagePermissionTester(object):
|
||||
def can_add_subpage(self):
|
||||
if not self.user.is_active:
|
||||
return False
|
||||
if not self.page.specific_class.allowed_subpage_models(): # this page model has an empty subpage_types list, so no subpages are allowed
|
||||
if not self.page.specific_class.creatable_subpage_models():
|
||||
return False
|
||||
return self.user.is_superuser or ('add' in self.permissions)
|
||||
|
||||
@ -1457,7 +1482,7 @@ class PagePermissionTester(object):
|
||||
"""
|
||||
if not self.user.is_active:
|
||||
return False
|
||||
if not self.page.specific_class.allowed_subpage_models(): # this page model has an empty subpage_types list, so no subpages are allowed
|
||||
if not self.page.specific_class.creatable_subpage_models():
|
||||
return False
|
||||
|
||||
return self.user.is_superuser or ('publish' in self.permissions)
|
||||
@ -1484,7 +1509,7 @@ class PagePermissionTester(object):
|
||||
|
||||
# reject moves that are forbidden by subpage_types / parent_page_types rules
|
||||
# (these rules apply to superusers too)
|
||||
if ContentType.objects.get_for_model(self.page.specific_class) not in destination.allowed_subpage_types():
|
||||
if not self.page.specific.can_move_to(destination):
|
||||
return False
|
||||
|
||||
# shortcut the trivial 'everything' / 'nothing' permissions
|
||||
|
@ -16,10 +16,14 @@ from wagtail.tests.testapp.models import (
|
||||
BusinessIndex, BusinessSubIndex, BusinessChild, StandardIndex,
|
||||
MTIBasePage, MTIChildPage, AbstractPage, TaggedPage,
|
||||
BlogCategory, BlogCategoryBlogPage, Advert, ManyToManyBlogPage,
|
||||
GenericSnippetPage, BusinessNowherePage)
|
||||
GenericSnippetPage, BusinessNowherePage, SingletonPage)
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
|
||||
def get_ct(model):
|
||||
return ContentType.objects.get_for_model(model)
|
||||
|
||||
|
||||
class TestSiteRouting(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
@ -236,7 +240,6 @@ class TestServeView(TestCase):
|
||||
from django.core.urlresolvers import clear_url_caches
|
||||
clear_url_caches()
|
||||
|
||||
|
||||
def test_serve(self):
|
||||
response = self.client.get('/events/christmas/')
|
||||
|
||||
@ -743,20 +746,20 @@ class TestSubpageTypeBusinessRules(TestCase, WagtailTestUtils):
|
||||
with self.ignore_deprecation_warnings():
|
||||
# SimplePage does not define any restrictions on subpage types
|
||||
# SimplePage is a valid subpage of SimplePage
|
||||
self.assertIn(ContentType.objects.get_for_model(SimplePage), SimplePage.allowed_subpage_types())
|
||||
self.assertIn(get_ct(SimplePage), SimplePage.allowed_subpage_types())
|
||||
# BusinessIndex is a valid subpage of SimplePage
|
||||
self.assertIn(ContentType.objects.get_for_model(BusinessIndex), SimplePage.allowed_subpage_types())
|
||||
self.assertIn(get_ct(BusinessIndex), SimplePage.allowed_subpage_types())
|
||||
# BusinessSubIndex is not valid, because it explicitly omits SimplePage from parent_page_types
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessSubIndex), SimplePage.allowed_subpage_types())
|
||||
self.assertNotIn(get_ct(BusinessSubIndex), SimplePage.allowed_subpage_types())
|
||||
|
||||
# BusinessChild has an empty subpage_types list, so does not allow anything
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), BusinessChild.allowed_subpage_types())
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessIndex), BusinessChild.allowed_subpage_types())
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessSubIndex), BusinessChild.allowed_subpage_types())
|
||||
self.assertNotIn(get_ct(SimplePage), BusinessChild.allowed_subpage_types())
|
||||
self.assertNotIn(get_ct(BusinessIndex), BusinessChild.allowed_subpage_types())
|
||||
self.assertNotIn(get_ct(BusinessSubIndex), BusinessChild.allowed_subpage_types())
|
||||
|
||||
# BusinessSubIndex only allows BusinessChild as subpage type
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), BusinessSubIndex.allowed_subpage_types())
|
||||
self.assertIn(ContentType.objects.get_for_model(BusinessChild), BusinessSubIndex.allowed_subpage_types())
|
||||
self.assertNotIn(get_ct(SimplePage), BusinessSubIndex.allowed_subpage_types())
|
||||
self.assertIn(get_ct(BusinessChild), BusinessSubIndex.allowed_subpage_types())
|
||||
|
||||
def test_allowed_parent_page_models(self):
|
||||
# SimplePage does not define any restrictions on parent page types
|
||||
@ -783,17 +786,80 @@ class TestSubpageTypeBusinessRules(TestCase, WagtailTestUtils):
|
||||
with self.ignore_deprecation_warnings():
|
||||
# SimplePage does not define any restrictions on parent page types
|
||||
# SimplePage is a valid parent page of SimplePage
|
||||
self.assertIn(ContentType.objects.get_for_model(SimplePage), SimplePage.allowed_parent_page_types())
|
||||
self.assertIn(get_ct(SimplePage), SimplePage.allowed_parent_page_types())
|
||||
# BusinessChild cannot be a parent of anything
|
||||
self.assertNotIn(ContentType.objects.get_for_model(BusinessChild), SimplePage.allowed_parent_page_types())
|
||||
self.assertNotIn(get_ct(BusinessChild), SimplePage.allowed_parent_page_types())
|
||||
|
||||
# BusinessNowherePage does not allow anything as a parent
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), BusinessNowherePage.allowed_parent_page_types())
|
||||
self.assertNotIn(ContentType.objects.get_for_model(StandardIndex), BusinessNowherePage.allowed_parent_page_types())
|
||||
self.assertNotIn(get_ct(SimplePage), BusinessNowherePage.allowed_parent_page_types())
|
||||
self.assertNotIn(get_ct(StandardIndex), BusinessNowherePage.allowed_parent_page_types())
|
||||
|
||||
# BusinessSubIndex only allows BusinessIndex as a parent
|
||||
self.assertNotIn(ContentType.objects.get_for_model(SimplePage), BusinessSubIndex.allowed_parent_page_types())
|
||||
self.assertIn(ContentType.objects.get_for_model(BusinessIndex), BusinessSubIndex.allowed_parent_page_types())
|
||||
self.assertNotIn(get_ct(SimplePage), BusinessSubIndex.allowed_parent_page_types())
|
||||
self.assertIn(get_ct(BusinessIndex), BusinessSubIndex.allowed_parent_page_types())
|
||||
|
||||
def test_can_exist_under(self):
|
||||
self.assertTrue(SimplePage.can_exist_under(SimplePage()))
|
||||
|
||||
# StandardIndex should only be allowed under a Page
|
||||
self.assertTrue(StandardIndex.can_exist_under(Page()))
|
||||
self.assertFalse(StandardIndex.can_exist_under(SimplePage()))
|
||||
|
||||
# The Business pages are quite restrictive in their structure
|
||||
self.assertTrue(BusinessSubIndex.can_exist_under(BusinessIndex()))
|
||||
self.assertTrue(BusinessChild.can_exist_under(BusinessIndex()))
|
||||
self.assertTrue(BusinessChild.can_exist_under(BusinessSubIndex()))
|
||||
|
||||
self.assertFalse(BusinessSubIndex.can_exist_under(SimplePage()))
|
||||
self.assertFalse(BusinessSubIndex.can_exist_under(BusinessSubIndex()))
|
||||
self.assertFalse(BusinessChild.can_exist_under(SimplePage()))
|
||||
|
||||
def test_can_create_at(self):
|
||||
# Pages are not `is_creatable`, and should not be creatable
|
||||
self.assertFalse(Page.can_create_at(Page()))
|
||||
|
||||
# SimplePage can be created under a simple page
|
||||
self.assertTrue(SimplePage.can_create_at(SimplePage()))
|
||||
|
||||
# StandardIndex can be created under a Page, but not a SimplePage
|
||||
self.assertTrue(StandardIndex.can_create_at(Page()))
|
||||
self.assertFalse(StandardIndex.can_create_at(SimplePage()))
|
||||
|
||||
# The Business pages are quite restrictive in their structure
|
||||
self.assertTrue(BusinessSubIndex.can_create_at(BusinessIndex()))
|
||||
self.assertTrue(BusinessChild.can_create_at(BusinessIndex()))
|
||||
self.assertTrue(BusinessChild.can_create_at(BusinessSubIndex()))
|
||||
|
||||
self.assertFalse(BusinessChild.can_create_at(SimplePage()))
|
||||
self.assertFalse(BusinessSubIndex.can_create_at(SimplePage()))
|
||||
|
||||
def test_can_move_to(self):
|
||||
self.assertTrue(SimplePage().can_move_to(SimplePage()))
|
||||
|
||||
# StandardIndex should only be allowed under a Page
|
||||
self.assertTrue(StandardIndex().can_move_to(Page()))
|
||||
self.assertFalse(StandardIndex().can_move_to(SimplePage()))
|
||||
|
||||
# The Business pages are quite restrictive in their structure
|
||||
self.assertTrue(BusinessSubIndex().can_move_to(BusinessIndex()))
|
||||
self.assertTrue(BusinessChild().can_move_to(BusinessIndex()))
|
||||
self.assertTrue(BusinessChild().can_move_to(BusinessSubIndex()))
|
||||
|
||||
self.assertFalse(BusinessChild().can_move_to(SimplePage()))
|
||||
self.assertFalse(BusinessSubIndex().can_move_to(SimplePage()))
|
||||
|
||||
def test_singleton_page_creation(self):
|
||||
root_page = Page.objects.get(url_path='/home/')
|
||||
|
||||
# A single singleton page should be creatable
|
||||
self.assertTrue(SingletonPage.can_create_at(root_page))
|
||||
|
||||
# Create a singleton page
|
||||
root_page.add_child(instance=SingletonPage(
|
||||
title='singleton', slug='singleton'))
|
||||
|
||||
# A second singleton page should not be creatable
|
||||
self.assertFalse(SingletonPage.can_create_at(root_page))
|
||||
|
||||
|
||||
class TestIssue735(TestCase):
|
||||
|
@ -141,8 +141,8 @@ class TestPagePermission(TestCase):
|
||||
|
||||
def test_superuser_has_full_permissions(self):
|
||||
user = get_user_model().objects.get(username='superuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
root = Page.objects.get(url_path='/')
|
||||
homepage = Page.objects.get(url_path='/home/').specific
|
||||
root = Page.objects.get(url_path='/').specific
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
board_meetings_page = BusinessSubIndex.objects.get(url_path='/home/events/businessy-events/board-meetings/')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user