Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions frappe/custom/doctype/custom_field/custom_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,53 @@ def _update_fieldname_references(field: CustomField, old_fieldname: str, new_fie
"insert_after",
new_fieldname,
)


def delete_custom_fields(custom_fields: dict, bypass_hooks: bool = False):
"""
Delete custom fields from doctypes.

:param custom_fields: Dict mapping doctype to field names.
:param bypass_hooks: If `True`, fast raw delete (skips hooks (doc events like on_trash)).

Example:

```
delete_custom_fields({"Address": ["custom_a", "custom_b"]})

delete_custom_fields({"ToDo": [{"fieldname": "cf_1"}]}, bypass_hooks=True)
````
"""
for doctype, fields in custom_fields.items():
fieldnames = []

if isinstance(fields, (list, tuple, set)):
for field in fields:
if isinstance(field, str):
fieldnames.append(field)
elif isinstance(field, dict) and field.get("fieldname"):
fieldnames.append(field["fieldname"])

if not fieldnames:
continue

fieldnames = tuple(set(fieldnames))

if bypass_hooks:
frappe.db.delete(
"Custom Field",
{
"fieldname": ("in", fieldnames),
"dt": doctype,
},
)
frappe.clear_cache(doctype=doctype)
else:
custom_field_names = frappe.get_all(
"Custom Field",
filters={"fieldname": ("in", fieldnames), "dt": doctype},
pluck="name",
)

for custom_field_name in custom_field_names:
frappe.get_doc("Custom Field", custom_field_name).delete(ignore_permissions=True, force=True)
49 changes: 49 additions & 0 deletions frappe/custom/doctype/custom_field/test_custom_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from frappe.custom.doctype.custom_field.custom_field import (
create_custom_field,
create_custom_fields,
delete_custom_fields,
rename_fieldname,
)
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.tests import IntegrationTestCase


Expand Down Expand Up @@ -183,3 +185,50 @@ def gen_fieldname():
self.assertFalse(doc.get(old))

field.delete()

def test_delete_custom_fields(self):
doctype = "ToDo"
fields = [
{
"fieldname": f"test_delete_{frappe.generate_hash(length=5)}",
"fieldtype": "Data",
"insert_after": "status",
}
for _ in range(4)
]
fieldnames = [f["fieldname"] for f in fields]

create_custom_fields({doctype: fields})

# create property setters for fields deleted via safe path (hooks should clean these up)
for fieldname in fieldnames[:2]:
make_property_setter(doctype, fieldname, "hidden", "1", "Check")

def field_exists(fieldname):
return frappe.db.exists("Custom Field", {"fieldname": fieldname, "dt": doctype})

def property_setter_exists(fieldname):
return frappe.db.exists("Property Setter", {"doc_type": doctype, "field_name": fieldname})

for fieldname in fieldnames:
self.assertTrue(field_exists(fieldname))
for fieldname in fieldnames[:2]:
self.assertTrue(property_setter_exists(fieldname))

# 1
delete_custom_fields({doctype: [fieldnames[0], fieldnames[0]]})
self.assertFalse(field_exists(fieldnames[0]))
self.assertFalse(property_setter_exists(fieldnames[0]))

# 2
delete_custom_fields({doctype: [{"fieldname": fieldnames[1]}]})
self.assertFalse(field_exists(fieldnames[1]))
self.assertFalse(property_setter_exists(fieldnames[1]))

# 3
delete_custom_fields({doctype: [fieldnames[2], fieldnames[2]]}, bypass_hooks=True)
self.assertFalse(field_exists(fieldnames[2]))

# 4
delete_custom_fields({doctype: [{"fieldname": fieldnames[3]}]}, bypass_hooks=True)
self.assertFalse(field_exists(fieldnames[3]))
2 changes: 1 addition & 1 deletion frappe/templates/includes/comments/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def get_limit():


@frappe.whitelist(allow_guest=True)
# @rate_limit(key="reference_name", limit=get_limit, seconds=60 * 60)
@rate_limit(limit=get_limit, seconds=60 * 60)
def add_comment(
comment: str, comment_email: str, comment_by: str, reference_doctype: str, reference_name: str, route: str
):
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"fast-deep-equal": "^2.0.1",
"fast-glob": "^3.2.5",
"frappe-charts": "2.0.0-rc27",
"frappe-datatable": "1.20.1",
"frappe-datatable": "1.20.2",
"frappe-gantt": "^1.2.1",
"frappe-quill-image-resize": "^3.0.9",
"gemoji": "^8.1.0",
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1433,10 +1433,10 @@ frappe-charts@2.0.0-rc27:
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc27.tgz#a04737d36bcce5381b25ad48896c43b02eb62852"
integrity sha512-J4WCrHYB6oR4Dfu28aaCxlUu64C/V+qJlNE1E0xpya2/yCeqDZ8LA6pS63SBMOdV2CTP8cJ6Isk5m+rZi9gElA==

frappe-datatable@1.20.1:
version "1.20.1"
resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.20.1.tgz#4009ea7b23fb2482729afd21383aa1acb749f35f"
integrity sha512-I1VMI8x1wGQEs6POylo1kuRlG4ZRB58cVD3AP6qVNlgzethh1ELBXCRPredo1cWXDm65IbuVuVsh8eSiuPiUCw==
frappe-datatable@1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.20.2.tgz#5cea64425bf35855ec5f14d916ff4ec2b4ca2bbf"
integrity sha512-XaN96/woV/VyRsWQgnbRmi1XV3lrnZkUWRP9ANNRkJSmniYddJVQKt7K9w6Ilq+vclNvJX96iQc7B6m77w1xXg==
dependencies:
hyperlist "^1.0.0-beta"
lodash "^4.17.5"
Expand Down
Loading