From 16d3c2c85c1a4bd8ad128e8030b81f09d41704ff Mon Sep 17 00:00:00 2001 From: sokumon Date: Thu, 27 Nov 2025 12:36:39 +0530 Subject: [PATCH 1/5] feat: generate sidebars based on module --- frappe/boot.py | 34 ++++++----- .../workspace_sidebar/workspace_sidebar.py | 60 +++++++++++++++++++ 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index ebfaa83fea..9bb2bb9a8e 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -527,7 +527,20 @@ def get_sentry_dsn(): return os.getenv("FRAPPE_SENTRY_DSN") +def add_user_specific_sidebar(sidebars): + try: + my_workspace_for_user = frappe.get_doc("Workspace Sidebar", f"My Workspaces-{frappe.session.user}") + sidebars.append( + {"name": my_workspace_for_user.name, "header_icon": my_workspace_for_user.header_icon} + ) + except frappe.DoesNotExistError: + my_workspace = frappe.get_doc("Workspace Sidebar", "My Workspaces") + sidebars.append({"name": my_workspace.name, "header_icon": my_workspace.header_icon}) + + def get_sidebar_items(): + from frappe.desk.doctype.workspace_sidebar.workspace_sidebar import create_sidebars_for_modules + sidebars = frappe.get_all( "Workspace Sidebar", fields=["name", "header_icon"], filters={"name": ["not like", "%My Workspaces%"]} ) @@ -535,11 +548,12 @@ def get_sidebar_items(): sidebar_items = {} for s in sidebars: - w = frappe.get_doc("Workspace Sidebar", s["name"]) + print(s) + w = frappe.get_doc("Workspace Sidebar", s.get("name")) sidebar_items[s["name"].lower()] = { - "label": s["name"], + "label": s.get("name"), "items": [], - "header_icon": s["header_icon"], + "header_icon": s.get("header_icon"), "module": w.module, "app": w.app, } @@ -568,26 +582,14 @@ def get_sidebar_items(): "report_type": report_type, "ref_doctype": ref_doctype, } - if ( "My Workspaces" in s["name"] or si.type == "Section Break" or w.is_item_allowed(si.link_to, si.link_type) ): - sidebar_items[s["name"].lower()]["items"].append(workspace_sidebar) + sidebar_items[s.get("name").lower()]["items"].append(workspace_sidebar) old_name = f"my workspaces-{frappe.session.user.lower()}" if old_name in sidebar_items.keys(): sidebar_items["my workspaces"] = sidebar_items.pop(old_name) return sidebar_items - - -def add_user_specific_sidebar(sidebars): - try: - my_workspace_for_user = frappe.get_doc("Workspace Sidebar", f"My Workspaces-{frappe.session.user}") - sidebars.append( - {"name": my_workspace_for_user.name, "header_icon": my_workspace_for_user.header_icon} - ) - except frappe.DoesNotExistError: - my_workspace = frappe.get_doc("Workspace Sidebar", "My Workspaces") - sidebars.append({"name": my_workspace.name, "header_icon": my_workspace.header_icon}) diff --git a/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py b/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py index 444673c7b2..83782de556 100644 --- a/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py +++ b/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py @@ -240,3 +240,63 @@ def add_to_my_workspace(workspace): except Exception as e: frappe.log_error(title="Error in Adding Private Workspaces", message=e) + + +def create_sidebars_for_modules(): + sidebars = [] + for module in frappe.get_all("Module Def", pluck="name"): + if not ( + frappe.db.exists("Workspace Sidebar", {"module": module}) + or frappe.db.exists("Workspace Sidebar", {"name": module}) + ): + print("Fetching information for Module", module) + module_info = get_module_info(module) + sidebar_items = create_sidebar_items(module_info) + sidebar = frappe.new_doc("Workspace Sidebar") + sidebar.title = module + sidebar.items = sidebar_items + sidebar.header_icon = "hammer" + sidebars.append(sidebar) + sidebar.save() + return sidebars + + +def get_module_info(module_name): + entities = ["Workspace", "Dashboard", "DocType", "Report", "Page"] + module_info = {} + + for entity in entities: + module_info[entity] = {} + filters = [{"module": module_name}] + if entity.lower() == "doctype": + filters.append({"istable": 0}) + module_info[entity] = frappe.get_all(entity, filters=filters, pluck="name") + + return module_info + + +def create_sidebar_items(module_info): + print(module_info) + sidebar_items = [] + for entity, items in module_info.items(): + if entity.lower() == "report": + section_break = frappe.new_doc("Workspace Sidebar Item") + section_break.update( + { + "type": "Section Break", + } + ) + sidebar_items.append(section_break) + for item in items: + item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item} + if entity.lower() == "workspace": + item_info["icon"] = "home" + + if entity.lower() == "doctype" and "settings" in item.lower(): + item_info["icon"] = "settings" + + sidebar_item = frappe.new_doc("Workspace Sidebar Item") + sidebar_item.update(item_info) + sidebar_items.append(sidebar_item) + + return sidebar_items From d9d231feb8f455b4f59280dd40da50d0eb2b3353 Mon Sep 17 00:00:00 2001 From: sokumon Date: Mon, 8 Dec 2025 12:27:59 +0530 Subject: [PATCH 2/5] refactor: move logic to a seperate patch --- frappe/boot.py | 25 ++++---- .../workspace_sidebar/workspace_sidebar.py | 60 ------------------ frappe/patches.txt | 1 + .../auto_generate_sidebar_from_modules.py | 61 +++++++++++++++++++ 4 files changed, 73 insertions(+), 74 deletions(-) create mode 100644 frappe/patches/v16_0/auto_generate_sidebar_from_modules.py diff --git a/frappe/boot.py b/frappe/boot.py index 9bb2bb9a8e..2af5107988 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -527,20 +527,7 @@ def get_sentry_dsn(): return os.getenv("FRAPPE_SENTRY_DSN") -def add_user_specific_sidebar(sidebars): - try: - my_workspace_for_user = frappe.get_doc("Workspace Sidebar", f"My Workspaces-{frappe.session.user}") - sidebars.append( - {"name": my_workspace_for_user.name, "header_icon": my_workspace_for_user.header_icon} - ) - except frappe.DoesNotExistError: - my_workspace = frappe.get_doc("Workspace Sidebar", "My Workspaces") - sidebars.append({"name": my_workspace.name, "header_icon": my_workspace.header_icon}) - - def get_sidebar_items(): - from frappe.desk.doctype.workspace_sidebar.workspace_sidebar import create_sidebars_for_modules - sidebars = frappe.get_all( "Workspace Sidebar", fields=["name", "header_icon"], filters={"name": ["not like", "%My Workspaces%"]} ) @@ -548,7 +535,6 @@ def get_sidebar_items(): sidebar_items = {} for s in sidebars: - print(s) w = frappe.get_doc("Workspace Sidebar", s.get("name")) sidebar_items[s["name"].lower()] = { "label": s.get("name"), @@ -593,3 +579,14 @@ def get_sidebar_items(): if old_name in sidebar_items.keys(): sidebar_items["my workspaces"] = sidebar_items.pop(old_name) return sidebar_items + + +def add_user_specific_sidebar(sidebars): + try: + my_workspace_for_user = frappe.get_doc("Workspace Sidebar", f"My Workspaces-{frappe.session.user}") + sidebars.append( + {"name": my_workspace_for_user.name, "header_icon": my_workspace_for_user.header_icon} + ) + except frappe.DoesNotExistError: + my_workspace = frappe.get_doc("Workspace Sidebar", "My Workspaces") + sidebars.append({"name": my_workspace.name, "header_icon": my_workspace.header_icon}) diff --git a/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py b/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py index 83782de556..444673c7b2 100644 --- a/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py +++ b/frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py @@ -240,63 +240,3 @@ def add_to_my_workspace(workspace): except Exception as e: frappe.log_error(title="Error in Adding Private Workspaces", message=e) - - -def create_sidebars_for_modules(): - sidebars = [] - for module in frappe.get_all("Module Def", pluck="name"): - if not ( - frappe.db.exists("Workspace Sidebar", {"module": module}) - or frappe.db.exists("Workspace Sidebar", {"name": module}) - ): - print("Fetching information for Module", module) - module_info = get_module_info(module) - sidebar_items = create_sidebar_items(module_info) - sidebar = frappe.new_doc("Workspace Sidebar") - sidebar.title = module - sidebar.items = sidebar_items - sidebar.header_icon = "hammer" - sidebars.append(sidebar) - sidebar.save() - return sidebars - - -def get_module_info(module_name): - entities = ["Workspace", "Dashboard", "DocType", "Report", "Page"] - module_info = {} - - for entity in entities: - module_info[entity] = {} - filters = [{"module": module_name}] - if entity.lower() == "doctype": - filters.append({"istable": 0}) - module_info[entity] = frappe.get_all(entity, filters=filters, pluck="name") - - return module_info - - -def create_sidebar_items(module_info): - print(module_info) - sidebar_items = [] - for entity, items in module_info.items(): - if entity.lower() == "report": - section_break = frappe.new_doc("Workspace Sidebar Item") - section_break.update( - { - "type": "Section Break", - } - ) - sidebar_items.append(section_break) - for item in items: - item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item} - if entity.lower() == "workspace": - item_info["icon"] = "home" - - if entity.lower() == "doctype" and "settings" in item.lower(): - item_info["icon"] = "settings" - - sidebar_item = frappe.new_doc("Workspace Sidebar Item") - sidebar_item.update(item_info) - sidebar_items.append(sidebar_item) - - return sidebar_items diff --git a/frappe/patches.txt b/frappe/patches.txt index 5b076fab59..93ff293ccb 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -252,3 +252,4 @@ frappe.patches.v16_0.auto_generate_desktop_icon_and_sidebar frappe.patches.v16_0.add_private_workspaces_to_sidebar frappe.core.doctype.communication_link.patches.copy_communication_date_to_link frappe.core.doctype.communication.patches.drop_ref_dt_dn_index +frappe.patches.v16_0.auto_generate_sidebar_from_modules diff --git a/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py b/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py new file mode 100644 index 0000000000..008c7a1b6b --- /dev/null +++ b/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py @@ -0,0 +1,61 @@ +import frappe + + +def execute(): + """Auto generate sidebar from module""" + sidebars = [] + for module in frappe.get_all("Module Def", pluck="name"): + if not ( + frappe.db.exists("Workspace Sidebar", {"module": module}) + or frappe.db.exists("Workspace Sidebar", {"name": module}) + ): + print("Fetching information for Module", module) + module_info = get_module_info(module) + sidebar_items = create_sidebar_items(module_info) + sidebar = frappe.new_doc("Workspace Sidebar") + sidebar.title = module + sidebar.items = sidebar_items + sidebar.header_icon = "hammer" + sidebars.append(sidebar) + sidebar.save() + return sidebars + + +def get_module_info(module_name): + entities = ["Workspace", "Dashboard", "DocType", "Report", "Page"] + module_info = {} + + for entity in entities: + module_info[entity] = {} + filters = [{"module": module_name}] + if entity.lower() == "doctype": + filters.append({"istable": 0}) + module_info[entity] = frappe.get_all(entity, filters=filters, pluck="name") + + return module_info + + +def create_sidebar_items(module_info): + sidebar_items = [] + for entity, items in module_info.items(): + if entity.lower() == "report": + section_break = frappe.new_doc("Workspace Sidebar Item") + section_break.update( + { + "type": "Section Break", + } + ) + sidebar_items.append(section_break) + for item in items: + item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item} + if entity.lower() == "workspace": + item_info["icon"] = "home" + + if entity.lower() == "doctype" and "settings" in item.lower(): + item_info["icon"] = "settings" + + sidebar_item = frappe.new_doc("Workspace Sidebar Item") + sidebar_item.update(item_info) + sidebar_items.append(sidebar_item) + + return sidebar_items From e2ff41dc89bfc1877cd7425c387808ceea7432d6 Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 9 Dec 2025 01:59:47 +0530 Subject: [PATCH 3/5] fix: enhance the sidebar which is generated --- .../auto_generate_sidebar_from_modules.py | 54 +++++++++++++++---- frappe/public/js/frappe/ui/sidebar/sidebar.js | 4 +- .../js/frappe/ui/sidebar/sidebar_item.js | 4 +- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py b/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py index 008c7a1b6b..3f78ffeb86 100644 --- a/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py +++ b/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py @@ -18,7 +18,6 @@ def execute(): sidebar.header_icon = "hammer" sidebars.append(sidebar) sidebar.save() - return sidebars def get_module_info(module_name): @@ -32,30 +31,63 @@ def get_module_info(module_name): filters.append({"istable": 0}) module_info[entity] = frappe.get_all(entity, filters=filters, pluck="name") + # if module info has no workspaces, then move doctypes to the front + if not module_info.get("Workspace"): + module_info = { + "DocType": module_info.get("DocType"), + "Workspace": module_info.get("Workspace"), + "Report": module_info.get("Report"), + "Dashboard": module_info.get("Dashboard"), + "Page": module_info.get("Page"), + } return module_info def create_sidebar_items(module_info): sidebar_items = [] + idx = 1 + + section_entities = {"report": "Reports", "dashboard": "Dashboards", "page": "Pages"} + for entity, items in module_info.items(): - if entity.lower() == "report": - section_break = frappe.new_doc("Workspace Sidebar Item") - section_break.update( - { - "type": "Section Break", - } - ) + section_break_added = False + entity_lower = entity.lower() + + if entity_lower in section_entities: + if entity_lower == "report": + section_break = add_section_breaks("Reports", idx) + elif entity_lower in ("dashboard", "page") and len(items) > 1: + section_break = add_section_breaks(section_entities[entity_lower], idx) + section_break_added = True sidebar_items.append(section_break) + idx += 1 + for item in items: - item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item} - if entity.lower() == "workspace": + print(entity, item) + item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item, "idx": idx} + + if entity_lower == "report": + item_info["child"] = 1 + + if entity_lower == "workspace": item_info["icon"] = "home" - if entity.lower() == "doctype" and "settings" in item.lower(): + if entity_lower == "doctype" and "settings" in item.lower(): item_info["icon"] = "settings" + if section_break_added: + item_info["child"] = 1 + sidebar_item = frappe.new_doc("Workspace Sidebar Item") sidebar_item.update(item_info) sidebar_items.append(sidebar_item) + idx += 1 + return sidebar_items + + +def add_section_breaks(label, idx): + section_break = frappe.new_doc("Workspace Sidebar Item") + section_break.update({"label": label, "type": "Section Break", "idx": idx}) + return section_break diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index a101dfddf4..5d7106de6f 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -161,11 +161,11 @@ frappe.ui.Sidebar = class Sidebar { let match = false; const that = this; $(".item-anchor").each(function () { - let href = $(this).attr("href")?.split("?")[0]; + let href = decodeURIComponent($(this).attr("href")?.split("?")[0]); const path = decodeURIComponent(window.location.pathname); // Match only if path equals href or starts with it followed by "/" or end of string - const isActive = new RegExp(`^${href}(?:/|$)`).test(path); + const isActive = href === path; if (href && isActive) { match = true; if (that.active_item) that.active_item.removeClass("active-sidebar"); diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js index bb88ec5f4a..d67d435fba 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js @@ -54,7 +54,9 @@ frappe.ui.sidebar_item.TypeLink = class SidebarItem { }); } } - return path; + if (path) { + return encodeURI(path); + } } prepare() {} make() { From 92a73cbd5e73e939d0083db40353c787f43ab96e Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 9 Dec 2025 15:16:37 +0530 Subject: [PATCH 4/5] refactor: move logic to a function --- frappe/boot.py | 19 ++- frappe/patches.txt | 1 - .../auto_generate_sidebar_from_modules.py | 93 ------------ frappe/public/js/frappe/ui/sidebar/sidebar.js | 2 +- frappe/utils/install.py | 133 ++++++++++++++++++ 5 files changed, 148 insertions(+), 100 deletions(-) delete mode 100644 frappe/patches/v16_0/auto_generate_sidebar_from_modules.py diff --git a/frappe/boot.py b/frappe/boot.py index 2af5107988..dc8c99199e 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -528,16 +528,25 @@ def get_sentry_dsn(): def get_sidebar_items(): + from frappe.utils.install import auto_generate_sidebar_from_module + sidebars = frappe.get_all( "Workspace Sidebar", fields=["name", "header_icon"], filters={"name": ["not like", "%My Workspaces%"]} ) + module_sidebars = auto_generate_sidebar_from_module() + sidebars.extend(module_sidebars) add_user_specific_sidebar(sidebars) sidebar_items = {} for s in sidebars: - w = frappe.get_doc("Workspace Sidebar", s.get("name")) - sidebar_items[s["name"].lower()] = { - "label": s.get("name"), + sidebar_title = s.get("name") + if sidebar_title: + w = frappe.get_doc("Workspace Sidebar", sidebar_title) + else: + sidebar_title = s.title + w = s + sidebar_items[sidebar_title.lower()] = { + "label": sidebar_title, "items": [], "header_icon": s.get("header_icon"), "module": w.module, @@ -569,11 +578,11 @@ def get_sidebar_items(): "ref_doctype": ref_doctype, } if ( - "My Workspaces" in s["name"] + "My Workspaces" in sidebar_title or si.type == "Section Break" or w.is_item_allowed(si.link_to, si.link_type) ): - sidebar_items[s.get("name").lower()]["items"].append(workspace_sidebar) + sidebar_items[sidebar_title.lower()]["items"].append(workspace_sidebar) old_name = f"my workspaces-{frappe.session.user.lower()}" if old_name in sidebar_items.keys(): diff --git a/frappe/patches.txt b/frappe/patches.txt index 93ff293ccb..5b076fab59 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -252,4 +252,3 @@ frappe.patches.v16_0.auto_generate_desktop_icon_and_sidebar frappe.patches.v16_0.add_private_workspaces_to_sidebar frappe.core.doctype.communication_link.patches.copy_communication_date_to_link frappe.core.doctype.communication.patches.drop_ref_dt_dn_index -frappe.patches.v16_0.auto_generate_sidebar_from_modules diff --git a/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py b/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py deleted file mode 100644 index 3f78ffeb86..0000000000 --- a/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py +++ /dev/null @@ -1,93 +0,0 @@ -import frappe - - -def execute(): - """Auto generate sidebar from module""" - sidebars = [] - for module in frappe.get_all("Module Def", pluck="name"): - if not ( - frappe.db.exists("Workspace Sidebar", {"module": module}) - or frappe.db.exists("Workspace Sidebar", {"name": module}) - ): - print("Fetching information for Module", module) - module_info = get_module_info(module) - sidebar_items = create_sidebar_items(module_info) - sidebar = frappe.new_doc("Workspace Sidebar") - sidebar.title = module - sidebar.items = sidebar_items - sidebar.header_icon = "hammer" - sidebars.append(sidebar) - sidebar.save() - - -def get_module_info(module_name): - entities = ["Workspace", "Dashboard", "DocType", "Report", "Page"] - module_info = {} - - for entity in entities: - module_info[entity] = {} - filters = [{"module": module_name}] - if entity.lower() == "doctype": - filters.append({"istable": 0}) - module_info[entity] = frappe.get_all(entity, filters=filters, pluck="name") - - # if module info has no workspaces, then move doctypes to the front - if not module_info.get("Workspace"): - module_info = { - "DocType": module_info.get("DocType"), - "Workspace": module_info.get("Workspace"), - "Report": module_info.get("Report"), - "Dashboard": module_info.get("Dashboard"), - "Page": module_info.get("Page"), - } - return module_info - - -def create_sidebar_items(module_info): - sidebar_items = [] - idx = 1 - - section_entities = {"report": "Reports", "dashboard": "Dashboards", "page": "Pages"} - - for entity, items in module_info.items(): - section_break_added = False - entity_lower = entity.lower() - - if entity_lower in section_entities: - if entity_lower == "report": - section_break = add_section_breaks("Reports", idx) - elif entity_lower in ("dashboard", "page") and len(items) > 1: - section_break = add_section_breaks(section_entities[entity_lower], idx) - section_break_added = True - sidebar_items.append(section_break) - idx += 1 - - for item in items: - print(entity, item) - item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item, "idx": idx} - - if entity_lower == "report": - item_info["child"] = 1 - - if entity_lower == "workspace": - item_info["icon"] = "home" - - if entity_lower == "doctype" and "settings" in item.lower(): - item_info["icon"] = "settings" - - if section_break_added: - item_info["child"] = 1 - - sidebar_item = frappe.new_doc("Workspace Sidebar Item") - sidebar_item.update(item_info) - sidebar_items.append(sidebar_item) - - idx += 1 - - return sidebar_items - - -def add_section_breaks(label, idx): - section_break = frappe.new_doc("Workspace Sidebar Item") - section_break.update({"label": label, "type": "Section Break", "idx": idx}) - return section_break diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index 5d7106de6f..e4e3810216 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -445,7 +445,7 @@ frappe.ui.Sidebar = class Sidebar { if (route.length == 2) { workspace_title = this.get_correct_workspace_sidebars(route[1]); } else { - workspace_title = this.get_correct_workspace_sidebars(route); + workspace_title = this.get_correct_workspace_sidebars(route[0]); } let module_name = workspace_title[0]; if (module_name) { diff --git a/frappe/utils/install.py b/frappe/utils/install.py index 60a393882e..2f885370c8 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -5,6 +5,7 @@ import getpass import frappe from frappe.geo.doctype.country.country import import_country_and_currency from frappe.utils import cint +from frappe.utils.caching import site_cache from frappe.utils.password import update_password @@ -221,3 +222,135 @@ def delete_desktop_icon_and_sidebar(app_name, dry_run=False): if dry_run: # Delete icons and sidebars frappe.db.commit() # nosemgrep + + +@site_cache() +def auto_generate_sidebar_from_module(): + """Auto generate sidebar from module""" + sidebars = [] + for module in frappe.get_all("Module Def", pluck="name"): + if not ( + frappe.db.exists("Workspace Sidebar", {"module": module}) + or frappe.db.exists("Workspace Sidebar", {"name": module}) + ): + module_info = get_module_info(module) + sidebar_items = create_sidebar_items(module_info) + sidebar = frappe.new_doc("Workspace Sidebar") + sidebar.title = module + sidebar.items = sidebar_items + sidebar.module = module + sidebar.header_icon = "hammer" + sidebar.app = frappe.local.module_app.get(frappe.scrub(module), None) + sidebars.append(sidebar) + return sidebars + + +def get_module_info(module_name): + entities = ["Workspace", "Dashboard", "DocType", "Report", "Page"] + module_info = {} + + for entity in entities: + module_info[entity] = {} + filters = [{"module": module_name}] + pluck = "name" + fieldnames = ["name"] + if entity.lower() == "doctype": + filters.append({"istable": 0}) + if entity.lower() == "page": + fieldnames.append("title") + pluck = None + module_info[entity] = frappe.get_all( + entity, filters=filters, fields=fieldnames, pluck=pluck, order_by="creation asc" + ) + + # if module info has no workspaces, then move doctypes to the front + if not module_info.get("Workspace"): + module_info = { + "DocType": module_info.get("DocType"), + "Workspace": module_info.get("Workspace"), + "Report": module_info.get("Report"), + "Dashboard": module_info.get("Dashboard"), + "Page": module_info.get("Page"), + } + top_doctypes = choose_top_doctypes(module_info.get("DocType")) + if top_doctypes: + module_info["DocType"] = choose_top_doctypes(module_info.get("DocType")) + return module_info + + +def choose_top_doctypes(doctype_names): + doctype_limit = 3 + if len(doctype_names) > doctype_limit: + try: + doctype_count_map = {} + for doctype in doctype_names: + doctype_count_map[doctype] = frappe.db.count(doctype) + top_doctypes = [ + name + for name, count in sorted(doctype_count_map.items(), key=lambda x: x[1], reverse=True)[ + :doctype_limit + ] + ] + return top_doctypes + except frappe.db.ProgrammingError: + # catches table not found errors + return None + + +def create_sidebar_items(module_info): + sidebar_items = [] + idx = 1 + + section_entities = {"report": "Reports", "dashboard": "Dashboards", "page": "Pages"} + + for entity, items in module_info.items(): + section_break_added = False + entity_lower = entity.lower() + + if entity_lower in section_entities: + if entity_lower == "report": + section_break = add_section_breaks("Reports", idx) + elif entity_lower in ("dashboard", "page") and len(items) > 1: + section_break = add_section_breaks(section_entities[entity_lower], idx) + section_break_added = True + sidebar_items.append(section_break) + idx += 1 + + for item in items: + print(entity, item) + item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item, "idx": idx} + + if entity_lower == "report": + item_info["child"] = 1 + item_info["icon"] = "table" + + if entity_lower == "page": + item_info["label"] = item.get("title") + item_info["link_to"] = item.get("name") + + if entity_lower == "workspace": + item_info["icon"] = "home" + item_info["icon"] = "wallpaper" + + if entity_lower == "page": + item_info["icon"] = "panel-top" + + if entity_lower == "doctype" and "settings" in item.lower(): + item_info["icon"] = "settings" + + if section_break_added: + item_info["child"] = 1 + + sidebar_item = frappe.new_doc("Workspace Sidebar Item") + sidebar_item.update(item_info) + sidebar_items.append(sidebar_item) + + idx += 1 + + return sidebar_items + + +def add_section_breaks(label, idx): + section_break = frappe.new_doc("Workspace Sidebar Item") + section_break.update({"label": label, "type": "Section Break", "idx": idx}) + return section_break From 9fc078e112d64002fbb3b8f13d893b47f5bf08e4 Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 9 Dec 2025 15:44:32 +0530 Subject: [PATCH 5/5] fix: add a simpler icon for list --- frappe/public/js/frappe/ui/sidebar/sidebar_item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js index d67d435fba..67d76c4689 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js @@ -63,7 +63,7 @@ frappe.ui.sidebar_item.TypeLink = class SidebarItem { this.path = this.get_path(); this.set_suffix(); if (!this.item.icon && !(this.item.child && this.item.parent.indent)) { - this.item.icon = "list-alt"; + this.item.icon = "list"; } this.wrapper = $( frappe.render_template("sidebar_item", {