The license.txt file has been replaced with LICENSE for quite a while now. INAL but it didn't seem accurate to say "hey, checkout license.txt although there's no such file". Apart from this, there were inconsistencies in the headers altogether...this change brings consistency.
252 lines
7.7 KiB
Python
252 lines
7.7 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: MIT. See LICENSE
|
|
import io
|
|
import os
|
|
|
|
import frappe
|
|
from frappe.build import scrub_html_template
|
|
from frappe.model.meta import Meta
|
|
from frappe.model.utils import render_include
|
|
from frappe.modules import get_module_path, load_doctype_module, scrub
|
|
from frappe.translate import extract_messages_from_code, make_dict_from_messages
|
|
from frappe.utils import get_html_format
|
|
|
|
|
|
def get_meta(doctype, cached=True):
|
|
# don't cache for developer mode as js files, templates may be edited
|
|
if cached and not frappe.conf.developer_mode:
|
|
meta = frappe.cache().hget("form_meta", doctype)
|
|
if meta:
|
|
meta = FormMeta(meta)
|
|
else:
|
|
meta = FormMeta(doctype)
|
|
frappe.cache().hset("form_meta", doctype, meta.as_dict())
|
|
else:
|
|
meta = FormMeta(doctype)
|
|
|
|
if frappe.local.lang != 'en':
|
|
meta.set_translations(frappe.local.lang)
|
|
|
|
return meta
|
|
|
|
class FormMeta(Meta):
|
|
def __init__(self, doctype):
|
|
super(FormMeta, self).__init__(doctype)
|
|
self.load_assets()
|
|
|
|
def load_assets(self):
|
|
if self.get('__assets_loaded', False):
|
|
return
|
|
|
|
self.add_search_fields()
|
|
self.add_linked_document_type()
|
|
|
|
if not self.istable:
|
|
self.add_code()
|
|
self.add_custom_script()
|
|
self.load_print_formats()
|
|
self.load_workflows()
|
|
self.load_templates()
|
|
self.load_dashboard()
|
|
self.load_kanban_meta()
|
|
|
|
self.set('__assets_loaded', True)
|
|
|
|
def as_dict(self, no_nulls=False):
|
|
d = super(FormMeta, self).as_dict(no_nulls=no_nulls)
|
|
|
|
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js",
|
|
"__linked_with", "__messages", "__print_formats", "__workflow_docs",
|
|
"__form_grid_templates", "__listview_template", "__tree_js",
|
|
"__dashboard", "__kanban_column_fields", '__templates',
|
|
'__custom_js', '__custom_list_js'):
|
|
d[k] = self.get(k)
|
|
|
|
# d['fields'] = d.get('fields', [])
|
|
|
|
for i, df in enumerate(d.get("fields") or []):
|
|
for k in ("search_fields", "is_custom_field", "linked_document_type"):
|
|
df[k] = self.get("fields")[i].get(k)
|
|
|
|
return d
|
|
|
|
def add_code(self):
|
|
if self.custom:
|
|
return
|
|
|
|
path = os.path.join(get_module_path(self.module), 'doctype', scrub(self.name))
|
|
def _get_path(fname):
|
|
return os.path.join(path, scrub(fname))
|
|
|
|
system_country = frappe.get_system_settings("country")
|
|
|
|
self._add_code(_get_path(self.name + '.js'), '__js')
|
|
if system_country:
|
|
self._add_code(_get_path(os.path.join('regional', system_country + '.js')), '__js')
|
|
|
|
self._add_code(_get_path(self.name + '.css'), "__css")
|
|
self._add_code(_get_path(self.name + '_list.js'), '__list_js')
|
|
if system_country:
|
|
self._add_code(_get_path(os.path.join('regional', system_country + '_list.js')), '__list_js')
|
|
|
|
self._add_code(_get_path(self.name + '_calendar.js'), '__calendar_js')
|
|
self._add_code(_get_path(self.name + '_tree.js'), '__tree_js')
|
|
|
|
listview_template = _get_path(self.name + '_list.html')
|
|
if os.path.exists(listview_template):
|
|
self.set("__listview_template", get_html_format(listview_template))
|
|
|
|
self.add_code_via_hook("doctype_js", "__js")
|
|
self.add_code_via_hook("doctype_list_js", "__list_js")
|
|
self.add_code_via_hook("doctype_tree_js", "__tree_js")
|
|
self.add_code_via_hook("doctype_calendar_js", "__calendar_js")
|
|
self.add_html_templates(path)
|
|
|
|
def _add_code(self, path, fieldname):
|
|
js = get_js(path)
|
|
if js:
|
|
comment = f"\n\n/* Adding {path} */\n\n"
|
|
sourceURL = f"\n\n//# sourceURL={scrub(self.name) + fieldname}"
|
|
self.set(fieldname, (self.get(fieldname) or "") + comment + js + sourceURL)
|
|
|
|
def add_html_templates(self, path):
|
|
if self.custom:
|
|
return
|
|
templates = dict()
|
|
for fname in os.listdir(path):
|
|
if fname.endswith(".html"):
|
|
with io.open(os.path.join(path, fname), 'r', encoding = 'utf-8') as f:
|
|
templates[fname.split('.')[0]] = scrub_html_template(f.read())
|
|
|
|
self.set("__templates", templates or None)
|
|
|
|
def add_code_via_hook(self, hook, fieldname):
|
|
for path in get_code_files_via_hooks(hook, self.name):
|
|
self._add_code(path, fieldname)
|
|
|
|
def add_custom_script(self):
|
|
"""embed all require files"""
|
|
# custom script
|
|
client_scripts = frappe.db.get_all("Client Script",
|
|
filters={"dt": self.name, "enabled": 1},
|
|
fields=["script", "view"],
|
|
order_by="creation asc"
|
|
) or ""
|
|
|
|
list_script = ''
|
|
form_script = ''
|
|
for script in client_scripts:
|
|
if script.view == 'List':
|
|
list_script += script.script
|
|
|
|
if script.view == 'Form':
|
|
form_script += script.script
|
|
|
|
file = scrub(self.name)
|
|
form_script += f"\n\n//# sourceURL={file}__custom_js"
|
|
list_script += f"\n\n//# sourceURL={file}__custom_list_js"
|
|
|
|
self.set("__custom_js", form_script)
|
|
self.set("__custom_list_js", list_script)
|
|
|
|
def add_search_fields(self):
|
|
"""add search fields found in the doctypes indicated by link fields' options"""
|
|
for df in self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}):
|
|
if df.options:
|
|
search_fields = frappe.get_meta(df.options).search_fields
|
|
if search_fields:
|
|
search_fields = search_fields.split(",")
|
|
df.search_fields = [sf.strip() for sf in search_fields]
|
|
|
|
def add_linked_document_type(self):
|
|
for df in self.get("fields", {"fieldtype": "Link"}):
|
|
if df.options:
|
|
try:
|
|
df.linked_document_type = frappe.get_meta(df.options).document_type
|
|
except frappe.DoesNotExistError:
|
|
# edge case where options="[Select]"
|
|
pass
|
|
|
|
def load_print_formats(self):
|
|
print_formats = frappe.db.sql("""select * FROM `tabPrint Format`
|
|
WHERE doc_type=%s AND docstatus<2 and disabled=0""", (self.name,), as_dict=1,
|
|
update={"doctype":"Print Format"})
|
|
|
|
self.set("__print_formats", print_formats, as_value=True)
|
|
|
|
def load_workflows(self):
|
|
# get active workflow
|
|
workflow_name = self.get_workflow()
|
|
workflow_docs = []
|
|
|
|
if workflow_name and frappe.db.exists("Workflow", workflow_name):
|
|
workflow = frappe.get_doc("Workflow", workflow_name)
|
|
workflow_docs.append(workflow)
|
|
|
|
for d in workflow.get("states"):
|
|
workflow_docs.append(frappe.get_doc("Workflow State", d.state))
|
|
|
|
self.set("__workflow_docs", workflow_docs, as_value=True)
|
|
|
|
|
|
def load_templates(self):
|
|
if not self.custom:
|
|
module = load_doctype_module(self.name)
|
|
app = module.__name__.split(".")[0]
|
|
templates = {}
|
|
if hasattr(module, "form_grid_templates"):
|
|
for key, path in module.form_grid_templates.items():
|
|
templates[key] = get_html_format(frappe.get_app_path(app, path))
|
|
|
|
self.set("__form_grid_templates", templates)
|
|
|
|
def set_translations(self, lang):
|
|
self.set("__messages", frappe.get_lang_dict("doctype", self.name))
|
|
|
|
# set translations for grid templates
|
|
if self.get("__form_grid_templates"):
|
|
for content in self.get("__form_grid_templates").values():
|
|
messages = extract_messages_from_code(content)
|
|
messages = make_dict_from_messages(messages)
|
|
self.get("__messages").update(messages, as_value=True)
|
|
|
|
def load_dashboard(self):
|
|
self.set('__dashboard', self.get_dashboard_data())
|
|
|
|
def load_kanban_meta(self):
|
|
self.load_kanban_column_fields()
|
|
|
|
def load_kanban_column_fields(self):
|
|
try:
|
|
values = frappe.get_list(
|
|
'Kanban Board', fields=['field_name'],
|
|
filters={'reference_doctype': self.name})
|
|
|
|
fields = [x['field_name'] for x in values]
|
|
fields = list(set(fields))
|
|
self.set("__kanban_column_fields", fields, as_value=True)
|
|
except frappe.PermissionError:
|
|
# no access to kanban board
|
|
pass
|
|
|
|
def get_code_files_via_hooks(hook, name):
|
|
code_files = []
|
|
for app_name in frappe.get_installed_apps():
|
|
code_hook = frappe.get_hooks(hook, default={}, app_name=app_name)
|
|
if not code_hook:
|
|
continue
|
|
|
|
files = code_hook.get(name, [])
|
|
if not isinstance(files, list):
|
|
files = [files]
|
|
|
|
for file in files:
|
|
path = frappe.get_app_path(app_name, *file.strip("/").split("/"))
|
|
code_files.append(path)
|
|
|
|
return code_files
|
|
|
|
def get_js(path):
|
|
js = frappe.read_file(path)
|
|
if js:
|
|
return render_include(js)
|