refactor: make app level folders first class

This commit is contained in:
sokumon 2025-10-27 03:06:51 +05:30
parent 541f8f3360
commit c057c730c5
6 changed files with 89 additions and 42 deletions

View file

@ -80,7 +80,8 @@
"depends_on": "eval: doc.standard == 1",
"fieldname": "app",
"fieldtype": "Autocomplete",
"label": "App"
"label": "App",
"options": "Installed Applications"
},
{
"fieldname": "link_to",
@ -102,7 +103,7 @@
}
],
"links": [],
"modified": "2025-10-24 01:21:38.915511",
"modified": "2025-10-26 23:18:18.367410",
"modified_by": "Administrator",
"module": "Desk",
"name": "Desktop Icon",

View file

@ -10,6 +10,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.modules.export_file import write_document_file
from frappe.modules.import_file import import_file_by_path
from frappe.modules.utils import create_directory_on_app_path, get_app_level_directory_path
class DesktopIcon(Document):
@ -51,7 +52,7 @@ class DesktopIcon(Document):
self.export_desktop_icon()
def export_desktop_icon(self):
folder_path = create_directory_if_not_exists(self.app)
folder_path = create_directory_on_app_path("desktop_icon", self.app)
file_path = os.path.join(folder_path, f"{frappe.scrub(self.label)}.json")
doc_export = self.as_dict(no_nulls=True, no_private_properties=True)
@ -59,7 +60,7 @@ class DesktopIcon(Document):
icon_file_doc.write(frappe.as_json(doc_export) + "\n")
def delete_desktop_icon_file(self):
folder_path = create_directory_if_not_exists(self.app)
folder_path = create_directory_on_app_path("desktop_icon", self.app)
file_path = os.path.join(folder_path, f"{frappe.scrub(self.label)}.json")
if not os.path.exists(file_path):
os.remove(file_path)
@ -68,22 +69,6 @@ class DesktopIcon(Document):
clear_desktop_icons_cache()
def create_directory_if_not_exists(app_name):
app_path = frappe.get_app_path(app_name)
desktop_icon_path = os.path.join(app_path, "desktop_icon")
if not os.path.exists(desktop_icon_path):
frappe.create_folder(desktop_icon_path)
return desktop_icon_path
def get_desktop_icon_directory(app_name):
app_path = frappe.get_app_path(app_name)
desktop_icon_path = os.path.join(app_path, "desktop_icon")
return desktop_icon_path
def after_doctype_insert():
pass
@ -440,7 +425,7 @@ def sync_desktop_icons():
def sync_icons(app_name):
icon_directory = get_desktop_icon_directory(app_name)
icon_directory = get_app_level_directory_path("desktop_icon", app_name)
if os.path.exists(icon_directory):
icon_files = [os.path.join(icon_directory, filename) for filename in os.listdir(icon_directory)]
for doc_path in icon_files:
@ -671,8 +656,9 @@ def create_desktop_icons_from_workspace():
):
frappe.db.set_value("Desktop Icon", parent_icon, "icon", icon.icon)
insert = False
if insert:
if insert and not frappe.db.exists("Desktop Icon", [{"label": icon.label, "type": icon.type}]):
icon.insert(ignore_if_duplicate=True)
frappe.db.commit()
def generate_color():
@ -687,7 +673,6 @@ def create_desktop_icons_from_installed_apps():
app_details = frappe.get_hooks("add_to_apps_screen", app_name=a)
if len(app_details) != 0:
icon = frappe.new_doc("Desktop Icon")
icon.route = app_details[0]["route"]
icon.label = app_details[0]["title"]
icon.type = "External"
icon.standard = 1
@ -695,8 +680,10 @@ def create_desktop_icons_from_installed_apps():
icon.icon_type = "App"
icon.link = app_details[0]["route"]
icon.logo_url = app_details[0]["logo"]
icon.save()
if not frappe.db.exists("Desktop Icon", [{"label": icon.label, "type": icon.type}]):
icon.save()
index += 1
frappe.db.commit()
@frappe.whitelist()
@ -713,6 +700,4 @@ def set_sequence(desktop_icons):
def create_desktop_icon():
create_desktop_icons_from_installed_apps()
frappe.db.commit()
create_desktop_icons_from_workspace()
frappe.db.commit()

View file

