angrybeanie_wagtail/env/lib/python3.12/site-packages/wagtail/admin/userbar.py
2025-07-25 21:32:16 +10:00

317 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)