diff --git a/docs/extending/custom_bulk_actions.rst b/docs/extending/custom_bulk_actions.rst new file mode 100644 index 0000000000..16cfe7d9dc --- /dev/null +++ b/docs/extending/custom_bulk_actions.rst @@ -0,0 +1,243 @@ +.. _custom_bulk_actions: + +Adding custom bulk actions +========================================== + +This document describes how to add custom bulk actions to different listings. + + +Registering a custom bulk action +-------------------------------- + + .. code-block:: python + + from wagtail.admin.views.bulk_action import BulkAction + from wagtail.core import hooks + + + @hooks.register('register_bulk_action') + class CustomDeleteBulkAction(BulkAction): + display_name = _("Delete") + aria_label = _("Delete objects") + action_type = "delete" + template_name = "/path/to/confirm_bulk_delete.html" + models = [...] + + @classmethod + def execute_action(cls, objects, **kwargs): + for obj in objects: + do_something(obj) + return num_parent_objects, num_child_objects # return the count of updated objects + +The attributes are as follows: + +- ``display_name`` - The label that will be displayed on the button in the user interface +- ``aria_label`` - The ``aria-label`` attribute that will be applied to the button in the user interface +- ``action_type`` - A unique identifier for the action. Will be required in the url for bulk actions +- ``template_name`` - The path to the confirmation template +- ``models`` - A list of models on which the bulk action can act +- ``action_priority`` (optional) - A number that is used to determine the placement of the button in the list of buttons +- ``classes`` (optional) - A set of CSS classnames that will be used on the button in the user interface + +An example for a confirmation template is as follows: + +.. code-block:: django + + + + {% extends 'wagtailadmin/bulk_actions/confirmation/base.html' %} + {% load i18n wagtailadmin_tags %} + + {% block titletag %}{% blocktrans count counter=items|length %}Delete 1 item{% plural %}Delete {{ counter }} items{% endblocktrans %}{% endblock %} + + {% block header %} + {% trans "Delete" as del_str %} + {% include "wagtailadmin/shared/header.html" with title=del_str icon="doc-empty-inverse" %} + {% endblock header %} + + {% block items_with_access %} + {% if items %} +

{% trans "Are you sure you want to delete these items?" %}

