From cbd1f8fe5cf66b54e96a9039e1dec6c089c3d761 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Wed, 1 Apr 2026 13:03:23 +0530 Subject: [PATCH 1/2] fix(bulk_update): update conditions block in bulk_update Update conditions block to strictly use json. Conditions parsed will now have to be written in json instead of plain strings. --- .../desk/doctype/bulk_update/bulk_update.json | 7 ++++--- .../desk/doctype/bulk_update/bulk_update.py | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/frappe/desk/doctype/bulk_update/bulk_update.json b/frappe/desk/doctype/bulk_update/bulk_update.json index a678a99e21..9c227c9b61 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.json +++ b/frappe/desk/doctype/bulk_update/bulk_update.json @@ -36,7 +36,7 @@ }, { "bold": 1, - "description": "SQL Conditions. Example: status=\"Open\"", + "description": "SQL Conditions. Example: {\"status\" : \"open\", \"priority\" : \"medium\"}", "fieldname": "condition", "fieldtype": "Small Text", "label": "Condition" @@ -52,7 +52,7 @@ ], "issingle": 1, "links": [], - "modified": "2024-03-23 16:01:29.575802", + "modified": "2026-04-01 12:18:08.821282", "modified_by": "Administrator", "module": "Desk", "name": "Bulk Update", @@ -70,8 +70,9 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index 75d9f8690d..c59a2b97f2 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -31,17 +31,18 @@ class BulkUpdate(Document): def bulk_update(self): self.check_permission("write") limit = self.limit if self.limit and cint(self.limit) < 500 else 500 - - condition = "" + query_args = {"doctype": self.document_type, "limit": limit, "pluck": "name"} if self.condition: - if ";" in self.condition: - frappe.throw(_("; not allowed in condition")) + try: + filters = frappe.parse_json(self.condition) + if isinstance(filters, dict): + if "or_filters" in filters: + query_args["or_filters"] = filters.pop("or_filters") + query_args["filters"] = filters + except Exception as e: + frappe.throw(_("The Bulk Update could not happen due to {0}").format(str(e))) - condition = f" where {self.condition}" - - docnames = frappe.db.sql_list( - f"""select name from `tab{self.document_type}`{condition} limit {limit} offset 0""" - ) + docnames = frappe.get_all(**query_args) return submit_cancel_or_update_docs( self.document_type, docnames, "update", {self.field: self.update_value} ) From c24d0a57316a3fca269924304eb210944c8fed0a Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Sun, 5 Apr 2026 09:02:54 +0530 Subject: [PATCH 2/2] test: add test for the whitelisted bulk_update method Added test for the whitelisted endpoint, in particular to test the parsing of the conditions. --- .../doctype/bulk_update/test_bulk_update.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/frappe/desk/doctype/bulk_update/test_bulk_update.py b/frappe/desk/doctype/bulk_update/test_bulk_update.py index 3d26ea0aca..a6c06a5302 100644 --- a/frappe/desk/doctype/bulk_update/test_bulk_update.py +++ b/frappe/desk/doctype/bulk_update/test_bulk_update.py @@ -103,3 +103,45 @@ class TestBulkUpdate(IntegrationTestCase): docnames_bg = frappe.get_all(self.doctype, {"docstatus": 0}, limit=20, pluck="name") submit_cancel_or_update_docs(self.doctype, docnames_bg, action="update", data=update_data) self.wait_for_assertion(lambda: check_child_field(docnames_bg, "_Test Child Updated")) + + def test_bulk_update_conditions(self): + """Test the whitelisted bulk update method""" + todo_names = [] + for i in range(5): + doc = frappe.get_doc( + { + "doctype": "ToDo", + "description": f"Bulk Update Status Test {i}", + "status": "Open" if i < 3 else "Closed", + } + ).insert() + todo_names.append(doc.name) + + try: + condition_json = frappe.as_json({"status": "Open", "name": ["in", todo_names]}) + + bulk_upd = frappe.get_doc( + { + "doctype": "Bulk Update", + "document_type": "ToDo", + "field": "status", + "update_value": "Closed", + "condition": condition_json, + "limit": 5, + } + ) + + bulk_upd.bulk_update() + + updated_docs = frappe.get_all("ToDo", filters={"name": ["in", todo_names]}, fields=["status"]) + + for doc in updated_docs: + self.assertEqual(doc.status, "Closed") + + remaining_open_count = frappe.db.count("ToDo", {"name": ["in", todo_names], "status": "Open"}) + self.assertEqual(remaining_open_count, 0) + + finally: + for name in todo_names: + frappe.delete_doc("ToDo", name) + frappe.db.commit()