Define base Page panels as placeholders within wagtail.models

This ensures that code such as `content_panels = Page.content_panels + [...]` works as expected even if wagtail.admin has not been loaded.

Fixes #12747
pull/12750/head
Matt Westcott 2025-01-03 17:05:40 +00:00 zatwierdzone przez Sage Abdullah
rodzic 09e26c3c2b
commit 3f91fcb3a3
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: EB1A33CC51CC0217
7 zmienionych plików z 92 dodań i 52 usunięć

Wyświetl plik

@ -5,6 +5,7 @@ from django.db.models.fields.reverse_related import ManyToOneRel
from django.forms.models import fields_for_model
from wagtail.admin.forms.models import formfield_for_dbfield
from wagtail.models import PanelPlaceholder
from .base import Panel
from .field_panel import FieldPanel
@ -62,6 +63,10 @@ def expand_panel_list(model, panels):
if isinstance(panel, Panel):
result.append(panel)
elif isinstance(panel, PanelPlaceholder):
if real_panel := panel.construct():
result.append(real_panel)
elif isinstance(panel, str):
field = model._meta.get_field(panel)
if isinstance(field, ManyToOneRel):

Wyświetl plik

@ -1,50 +1,12 @@
from django.conf import settings
from django.utils.translation import gettext_lazy
from wagtail.admin.forms.pages import WagtailAdminPageForm
from wagtail.models import Page
from wagtail.utils.decorators import cached_classmethod
from .comment_panel import CommentPanel
from .field_panel import FieldPanel
from .group import MultiFieldPanel, ObjectList, TabbedInterface
from .publishing_panel import PublishingPanel
from .title_field_panel import TitleFieldPanel
from .group import ObjectList, TabbedInterface
def set_default_page_edit_handlers(cls):
cls.content_panels = [
TitleFieldPanel("title"),
]
cls.promote_panels = [
MultiFieldPanel(
[
FieldPanel("slug"),
FieldPanel("seo_title"),
FieldPanel("search_description"),
],
gettext_lazy("For search engines"),
),
MultiFieldPanel(
[
FieldPanel("show_in_menus"),
],
gettext_lazy("For site menus"),
),
]
cls.settings_panels = [
PublishingPanel(),
]
if getattr(settings, "WAGTAILADMIN_COMMENTS_ENABLED", True):
cls.settings_panels.append(CommentPanel())
cls.base_form_class = WagtailAdminPageForm
set_default_page_edit_handlers(Page)
Page.base_form_class = WagtailAdminPageForm
@cached_classmethod

Wyświetl plik

@ -5,7 +5,6 @@ from django.dispatch import receiver
from wagtail.models import Page
from .model_utils import get_edit_handler
from .page_utils import set_default_page_edit_handlers
@receiver(setting_changed)
@ -14,7 +13,6 @@ def reset_edit_handler_cache(**kwargs):
Clear page edit handler cache when global WAGTAILADMIN_COMMENTS_ENABLED settings are changed
"""
if kwargs["setting"] == "WAGTAILADMIN_COMMENTS_ENABLED":
set_default_page_edit_handlers(Page)
for model in apps.get_models():
if issubclass(model, Page):
model.get_edit_handler.cache_clear()

Wyświetl plik

@ -1,5 +1,4 @@
import datetime
import unittest
from unittest import mock
from django.contrib.auth.models import Group, Permission
@ -428,7 +427,6 @@ class TestPageCreation(WagtailTestUtils, TestCase):
)
self.assertRedirects(response, "/admin/")
@unittest.expectedFailure
def test_create_page_defined_before_admin_load(self):
"""
Test that a page model defined before wagtail.admin is loaded has all fields present

Wyświetl plik