+ + {% endif %} + {% endblock items_with_access %} + + {% block items_with_no_access %} + + {% blocktrans asvar no_access_msg count counter=items_with_no_access|length %}You don't have permission to delete this item{% plural %}You don't have permission to delete these items{% endblocktrans %} + {% include './list_items_with_no_access.html' with items=items_with_no_access no_access_msg=no_access_msg %} + + {% endblock items_with_no_access %} + + {% block form_section %} + {% if items %} + {% trans 'Yes, delete' as action_button_text %} + {% trans "No, don't delete" as no_action_button_text %} + {% include 'wagtailadmin/bulk_actions/confirmation/form.html' with action_button_class="serious" %} + {% else %} + {% include 'wagtailadmin/bulk_actions/confirmation/go_back.html' %} + {% endif %} + {% endblock form_section %} + + +.. code-block:: django + + + {% extends 'wagtailadmin/bulk_actions/confirmation/list_items_with_no_access.html' %} + {% load i18n %} + + {% block per_item %} + {% if item.can_edit %} + {{ item.item.title }} + {% else %} + {{ item.item.title }} + {% endif %} + {% endblock per_item %} + + +The ``execute_action`` classmethod is the only method that must be overridden for the bulk action to work properly. It +takes a list of objects as the only required argument, and a bunch of keyword arguments that can be supplied by overriding +the ``get_execution_context`` method. For example. + + .. code-block:: python + + @classmethod + def execute_action(cls, objects, **kwargs): + # the kwargs here is the output of the get_execution_context method + user = kwargs.get('user', None) + num_parent_objects, num_child_objects = 0, 0 + # you could run the action per object or run them in bulk using django's bulk update and delete methods + for obj in objects: + num_child_objects += obj.get_children().count() + num_parent_objects += 1 + obj.delete(user=user) + num_parent_objects += 1 + return num_parent_objects, num_child_objects + + +The ``get_execution_context`` method can be overridden to provide context to the ``execute_action`` + + .. code-block:: python + + def get_execution_context(self): + return { + 'user': self.request.user + } + + +The ``get_context_data`` method can be overridden to pass additional context to the confirmation template. + + .. code-block:: python + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['new_key'] = some_value + return context + + +The ``check_perm`` method can be overridden to check if an object has some permission or not. objects for which the ``check_perm`` +returns ``False`` will be available in the context under the key ``'items_with_no_access'``. + + .. code-block:: python + + def check_perm(self, obj): + return obj.has_perm('some_perm') # returns True or False + + +The success message shown on the admin can be customised by overriding the ``get_success_message`` method. + + .. code-block:: python + + def get_success_message(self, num_parent_objects, num_child_objects): + return _("{} objects, including {} child objects have been updated".format(num_parent_objects, num_child_objects)) + + + +Adding bulk actions to the page explorer +---------------------------------------- + +When creating a custom bulk action class for pages, subclass from ``wagtail.admin.views.pages.bulk_actions.page_bulk_action.PageBulkAction`` +instead of ``wagtail.admin.views.bulk_action.BulkAction`` + +Basic example +~~~~~~~~~~~~~ + + .. code-block:: python + + from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction + from wagtail.core import hooks + + + @hooks.register('register_bulk_action') + class CustomPageBulkAction(PageBulkAction): + ... + + + +Adding bulk actions to the Images listing +----------------------------------------- + +When creating a custom bulk action class for images, subclass from ``wagtail.images.views.bulk_actions.image_bulk_action.ImageBulkAction`` +instead of ``wagtail.admin.views.bulk_action.BulkAction`` + +Basic example +~~~~~~~~~~~~~ + + .. code-block:: python + + from wagtail.images.views.bulk_actions.image_bulk_action import ImageBulkAction + from wagtail.core import hooks + + + @hooks.register('register_bulk_action') + class CustomImageBulkAction(ImageBulkAction): + ... + + + +Adding bulk actions to the documents listing +-------------------------------------------- + +When creating a custom bulk action class for documents, subclass from ``wagtail.documents.views.bulk_actions.document_bulk_action.DocumentBulkAction`` +instead of ``wagtail.admin.views.bulk_action.BulkAction`` + +Basic example +~~~~~~~~~~~~~ + + .. code-block:: python + + from wagtail.documents.views.bulk_actions.document_bulk_action import DocumentBulkAction + from wagtail.core import hooks + + + @hooks.register('register_bulk_action') + class CustomDocumentBulkAction(DocumentBulkAction): + ... + + + +Adding bulk actions to the user listing +--------------------------------------- + +When creating a custom bulk action class for users, subclass from ``wagtail.users.views.bulk_actions.user_bulk_action.UserBulkAction`` +instead of ``wagtail.admin.views.bulk_action.BulkAction`` + +Basic example +~~~~~~~~~~~~~ + + .. code-block:: python + + from wagtail.users.views.bulk_actions.user_bulk_action import UserBulkAction + from wagtail.core import hooks + + + @hooks.register('register_bulk_action') + class CustomUserBulkAction(UserBulkAction): + ... + diff --git a/docs/extending/index.rst b/docs/extending/index.rst index 32077a3e87..6c6b7d1451 100644 --- a/docs/extending/index.rst +++ b/docs/extending/index.rst @@ -19,3 +19,4 @@ This section describes the various mechanisms that can be used to integrate your rich_text_internals extending_draftail extending_hallo + custom_bulk_actions diff --git a/docs/reference/hooks.rst b/docs/reference/hooks.rst index 3663c32158..24af9fa7c1 100644 --- a/docs/reference/hooks.rst +++ b/docs/reference/hooks.rst @@ -433,7 +433,6 @@ More details about the options that are available can be found at :doc:`/extendi return queryset - Editor interface ---------------- @@ -1390,6 +1389,81 @@ Hooks for working with registered Snippets. def remove_snippet_listing_button_item(buttons, snippet, user, context=None): buttons.pop() # Removes the 'delete' button + +Bulk actions +------------ + +Hooks for registering and customising bulk actions. See :ref:`here ` on how to write custom bulk actions. + + +.. _register_bulk_action: + +``register_bulk_action`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Registers a new bulk action to add to the list of bulk actions in the explorer + + This hook must be registered with a sub-class of ``BulkAction`` . For example: + + .. code-block:: python + + from wagtail.admin.views.bulk_action import BulkAction + from wagtail.core import hooks + + + @hooks.register('register_bulk_action') + class CustomBulkAction(BulkAction): + display_name = _("Custom Action") + action_type = "action" + aria_label = _("Do custom action") + template_name = "/path/to/template" + models = [...] # list of models the action should execute upon + + + @classmethod + def execute_action(cls, objects, **kwargs): + for object in objects: + do_something(object) + return num_parent_objects, num_child_objects # return the count of updated objects + + +.. _before_bulk_action: + +``before_bulk_action`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Do something right before a bulk action is executed (before the ``execute_action`` method is called) + + This hook can be used to return an HTTP response. For example: + + .. code-block:: python + + from wagtail.core import hooks + + @hooks.register('before_bulk_action) + def hook_func(request, action_type, objects, action_class_instance): + ... + + +.. _after_bulk_action: + +``after_bulk_action`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Do something right after a bulk action is executed (after the ``execute_action`` method is called) + + This hook can be used to return an HTTP response. For example: + + .. code-block:: python + + from wagtail.core import hooks + + @hooks.register('after_bulk_action) + def hook_func(request, action_type, objects, action_class_instance): + ... + + + Audit log ---------