0
0
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:
Tim Heap 2015-10-10 13:17:54 +11:00 committed by Matt Westcott
parent c1c77e4e70
commit 0071d85aa0
7 changed files with 162 additions and 33 deletions

View File

@ -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`

View 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',),
),
]

View File

@ -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')

View File

@ -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())

View File

@ -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

View File

@ -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):

View File

@ -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/')