@ -30,6 +30,7 @@ from wagtail.admin.panels import (
PublishingPanel,
TabbedInterface,
TitleFieldPanel,
expand_panel_list,
extract_panel_definitions_from_model_class,
get_form_for_model,
)
@ -1726,7 +1727,10 @@ class TestCommentPanel(WagtailTestUtils, TestCase):
Test that the comment panel is missing if WAGTAILADMIN_COMMENTS_ENABLED=False
"""
self.assertFalse(
any(isinstance(panel, CommentPanel) for panel in Page.settings_panels)
any(
isinstance(panel, CommentPanel)
for panel in expand_panel_list(Page, Page.settings_panels)
)
)
form_class = Page.get_edit_handler().get_form_class()
form = form_class()
@ -1737,7 +1741,10 @@ class TestCommentPanel(WagtailTestUtils, TestCase):
Test that the comment panel is present by default
"""
self.assertTrue(
any(isinstance(panel, CommentPanel) for panel in Page.settings_panels)
any(
isinstance(panel, CommentPanel)
for panel in expand_panel_list(Page, Page.settings_panels)
)
)
form_class = Page.get_edit_handler().get_form_class()
form = form_class()
@ -2024,7 +2031,10 @@ class TestPublishingPanel(WagtailTestUtils, TestCase):
Test that the publishing panel is present by default
"""
self.assertTrue(
any(isinstance(panel, PublishingPanel) for panel in Page.settings_panels)
any(
isinstance(panel, PublishingPanel)
for panel in expand_panel_list(Page, Page.settings_panels)
)
)
form_class = Page.get_edit_handler().get_form_class()
form = form_class()

Wyświetl plik

@ -128,6 +128,7 @@ from .media import ( # noqa: F401
UploadedFile,
get_root_collection_id,
)
from .panels import CommentPanelPlaceholder, PanelPlaceholder
from .reference_index import ReferenceIndex # noqa: F401
from .sites import Site, SiteManager, SiteRootPath # noqa: F401
from .specific import SpecificMixin
@ -1405,11 +1406,40 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
COMMENTS_RELATION_NAME,
]
# Define these attributes early to avoid masking errors. (Issue #3078)
# The canonical definition is in wagtailadmin.panels.
content_panels = []
promote_panels = []
settings_panels = []
# Real panel classes are defined in wagtail.admin.panels, which we can't import here
# because it would create a circular import. Instead, define them with placeholders
# to be replaced with the real classes by `wagtail.admin.panels.model_utils.expand_panel_list`.
content_panels = [
PanelPlaceholder("wagtail.admin.panels.TitleFieldPanel", ["title"], {}),
]
promote_panels = [
PanelPlaceholder(
"wagtail.admin.panels.MultiFieldPanel",
[
[
"slug",
"seo_title",
"search_description",
],
_("For search engines"),
],
{},
),
PanelPlaceholder(
"wagtail.admin.panels.MultiFieldPanel",
[
[
"show_in_menus",
],
_("For site menus"),
],
{},
),
]
settings_panels = [
PanelPlaceholder("wagtail.admin.panels.PublishingPanel", [], {}),
CommentPanelPlaceholder(),
]
# Privacy options for page
private_page_options = ["password", "groups", "login"]

Wyświetl plik

@ -0,0 +1,37 @@
# Placeholder for panel types defined in wagtail.admin.panels.
# These are needed because we wish to define properties such as `content_panels` on core models
# such as Page, but importing from wagtail.admin would create a circular import. We therefore use a
# placeholder object, and swap it out for the real panel class inside
# `wagtail.admin.panels.model_utils.expand_panel_list` at the same time as converting strings to
# FieldPanel instances.
from django.conf import settings
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
class PanelPlaceholder:
def __init__(self, path, args, kwargs):
self.path = path
self.args = args
self.kwargs = kwargs
@cached_property
def panel_class(self):
return import_string(self.path)
def construct(self):
return self.panel_class(*self.args, **self.kwargs)
class CommentPanelPlaceholder(PanelPlaceholder):
def __init__(self):
super().__init__(
"wagtail.admin.panels.CommentPanel",
[],
{},
)
def construct(self):
if getattr(settings, "WAGTAILADMIN_COMMENTS_ENABLED", True):
return super().construct()