From 2c7f77afab603ca2fcccde3f4d3338c99534f08d Mon Sep 17 00:00:00 2001 From: sokumon Date: Fri, 21 Nov 2025 12:34:11 +0530 Subject: [PATCH 1/7] feat: removal of orphan entities --- frappe/migrate.py | 2 ++ frappe/model/sync.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/frappe/migrate.py b/frappe/migrate.py index 623fa1bcbe..78a0fd27c3 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -182,6 +182,8 @@ class SiteMigration: print("Removing orphan doctypes...") frappe.model.sync.remove_orphan_doctypes() + frappe.model.sync.remove_orphan_entities() + print("Syncing portal menu...") frappe.get_single("Portal Settings").sync_menu() diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 4d6b01c14b..14b8056333 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -197,3 +197,37 @@ def remove_orphan_doctypes(): update_progress_bar("Deleting orphaned DocTypes", i, len(orphan_doctypes)) frappe.db.commit() print() + + +def remove_orphan_entities(): + entites = ["Workspace", "Dashboard", "Page", "Report"] + entity_filter_map = { + "Workspace": {"public": 1}, + "Page": {"standard": "Yes"}, + "Report": {"is_standard": "Yes"}, + "Dashboard": {"is_standard": False}, + } + for entity in entites: + print(f"Removing orphan {entity}s") + all_enitities = frappe.get_all( + entity, filters=entity_filter_map.get(entity), fields=["name", "module"] + ) + for i, w in enumerate(all_enitities): + if w.module: + try: + module_path = frappe.get_module_path(w.module) + if not check_if_record_exists(module_path, entity, w.module, w.name): + print(f"Deleting entity {entity} {w.name}") + frappe.delete_doc(entity, w.name, force=True, ignore_missing=True) + update_progress_bar(f"Deleting orphaned {entity}", i, len(all_enitities)) + print() + + except Exception as e: + print(e) + frappe.db.commit() + + +def check_if_record_exists(module_path, entity_type, module_name, name): + name = frappe.scrub(name) + entity_path = os.path.join(module_path, entity_type.lower(), name.lower(), f"{name.lower()}.json") + return os.path.exists(entity_path) From fd79a52695250f7d027c54b7a619dc82b0b38211 Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 25 Nov 2025 09:59:09 +0530 Subject: [PATCH 2/7] fix: delete icon and sidebar based on workspace --- frappe/desk/doctype/workspace/workspace.py | 21 +++++++++++++++++++++ frappe/model/sync.py | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index c45b249cf7..731b1c8be8 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -127,6 +127,8 @@ class Workspace(Document): def on_trash(self): if self.public and not is_workspace_manager(): frappe.throw(_("You need to be Workspace Manager to delete a public workspace.")) + self.delete_desktop_icon() + self.delete_workspace_sidebar() self.delete_from_my_workspaces() def delete_from_my_workspaces(self): @@ -143,6 +145,25 @@ class Workspace(Document): if self.module and frappe.conf.developer_mode: delete_folder(self.module, "Workspace", self.title) + def delete_desktop_icon(self): + if self.public: + desktop_icon = frappe.get_all( + "Desktop Icon", + filters=[{"link_type": "Workspace"}, {"link_to": self.name}], + limit=1, + pluck="name", + ) + if desktop_icon: + frappe.delete_doc("Desktop Icon", desktop_icon) + + def delete_workspace_sidebar(self): + if self.public: + workspace_sidebar = frappe.get_all( + "Workspace Sidebar", filters=[{"name": self.name}], limit=1, pluck="name" + ) + if workspace_sidebar: + frappe.delete_doc("Workspace Sidebar", workspace_sidebar[0]) + @staticmethod def get_module_wise_workspaces(): workspaces = frappe.get_all( diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 14b8056333..040bf54d56 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -224,7 +224,8 @@ def remove_orphan_entities(): except Exception as e: print(e) - frappe.db.commit() + # save the deleted ones + frappe.db.commit() # nosemgrep def check_if_record_exists(module_path, entity_type, module_name, name): From 826b5f0a0fe8046a3f66cb7e4d56753bb82831d6 Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 25 Nov 2025 18:07:09 +0530 Subject: [PATCH 3/7] fix: handle renamed icons via removing icons with no fixture --- frappe/migrate.py | 1 + frappe/model/sync.py | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/frappe/migrate.py b/frappe/migrate.py index 78a0fd27c3..043193e5ad 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -184,6 +184,7 @@ class SiteMigration: frappe.model.sync.remove_orphan_entities() + frappe.model.sync.delete_duplicate_icons() print("Syncing portal menu...") frappe.get_single("Portal Settings").sync_menu() diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 040bf54d56..68d591f6d7 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -216,7 +216,7 @@ def remove_orphan_entities(): if w.module: try: module_path = frappe.get_module_path(w.module) - if not check_if_record_exists(module_path, entity, w.module, w.name): + if not check_if_record_exists(module_path, entity, w.module, name=w.name): print(f"Deleting entity {entity} {w.name}") frappe.delete_doc(entity, w.name, force=True, ignore_missing=True) update_progress_bar(f"Deleting orphaned {entity}", i, len(all_enitities)) @@ -224,11 +224,29 @@ def remove_orphan_entities(): except Exception as e: print(e) - # save the deleted ones + # save the deleted icons frappe.db.commit() # nosemgrep -def check_if_record_exists(module_path, entity_type, module_name, name): +def check_if_record_exists(type, module_path, entity_type, name=None): name = frappe.scrub(name) entity_path = os.path.join(module_path, entity_type.lower(), name.lower(), f"{name.lower()}.json") + if type == "app": + entity_path = os.path.join(module_path, frappe.scrub(entity_type.lower()), f"{name.lower()}.json") return os.path.exists(entity_path) + + +def delete_duplicate_icons(): + # This handles rename for apps remove + for app in frappe.get_installed_apps(): + icons = frappe.get_all("Desktop Icon", filters=[{"icon_type": "App"}, {"app": app}], pluck="name") + + if len(icons) > 1: + for i in icons: + app_path = frappe.get_app_path(app) + if not check_if_record_exists("app", app_path, "Desktop Icon", name=i): + print(f"Deleting icon {i}") + frappe.delete_doc("Desktop Icon", i) + + # save the deleted icons + frappe.db.commit() # semgrep From d7c96412a45b49b3aaa59620f9740a0d04cd4205 Mon Sep 17 00:00:00 2001 From: sokumon Date: Wed, 26 Nov 2025 13:41:09 +0530 Subject: [PATCH 4/7] fix: semgrep issue --- frappe/model/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 68d591f6d7..0ce14e2753 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -249,4 +249,4 @@ def delete_duplicate_icons(): frappe.delete_doc("Desktop Icon", i) # save the deleted icons - frappe.db.commit() # semgrep + frappe.db.commit() # nosemgrep From 8bf734bb4ac86c1a69e370642bcc47829e0b8dd9 Mon Sep 17 00:00:00 2001 From: sokumon Date: Wed, 26 Nov 2025 14:40:58 +0530 Subject: [PATCH 5/7] fix: check for if the record exists --- frappe/model/sync.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 0ce14e2753..8cabed0e1e 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -216,7 +216,9 @@ def remove_orphan_entities(): if w.module: try: module_path = frappe.get_module_path(w.module) - if not check_if_record_exists(module_path, entity, w.module, name=w.name): + if not check_if_record_exists( + type="module", path=module_path, entity_type=entity, name=w.name + ): print(f"Deleting entity {entity} {w.name}") frappe.delete_doc(entity, w.name, force=True, ignore_missing=True) update_progress_bar(f"Deleting orphaned {entity}", i, len(all_enitities)) @@ -228,23 +230,25 @@ def remove_orphan_entities(): frappe.db.commit() # nosemgrep -def check_if_record_exists(type, module_path, entity_type, name=None): - name = frappe.scrub(name) - entity_path = os.path.join(module_path, entity_type.lower(), name.lower(), f"{name.lower()}.json") +def check_if_record_exists(type=None, path=None, entity_type=None, name=None): + scrubbed_name = frappe.scrub(name.lower()) + scrubbed_entity_type = frappe.scrub(entity_type.lower()) if type == "app": - entity_path = os.path.join(module_path, frappe.scrub(entity_type.lower()), f"{name.lower()}.json") + entity_path = os.path.join(path, scrubbed_entity_type, f"{scrubbed_name}.json") + else: + entity_path = os.path.join(path, scrubbed_entity_type, scrubbed_name, f"{scrubbed_name}.json") return os.path.exists(entity_path) def delete_duplicate_icons(): - # This handles rename for apps remove + # This handles app icons which are renamed. Removes the old entry from db. for app in frappe.get_installed_apps(): icons = frappe.get_all("Desktop Icon", filters=[{"icon_type": "App"}, {"app": app}], pluck="name") if len(icons) > 1: for i in icons: app_path = frappe.get_app_path(app) - if not check_if_record_exists("app", app_path, "Desktop Icon", name=i): + if not check_if_record_exists(type="app", path=app_path, entity_type="Desktop Icon", name=i): print(f"Deleting icon {i}") frappe.delete_doc("Desktop Icon", i) From 632eb8e50bbff167b5ed100bbd435987dc593a65 Mon Sep 17 00:00:00 2001 From: sokumon Date: Thu, 27 Nov 2025 17:03:52 +0530 Subject: [PATCH 6/7] fix: use correct filter for dashboard --- frappe/model/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 8cabed0e1e..c177b9af17 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -205,7 +205,7 @@ def remove_orphan_entities(): "Workspace": {"public": 1}, "Page": {"standard": "Yes"}, "Report": {"is_standard": "Yes"}, - "Dashboard": {"is_standard": False}, + "Dashboard": {"is_standard": True}, } for entity in entites: print(f"Removing orphan {entity}s") From 2c815fa34f4ffe923092ad9e8beb4f558cbd55e4 Mon Sep 17 00:00:00 2001 From: sokumon Date: Mon, 1 Dec 2025 14:30:54 +0530 Subject: [PATCH 7/7] fix: dont pass list pass first item --- frappe/desk/doctype/workspace/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 731b1c8be8..40c75bc697 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -154,7 +154,7 @@ class Workspace(Document): pluck="name", ) if desktop_icon: - frappe.delete_doc("Desktop Icon", desktop_icon) + frappe.delete_doc("Desktop Icon", desktop_icon[0]) def delete_workspace_sidebar(self): if self.public: