Repository: wagtail/wagtail
File: wagtail/contrib/settings/views.py (Python)

Code before:
from functools import lru_cache

from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.text import capfirst
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy

from wagtail.admin import messages
from wagtail.admin.panels import (
    ObjectList,
    extract_panel_definitions_from_model_class,
)
from wagtail.admin.ui.side_panels import PreviewSidePanel
from wagtail.admin.views import generic
from wagtail.admin.views.generic import preview
from wagtail.models import PreviewableMixin, Site
from wagtail.permission_policies import ModelPermissionPolicy

from .forms import SiteSwitchForm
from .models import BaseGenericSetting, BaseSiteSetting
from .registry import registry


def get_model_from_url_params(app_name, model_name):
    """
    retrieve a content type from an app_name / model_name combo.
    Throw Http404 if not a valid setting type
    """
    model = registry.get_by_natural_key(app_name, model_name)
    if model is None:
        raise Http404
    return model


@lru_cache(maxsize=None)
def get_setting_edit_handler(model):
    if hasattr(model, "edit_handler"):
        edit_handler = model.edit_handler
    else:
        if issubclass(model, BaseSiteSetting):
            panels = extract_panel_definitions_from_model_class(model, ["site"])
        elif issubclass(model, BaseGenericSetting):
            panels = extract_panel_definitions_from_model_class(model)
        else:
            raise NotImplementedError

        edit_handler = ObjectList(panels)
    return edit_handler.bind_to_model(model)


def redirect_to_relevant_instance(request, app_name, model_name):
    model = get_model_from_url_params(app_name, model_name)

    if issubclass(model, BaseSiteSetting):
        # Redirect the user to the edit page for the current site
        # (or the current request does not correspond to a site, the first site in the list)
        site_request = Site.find_for_request(request)
        site = site_request or Site.objects.first()
        if not site:
            messages.error(
                request,
                _("This setting could not be opened because there is no site defined."),
            )
            return redirect("wagtailadmin_home")
        return redirect(
            "wagtailsettings:edit",
            app_name,
            model_name,
            site.pk,
        )
    elif issubclass(model, BaseGenericSetting):
        return redirect(
            "wagtailsettings:edit",
            app_name,
            model_name,
            model.load(request_or_site=request).id,
        )
    else:
        raise NotImplementedError


class EditView(generic.EditView):
    template_name = "wagtailsettings/edit.html"
    edit_url_name = "wagtailsettings:edit"
    preview_url_name = "wagtailsettings:preview_on_edit"
    error_message = gettext_lazy("The setting could not be saved due to errors.")
    permission_required = "change"

    def setup(self, request, app_name, model_name, *args, **kwargs):
        self.app_name = app_name
        self.model_name = model_name
        self.model = get_model_from_url_params(app_name, model_name)
        self.permission_policy = ModelPermissionPolicy(self.model)
        self.pk = kwargs.get(self.pk_url_kwarg)
        super().setup(request, app_name, model_name, *args, **kwargs)

    def get_header_icon(self):
        return registry._model_icons.get(self.model)

    def get_object(self, queryset=None):
        self.site = None
        if issubclass(self.model, BaseSiteSetting):
            self.site = get_object_or_404(Site, pk=self.pk)
            return self.model.for_site(self.site)
        else:
            return get_object_or_404(self.model, pk=self.pk)

    def get_panel(self):
        return get_setting_edit_handler(self.model)

    def get_edit_url(self):
        return reverse(
            self.edit_url_name,
            args=(self.app_name, self.model_name, self.pk),
        )

    def get_success_buttons(self):
        return None

    def get_page_subtitle(self):
        return capfirst(self.model._meta.verbose_name)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        site_switcher = None
        if self.site and Site.objects.count() > 1:
            site_switcher = SiteSwitchForm(self.site, self.model)
            context["media"] += site_switcher.media

        context["site_switcher"] = site_switcher
        return context

    def get_success_url(self):
        return self.get_edit_url()

    def get_success_message(self):
        return capfirst(
            _("%(setting_type)s updated.")
            % {"setting_type": self.model._meta.verbose_name}
        )

    def get_preview_url(self):
        if isinstance(self.object, PreviewableMixin) and self.object.is_previewable():
            return reverse(
                self.preview_url_name,
                args=(self.app_name, self.model_name, self.pk),
            )

    def get_side_panels(self):
        side_panels = super().get_side_panels()
        if preview_url := self.get_preview_url():
            side_panels.append(
                PreviewSidePanel(self.object, self.request, preview_url=preview_url)
            )
        return side_panels


