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

348 lines
12 KiB
Python
Raw Permalink Normal View History

2025-07-25 21:32:16 +10:00
"""Handles rendering of the list of actions in the footer of the page create/edit views."""
from django.conf import settings
from django.forms import Media
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from wagtail import hooks
from wagtail.admin.ui.components import Component
class ActionMenuItem(Component):
"""Defines an item in the actions drop-up on the page creation/edit view"""
order = 100 # default order index if one is not specified on init
template_name = "wagtailadmin/pages/action_menu/menu_item.html"
label = ""
name = None
classname = ""
icon_name = ""
def __init__(self, order=None):
if order is not None:
self.order = order
def get_user_page_permissions_tester(self, context):
if "user_page_permissions_tester" in context:
return context["user_page_permissions_tester"]
return context["page"].permissions_for_user(context["request"].user)
def is_shown(self, context):
"""
Whether this action should be shown on this request; permission checks etc should go here.
By default, actions are shown for unlocked pages, hidden for locked pages
context = dictionary containing at least:
'request' = the current request object
'view' = 'create', 'edit' or 'revisions_revert'
'page' (if view = 'edit' or 'revisions_revert') = the page being edited
'parent_page' (if view = 'create') = the parent page of the page being created
'lock' = a Lock object if the page is locked, otherwise None
'locked_for_user' = True if the lock prevents the current user from editing the page
may also contain:
'user_page_permissions_tester' = a PagePermissionTester for the current user and page
"""
return context["view"] == "create" or not context["locked_for_user"]
def get_context_data(self, parent_context):
"""Defines context for the template, overridable to use more data"""
context = parent_context.copy()
url = self.get_url(parent_context)
context.update(
{
"label": self.label,
"url": url,
"name": self.name,
"classname": self.classname,
"icon_name": self.icon_name,
"request": parent_context["request"],
}
)
return context
def get_url(self, parent_context):
return None
class PublishMenuItem(ActionMenuItem):
label = _("Publish")
name = "action-publish"
template_name = "wagtailadmin/pages/action_menu/publish.html"
icon_name = "upload"
def is_shown(self, context):
if context["view"] == "create":
return (
context["parent_page"]
.permissions_for_user(context["request"].user)
.can_publish_subpage()
)
else: # view == 'edit' or 'revisions_revert'
perms_tester = self.get_user_page_permissions_tester(context)
return not context["locked_for_user"] and perms_tester.can_publish()
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
page = context.get("page")
context["is_scheduled"] = page and page.go_live_at
context["is_revision"] = context["view"] == "revisions_revert"
return context
class SubmitForModerationMenuItem(ActionMenuItem):
label = _("Submit for moderation")
name = "action-submit"
icon_name = "resubmit"
def is_shown(self, context):
if not getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True):
return False
if context["view"] == "create":
return context["parent_page"].has_workflow
if context["view"] == "edit":
perms_tester = self.get_user_page_permissions_tester(context)
return (
perms_tester.can_submit_for_moderation()
and not context["locked_for_user"]
)
# context == revisions_revert
return False
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
page = context.get("page")
workflow_state = page.current_workflow_state if page else None
if (
workflow_state
and workflow_state.status == workflow_state.STATUS_NEEDS_CHANGES
):
context["label"] = _("Resubmit to %(task_name)s") % {
"task_name": workflow_state.current_task_state.task.name
}
elif page:
workflow = page.get_workflow()
if workflow:
context["label"] = _("Submit to %(workflow_name)s") % {
"workflow_name": workflow.name
}
return context
class WorkflowMenuItem(ActionMenuItem):
template_name = "wagtailadmin/pages/action_menu/workflow_menu_item.html"
def __init__(self, name, label, launch_modal, *args, **kwargs):
self.name = name
self.label = label
self.launch_modal = launch_modal
if kwargs.get("icon_name"):
self.icon_name = kwargs.pop("icon_name")
super().__init__(*args, **kwargs)
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
context["launch_modal"] = self.launch_modal
context["current_task_state"] = context["page"].current_workflow_task_state
return context
def is_shown(self, context):
if context["view"] == "edit":
return not context["locked_for_user"]
class RestartWorkflowMenuItem(ActionMenuItem):
label = _("Restart workflow ")
name = "action-restart-workflow"
classname = "button--icon-flipped"
icon_name = "login"
def is_shown(self, context):
if not getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True):
return False
elif context["view"] == "edit":
workflow_state = context["page"].current_workflow_state
perms_tester = self.get_user_page_permissions_tester(context)
return (
perms_tester.can_submit_for_moderation()
and not context["locked_for_user"]
and workflow_state
and workflow_state.user_can_cancel(context["request"].user)
)
else:
return False
class CancelWorkflowMenuItem(ActionMenuItem):
label = _("Cancel workflow ")
name = "action-cancel-workflow"
icon_name = "error"
def is_shown(self, context):
if context["view"] == "edit":
workflow_state = context["page"].current_workflow_state
return workflow_state and workflow_state.user_can_cancel(
context["request"].user
)
return False
class UnpublishMenuItem(ActionMenuItem):
label = _("Unpublish")
name = "action-unpublish"
icon_name = "download"
def is_shown(self, context):
if context["view"] == "edit":
perms_tester = self.get_user_page_permissions_tester(context)
return not context["locked_for_user"] and perms_tester.can_unpublish()
def get_url(self, context):
return reverse("wagtailadmin_pages:unpublish", args=(context["page"].id,))
class SaveDraftMenuItem(ActionMenuItem):
name = "action-save-draft"
label = _("Save Draft")
template_name = "wagtailadmin/pages/action_menu/save_draft.html"
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
context["is_revision"] = context["view"] == "revisions_revert"
return context
class PageLockedMenuItem(ActionMenuItem):
name = "action-page-locked"
label = _("Page locked")
template_name = "wagtailadmin/pages/action_menu/page_locked.html"
def is_shown(self, context):
return "page" in context and context["locked_for_user"]
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
context["is_revision"] = context["view"] == "revisions_revert"
return context
BASE_PAGE_ACTION_MENU_ITEMS = None
def _get_base_page_action_menu_items():
"""
Retrieve the global list of menu items for the page action menu,
which may then be customized on a per-request basis
"""
global BASE_PAGE_ACTION_MENU_ITEMS
if BASE_PAGE_ACTION_MENU_ITEMS is None:
BASE_PAGE_ACTION_MENU_ITEMS = [
SaveDraftMenuItem(order=0),
UnpublishMenuItem(order=20),
PublishMenuItem(order=30),
CancelWorkflowMenuItem(order=40),
RestartWorkflowMenuItem(order=50),
SubmitForModerationMenuItem(order=60),
PageLockedMenuItem(order=10000),
]
for hook in hooks.get_hooks("register_page_action_menu_item"):
action_menu_item = hook()
if action_menu_item:
BASE_PAGE_ACTION_MENU_ITEMS.append(action_menu_item)
return BASE_PAGE_ACTION_MENU_ITEMS
class PageActionMenu:
template = "wagtailadmin/pages/action_menu/menu.html"
def __init__(self, request, **kwargs):
self.request = request
self.context = kwargs
self.context["request"] = request
page = self.context.get("page")
if page:
self.context["user_page_permissions_tester"] = page.permissions_for_user(
self.request.user
)
self.menu_items = []
if page:
task = page.current_workflow_task
current_workflow_state = page.current_workflow_state
is_final_task = (
current_workflow_state and current_workflow_state.is_at_final_task
)
if task:
actions = task.get_actions(page, request.user)
workflow_menu_items = []
for name, label, launch_modal in actions:
icon_name = "edit"
if name == "approve":
if is_final_task and not getattr(
settings,
"WAGTAIL_WORKFLOW_REQUIRE_REAPPROVAL_ON_EDIT",
False,
):
label = _("%(label)s and Publish") % {"label": label}
icon_name = "success"
item = WorkflowMenuItem(
name, label, launch_modal, icon_name=icon_name
)
if item.is_shown(self.context):
workflow_menu_items.append(item)
self.menu_items.extend(workflow_menu_items)
for menu_item in _get_base_page_action_menu_items():
if menu_item.is_shown(self.context):
self.menu_items.append(menu_item)
self.menu_items.sort(key=lambda item: item.order)
for hook in hooks.get_hooks("construct_page_action_menu"):
hook(self.menu_items, self.request, self.context)
try:
self.default_item = self.menu_items.pop(0)
except IndexError:
self.default_item = None
def render_html(self):
if not self.default_item:
return ""
rendered_menu_items = [
menu_item.render_html(self.context) for menu_item in self.menu_items
]
rendered_default_item = self.default_item.render_html(self.context)
return render_to_string(
self.template,
{
"default_menu_item": rendered_default_item,
"show_menu": bool(self.menu_items),
"rendered_menu_items": rendered_menu_items,
},
request=self.request,
)
@cached_property
def media(self):
media = self.default_item.media if self.default_item else Media()
for item in self.menu_items:
media += item.media
return media