From 29cf5dba0d630a662248bb2994d567c7ba30be9a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 4 Feb 2026 16:35:35 +0530 Subject: [PATCH 1/8] fix: don't show bulk action options in menu if disabled --- frappe/public/js/frappe/list/list_view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 919231fa5b..e4f72db703 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -2525,6 +2525,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()); From 2146221bee7e8f888bd4974b601bcadad2357158 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 4 Feb 2026 17:44:58 +0530 Subject: [PATCH 2/8] fix: check for bulk actions enabled on user before allowing bulk actions in whitelisted methods --- .../automation/doctype/assignment_rule/assignment_rule.py | 3 +++ frappe/desk/doctype/bulk_update/bulk_update.py | 3 +++ frappe/desk/form/assign_to.py | 6 ++++++ frappe/desk/reportview.py | 3 +++ frappe/utils/print_format.py | 3 +++ 5 files changed, 18 insertions(+) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index aa7b3551b5..bc1843287b 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, docnames): + 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/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index a2440412c3..fe511431be 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -47,6 +47,9 @@ class BulkUpdate(Document): @frappe.whitelist() def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None, task_id=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/form/assign_to.py b/frappe/desk/form/assign_to.py index bcebd4db0e..4b003b18f3 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -141,6 +141,9 @@ def add(args=None, *, ignore_permissions=False): @frappe.whitelist() def add_multiple(args=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 not args: args = frappe.local.form_dict @@ -180,6 +183,9 @@ def remove(doctype, name, assign_to, ignore_permissions=False): @frappe.whitelist() def remove_multiple(doctype, names, ignore_permissions=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 72b9d5bac0..512807b381 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -596,6 +596,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/utils/print_format.py b/frappe/utils/print_format.py index 8b75c14a6c..282b1ec1da 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -34,6 +34,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) From 26051145116a872ea063e3278b5a6bb58844bfce Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 5 Feb 2026 17:05:35 +0530 Subject: [PATCH 3/8] fix: add bulk action validation for tags --- frappe/desk/doctype/tag/tag.py | 5 +++++ frappe/utils/print_format.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 6235509a9d..f225da8601 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, dt, dn, color=None): @frappe.whitelist() def add_tags(tags, dt, docs, color=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/utils/print_format.py b/frappe/utils/print_format.py index 282b1ec1da..d47944cf59 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -52,6 +52,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]) From 400d4e555806413afdaaec3d2ec49e80be886ff8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 16 Feb 2026 23:48:13 +0530 Subject: [PATCH 4/8] fix: type hints for bulk apply --- frappe/automation/doctype/assignment_rule/assignment_rule.py | 2 +- frappe/desk/form/assign_to.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index bc1843287b..21fb77dfdd 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -199,7 +199,7 @@ def get_assignments(doc) -> list[dict]: @frappe.whitelist() -def bulk_apply(doctype, docnames): +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) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 4b003b18f3..38815d3c7a 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -182,7 +182,7 @@ def remove(doctype, name, assign_to, ignore_permissions=False): @frappe.whitelist() -def remove_multiple(doctype, names, ignore_permissions=False): +def remove_multiple(doctype: str, names: str | list[str], ignore_permissions: bool = False) -> 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) From cf842e021dc04a11c3dfc97667f13faae408b440 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 17 Feb 2026 00:08:25 +0530 Subject: [PATCH 5/8] fix: add type hints to bulk update --- frappe/desk/doctype/bulk_update/bulk_update.py | 8 +++++++- frappe/desk/doctype/tag/tag.py | 2 +- frappe/desk/form/assign_to.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index fe511431be..77a5cf290d 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -46,7 +46,13 @@ class BulkUpdate(Document): @frappe.whitelist() -def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None, task_id=None): +def submit_cancel_or_update_docs( + doctype: str, + docnames: list[str] | str, + action: str = "submit", + data: dict | str | 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) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index f225da8601..ec42b14ebc 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -42,7 +42,7 @@ def add_tag(tag, dt, dn, color=None): @frappe.whitelist() -def add_tags(tags, dt, docs, color=None): +def add_tags(tags: str, dt: str, docs: str | list, color: str | None = 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"): diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 38815d3c7a..94f3479757 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -140,7 +140,7 @@ def add(args=None, *, ignore_permissions=False): @frappe.whitelist() -def add_multiple(args=None): +def add_multiple(args: dict | None = None) -> 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) From 4f33c88d4f3cad8038f2f2a58c4c6dfe4a85ba71 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 17 Feb 2026 17:57:19 +0530 Subject: [PATCH 6/8] fix: remove unused args param --- frappe/desk/form/assign_to.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 94f3479757..09ffeb1a6c 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -140,13 +140,11 @@ def add(args=None, *, ignore_permissions=False): @frappe.whitelist() -def add_multiple(args: dict | None = None) -> None: +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) - if not args: - args = frappe.local.form_dict - + args = frappe.local.form_dict docname_list = json.loads(args["name"]) for docname in docname_list: From dfae42a2dafa06544a29c73a04b26e202aa4289a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 20 Feb 2026 15:20:57 +0100 Subject: [PATCH 7/8] fix: perm level for allowing bulk actions field --- frappe/core/doctype/user/user.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index d6f9c13cbe..025658122d 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -604,7 +604,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", @@ -801,7 +801,8 @@ "default": "1", "fieldname": "bulk_actions", "fieldtype": "Check", - "label": "Bulk Actions" + "label": "Bulk Actions", + "permlevel": 1 }, { "default": "1", @@ -910,7 +911,7 @@ } ], "make_attachments_public": 1, - "modified": "2026-01-12 16:04:21.542524", + "modified": "2026-02-20 19:47:59.211390", "modified_by": "Administrator", "module": "Core", "name": "User", From 85dc6d15b02dc073b358270aa7ddd5c1fad25e9a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 22 Feb 2026 13:53:48 +0530 Subject: [PATCH 8/8] fix: labels in user desk settings --- frappe/core/doctype/user/user.json | 24 ++++++++++++------------ frappe/desk/form/assign_to.py | 5 ++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 1c6b2b503b..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", @@ -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,20 +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, @@ -814,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", @@ -851,7 +851,7 @@ "default": "0", "fieldname": "form_navigation_buttons", "fieldtype": "Check", - "label": "Navigation Buttons" + "label": "Show navigation buttons" } ], "icon": "fa fa-user", @@ -905,7 +905,7 @@ } ], "make_attachments_public": 1, - "modified": "2026-02-20 19:47:59.211390", + "modified": "2026-02-22 13:44:36.317890", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 2ad40a22ea..c84dc29784 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -141,12 +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): +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) - if not args: - args = frappe.local.form_dict + args = frappe.local.form_dict docname_list = json.loads(args["name"])