These are prone to breakage and shouldn't be deleted automatically. This mostly works fine but there can be cases where it doesn't. So best to leave them untouched.
176 lines
4.7 KiB
Python
176 lines
4.7 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: MIT. See LICENSE
|
|
"""
|
|
Sync's doctype and docfields from txt files to database
|
|
perms will get synced only if none exist
|
|
"""
|
|
import os
|
|
|
|
import frappe
|
|
from frappe.cache_manager import clear_controller_cache
|
|
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.utils import update_progress_bar
|
|
|
|
IMPORTABLE_DOCTYPES = [
|
|
("core", "doctype"),
|
|
("core", "page"),
|
|
("core", "report"),
|
|
("desk", "dashboard_chart_source"),
|
|
("printing", "print_format"),
|
|
("website", "web_page"),
|
|
("website", "website_theme"),
|
|
("website", "web_form"),
|
|
("website", "web_template"),
|
|
("email", "notification"),
|
|
("printing", "print_style"),
|
|
("desk", "workspace"),
|
|
("desk", "onboarding_step"),
|
|
("desk", "module_onboarding"),
|
|
("desk", "form_tour"),
|
|
("custom", "client_script"),
|
|
("core", "server_script"),
|
|
("custom", "custom_field"),
|
|
("custom", "property_setter"),
|
|
]
|
|
|
|
|
|
def sync_all(force=0, reset_permissions=False):
|
|
_patch_mode(True)
|
|
|
|
for app in frappe.get_installed_apps():
|
|
sync_for(app, force, reset_permissions=reset_permissions)
|
|
|
|
_patch_mode(False)
|
|
|
|
frappe.clear_cache()
|
|
|
|
|
|
def sync_for(app_name, force=0, reset_permissions=False):
|
|
files = []
|
|
|
|
if app_name == "frappe":
|
|
# these need to go first at time of install
|
|
|
|
FRAPPE_PATH = frappe.get_app_path("frappe")
|
|
|
|
for core_module in [
|
|
"docfield",
|
|
"docperm",
|
|
"doctype_action",
|
|
"doctype_link",
|
|
"doctype_state",
|
|
"role",
|
|
"has_role",
|
|
"doctype",
|
|
]:
|
|
files.append(os.path.join(FRAPPE_PATH, "core", "doctype", core_module, f"{core_module}.json"))
|
|
|
|
for custom_module in ["custom_field", "property_setter"]:
|
|
files.append(
|
|
os.path.join(FRAPPE_PATH, "custom", "doctype", custom_module, f"{custom_module}.json")
|
|
)
|
|
|
|
for website_module in ["web_form", "web_template", "web_form_field", "portal_menu_item"]:
|
|
files.append(
|
|
os.path.join(FRAPPE_PATH, "website", "doctype", website_module, f"{website_module}.json")
|
|
)
|
|
|
|
for desk_module in [
|
|
"number_card",
|
|
"dashboard_chart",
|
|
"dashboard",
|
|
"onboarding_permission",
|
|
"onboarding_step",
|
|
"onboarding_step_map",
|
|
"module_onboarding",
|
|
"workspace_link",
|
|
"workspace_chart",
|
|
"workspace_shortcut",
|
|
"workspace_quick_list",
|
|
"workspace_number_card",
|
|
"workspace_custom_block",
|
|
"workspace",
|
|
]:
|
|
files.append(os.path.join(FRAPPE_PATH, "desk", "doctype", desk_module, f"{desk_module}.json"))
|
|
|
|
for module_name, document_type in IMPORTABLE_DOCTYPES:
|
|
file = os.path.join(FRAPPE_PATH, module_name, "doctype", document_type, f"{document_type}.json")
|
|
if file not in files:
|
|
files.append(file)
|
|
|
|
for module_name in frappe.local.app_modules.get(app_name) or []:
|
|
folder = os.path.dirname(frappe.get_module(app_name + "." + module_name).__file__)
|
|
files = get_doc_files(files=files, start_path=folder)
|
|
|
|
l = len(files)
|
|
|
|
if l:
|
|
for i, doc_path in enumerate(files):
|
|
import_file_by_path(
|
|
doc_path, force=force, ignore_version=True, reset_permissions=reset_permissions
|
|
)
|
|
|
|
frappe.db.commit()
|
|
|
|
# show progress bar
|
|
update_progress_bar(f"Updating DocTypes for {app_name}", i, l)
|
|
|
|
# print each progress bar on new line
|
|
print()
|
|
|
|
|
|
def get_doc_files(files, start_path):
|
|
"""walk and sync all doctypes and pages"""
|
|
|
|
files = files or []
|
|
|
|
for _module, doctype in IMPORTABLE_DOCTYPES:
|
|
doctype_path = os.path.join(start_path, doctype)
|
|
if os.path.exists(doctype_path):
|
|
for docname in os.listdir(doctype_path):
|
|
if os.path.isdir(os.path.join(doctype_path, docname)):
|
|
doc_path = os.path.join(doctype_path, docname, docname) + ".json"
|
|
if os.path.exists(doc_path):
|
|
if doc_path not in files:
|
|
files.append(doc_path)
|
|
|
|
return files
|
|
|
|
|
|
def remove_orphan_doctypes():
|
|
"""Find and remove any orphaned doctypes.
|
|
|
|
These are doctypes for which code and schema file is
|
|
deleted but entry is present in DocType table.
|
|
|
|
Note: Deleting the entry doesn't delete any data.
|
|
So this is supposed to be non-destrictive operation.
|
|
"""
|
|
|
|
doctype_names = frappe.get_all("DocType", {"custom": 0}, pluck="name")
|
|
orphan_doctypes = []
|
|
|
|
clear_controller_cache()
|
|
class_overrides = frappe.get_hooks("override_doctype_class", {})
|
|
|
|
for doctype in doctype_names:
|
|
if doctype in class_overrides:
|
|
continue
|
|
try:
|
|
get_controller(doctype=doctype)
|
|
except ImportError:
|
|
orphan_doctypes.append(doctype)
|
|
except Exception:
|
|
continue
|
|
|
|
if not orphan_doctypes:
|
|
return
|
|
|
|
print(f"Orphaned DocType(s) found: {', '.join(orphan_doctypes)}")
|
|
for i, name in enumerate(orphan_doctypes):
|
|
frappe.delete_doc("DocType", name, force=True, ignore_missing=True)
|
|
update_progress_bar("Deleting orphaned DocTypes", i, len(orphan_doctypes))
|
|
frappe.db.commit()
|
|
print()
|