# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt from __future__ import unicode_literals import frappe import json from frappe import _ from frappe.boot import get_allowed_pages, get_allowed_reports from frappe.desk.doctype.desktop_icon.desktop_icon import set_hidden, clear_desktop_icons_cache @frappe.whitelist() def get(module): """Returns data (sections, list of reports, counts) to render module view in desk: `/desk/#Module/[name]`.""" data = get_data(module) out = { "data": data } return out @frappe.whitelist() def hide_module(module): set_hidden(module, frappe.session.user, 1) clear_desktop_icons_cache() def get_data(module, build=True): """Get module data for the module view `desk/#Module/[name]`""" doctype_info = get_doctype_info(module) data = build_config_from_file(module) if not data: data = build_standard_config(module, doctype_info) else: add_custom_doctypes(data, doctype_info) add_section(data, _("Custom Reports"), "fa fa-list-alt", get_report_list(module)) data = combine_common_sections(data) data = apply_permissions(data) # set_last_modified(data) if build: exists_cache = {} def doctype_contains_a_record(name): exists = exists_cache.get(name) if not exists: if not frappe.db.get_value('DocType', name, 'issingle'): exists = frappe.db.count(name) else: exists = True exists_cache[name] = exists return exists for section in data: for item in section["items"]: # Onboarding # First disable based on exists of depends_on list doctype = item.get("doctype") dependencies = item.get("dependencies") or None if not dependencies and doctype: item["dependencies"] = [doctype] dependencies = item.get("dependencies") if dependencies: incomplete_dependencies = [d for d in dependencies if not doctype_contains_a_record(d)] if len(incomplete_dependencies): item["incomplete_dependencies"] = incomplete_dependencies if item.get("onboard"): # Mark Spotlights for initial if item.get("type") == "doctype": name = item.get("name") count = doctype_contains_a_record(name) item["count"] = count return data def build_config_from_file(module): """Build module info from `app/config/desktop.py` files.""" data = [] module = frappe.scrub(module) for app in frappe.get_installed_apps(): try: data += get_config(app, module) except ImportError: pass return filter_by_restrict_to_domain(data) def filter_by_restrict_to_domain(data): """ filter Pages and DocType depending on the Active Module(s) """ mapper = { "page": "Page", "doctype": "DocType" } active_domains = frappe.get_active_domains() for d in data: _items = [] for item in d.get("items", []): doctype = mapper.get(item.get("type")) doctype_domain = frappe.db.get_value(doctype, item.get("name"), "restrict_to_domain") or '' if not doctype_domain or (doctype_domain in active_domains): _items.append(item) d.update({ "items": _items }) return data def build_standard_config(module, doctype_info): """Build standard module data from DocTypes.""" if not frappe.db.get_value("Module Def", module): frappe.throw(_("Module Not Found")) data = [] add_section(data, _("Documents"), "fa fa-star", [d for d in doctype_info if d.document_type in ("Document", "Transaction")]) add_section(data, _("Setup"), "fa fa-cog", [d for d in doctype_info if d.document_type in ("Master", "Setup", "")]) add_section(data, _("Standard Reports"), "fa fa-list", get_report_list(module, is_standard="Yes")) return data def add_section(data, label, icon, items): """Adds a section to the module data.""" if not items: return data.append({ "label": label, "icon": icon, "items": items }) def add_custom_doctypes(data, doctype_info): """Adds Custom DocTypes to modules setup via `config/desktop.py`.""" add_section(data, _("Documents"), "fa fa-star", [d for d in doctype_info if (d.custom and d.document_type in ("Document", "Transaction"))]) add_section(data, _("Setup"), "fa fa-cog", [d for d in doctype_info if (d.custom and d.document_type in ("Setup", "Master", ""))]) def get_doctype_info(module): """Returns list of non child DocTypes for given module.""" active_domains = frappe.get_active_domains() doctype_info = frappe.get_all("DocType", filters={ "module": module, "istable": 0 }, or_filters={ "ifnull(restrict_to_domain, '')": "", "restrict_to_domain": ("in", active_domains) }, fields=["'doctype' as type", "name", "description", "document_type", "custom", "issingle"], order_by="custom asc, document_type desc, name asc") for d in doctype_info: d.document_type = d.document_type or "" d.description = _(d.description or "") return doctype_info def combine_common_sections(data): """Combine sections declared in separate apps.""" sections = [] sections_dict = {} for each in data: if each["label"] not in sections_dict: sections_dict[each["label"]] = each sections.append(each) else: sections_dict[each["label"]]["items"] += each["items"] return sections def apply_permissions(data): default_country = frappe.db.get_default("country") user = frappe.get_user() user.build_permissions() allowed_pages = get_allowed_pages() allowed_reports = get_allowed_reports() new_data = [] for section in data: new_items = [] for item in (section.get("items") or []): item = frappe._dict(item) if item.country and item.country!=default_country: continue if ((item.type=="doctype" and item.name in user.can_read) or (item.type=="page" and item.name in allowed_pages) or (item.type=="report" and item.name in allowed_reports) or item.type=="help"): new_items.append(item) if new_items: new_section = section.copy() new_section["items"] = new_items new_data.append(new_section) return new_data def get_config(app, module): """Load module info from `[app].config.[module]`.""" config = frappe.get_module("{app}.config.{module}".format(app=app, module=module)) config = config.get_data() sections = [s for s in config if s.get("condition", True)] for section in sections: for item in section["items"]: if item["type"]=="report" and frappe.db.get_value("Report", item["name"], "disabled")==1: section["items"].remove(item) continue if not "label" in item: item["label"] = _(item["name"]) return sections def config_exists(app, module): try: frappe.get_module("{app}.config.{module}".format(app=app, module=module)) return True except ImportError: return False def add_setup_section(config, app, module, label, icon): """Add common sections to `/desk#Module/Setup`""" try: setup_section = get_setup_section(app, module, label, icon) if setup_section: config.append(setup_section) except ImportError: pass def get_setup_section(app, module, label, icon): """Get the setup section from each module (for global Setup page).""" config = get_config(app, module) for section in config: if section.get("label")==_("Setup"): return { "label": label, "icon": icon, "items": section["items"] } def get_onboard_items(app, module): try: sections = get_config(app, module) except ImportError: return [] onboard_items = [] fallback_items = [] if not sections: doctype_info = get_doctype_info(module) sections = build_standard_config(module, doctype_info) for section in sections: for item in section["items"]: if item.get("onboard", 0) == 1: onboard_items.append(item) # in case onboard is not set fallback_items.append(item) if len(onboard_items) > 5: return onboard_items return onboard_items or fallback_items @frappe.whitelist() def get_links(app, module): try: sections = get_config(app, frappe.scrub(module)) except ImportError: return [] link_names = [] for section in sections: for item in section["items"]: link_names.append(item.get("label")) print(link_names) return link_names @frappe.whitelist() def get_module_link_items_from_dict(module_link_list_map): module_link_list_map = json.loads(module_link_list_map) module_links = {} for module, data in module_link_list_map.items(): print(data) module_links[module] = get_module_link_items_from_list(data["app"], module, data["links"]) return module_links def get_module_link_items_from_list(app, module, list_of_link_names): try: sections = get_config(app, frappe.scrub(module)) except ImportError: return [] links = [] for section in sections: for item in section["items"]: if item.get("label", "") in list_of_link_names: links.append(item) return links def set_last_modified(data): for section in data: for item in section["items"]: if item["type"] == "doctype": item["last_modified"] = get_last_modified(item["name"]) def get_last_modified(doctype): def _get(): try: last_modified = frappe.get_all(doctype, fields=["max(modified)"], as_list=True, limit_page_length=1)[0][0] except Exception as e: if frappe.db.is_table_missing(e): last_modified = None else: raise # hack: save as -1 so that it is cached if last_modified==None: last_modified = -1 return last_modified last_modified = frappe.cache().hget("last_modified", doctype, _get) if last_modified==-1: last_modified = None return last_modified def get_report_list(module, is_standard="No"): """Returns list on new style reports for modules.""" reports = frappe.get_list("Report", fields=["name", "ref_doctype", "report_type"], filters= {"is_standard": is_standard, "disabled": 0, "module": module}, order_by="name") out = [] for r in reports: out.append({ "type": "report", "doctype": r.ref_doctype, "is_query_report": 1 if r.report_type in ("Query Report", "Script Report") else 0, "label": _(r.name), "name": r.name }) return out