diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py index 0a38a16128..394faf0771 100644 --- a/frappe/custom/doctype/property_setter/property_setter.py +++ b/frappe/custom/doctype/property_setter/property_setter.py @@ -111,6 +111,60 @@ def delete_property_setter(doc_type, property=None, field_name=None, row_name=No if row_name: filters["row_name"] = row_name - property_setters = frappe.db.get_values("Property Setter", filters) + _delete_property_setters(filters) + + +def bulk_delete_property_setters(property_setters: list[dict], bypass_hooks: bool = False): + """ + Delete property setters. + + :param property_setters: List of filters for Property Setter rows. + :param bypass_hooks: If `True`, raw delete without doc hooks. + + Example of `property_setters`: + ``` + [ + {"doctype": "ToDo", "fieldname": "status", "property": "hidden"}, + {"doctype": "ToDo", "fieldname": "status", "property": "read_only"}, + ] + ``` + + --- + + Note: `doctype` and `fieldname` are mandatory. + """ + field_map = { + "doctype": "doc_type", + "fieldname": "field_name", + } + + doctypes_to_clear = set() + + for property_setter in property_setters: + filters = property_setter.copy() + + for key, fieldname in field_map.items(): + if key in filters: + filters[fieldname] = filters.pop(key) + + if not filters: + continue + + if not filters.get("doc_type") or not filters.get("field_name"): + frappe.throw(_("`doctype` and `fieldname` are required for deleting property setters.")) + + if bypass_hooks: + frappe.db.delete("Property Setter", filters) + doctypes_to_clear.add(filters["doc_type"]) + else: + _delete_property_setters(filters) + + for doctype in doctypes_to_clear: + frappe.clear_cache(doctype=doctype) + + +def _delete_property_setters(filters: dict): + property_setters = frappe.get_all("Property Setter", filters=filters, pluck="name") + for ps in property_setters: frappe.get_doc("Property Setter", ps).delete(ignore_permissions=True, force=True) diff --git a/frappe/custom/doctype/property_setter/test_property_setter.py b/frappe/custom/doctype/property_setter/test_property_setter.py index ea1887cf76..e52ea61864 100644 --- a/frappe/custom/doctype/property_setter/test_property_setter.py +++ b/frappe/custom/doctype/property_setter/test_property_setter.py @@ -1,7 +1,48 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import frappe +from frappe.custom.doctype.property_setter.property_setter import ( + bulk_delete_property_setters, +) from frappe.tests import IntegrationTestCase class TestPropertySetter(IntegrationTestCase): - pass + def test_bulk_delete_property_setters(self): + doctype = "ToDo" + fieldname = "status" + + property_1 = "hidden" + property_2 = "no_copy" + properties = [property_1, property_2] + + for property_name in properties: + frappe.make_property_setter( + { + "doctype": doctype, + "fieldname": fieldname, + "property": property_name, + "value": 1, + "property_type": "Check", + } + ) + + def property_setter_exists(property_name): + return frappe.db.exists( + "Property Setter", + {"doc_type": doctype, "field_name": fieldname, "property": property_name}, + ) + + for property_name in properties: + self.assertTrue(property_setter_exists(property_name)) + + # 1 + bulk_delete_property_setters( + [{"doctype": doctype, "fieldname": fieldname, "property": property_1}], + bypass_hooks=True, + ) + self.assertFalse(property_setter_exists(property_1)) + + # 2 + bulk_delete_property_setters([{"doc_type": doctype, "field_name": fieldname, "property": property_2}]) + self.assertFalse(property_setter_exists(property_2))