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

353 lines
11 KiB
Python

"""Handles rendering of the list of actions in the footer of the snippet create/edit views."""
from functools import lru_cache
from django.conf import settings
from django.contrib.admin.utils import quote
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
from wagtail.models import DraftStateMixin, LockableMixin, WorkflowMixin
from wagtail.snippets.permissions import get_permission_name
class ActionMenuItem(Component):
"""Defines an item in the actions drop-up on the snippet creation/edit view"""
order = 100 # default order index if one is not specified on init
template_name = "wagtailsnippets/snippets/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 is_shown(self, context):
"""
Whether this action should be shown on this request; permission checks etc should go here.
request = the current request object
context = dictionary containing at least:
'view' = 'create' or 'edit'
'model' = the model of the snippet being created/edited
'instance' (if view = 'edit') = the snippet being edited
"""
return not context.get("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)
instance = parent_context.get("instance")
is_scheduled = (
parent_context.get("draftstate_enabled")
and instance
and instance.go_live_at
)
context.update(
{
"label": self.label,
"url": url,
"name": self.name,
"classname": self.classname,
"icon_name": self.icon_name,
"request": parent_context["request"],
"is_scheduled": is_scheduled,
"is_revision": parent_context["view"] == "revisions_revert",
}
)
return context
def get_url(self, parent_context):
return None
class PublishMenuItem(ActionMenuItem):
name = "action-publish"
label = _("Publish")
icon_name = "upload"
template_name = "wagtailsnippets/snippets/action_menu/publish.html"
def is_shown(self, context):
publish_permission = get_permission_name("publish", context["model"])
return context["request"].user.has_perm(publish_permission) and not context.get(
"locked_for_user"
)
class SubmitForModerationMenuItem(ActionMenuItem):
name = "action-submit"
label = _("Submit for moderation")
icon_name = "resubmit"
def is_shown(self, context):
if not getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True):
return False
if context.get("locked_for_user"):
return False
if context["view"] == "create":
return context["model"].get_default_workflow() is not None
return (
context["view"] == "edit"
and context["instance"].has_workflow
and not context["instance"].workflow_in_progress
)
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
instance = parent_context.get("instance")
workflow_state = instance.current_workflow_state if instance 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
}
else:
if instance:
workflow = instance.get_workflow()
else:
workflow = context["model"].get_default_workflow()
if workflow:
context["label"] = _("Submit to %(workflow_name)s") % {
"workflow_name": workflow.name
}
return context
class WorkflowMenuItem(ActionMenuItem):
template_name = "wagtailsnippets/snippets/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
return context
def is_shown(self, context):
return context["view"] == "edit" and not context.get("locked_for_user")
def get_url(self, parent_context):
instance = parent_context["instance"]
url_name = instance.snippet_viewset.get_url_name("collect_workflow_action_data")
return reverse(
url_name,
args=(
quote(instance.pk),
self.name,
instance.current_workflow_task_state.pk,
),
)
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
if context["view"] != "edit":
return False
workflow_state = context["instance"].current_workflow_state
return (
not context.get("locked_for_user")
and context["instance"].has_workflow
and not context["instance"].workflow_in_progress
and workflow_state
and workflow_state.user_can_cancel(context["request"].user)
)
class CancelWorkflowMenuItem(ActionMenuItem):
label = _("Cancel workflow ")
name = "action-cancel-workflow"
icon_name = "error"
def is_shown(self, context):
if context["view"] != "edit":
return False
workflow_state = context["instance"].current_workflow_state
return workflow_state and workflow_state.user_can_cancel(
context["request"].user
)
class UnpublishMenuItem(ActionMenuItem):
label = _("Unpublish")
name = "action-unpublish"
icon_name = "download"
def is_shown(self, context):
if context.get("locked_for_user"):
return False
if context["view"] == "edit" and context["instance"].live:
publish_permission = get_permission_name("publish", context["model"])
return context["request"].user.has_perm(publish_permission)
return False
def get_url(self, context):
instance = context["instance"]
url_name = instance.snippet_viewset.get_url_name("unpublish")
return reverse(url_name, args=[quote(instance.pk)])
class SaveMenuItem(ActionMenuItem):
name = "action-save"
label = _("Save")
icon_name = "download"
template_name = "wagtailsnippets/snippets/action_menu/save.html"
class LockedMenuItem(ActionMenuItem):
name = "action-locked"
label = _("Locked")
template_name = "wagtailsnippets/snippets/action_menu/locked.html"
def is_shown(self, context):
return context.get("locked_for_user")
@lru_cache(maxsize=None)
def get_base_snippet_action_menu_items(model):
"""
Retrieve the global list of menu items for the snippet action menu,
which may then be customised on a per-request basis
"""
menu_items = [
SaveMenuItem(order=0),
]
if issubclass(model, DraftStateMixin):
menu_items += [
UnpublishMenuItem(order=20),
PublishMenuItem(order=30),
]
if issubclass(model, WorkflowMixin):
menu_items += [
CancelWorkflowMenuItem(order=40),
RestartWorkflowMenuItem(order=50),
SubmitForModerationMenuItem(order=60),
]
if issubclass(model, LockableMixin):
menu_items.append(LockedMenuItem(order=10000))
for hook in hooks.get_hooks("register_snippet_action_menu_item"):
action_menu_item = hook(model)
if action_menu_item:
menu_items.append(action_menu_item)
return menu_items
class SnippetActionMenu:
template = "wagtailsnippets/snippets/action_menu/menu.html"
def __init__(self, request, **kwargs):
self.request = request
self.context = kwargs
self.context["request"] = request
instance = self.context.get("instance")
if instance:
self.context["model"] = type(instance)
self.context["draftstate_enabled"] = issubclass(
self.context["model"], DraftStateMixin
)
self.menu_items = [
menu_item
for menu_item in get_base_snippet_action_menu_items(self.context["model"])
if menu_item.is_shown(self.context)
]
if instance and isinstance(instance, WorkflowMixin):
task = instance.current_workflow_task
current_workflow_state = instance.current_workflow_state
is_final_task = (
current_workflow_state and current_workflow_state.is_at_final_task
)
if task:
actions = task.get_actions(instance, request.user)
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):
self.menu_items.append(item)
self.menu_items.sort(key=lambda item: item.order)
for hook in hooks.get_hooks("construct_snippet_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