@ -7,7 +7,8 @@
"engine": "InnoDB",
"field_order": [
"title",
"module",
"header_icon",
"app",
"items"
],
"fields": [
@ -24,16 +25,21 @@
"options": "Workspace Sidebar Item"
},
{
"fieldname": "module",
"fieldtype": "Link",
"label": "Module",
"options": "Module Def"
"fieldname": "header_icon",
"fieldtype": "Icon",
"label": "Header Icon"
},
{
"fieldname": "app",
"fieldtype": "Autocomplete",
"label": "App",
"options": "Installed Applications"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-10-01 07:52:51.149158",
"modified": "2025-10-26 23:21:30.166695",
"modified_by": "Administrator",
"module": "Desk",
"name": "Workspace Sidebar",

View file

@ -1,13 +1,14 @@
# Copyright (c) 2025, Frappe Technologies and contributors
# For license information, please see license.txt
import os
from json import JSONDecodeError, dumps, loads
import frappe
from frappe import _
from frappe.desk.doctype.workspace.workspace import is_workspace_manager
from frappe.model.document import Document
from frappe.modules.export_file import delete_folder, export_to_files
from frappe.modules.utils import create_directory_on_app_path
class WorkspaceSidebar(Document):
@ -20,30 +21,54 @@ class WorkspaceSidebar(Document):
from frappe.desk.doctype.workspace_sidebar_item.workspace_sidebar_item import WorkspaceSidebarItem
from frappe.types import DF
app: DF.Autocomplete | None
items: DF.Table[WorkspaceSidebarItem]
title: DF.Data | None
# end: auto-generated types
def on_update(self):
if self.module and frappe.conf.developer_mode:
export_to_files(record_list=[["Workspace Sidebar", self.name]], record_module=self.module)
if frappe.conf.developer_mode:
if self.app:
self.export_sidebar()
def export_sidebar(self):
folder_path = create_directory_on_app_path("workspace_sidebar", self.app)
file_path = os.path.join(folder_path, f"{frappe.scrub(self.title)}.json")
doc_export = self.as_dict(no_nulls=True, no_private_properties=True)
with open(file_path, "w+") as doc_file:
doc_file.write(frappe.as_json(doc_export) + "\n")
def delete_desktop_icon_file(self):
folder_path = create_directory_on_app_path("workspace_sidebar", self.app)
file_path = os.path.join(folder_path, f"{frappe.scrub(self.title)}.json")
if not os.path.exists(file_path):
os.remove(file_path)
def on_trash(self):
if not is_workspace_manager():
if is_workspace_manager():
if frappe.conf.developer_mode and self.standard == 1 and self.app:
self.delete_desktop_icon_file()
else:
frappe.throw(_("You need to be Workspace Manager to delete a public workspace."))
def after_delete(self):
if self.module and frappe.conf.developer_mode:
delete_folder(self.module, "Workspace Sidebar", self.name)
def create_workspace_sidebar_for_workspaces():
all_workspaces = frappe.get_all("Workspace", pluck="name")
from frappe.query_builder import DocType
workspace = DocType("Workspace")
all_workspaces = (
frappe.qb.from_(workspace)
.select(workspace.name)
.where((workspace.public == 1) & (workspace.name != "Welcome Workspace"))
).run(pluck=True)
existing_sidebars = frappe.get_all("Workspace Sidebar", pluck="title")
for workspace in all_workspaces:
if workspace not in existing_sidebars:
sidebar = frappe.new_doc("Workspace Sidebar")
sidebar.title = workspace
sidebar.header_icon = frappe.db.get_value("Workspace", workspace, "icon")
sidebar.save()

View file

@ -13,6 +13,7 @@ from frappe.desk.doctype.desktop_icon.desktop_icon import sync_desktop_icons
from frappe.model.base_document import get_controller
from frappe.modules.import_file import import_file_by_path
from frappe.modules.patch_handler import _patch_mode
from frappe.modules.utils import get_app_level_directory_path
from frappe.utils import update_progress_bar
IMPORTABLE_DOCTYPES = [
@ -108,8 +109,15 @@ def sync_for(app_name, force=0, reset_permissions=False):
folder = os.path.dirname(frappe.get_module(app_name + "." + module_name).__file__)
files = get_doc_files(files=files, start_path=folder)
l = len(files)
app_level_folders = ["desktop_icon", "workspace_sidebar"]
for folder_name in app_level_folders:
directory_path = get_app_level_directory_path(folder_name, app_name)
if os.path.exists(directory_path):
icon_files = [os.path.join(directory_path, filename) for filename in os.listdir(directory_path)]
for doc_path in icon_files:
files.append(doc_path)
l = len(files)
if l:
for i, doc_path in enumerate(files):
imported = import_file_by_path(

View file

@ -7,6 +7,7 @@ Utilities for using modules
import json
import os
from pathlib import Path
import shutil
from textwrap import dedent, indent
from typing import TYPE_CHECKING, Union
@ -358,3 +359,24 @@ def make_boilerplate(
custom_controller=controller_body,
)
target.write(frappe.as_unicode(controller_file_content))
def create_directory_on_app_path(folder_name, app_name):
app_path = frappe.get_app_path(app_name)
folder_path = os.path.join(app_path, folder_name)
if not os.path.exists(folder_path):
frappe.create_folder(folder_path)
return folder_path
def get_app_level_directory_path(folder_name, app_name):
app_path = frappe.get_app_path(app_name)
path = os.path.join(app_path, folder_name)
return path
def delete_app_level_folder(folder_name, app_name):
path = get_app_level_directory_path(folder_name, app_name)
shutil.rmtree(path, ignore_errors=True)