class PreviewOnEdit(preview.PreviewOnEdit):
    def setup(self, request, app_name, model_name, *args, **kwargs):
        self.app_name = app_name
        self.model_name = model_name
        self.model = get_model_from_url_params(app_name, model_name)
        self.pk = kwargs.get("pk")
        super().setup(request, app_name, model_name, *args, **kwargs)

    def get_object(self, queryset=None):
        self.site = None
        if issubclass(self.model, BaseSiteSetting):
            self.site = get_object_or_404(Site, pk=self.pk)
            return self.model.for_site(self.site)
        else:
            return get_object_or_404(self.model, pk=self.pk)


Review comment (refactor):
I think we'd want to have the `ChecksSidePanel` as well whenever we have the `PreviewSidePanel` to ensure that we have accessibility checks

```diff
diff --git a/wagtail/contrib/settings/tests/generic/test_preview.py b/wagtail/contrib/settings/tests/generic/test_preview.py
index dc6c97d555..88ebf775ce 100644
--- a/wagtail/contrib/settings/tests/generic/test_preview.py
+++ b/wagtail/contrib/settings/tests/generic/test_preview.py
@@ -125,6 +125,12 @@ class TestEnablePreview(WagtailTestUtils, TestCase):
         self.assertEqual("w-tooltip w-kbd", toggle_button["data-controller"])
         self.assertEqual("mod+p", toggle_button["data-w-kbd-key-value"])

+        # Should have the checks side panel
+        soup = self.get_soup(response.content)
+        self.assertIsNotNone(soup.select_one('[data-side-panel="checks"]'))
+        toggle_button = soup.find("button", {"data-side-panel-toggle": "checks"})
+        self.assertIsNotNone(toggle_button)
+
         # Should set the preview URL value on the controller
         controller = soup.select_one('[data-controller="w-preview"]')
         self.assertIsNotNone(controller)
diff --git a/wagtail/contrib/settings/views.py b/wagtail/contrib/settings/views.py
index 218c59a278..050431b114 100644
--- a/wagtail/contrib/settings/views.py
+++ b/wagtail/contrib/settings/views.py
@@ -12,7 +12,7 @@ from wagtail.admin.panels import (
     ObjectList,
     extract_panel_definitions_from_model_class,
 )
-from wagtail.admin.ui.side_panels import PreviewSidePanel
+from wagtail.admin.ui.side_panels import ChecksSidePanel, PreviewSidePanel
 from wagtail.admin.views import generic
 from wagtail.admin.views.generic import preview
 from wagtail.models import PreviewableMixin, Site
@@ -155,6 +155,7 @@ class EditView(generic.EditView):
             side_panels.append(
                 PreviewSidePanel(self.object, self.request, preview_url=preview_url)
             )
+            side_panels.append(ChecksSidePanel(self.object, self.request))
         return side_panels


```

Code after:
        context["site_switcher"] = site_switcher
        context["site_for_header"] = site_for_header
        return context

    def get_success_url(self):
        return self.get_edit_url()

    def get_success_message(self):
        return capfirst(
            _("%(setting_type)s updated.")
            % {"setting_type": self.model._meta.verbose_name}
        )

    def get_preview_url(self):
        if isinstance(self.object, PreviewableMixin) and self.object.is_previewable():
            return reverse(
                self.preview_url_name,
                args=(self.app_name, self.model_name, self.pk),
            )

    def get_side_panels(self):
        side_panels = super().get_side_panels()
        if preview_url := self.get_preview_url():
            side_panels.append(
                PreviewSidePanel(self.object, self.request, preview_url=preview_url)
            )
            side_panels.append(ChecksSidePanel(self.object, self.request))
        return side_panels


class PreviewOnEdit(preview.PreviewOnEdit):
    def setup(self, request, app_name, model_name, *args, **kwargs):
        self.app_name = app_name
        self.model_name = model_name
        self.model = get_model_from_url_params(app_name, model_name)
        self.pk = kwargs.get("pk")
        super().setup(request, app_name, model_name, *args, **kwargs)

    def get_object(self, queryset=None):
        self.site = None
        if issubclass(self.model, BaseSiteSetting):
            self.site = get_object_or_404(Site, pk=self.pk)
            return self.model.for_site(self.site)
        else:
            return get_object_or_404(self.model, pk=self.pk)

    def get_extra_request_attrs(self):
        attrs = super().get_extra_request_attrs()
        # Inject current instance into request so the context processor uses it
        attrs[self.object.get_cache_attr_name()] = self.object
        return attrs