diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index 0d785fc853..21fb77dfdd 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -200,6 +200,9 @@ def get_assignments(doc) -> list[dict]: @frappe.whitelist() def bulk_apply(doctype: str, docnames: str | list[str]): + if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"): + frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError) + docnames = frappe.parse_json(docnames) background = len(docnames) > 5 diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 8dcb413e78..bdc97b4993 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -53,8 +53,8 @@ "search_bar", "notifications", "list_settings_section", - "list_sidebar", "bulk_actions", + "list_sidebar", "view_switcher", "form_settings_section", "form_sidebar", @@ -598,7 +598,7 @@ "unique": 1 }, { - "description": "\n Click here to learn about token-based authentication\n", + "description": "\n Click here to learn about token-based authentication\n", "fieldname": "generate_keys", "fieldtype": "Button", "label": "Generate Keys", @@ -771,13 +771,13 @@ "default": "1", "fieldname": "search_bar", "fieldtype": "Check", - "label": "Search Bar" + "label": "Show search bar" }, { "default": "1", "fieldname": "notifications", "fieldtype": "Check", - "label": "Notifications" + "label": "Allow notifications" }, { "collapsible": 1, @@ -789,19 +789,20 @@ "default": "1", "fieldname": "list_sidebar", "fieldtype": "Check", - "label": "Sidebar" + "label": "Show sidebar" }, { "default": "1", "fieldname": "bulk_actions", "fieldtype": "Check", - "label": "Bulk Actions" + "label": "Allow bulk actions", + "permlevel": 1 }, { "default": "1", "fieldname": "view_switcher", "fieldtype": "Check", - "label": "View Switcher" + "label": "Show view switcher" }, { "collapsible": 1, @@ -813,25 +814,25 @@ "default": "1", "fieldname": "form_sidebar", "fieldtype": "Check", - "label": "Sidebar" + "label": "Show sidebar" }, { "default": "1", "fieldname": "timeline", "fieldtype": "Check", - "label": "Timeline" + "label": "Show timeline" }, { "default": "1", "fieldname": "dashboard", "fieldtype": "Check", - "label": "Dashboard" + "label": "Show dashboard" }, { "default": "0", "fieldname": "show_absolute_datetime_in_timeline", "fieldtype": "Check", - "label": "Show Absolute Datetime in Timeline" + "label": "Show absolute datetime in timeline" }, { "fieldname": "sessions_tab", @@ -850,7 +851,7 @@ "default": "0", "fieldname": "form_navigation_buttons", "fieldtype": "Check", - "label": "Navigation Buttons" + "label": "Show navigation buttons" } ], "icon": "fa fa-user", @@ -904,7 +905,7 @@ } ], "make_attachments_public": 1, - "modified": "2026-01-12 16:04:21.542524", + "modified": "2026-02-22 13:44:36.317890", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index 28b4415ddf..75d9f8690d 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -54,7 +54,10 @@ def submit_cancel_or_update_docs( action: str = "submit", data: str | dict[str, Any] | None = None, task_id: str | None = None, -): +) -> list[str] | None: + if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"): + frappe.throw(_("You are not allowed to perform bulk actions."), frappe.PermissionError) + if isinstance(docnames, str): docnames = frappe.parse_json(docnames) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 04f1053ead..da874a06e7 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE import frappe +from frappe import _ from frappe.model.document import Document from frappe.query_builder import DocType from frappe.utils import unique @@ -43,6 +44,10 @@ def add_tag(tag: str, dt: str, dn: str, color: str | None = None): @frappe.whitelist() def add_tags(tags: str | list[str], dt: str, docs: str | list[str], color: str | None = None): "adds a new tag to a record, and creates the Tag master" + + if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"): + frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError) + tags = frappe.parse_json(tags) docs = frappe.parse_json(docs) for doc in docs: diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 2af0a786a3..c84dc29784 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -141,9 +141,11 @@ def add(args: dict[str, Any] | None = None, *, ignore_permissions: bool | int = @frappe.whitelist() -def add_multiple(args: dict[str, Any] | None = None): - if not args: - args = frappe.local.form_dict +def add_multiple() -> None: + if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"): + frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError) + + args = frappe.local.form_dict docname_list = json.loads(args["name"]) @@ -181,6 +183,9 @@ def remove(doctype: str, name: str | int, assign_to: str, ignore_permissions: bo @frappe.whitelist() def remove_multiple(doctype: str, names: str, ignore_permissions: bool | int = False): + if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"): + frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError) + docname_list = json.loads(names) for name in docname_list: diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index c9c42cd2f1..14cc6a8756 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -597,6 +597,9 @@ def parse_field(field: str) -> tuple[str | None, str]: @frappe.whitelist(methods=["POST", "DELETE"]) def delete_items(): """delete selected items""" + if not (frappe.get_cached_value("User", frappe.session.user, "bulk_actions")): + frappe.throw(_("You are not allowed to perform bulk actions."), frappe.PermissionError) + import json items = sorted(json.loads(frappe.form_dict.get("items")), reverse=True) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 4fcd01514b..d9c6514556 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -2533,6 +2533,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { // Copy to clipboard actions_menu_items.push(copy_to_clipboard()); + if (!frappe.boot.desk_settings?.bulk_actions) return actions_menu_items; + // bulk edit if (has_editable_fields(doctype) && is_bulk_edit_allowed(doctype)) { actions_menu_items.push(bulk_edit()); diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index 39b0dc4e47..3a3770bce1 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -36,6 +36,9 @@ def download_multi_pdf( """ Calls _download_multi_pdf with the given parameters and returns the response """ + if not (frappe.get_cached_value("User", frappe.session.user, "bulk_actions")): + frappe.throw(_("You are not allowed to perform bulk actions."), frappe.PermissionError) + return _download_multi_pdf(doctype, name, format, no_letterhead, letterhead, options) @@ -51,6 +54,9 @@ def download_multi_pdf_async( """ Calls _download_multi_pdf with the given parameters in a background job, returns task ID """ + if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"): + frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError) + task_id = str(uuid.uuid4()) if isinstance(doctype, dict): doc_count = sum([len(doctype[dt]) for dt in doctype])