"""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