angrybeanie_wagtail/env/lib/python3.12/site-packages/wagtail/admin/userbar.py

318 lines
12 KiB
Python
Raw Normal View History

2025-07-25 21:32:16 +10:00
from warnings import warn
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from wagtail import hooks
from wagtail.coreutils import accepts_kwarg
from wagtail.utils.deprecation import RemovedInWagtail80Warning
class BaseItem:
template = "wagtailadmin/userbar/item_base.html"
def get_context_data(self, request):
return {"self": self, "request": request}
def render(self, request):
return render_to_string(self.template, self.get_context_data(request))
class AdminItem(BaseItem):
template = "wagtailadmin/userbar/item_admin.html"
def render(self, request):
# Don't render if user doesn't have permission to access the admin area
if not request.user.has_perm("wagtailadmin.access_admin"):
return ""
return super().render(request)
class AccessibilityItem(BaseItem):
"""A userbar item that runs the accessibility checker."""
#: The template to use for rendering the item.
template = "wagtailadmin/userbar/item_accessibility.html"
#: A list of CSS selector(s) to test specific parts of the page.
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/context.md#the-include-property>`__.
axe_include = ["body"]
#: A list of CSS selector(s) to exclude specific parts of the page from testing.
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/context.md#exclude-elements-from-test>`__.
axe_exclude = []
# Make sure that the userbar is not tested.
_axe_default_exclude = [{"fromShadowDOM": ["wagtail-userbar"]}]
#: A list of `axe-core tags <https://github.com/dequelabs/axe-core/blob/master/doc/API.md#axe-core-tags>`_
#: or a list of `axe-core rule IDs <https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md>`_
#: (not a mix of both).
#: Setting this to a falsy value (e.g. ``None``) will omit the ``runOnly`` option and make Axe run with all non-experimental rules enabled.
axe_run_only = [
"button-name",
"empty-heading",
"empty-table-header",
"frame-title",
"heading-order",
"input-button-name",
"link-name",
"p-as-heading",
"alt-text-quality",
]
#: A dictionary that maps axe-core rule IDs to a dictionary of rule options,
#: commonly in the format of ``{"enabled": True/False}``. This can be used in
#: conjunction with :attr:`axe_run_only` to enable or disable specific rules.
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter-examples>`__.
axe_rules = {}
#: A list to add custom Axe rules or override their properties,
#: alongside with ``axe_custom_checks``. Includes Wagtails custom rules.
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure>`_.
axe_custom_rules = [
{
"id": "alt-text-quality",
"impact": "serious",
"selector": "img[alt]",
"tags": ["best-practice"],
"any": ["check-image-alt-text"],
# If omitted, defaults to True and overrides configs in `axe_run_only`.
"enabled": True,
},
]
#: A list to add custom Axe checks or override their properties.
#: Should be used in conjunction with ``axe_custom_rules``.
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure>`_.
axe_custom_checks = [
{
"id": "check-image-alt-text",
"options": {"pattern": "\\.(avif|gif|jpg|jpeg|png|svg|webp)$|_"},
},
]
#: A dictionary that maps axe-core rule IDs to custom translatable strings
#: to use as the error messages. If an enabled rule does not exist in this
#: dictionary, Axe's error message for the rule will be used as fallback.
axe_messages = {
"button-name": {
"error_name": _("Button text is empty"),
"help_text": _("Use meaningful text for screen reader users"),
},
"empty-heading": {
"error_name": _("Empty heading found"),
"help_text": _("Use meaningful text for screen reader users"),
},
"empty-table-header": {
"error_name": _("Table header text is empty"),
"help_text": _("Use meaningful text for screen reader users"),
},
"frame-title": {
"error_name": _("Empty frame title found"),
"help_text": _("Use a meaningful title for screen reader users"),
},
"heading-order": {
"error_name": _("Incorrect heading hierarchy"),
"help_text": _("Avoid skipping levels"),
},
"input-button-name": {
"error_name": _("Input button text is empty"),
"help_text": _("Use meaningful text for screen reader users"),
},
"link-name": {
"error_name": _("Link text is empty"),
"help_text": _("Use meaningful text for screen reader users"),
},
"p-as-heading": {
"error_name": _("Misusing paragraphs as headings"),
"help_text": _("Use proper heading tags"),
},
"alt-text-quality": {
"error_name": _("Image alt text has inappropriate pattern"),
"help_text": _("Use meaningful text"),
},
}
def get_axe_include(self, request):
"""Returns a list of CSS selector(s) to test specific parts of the page."""
return self.axe_include
def get_axe_exclude(self, request):
"""Returns a list of CSS selector(s) to exclude specific parts of the page from testing."""
return self.axe_exclude + self._axe_default_exclude
def get_axe_run_only(self, request):
"""Returns a list of axe-core tags or a list of axe-core rule IDs (not a mix of both)."""
return self.axe_run_only
def get_axe_rules(self, request):
"""Returns a dictionary that maps axe-core rule IDs to a dictionary of rule options."""
return self.axe_rules
def get_axe_custom_rules(self, request):
"""List of rule objects per axe.run API."""
return self.axe_custom_rules
def get_axe_custom_checks(self, request):
"""List of check objects per axe.run API, without evaluate function."""
return self.axe_custom_checks
def get_axe_messages(self, request):
"""Returns a dictionary that maps axe-core rule IDs to custom translatable strings."""
return self.axe_messages
def get_axe_context(self, request):
"""
Returns the `context object <https://github.com/dequelabs/axe-core/blob/develop/doc/context.md>`_
to be passed as the
`context parameter <https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter>`_
for ``axe.run``.
"""
return {
"include": self.get_axe_include(request),
"exclude": self.get_axe_exclude(request),
}
def get_axe_options(self, request):
"""
Returns the options object to be passed as the
`options parameter <https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter>`_
for ``axe.run``.
"""
options = {
"runOnly": self.get_axe_run_only(request),
"rules": self.get_axe_rules(request),
}
# If the runOnly option is omitted, Axe will run all rules except those
# with the "experimental" flag or that are disabled in the rules option.
# The runOnly has to be omitted (instead of set to an empty list or null)
# for this to work, so we remove it if it's falsy.
if not options["runOnly"]:
options.pop("runOnly")
return options
def get_axe_spec(self, request):
"""Returns spec for Axe, including custom rules and custom checks"""
return {
"rules": self.get_axe_custom_rules(request),
"checks": self.get_axe_custom_checks(request),
}
def get_axe_configuration(self, request):
return {
"context": self.get_axe_context(request),
"options": self.get_axe_options(request),
"messages": self.get_axe_messages(request),
"spec": self.get_axe_spec(request),
}
def get_context_data(self, request):
return {
**super().get_context_data(request),
"axe_configuration": self.get_axe_configuration(request),
}
def render(self, request):
# Don't render if user doesn't have permission to access the admin area
if not request.user.has_perm("wagtailadmin.access_admin"):
return ""
return super().render(request)
class AddPageItem(BaseItem):
template = "wagtailadmin/userbar/item_page_add.html"
def __init__(self, page):
self.page = page
self.parent_page = page.get_parent()
def render(self, request):
# Don't render if the page doesn't have an id
if not self.page.id:
return ""
# Don't render if user doesn't have permission to access the admin area
if not request.user.has_perm("wagtailadmin.access_admin"):
return ""
# Don't render if user doesn't have ability to add children here
permission_checker = self.page.permissions_for_user(request.user)
if not permission_checker.can_add_subpage():
return ""
return super().render(request)
class ExplorePageItem(BaseItem):
template = "wagtailadmin/userbar/item_page_explore.html"
def __init__(self, page):
self.page = page
self.parent_page = page.get_parent()
def render(self, request):
# Don't render if the page doesn't have an id
if not self.page.id:
return ""
# Don't render if user doesn't have permission to access the admin area
if not request.user.has_perm("wagtailadmin.access_admin"):
return ""
# Don't render if user doesn't have ability to edit or publish subpages on the parent page
permission_checker = self.parent_page.permissions_for_user(request.user)
if (
not permission_checker.can_edit()
and not permission_checker.can_publish_subpage()
):
return ""
return super().render(request)
class EditPageItem(BaseItem):
template = "wagtailadmin/userbar/item_page_edit.html"
def __init__(self, page):
self.page = page
def render(self, request):
# Don't render if the page doesn't have an id
if not self.page.id:
return ""
# Don't render if request is a preview. This is to avoid confusion that
# might arise when the user clicks edit on a preview.
try:
if request.is_preview and request.is_editing:
return ""
except AttributeError:
pass
# Don't render if user doesn't have permission to access the admin area
if not request.user.has_perm("wagtailadmin.access_admin"):
return ""
# Don't render if the user doesn't have permission to edit this page
permission_checker = self.page.permissions_for_user(request.user)
if not permission_checker.can_edit():
return ""
return super().render(request)
def apply_userbar_hooks(request, items, page):
for fn in hooks.get_hooks("construct_wagtail_userbar"):
if accepts_kwarg(fn, "page"):
fn(request, items, page)
else:
warn(
"`construct_wagtail_userbar` hook functions should accept a `page` argument in third position -"
f" {fn.__module__}.{fn.__name__} needs to be updated",
category=RemovedInWagtail80Warning,
)
fn(request, items)