Skip to content

Commit

Permalink
Add support for controlling which CMS Page types can be used in the s…
Browse files Browse the repository at this point in the history
…ite (#14846)

* Add support for controlling which CMS Page types can be used in the site

This means we can control when a page is available for use in the CMS, versus
simply being in the codebase. Also, note that removing a particular page class
from this allowlist will not break existing pages that are of that class, but
will stop anyone adding a _new_ one.

NB: EVERY TIME we add a new Wagtail Page subclass to the CMS, we must add
to the CMS_ALLOWED_PAGE_MODELS setting if we want it to be selectable as
a new child page in Production (or ticket up when we do want to add it to
the setting)

* Fix DEV mode allowance of all page types
  • Loading branch information
stevejalim authored Jul 23, 2024
1 parent 9f3ba2f commit e7a9b14
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 2 deletions.
9 changes: 9 additions & 0 deletions bedrock/cms/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

from django.conf import settings
from django.utils.cache import add_never_cache_headers
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
Expand Down Expand Up @@ -34,6 +35,14 @@ class AbstractBedrockCMSPage(WagtailBasePage):
class Meta:
abstract = True

@classmethod
def can_create_at(cls, parent):
"""Only allow users to add new child pages that are permitted by configuration."""
page_model_signature = f"{cls._meta.app_label}.{cls._meta.object_name}"
if settings.CMS_ALLOWED_PAGE_MODELS == ["__all__"] or page_model_signature in settings.CMS_ALLOWED_PAGE_MODELS:
return super().can_create_at(parent)
return False

def _patch_request_for_bedrock(self, request):
# Add hints that help us integrate CMS pages with core Bedrock logic
request.is_cms_page = True
Expand Down
32 changes: 31 additions & 1 deletion bedrock/cms/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@

from unittest import mock

from django.test import override_settings

import pytest

from bedrock.cms.models import AbstractBedrockCMSPage, SimpleRichTextPage
from bedrock.cms.models import (
AbstractBedrockCMSPage,
SimpleRichTextPage,
StructuralPage,
)
from bedrock.cms.tests.factories import StructuralPageFactory

pytestmark = [
Expand Down Expand Up @@ -67,3 +73,27 @@ def test_StructuralPage_serve_methods(

preview_result = sp.serve_preview(request)
assert preview_result.headers["location"].endswith(root_page.url)


@pytest.mark.parametrize(
"config, page_class, success_expected",
(
("__all__", SimpleRichTextPage, True), # same as default
("mozorg.SomeOtherPageClass,cms.StructuralPage,cms.SimpleRichTextPage", StructuralPage, True),
("cms.SimpleRichTextPage", SimpleRichTextPage, True),
("cms.SimpleRichTextPage,mozorg.SomeOtherPageClass", SimpleRichTextPage, True),
("mozorg.SomeOtherPageClass,cms.SimpleRichTextPage", SimpleRichTextPage, True),
("mozorg.SomeOtherPageClass,mozorg.SomeOtherPageClass", SimpleRichTextPage, False),
("mozorg.SomeOtherPageClass", SimpleRichTextPage, False),
("mozorg.SomeOtherPageClass,legal.SomeLegalPageClass", StructuralPage, False),
),
)
def test_CMS_ALLOWED_PAGE_MODELS_controls_Page_can_create_at(
config,
page_class,
success_expected,
minimal_site,
):
home_page = SimpleRichTextPage.objects.last()
with override_settings(Dev=False, CMS_ALLOWED_PAGE_MODELS=config.split(",")):
assert page_class.can_create_at(home_page) == success_expected
21 changes: 21 additions & 0 deletions bedrock/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2164,3 +2164,24 @@ def lazy_wagtail_langs():
]

WAGTAILIMAGES_IMAGE_MODEL = "cms.BedrockImage"

# Custom code in bedrock.cms.models.base.AbstractBedrockCMSPage limits what page
# models can be added as a child page.
#
# This means we can control when a page is available for use in the CMS, versus
# simply being in the codebase. Also, note that removing a particular page class
# from this allowlist will not break existing pages that are of that class, but
# will stop anyone adding a _new_ one.
#
# NB: EVERY TIME you add a new Wagtail Page subclass to the CMS, you must enable
# it here if you want it to be selectable as a new child page in Production

_allowed_page_models = [
"cms.SimpleRichTextPage",
"cms.StructuralPage",
]

if DEV is True:
CMS_ALLOWED_PAGE_MODELS = ["__all__"]
else:
CMS_ALLOWED_PAGE_MODELS = _allowed_page_models
19 changes: 18 additions & 1 deletion docs/cms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,24 @@ Editing current content surfaces

`Wagtail Editor Guide`_.

Bedrock-specific details to come.
.. note::
This is initial documentation, noting relevant things that exist already, but much fuller recommendations will follow

The ``CMS_ALLOWED_PAGE_MODELS`` setting
=======================================

When you add a new page to the CMS, it will be available to add as a new child page immediately if ``DEV=True``. This means it'll be on Dev (www-dev), but not in Staging or Prod.

So if you ship a page that needs to be used immediately in Production (which will generally be most cases), you must remember to add it to ``CMS_ALLOWED_PAGE_MODELS`` in Bedrock's settings. If you do not, it will not be selectable as a new Child Page in the CMS.

Why do we have this behaviour?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Two reasons:

1. This setting allows us to complete initial/eager work to add a new page type, but stop it being used in Production until we are ready for it (e.g. a special new campaign page type that we wanted to get ready in good time). While there will be guard rails and approval workflows around publishing, without this it could still be possible for part of the org to start using a new page without us realising it was off-limits, and possibly before it is allowed to be released.

2. This approach allows us to gracefully deprecate pages: if a page is removed in ``settings.CMS_ALLOWED_PAGE_MODELS``, that doesn't mean it disappears from Prod or can't be edited - it just stops a NEW one being added in Prod.

Migrating Django pages to the CMS
=================================
Expand Down

0 comments on commit e7a9b14

Please sign in to comment.