from django.conf import settings from django.contrib.auth.models import Permission from django.urls import reverse, reverse_lazy from django.utils.functional import cached_property from django.utils.http import urlencode from django.utils.translation import gettext_lazy as _ from draftjs_exporter.dom import DOM import wagtail.admin.rich_text.editors.draftail.features as draftail_features from wagtail import __version__, hooks from wagtail.admin.admin_url_finder import ( ModelAdminURLFinder, register_admin_url_finder, ) from wagtail.admin.auth import user_has_any_page_permission from wagtail.admin.forms.collections import GroupCollectionManagementPermissionFormSet from wagtail.admin.menu import ( DismissibleMenuItem, DismissibleSubmenuMenuItem, MenuItem, SubmenuMenuItem, help_menu, reports_menu, settings_menu, ) from wagtail.admin.rich_text.converters.contentstate import link_entity from wagtail.admin.rich_text.converters.editor_html import ( LinkTypeRule, PageLinkHandler, WhitelistRule, ) from wagtail.admin.rich_text.converters.html_to_contentstate import ( BlockElementHandler, ExternalLinkElementHandler, HorizontalRuleHandler, InlineStyleElementHandler, ListElementHandler, ListItemElementHandler, PageLinkElementHandler, ) from wagtail.admin.search import SearchArea from wagtail.admin.site_summary import PagesSummaryItem from wagtail.admin.ui.sidebar import ( PageExplorerMenuItem as PageExplorerMenuItemComponent, ) from wagtail.admin.ui.sidebar import SubMenuItem as SubMenuItemComponent from wagtail.admin.views.pages.bulk_actions import ( DeleteBulkAction, MoveBulkAction, PublishBulkAction, UnpublishBulkAction, ) from wagtail.admin.viewsets import viewsets from wagtail.admin.widgets import ButtonWithDropdownFromHook, PageListingButton from wagtail.models import Collection, Page, Task, Workflow from wagtail.permissions import ( collection_permission_policy, page_permission_policy, task_permission_policy, workflow_permission_policy, ) from wagtail.templatetags.wagtailcore_tags import ( wagtail_feature_release_editor_guide_link, wagtail_feature_release_whats_new_link, ) from wagtail.utils.version import get_main_version from wagtail.whitelist import allow_without_attributes, attribute_rule, check_url class ExplorerMenuItem(MenuItem): def is_shown(self, request): return user_has_any_page_permission(request.user) def get_context(self, request): context = super().get_context(request) start_page = page_permission_policy.explorable_root_instance(request.user) if start_page: context["start_page_id"] = start_page.id return context def render_component(self, request): start_page = page_permission_policy.explorable_root_instance(request.user) if start_page: return PageExplorerMenuItemComponent( self.name, self.label, self.url, start_page.id, icon_name=self.icon_name, classname=self.classname, ) else: return super().render_component(request) @hooks.register("register_admin_menu_item") def register_explorer_menu_item(): return ExplorerMenuItem( _("Pages"), reverse("wagtailadmin_explore_root"), name="explorer", icon_name="folder-open-inverse", order=100, ) class SettingsMenuItem(SubmenuMenuItem): def render_component(self, request): return SubMenuItemComponent( self.name, self.label, self.menu.render_component(request), icon_name=self.icon_name, classname=self.classname, footer_text="Wagtail v" + __version__, ) @hooks.register("register_admin_menu_item") def register_settings_menu(): return SettingsMenuItem( _("Settings"), settings_menu, name="settings", icon_name="cogs", order=10000, ) @hooks.register("register_permissions") def register_permissions(): return Permission.objects.filter( content_type__app_label="wagtailadmin", codename="access_admin" ) class PageSearchArea(SearchArea): def __init__(self): super().__init__( _("Pages"), reverse("wagtailadmin_pages:search"), name="pages", icon_name="folder-open-inverse", order=100, ) def is_shown(self, request): return user_has_any_page_permission(request.user) @hooks.register("register_admin_search_area") def register_pages_search_area(): return PageSearchArea() @hooks.register("register_group_permission_panel") def register_collection_permissions_panel(): return GroupCollectionManagementPermissionFormSet class CollectionsMenuItem(MenuItem): def is_shown(self, request): return collection_permission_policy.user_has_any_permission( request.user, ["add", "change", "delete"] ) @hooks.register("register_settings_menu_item") def register_collections_menu_item(): return CollectionsMenuItem( _("Collections"), reverse("wagtailadmin_collections:index"), name="collections", icon_name="folder-open-1", order=700, ) class WorkflowsMenuItem(MenuItem): def is_shown(self, request): if not getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True): return False return workflow_permission_policy.user_has_any_permission( request.user, ["add", "change", "delete"] ) class WorkflowTasksMenuItem(MenuItem): def is_shown(self, request): if not getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True): return False return task_permission_policy.user_has_any_permission( request.user, ["add", "change", "delete"] ) @hooks.register("register_settings_menu_item") def register_workflows_menu_item(): return WorkflowsMenuItem( _("Workflows"), reverse("wagtailadmin_workflows:index"), name="workflows", icon_name="tasks", order=100, ) @hooks.register("register_settings_menu_item") def register_workflow_tasks_menu_item(): return WorkflowTasksMenuItem( _("Workflow tasks"), reverse("wagtailadmin_workflows:task_index"), name="workflow-tasks", icon_name="thumbtack", order=150, ) @hooks.register("register_page_listing_buttons") def page_listing_buttons(page, user, next_url=None): yield ButtonWithDropdownFromHook( "", hook_name="register_page_listing_more_buttons", page=page, user=user, next_url=next_url, icon_name="dots-horizontal", attrs={ "target": "_blank", "rel": "noreferrer", "aria-label": _("More options for '%(title)s'") % {"title": page.get_admin_display_title()}, }, priority=50, ) class PageListingEditButton(PageListingButton): label = _("Edit") icon_name = "edit" aria_label_format = _("Edit '%(title)s'") url_name = "wagtailadmin_pages:edit" @property def show(self): return self.page_perms.can_edit() class PageListingViewDraftButton(PageListingButton): label = _("View draft") icon_name = "draft" aria_label_format = _("Preview draft version of '%(title)s'") url_name = "wagtailadmin_pages:view_draft" attrs = {"rel": "noreferrer"} @property def show(self): return self.page.has_unpublished_changes and self.page.is_previewable() class PageListingViewLiveButton(PageListingButton): label = _("View live") icon_name = "doc-empty" aria_label_format = _("View live version of '%(title)s'") attrs = {"rel": "noreferrer"} @property def show(self): return self.page.live and self.page.url class PageListingAddChildPageButton(PageListingButton): label = _("Add child page") icon_name = "circle-plus" aria_label_format = _("Add a child page to '%(title)s'") url_name = "wagtailadmin_pages:add_subpage" @property def show(self): return self.page_perms.can_add_subpage() class PageListingMoveButton(PageListingButton): label = _("Move") icon_name = "arrow-right-full" aria_label_format = _("Move page '%(title)s'") url_name = "wagtailadmin_pages:move" @property def show(self): return self.page_perms.can_move() class PageListingCopyButton(PageListingButton): label = _("Copy") icon_name = "copy" aria_label_format = _("Copy page '%(title)s'") url_name = "wagtailadmin_pages:copy" @property def show(self): return self.page_perms.can_copy() class PageListingDeleteButton(PageListingButton): label = _("Delete") icon_name = "bin" aria_label_format = _("Delete page '%(title)s'") @cached_property def url(self): if self.page: url = reverse("wagtailadmin_pages:delete", args=[self.page.id]) if self.next_url: if self.next_url == reverse( "wagtailadmin_explore", args=[self.page.id] ): # cannot redirect to the explore view after deleting the page pass elif self.next_url == reverse( "wagtailadmin_pages:edit", args=[self.page.id] ): # cannot redirect to the edit view after deleting the page pass else: # OK to add the 'next' parameter url += "?" + urlencode({"next": self.next_url}) return url @property def show(self): return self.page_perms.can_delete() class PageListingUnpublishButton(PageListingButton): label = _("Unpublish") icon_name = "download" aria_label_format = _("Unpublish page '%(title)s'") url_name = "wagtailadmin_pages:unpublish" @property def show(self): return self.page_perms.can_unpublish() class PageListingHistoryButton(PageListingButton): label = _("History") icon_name = "history" aria_label_format = _("View page history for '%(title)s'") url_name = "wagtailadmin_pages:history" @property def show(self): return self.page_perms.can_view_revisions() class PageListingSortMenuOrderButton(PageListingButton): label = _("Sort menu order") icon_name = "list-ul" aria_label_format = _("Change ordering of child pages of '%(title)s'") @property def show(self): return self.page_perms.can_reorder_children() @cached_property def url(self): return reverse("wagtailadmin_explore", args=[self.page.id]) + "?ordering=ord" @hooks.register("register_page_listing_more_buttons") def page_listing_more_buttons(page, user, next_url=None): yield PageListingEditButton(page=page, user=user, next_url=next_url, priority=2) yield PageListingViewDraftButton(page=page, user=user, priority=4) yield PageListingViewLiveButton(page=page, user=user, url=page.url, priority=6) yield PageListingAddChildPageButton( page=page, user=user, next_url=next_url, priority=8 ) yield PageListingMoveButton(page=page, user=user, priority=10) yield PageListingCopyButton(page=page, user=user, next_url=next_url, priority=20) yield PageListingDeleteButton(page=page, user=user, next_url=next_url, priority=30) yield PageListingUnpublishButton( page=page, user=user, next_url=next_url, priority=40 ) yield PageListingHistoryButton(page=page, user=user, priority=50) yield PageListingSortMenuOrderButton(page=page, user=user, priority=60) @hooks.register("register_page_header_buttons") def page_header_buttons(page, user, view_name, next_url=None): yield PageListingEditButton(page=page, user=user, priority=10) # "add child" is a separate primary action on the index page if view_name != "index": yield PageListingAddChildPageButton(page=page, user=user, priority=15) yield PageListingMoveButton(page=page, user=user, priority=20) yield PageListingCopyButton(page=page, user=user, next_url=next_url, priority=30) yield PageListingDeleteButton(page=page, user=user, next_url=next_url, priority=50) yield PageListingUnpublishButton( page=page, user=user, next_url=next_url, priority=60 ) yield PageListingHistoryButton(page=page, user=user, priority=65) yield PageListingSortMenuOrderButton(page=page, user=user, priority=70) @hooks.register("register_admin_urls") def register_viewsets_urls(): viewsets.populate() return viewsets.get_urlpatterns() @hooks.register("register_rich_text_features") def register_core_features(features): features.register_converter_rule( "editorhtml", "link", [ WhitelistRule("a", attribute_rule({"href": check_url})), LinkTypeRule("page", PageLinkHandler), ], ) features.register_converter_rule( "editorhtml", "bold", [ WhitelistRule("b", allow_without_attributes), WhitelistRule("strong", allow_without_attributes), ], ) features.register_converter_rule( "editorhtml", "italic", [ WhitelistRule("i", allow_without_attributes), WhitelistRule("em", allow_without_attributes), ], ) headings_elements = ["h1", "h2", "h3", "h4", "h5", "h6"] for order, element in enumerate(headings_elements): features.register_converter_rule( "editorhtml", element, [WhitelistRule(element, allow_without_attributes)] ) features.register_converter_rule( "editorhtml", "ol", [ WhitelistRule("ol", allow_without_attributes), WhitelistRule("li", allow_without_attributes), ], ) features.register_converter_rule( "editorhtml", "ul", [ WhitelistRule("ul", allow_without_attributes), WhitelistRule("li", allow_without_attributes), ], ) # Draftail features.register_editor_plugin( "draftail", "hr", draftail_features.BooleanFeature("enableHorizontalRule") ) features.register_converter_rule( "contentstate", "hr", { "from_database_format": { "hr": HorizontalRuleHandler(), }, "to_database_format": { "entity_decorators": { "HORIZONTAL_RULE": lambda props: DOM.create_element("hr") } }, }, ) features.register_editor_plugin( "draftail", "h1", draftail_features.BlockFeature( { "icon": "h1", "type": "header-one", "description": _("Heading 1"), } ), ) features.register_converter_rule( "contentstate", "h1", { "from_database_format": { "h1": BlockElementHandler("header-one"), }, "to_database_format": {"block_map": {"header-one": "h1"}}, }, ) features.register_editor_plugin( "draftail", "h2", draftail_features.BlockFeature( { "icon": "h2", "type": "header-two", "description": _("Heading 2"), } ), ) features.register_converter_rule( "contentstate", "h2", { "from_database_format": { "h2": BlockElementHandler("header-two"), }, "to_database_format": {"block_map": {"header-two": "h2"}}, }, ) features.register_editor_plugin( "draftail", "h3", draftail_features.BlockFeature( { "icon": "h3", "type": "header-three", "description": _("Heading 3"), } ), ) features.register_converter_rule( "contentstate", "h3", { "from_database_format": { "h3": BlockElementHandler("header-three"), }, "to_database_format": {"block_map": {"header-three": "h3"}}, }, ) features.register_editor_plugin( "draftail", "h4", draftail_features.BlockFeature( { "icon": "h4", "type": "header-four", "description": _("Heading 4"), } ), ) features.register_converter_rule( "contentstate", "h4", { "from_database_format": { "h4": BlockElementHandler("header-four"), }, "to_database_format": {"block_map": {"header-four": "h4"}}, }, ) features.register_editor_plugin( "draftail", "h5", draftail_features.BlockFeature( { "icon": "h5", "type": "header-five", "description": _("Heading 5"), } ), ) features.register_converter_rule( "contentstate", "h5", { "from_database_format": { "h5": BlockElementHandler("header-five"), }, "to_database_format": {"block_map": {"header-five": "h5"}}, }, ) features.register_editor_plugin( "draftail", "h6", draftail_features.BlockFeature( { "icon": "h6", "type": "header-six", "description": _("Heading 6"), } ), ) features.register_converter_rule( "contentstate", "h6", { "from_database_format": { "h6": BlockElementHandler("header-six"), }, "to_database_format": {"block_map": {"header-six": "h6"}}, }, ) features.register_editor_plugin( "draftail", "ul", draftail_features.BlockFeature( { "type": "unordered-list-item", "icon": "list-ul", "description": _("Bulleted list"), } ), ) features.register_converter_rule( "contentstate", "ul", { "from_database_format": { "ul": ListElementHandler("unordered-list-item"), "li": ListItemElementHandler(), }, "to_database_format": { "block_map": {"unordered-list-item": {"element": "li", "wrapper": "ul"}} }, }, ) features.register_editor_plugin( "draftail", "ol", draftail_features.BlockFeature( { "type": "ordered-list-item", "icon": "list-ol", "description": _("Numbered list"), } ), ) features.register_converter_rule( "contentstate", "ol", { "from_database_format": { "ol": ListElementHandler("ordered-list-item"), "li": ListItemElementHandler(), }, "to_database_format": { "block_map": {"ordered-list-item": {"element": "li", "wrapper": "ol"}} }, }, ) features.register_editor_plugin( "draftail", "blockquote", draftail_features.BlockFeature( { "type": "blockquote", "icon": "openquote", "description": _("Blockquote"), } ), ) features.register_converter_rule( "contentstate", "blockquote", { "from_database_format": { "blockquote": BlockElementHandler("blockquote"), }, "to_database_format": {"block_map": {"blockquote": "blockquote"}}, }, ) features.register_editor_plugin( "draftail", "bold", draftail_features.InlineStyleFeature( { "type": "BOLD", "icon": "bold", "description": _("Bold"), } ), ) features.register_converter_rule( "contentstate", "bold", { "from_database_format": { "b": InlineStyleElementHandler("BOLD"), "strong": InlineStyleElementHandler("BOLD"), }, "to_database_format": {"style_map": {"BOLD": "b"}}, }, ) features.register_editor_plugin( "draftail", "italic", draftail_features.InlineStyleFeature( { "type": "ITALIC", "icon": "italic", "description": _("Italic"), } ), ) features.register_converter_rule( "contentstate", "italic", { "from_database_format": { "i": InlineStyleElementHandler("ITALIC"), "em": InlineStyleElementHandler("ITALIC"), }, "to_database_format": {"style_map": {"ITALIC": "i"}}, }, ) features.register_editor_plugin( "draftail", "link", draftail_features.EntityFeature( { "type": "LINK", "icon": "link", "description": _("Link"), # We want to enforce constraints on which links can be pasted into rich text. # Keep only the attributes Wagtail needs. "attributes": ["url", "id", "parentId"], "allowlist": { # Keep pasted links with http/https protocol, and not-pasted links (href = undefined). "href": "^(http:|https:|undefined$)", }, "chooserUrls": { "pageChooser": reverse_lazy("wagtailadmin_choose_page"), "externalLinkChooser": reverse_lazy( "wagtailadmin_choose_page_external_link" ), "emailLinkChooser": reverse_lazy( "wagtailadmin_choose_page_email_link" ), "phoneLinkChooser": reverse_lazy( "wagtailadmin_choose_page_phone_link" ), "anchorLinkChooser": reverse_lazy( "wagtailadmin_choose_page_anchor_link" ), }, }, js=[ "wagtailadmin/js/page-chooser-modal.js", ], ), ) features.register_converter_rule( "contentstate", "link", { "from_database_format": { "a[href]": ExternalLinkElementHandler("LINK"), 'a[linktype="page"]': PageLinkElementHandler("LINK"), }, "to_database_format": {"entity_decorators": {"LINK": link_entity}}, }, ) features.register_editor_plugin( "draftail", "superscript", draftail_features.InlineStyleFeature( { "type": "SUPERSCRIPT", "icon": "superscript", "description": _("Superscript"), } ), ) features.register_converter_rule( "contentstate", "superscript", { "from_database_format": { "sup": InlineStyleElementHandler("SUPERSCRIPT"), }, "to_database_format": {"style_map": {"SUPERSCRIPT": "sup"}}, }, ) features.register_editor_plugin( "draftail", "subscript", draftail_features.InlineStyleFeature( { "type": "SUBSCRIPT", "icon": "subscript", "description": _("Subscript"), } ), ) features.register_converter_rule( "contentstate", "subscript", { "from_database_format": { "sub": InlineStyleElementHandler("SUBSCRIPT"), }, "to_database_format": {"style_map": {"SUBSCRIPT": "sub"}}, }, ) features.register_editor_plugin( "draftail", "strikethrough", draftail_features.InlineStyleFeature( { "type": "STRIKETHROUGH", "icon": "strikethrough", "description": _("Strikethrough"), } ), ) features.register_converter_rule( "contentstate", "strikethrough", { "from_database_format": { "s": InlineStyleElementHandler("STRIKETHROUGH"), }, "to_database_format": {"style_map": {"STRIKETHROUGH": "s"}}, }, ) features.register_editor_plugin( "draftail", "code", draftail_features.InlineStyleFeature( { "type": "CODE", "icon": "code", "description": _("Code"), } ), ) features.register_converter_rule( "contentstate", "code", { "from_database_format": { "code": InlineStyleElementHandler("CODE"), }, "to_database_format": {"style_map": {"CODE": "code"}}, }, ) class LockedPagesMenuItem(MenuItem): def is_shown(self, request): return page_permission_policy.user_has_permission(request.user, "unlock") class WorkflowReportMenuItem(MenuItem): def is_shown(self, request): return getattr( settings, "WAGTAIL_WORKFLOW_ENABLED", True ) and page_permission_policy.user_has_any_permission( request.user, ["add", "change", "publish"] ) class SiteHistoryReportMenuItem(MenuItem): def is_shown(self, request): return page_permission_policy.explorable_root_instance(request.user) is not None class AgingPagesReportMenuItem(MenuItem): def is_shown(self, request): return getattr( settings, "WAGTAIL_AGING_PAGES_ENABLED", True ) and page_permission_policy.user_has_any_permission( request.user, ["add", "change", "publish"] ) class PageTypesReportMenuItem(MenuItem): def is_shown(self, request): return page_permission_policy.user_has_any_permission( request.user, ["add", "change", "publish"] ) @hooks.register("register_reports_menu_item") def register_locked_pages_menu_item(): return LockedPagesMenuItem( _("Locked pages"), reverse("wagtailadmin_reports:locked_pages"), name="locked-pages", icon_name="lock", order=700, ) @hooks.register("register_reports_menu_item") def register_workflow_report_menu_item(): return WorkflowReportMenuItem( _("Workflows"), reverse("wagtailadmin_reports:workflow"), name="workflows", icon_name="tasks", order=800, ) @hooks.register("register_reports_menu_item") def register_workflow_tasks_report_menu_item(): return WorkflowReportMenuItem( _("Workflow tasks"), reverse("wagtailadmin_reports:workflow_tasks"), name="workflow-tasks", icon_name="thumbtack", order=900, ) @hooks.register("register_reports_menu_item") def register_site_history_report_menu_item(): return SiteHistoryReportMenuItem( _("Site history"), reverse("wagtailadmin_reports:site_history"), name="site-history", icon_name="history", order=1000, ) @hooks.register("register_reports_menu_item") def register_aging_pages_report_menu_item(): return AgingPagesReportMenuItem( _("Aging pages"), reverse("wagtailadmin_reports:aging_pages"), name="aging-pages", icon_name="time", order=1100, ) @hooks.register("register_reports_menu_item") def register_page_types_report_menu_item(): return PageTypesReportMenuItem( _("Page types usage"), reverse("wagtailadmin_reports:page_types_usage"), name="page-types-usage", icon_name="doc-empty-inverse", order=1200, ) @hooks.register("register_admin_menu_item") def register_reports_menu(): return SubmenuMenuItem( _("Reports"), reports_menu, name="reports", icon_name="site", order=9000, ) @hooks.register("register_help_menu_item") def register_whats_new_in_wagtail_version_menu_item(): version = get_main_version(include_patch=False) return DismissibleMenuItem( _("What's new in Wagtail %(version)s") % {"version": version}, wagtail_feature_release_whats_new_link(), icon_name="help", order=1000, attrs={"target": "_blank", "rel": "noreferrer"}, name=f"whats-new-in-wagtail-{version}", ) @hooks.register("register_help_menu_item") def register_editors_guide_menu_item(): return DismissibleMenuItem( _("Editor Guide"), wagtail_feature_release_editor_guide_link(), icon_name="help", order=1100, attrs={"target": "_blank", "rel": "noreferrer"}, name="editor-guide", ) @hooks.register("register_help_menu_item") def register_keyboard_shortcuts_menu_item(): """ Triggers the keyboard shortcuts dialog to open when clicked while preventing the default link click action. """ return MenuItem( _("Shortcuts"), icon_name="keyboard", order=1200, attrs={ "role": "button", # Ensure screen readers announce this as a button "data-a11y-dialog-show": "keyboard-shortcuts-dialog", "data-action": "w-action#noop:prevent:stop", "data-controller": "w-action", }, name="keyboard-shortcuts-trigger", url="#", ) @hooks.register("register_admin_menu_item") def register_help_menu(): return DismissibleSubmenuMenuItem( _("Help"), help_menu, name="help", icon_name="help", order=11000, ) @hooks.register("register_icons") def register_icons(icons): for icon in [ "arrow-down.svg", "arrow-right-full.svg", "arrow-left.svg", "arrow-right.svg", "arrow-up.svg", "bars.svg", "bin.svg", "bold.svg", "breadcrumb-expand.svg", "calendar.svg", "calendar-alt.svg", "calendar-check.svg", "check.svg", "circle-check.svg", "circle-plus.svg", "circle-xmark.svg", "clipboard-list.svg", "code.svg", "cog.svg", "cogs.svg", "copy.svg", "collapse-down.svg", "collapse-up.svg", "comment.svg", "comment-add.svg", "comment-add-reversed.svg", "cross.svg", "crosshairs.svg", "cut.svg", "date.svg", "decimal.svg", "desktop.svg", "doc-empty-inverse.svg", "doc-empty.svg", "doc-full-inverse.svg", "doc-full.svg", "dots-horizontal.svg", "download.svg", "draft.svg", "edit.svg", "expand-right.svg", "error.svg", "folder-inverse.svg", "folder-open-1.svg", "folder-open-inverse.svg", "folder.svg", "form.svg", "glasses.svg", "globe.svg", "grip.svg", "group.svg", "h1.svg", "h2.svg", "h3.svg", "h4.svg", "h5.svg", "h6.svg", "help.svg", "history.svg", "home.svg", "image.svg", "info-circle.svg", "italic.svg", "key.svg", "keyboard.svg", "link.svg", "link-external.svg", "list-ol.svg", "list-ul.svg", "lock-open.svg", "lock.svg", "login.svg", "logout.svg", "mail.svg", "media.svg", "minus.svg", "mobile-alt.svg", "no-view.svg", "openquote.svg", "order-down.svg", "order-up.svg", "order.svg", "password.svg", "pick.svg", "pilcrow.svg", "placeholder.svg", "plus-inverse.svg", "plus.svg", "radio-empty.svg", "radio-full.svg", "redirect.svg", "regex.svg", "resubmit.svg", "rotate.svg", "search.svg", "site.svg", "sliders.svg", "snippet.svg", "spinner.svg", "strikethrough.svg", "success.svg", "subscript.svg", "superscript.svg", "table.svg", "tablet-alt.svg", "tag.svg", "tasks.svg", "thumbtack.svg", "thumbtack-crossed.svg", "tick-inverse.svg", "time.svg", "title.svg", "upload.svg", "user.svg", "view.svg", "wagtail.svg", "warning.svg", ]: icons.append(f"wagtailadmin/icons/{icon}") return icons @hooks.register("construct_homepage_summary_items") def add_pages_summary_item(request, items): items.insert(0, PagesSummaryItem(request)) class PageAdminURLFinder: def __init__(self, user): self.user = user def get_edit_url(self, instance): if self.user and not instance.permissions_for_user(self.user).can_edit(): return None else: return reverse("wagtailadmin_pages:edit", args=(instance.pk,)) register_admin_url_finder(Page, PageAdminURLFinder) class CollectionAdminURLFinder(ModelAdminURLFinder): permission_policy = collection_permission_policy edit_url_name = "wagtailadmin_collections:edit" register_admin_url_finder(Collection, CollectionAdminURLFinder) class WorkflowAdminURLFinder(ModelAdminURLFinder): permission_policy = workflow_permission_policy edit_url_name = "wagtailadmin_workflows:edit" register_admin_url_finder(Workflow, WorkflowAdminURLFinder) class WorkflowTaskAdminURLFinder(ModelAdminURLFinder): permission_policy = task_permission_policy edit_url_name = "wagtailadmin_workflows:edit_task" register_admin_url_finder(Task, WorkflowTaskAdminURLFinder) for action_class in [ DeleteBulkAction, MoveBulkAction, PublishBulkAction, UnpublishBulkAction, ]: hooks.register("register_bulk_action", action_class)