From 4ecb9bc57d7766e1e5c284f2149b265d34ec4dba Mon Sep 17 00:00:00 2001 From: Aarol D'Souza <98270103+AarDG10@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:30:51 +0530 Subject: [PATCH] fix: clear cache and prevent data access after DocType deletion (#34307) * fix: clear cache and prevent data access after DocType deletion * fix: Handle potential DB failures during migrate --------- Co-authored-by: Ankush Menat --- frappe/cache_manager.py | 27 ++++++++++++++------- frappe/core/doctype/doctype/test_doctype.py | 10 ++++++++ frappe/model/delete_doc.py | 2 ++ frappe/tests/test_fixture_import.py | 8 +++--- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index ed68b2b518..fed740dae2 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -147,17 +147,26 @@ def _clear_doctype_cache_from_redis(doctype: str | None = None): clear_single(doctype) # clear all parent doctypes - for dt in frappe.get_all( - "DocField", "parent", dict(fieldtype=["in", frappe.model.table_fields], options=doctype) - ): - clear_single(dt.parent) - - # clear all parent doctypes - if not frappe.flags.in_install: + try: for dt in frappe.get_all( - "Custom Field", "dt", dict(fieldtype=["in", frappe.model.table_fields], options=doctype) + "DocField", + "parent", + dict(fieldtype=["in", frappe.model.table_fields], options=doctype), + ignore_ddl=True, ): - clear_single(dt.dt) + clear_single(dt.parent) + + # clear all parent doctypes + if not frappe.flags.in_install: + for dt in frappe.get_all( + "Custom Field", + "dt", + dict(fieldtype=["in", frappe.model.table_fields], options=doctype), + ignore_ddl=True, + ): + clear_single(dt.dt) + except frappe.DoesNotExistError: + pass # core doctypes getting migrated. # clear all notifications delete_notification_count_for(doctype) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 5677098fa8..20820fbaa6 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -856,6 +856,16 @@ class TestDocType(IntegrationTestCase): ) self.assertRaises(frappe.ValidationError, doctype.insert) + def test_delete_doc_clears_cache(self): + dt = new_doctype( + fields=[{"fieldname": "test_fdname", "fieldtype": "Data", "label": "Test Field"}], + ).insert() + frappe.get_meta(dt.name) + frappe.delete_doc("DocType", dt.name, force=1, delete_permanently=False) + frappe.db.commit() + with self.assertRaises(frappe.DoesNotExistError): + frappe.get_meta(dt.name) + def new_doctype( name: str | None = None, diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index c8096f3aad..5cf52724a4 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -144,6 +144,8 @@ def delete_doc( # in case a doctype doesnt have any controller code nor any app and module pass + frappe.clear_cache(doctype=name) + else: # Lock the doc without waiting try: diff --git a/frappe/tests/test_fixture_import.py b/frappe/tests/test_fixture_import.py index 976c4df0db..836f413e6f 100644 --- a/frappe/tests/test_fixture_import.py +++ b/frappe/tests/test_fixture_import.py @@ -32,6 +32,7 @@ class TestFixtureImport(IntegrationTestCase): self.assertFalse(frappe.db.exists("DocType", "temp_doctype")) self.create_new_doctype("temp_doctype") + frappe.db.commit() dummy_name_list = ["jhon", "jane"] path_to_exported_fixtures = self.insert_dummy_data_and_export("temp_doctype", dummy_name_list) @@ -39,9 +40,6 @@ class TestFixtureImport(IntegrationTestCase): import_doc(path_to_exported_fixtures) - delete_doc("DocType", "temp_doctype", delete_permanently=True) - os.remove(path_to_exported_fixtures) - self.assertEqual(frappe.db.count("temp_doctype"), len(dummy_name_list)) data = frappe.get_all("temp_doctype", "member_name") @@ -53,6 +51,10 @@ class TestFixtureImport(IntegrationTestCase): self.assertEqual(set(dummy_name_list), imported_data) + delete_doc("DocType", "temp_doctype", delete_permanently=True) + frappe.db.commit() + os.remove(path_to_exported_fixtures) + def test_singles_fixtures_import(self): self.assertFalse(frappe.db.exists("DocType", "temp_singles"))