25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

654 lines
18 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. """
  4. globals attached to frappe module
  5. + some utility functions that should probably be moved
  6. """
  7. from __future__ import unicode_literals
  8. from werkzeug.local import Local, release_local
  9. import os, importlib, inspect, logging, json
  10. # public
  11. from frappe.__version__ import __version__
  12. from .exceptions import *
  13. from .utils.jinja import get_jenv, get_template, render_template
  14. local = Local()
  15. class _dict(dict):
  16. """dict like object that exposes keys as attributes"""
  17. def __getattr__(self, key):
  18. ret = self.get(key)
  19. if not ret and key.startswith("__"):
  20. raise AttributeError()
  21. return ret
  22. def __setattr__(self, key, value):
  23. self[key] = value
  24. def __getstate__(self):
  25. return self
  26. def __setstate__(self, d):
  27. self.update(d)
  28. def update(self, d):
  29. """update and return self -- the missing dict feature in python"""
  30. super(_dict, self).update(d)
  31. return self
  32. def copy(self):
  33. return _dict(dict(self).copy())
  34. def _(msg):
  35. """translate object in current lang, if exists"""
  36. if local.lang == "en":
  37. return msg
  38. from frappe.translate import get_full_dict
  39. return get_full_dict(local.lang).get(msg, msg)
  40. def get_lang_dict(fortype, name=None):
  41. if local.lang=="en":
  42. return {}
  43. from frappe.translate import get_dict
  44. return get_dict(fortype, name)
  45. def set_user_lang(user, user_language=None):
  46. from frappe.translate import get_user_lang
  47. local.lang = get_user_lang(user)
  48. # local-globals
  49. db = local("db")
  50. conf = local("conf")
  51. form = form_dict = local("form_dict")
  52. request = local("request")
  53. request_method = local("request_method")
  54. response = local("response")
  55. session = local("session")
  56. user = local("user")
  57. flags = local("flags")
  58. error_log = local("error_log")
  59. debug_log = local("debug_log")
  60. message_log = local("message_log")
  61. lang = local("lang")
  62. def init(site, sites_path=None):
  63. if getattr(local, "initialised", None):
  64. return
  65. if not sites_path:
  66. sites_path = '.'
  67. local.error_log = []
  68. local.message_log = []
  69. local.debug_log = []
  70. local.flags = _dict({})
  71. local.rollback_observers = []
  72. local.test_objects = {}
  73. local.site = site
  74. local.sites_path = sites_path
  75. local.site_path = os.path.join(sites_path, site)
  76. local.request_method = request.method if request else None
  77. local.request_ip = None
  78. local.response = _dict({"docs":[]})
  79. local.conf = _dict(get_site_config())
  80. local.lang = local.conf.lang or "en"
  81. local.module_app = None
  82. local.app_modules = None
  83. local.user = None
  84. local.role_permissions = {}
  85. local.jenv = None
  86. local.jloader =None
  87. local.cache = {}
  88. setup_module_map()
  89. local.initialised = True
  90. def connect(site=None, db_name=None):
  91. from database import Database
  92. if site:
  93. init(site)
  94. local.db = Database(user=db_name or local.conf.db_name)
  95. local.form_dict = _dict()
  96. local.session = _dict()
  97. set_user("Administrator")
  98. def get_site_config(sites_path=None, site_path=None):
  99. config = {}
  100. sites_path = sites_path or getattr(local, "sites_path", None)
  101. site_path = site_path or getattr(local, "site_path", None)
  102. if sites_path:
  103. common_site_config = os.path.join(sites_path, "common_site_config.json")
  104. if os.path.exists(common_site_config):
  105. config.update(get_file_json(common_site_config))
  106. if site_path:
  107. site_config = os.path.join(site_path, "site_config.json")
  108. if os.path.exists(site_config):
  109. config.update(get_file_json(site_config))
  110. return _dict(config)
  111. def destroy():
  112. """closes connection and releases werkzeug local"""
  113. if db:
  114. db.close()
  115. release_local(local)
  116. _memc = None
  117. # memcache
  118. def cache():
  119. global _memc
  120. if not _memc:
  121. from frappe.memc import MClient
  122. _memc = MClient(['localhost:11211'])
  123. return _memc
  124. def get_traceback():
  125. import utils
  126. return utils.get_traceback()
  127. def errprint(msg):
  128. from utils import cstr
  129. if not request or (not "cmd" in local.form_dict):
  130. print cstr(msg)
  131. error_log.append(cstr(msg))
  132. def log(msg):
  133. if not request:
  134. if conf.get("logging") or False:
  135. print repr(msg)
  136. from utils import cstr
  137. debug_log.append(cstr(msg))
  138. def msgprint(msg, small=0, raise_exception=0, as_table=False):
  139. def _raise_exception():
  140. if raise_exception:
  141. if flags.rollback_on_exception:
  142. db.rollback()
  143. import inspect
  144. if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception):
  145. raise raise_exception, msg
  146. else:
  147. raise ValidationError, msg
  148. if flags.mute_messages:
  149. _raise_exception()
  150. return
  151. from utils import cstr
  152. if as_table and type(msg) in (list, tuple):
  153. msg = '<table border="1px" style="border-collapse: collapse" cellpadding="2px">' + ''.join(['<tr>'+''.join(['<td>%s</td>' % c for c in r])+'</tr>' for r in msg]) + '</table>'
  154. if flags.print_messages:
  155. print "Message: " + repr(msg)
  156. message_log.append((small and '__small:' or '')+cstr(msg or ''))
  157. _raise_exception()
  158. def throw(msg, exc=ValidationError):
  159. msgprint(msg, raise_exception=exc)
  160. def create_folder(path, with_init=False):
  161. from frappe.utils import touch_file
  162. if not os.path.exists(path):
  163. os.makedirs(path)
  164. if with_init:
  165. touch_file(os.path.join(path, "__init__.py"))
  166. def set_user(username):
  167. from frappe.utils.user import User
  168. local.session.user = username
  169. local.session.sid = username
  170. local.cache = {}
  171. local.form_dict = _dict()
  172. local.jenv = None
  173. local.session.data = {}
  174. local.user = User(username)
  175. local.role_permissions = {}
  176. def get_request_header(key, default=None):
  177. return request.headers.get(key, default)
  178. def sendmail(recipients=(), sender="", subject="No Subject", message="No Message",
  179. as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None,
  180. add_unsubscribe_link=False, attachments=None):
  181. if bulk:
  182. import frappe.utils.email_lib.bulk
  183. frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender,
  184. subject=subject, message=message, ref_doctype = ref_doctype,
  185. ref_docname = ref_docname, add_unsubscribe_link=add_unsubscribe_link, attachments=attachments)
  186. else:
  187. import frappe.utils.email_lib
  188. if as_markdown:
  189. frappe.utils.email_lib.sendmail_md(recipients, sender=sender,
  190. subject=subject, msg=message, attachments=attachments)
  191. else:
  192. frappe.utils.email_lib.sendmail(recipients, sender=sender,
  193. subject=subject, msg=message, attachments=attachments)
  194. logger = None
  195. whitelisted = []
  196. guest_methods = []
  197. def whitelist(allow_guest=False):
  198. """
  199. decorator for whitelisting a function
  200. Note: if the function is allowed to be accessed by a guest user,
  201. it must explicitly be marked as allow_guest=True
  202. for specific roles, set allow_roles = ['Administrator'] etc.
  203. """
  204. def innerfn(fn):
  205. global whitelisted, guest_methods
  206. whitelisted.append(fn)
  207. if allow_guest:
  208. guest_methods.append(fn)
  209. return fn
  210. return innerfn
  211. def only_for(roles):
  212. if not isinstance(roles, (tuple, list)):
  213. roles = (roles,)
  214. roles = set(roles)
  215. myroles = set(get_roles())
  216. if not roles.intersection(myroles):
  217. raise PermissionError
  218. def clear_cache(user=None, doctype=None):
  219. """clear cache"""
  220. import frappe.sessions
  221. if doctype:
  222. import frappe.model.meta
  223. frappe.model.meta.clear_cache(doctype)
  224. reset_metadata_version()
  225. elif user:
  226. frappe.sessions.clear_cache(user)
  227. else: # everything
  228. import translate
  229. frappe.sessions.clear_cache()
  230. translate.clear_cache()
  231. reset_metadata_version()
  232. for fn in frappe.get_hooks("clear_cache"):
  233. get_attr(fn)()
  234. frappe.local.role_permissions = {}
  235. def get_roles(username=None):
  236. if not local.session:
  237. return ["Guest"]
  238. return get_user(username).get_roles()
  239. def get_user(username):
  240. from frappe.utils.user import User
  241. if not username or username == local.session.user:
  242. return local.user
  243. else:
  244. return User(username)
  245. def has_permission(doctype, ptype="read", doc=None, user=None):
  246. import frappe.permissions
  247. return frappe.permissions.has_permission(doctype, ptype, doc, user=user)
  248. def is_table(doctype):
  249. tables = cache().get_value("is_table")
  250. if tables==None:
  251. tables = db.sql_list("select name from tabDocType where ifnull(istable,0)=1")
  252. cache().set_value("is_table", tables)
  253. return doctype in tables
  254. def clear_perms(doctype):
  255. db.sql("""delete from tabDocPerm where parent=%s""", doctype)
  256. def reset_perms(doctype):
  257. from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for
  258. delete_notification_count_for(doctype)
  259. clear_perms(doctype)
  260. reload_doc(db.get_value("DocType", doctype, "module"),
  261. "DocType", doctype, force=True)
  262. def generate_hash(txt=None):
  263. """Generates random hash for session id"""
  264. import hashlib, time
  265. from .utils import random_string
  266. return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest()
  267. def reset_metadata_version():
  268. v = generate_hash()
  269. cache().set_value("metadata_version", v)
  270. return v
  271. def new_doc(doctype, parent_doc=None, parentfield=None):
  272. from frappe.model.create_new import get_new_doc
  273. return get_new_doc(doctype, parent_doc, parentfield)
  274. def set_value(doctype, docname, fieldname, value):
  275. import frappe.client
  276. return frappe.client.set_value(doctype, docname, fieldname, value)
  277. def get_doc(arg1, arg2=None):
  278. import frappe.model.document
  279. return frappe.model.document.get_doc(arg1, arg2)
  280. def get_meta(doctype, cached=True):
  281. import frappe.model.meta
  282. return frappe.model.meta.get_meta(doctype, cached=cached)
  283. def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False):
  284. import frappe.model.delete_doc
  285. frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions)
  286. def delete_doc_if_exists(doctype, name):
  287. if db.exists(doctype, name):
  288. delete_doc(doctype, name)
  289. def reload_doc(module, dt=None, dn=None, force=False):
  290. import frappe.modules
  291. return frappe.modules.reload_doc(module, dt, dn, force=force)
  292. def rename_doc(doctype, old, new, debug=0, force=False, merge=False, ignore_permissions=False):
  293. from frappe.model.rename_doc import rename_doc
  294. return rename_doc(doctype, old, new, force=force, merge=merge, ignore_permissions=ignore_permissions)
  295. def insert(doclist):
  296. import frappe.model
  297. return frappe.model.insert(doclist)
  298. def get_module(modulename):
  299. return importlib.import_module(modulename)
  300. def scrub(txt):
  301. return txt.replace(' ','_').replace('-', '_').lower()
  302. def unscrub(txt):
  303. return txt.replace('_',' ').replace('-', ' ').title()
  304. def get_module_path(module, *joins):
  305. module = scrub(module)
  306. return get_pymodule_path(local.module_app[module] + "." + module, *joins)
  307. def get_app_path(app_name, *joins):
  308. return get_pymodule_path(app_name, *joins)
  309. def get_site_path(*joins):
  310. return os.path.join(local.site_path, *joins)
  311. def get_pymodule_path(modulename, *joins):
  312. joins = [scrub(part) for part in joins]
  313. return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__), *joins)
  314. def get_module_list(app_name):
  315. return get_file_items(os.path.join(os.path.dirname(get_module(app_name).__file__), "modules.txt"))
  316. def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None):
  317. if not sites_path:
  318. sites_path = local.sites_path
  319. apps = get_file_items(os.path.join(sites_path, "apps.txt"), raise_not_found=True)
  320. if with_internal_apps:
  321. apps.extend(get_file_items(os.path.join(local.site_path, "apps.txt")))
  322. if with_frappe:
  323. apps.insert(0, 'frappe')
  324. return apps
  325. def get_installed_apps():
  326. if getattr(flags, "in_install_db", True):
  327. return []
  328. installed = json.loads(db.get_global("installed_apps") or "[]")
  329. return installed
  330. @whitelist()
  331. def get_versions():
  332. versions = {}
  333. for app in get_installed_apps():
  334. versions[app] = {
  335. "title": get_hooks("app_title", app_name=app),
  336. "description": get_hooks("app_description", app_name=app)
  337. }
  338. try:
  339. versions[app]["version"] = get_attr(app + ".__version__")
  340. except AttributeError:
  341. versions[app]["version"] = '0.0.1'
  342. return versions
  343. def get_hooks(hook=None, default=None, app_name=None):
  344. def load_app_hooks(app_name=None):
  345. hooks = {}
  346. for app in [app_name] if app_name else get_installed_apps():
  347. app = "frappe" if app=="webnotes" else app
  348. app_hooks = get_module(app + ".hooks")
  349. for key in dir(app_hooks):
  350. if not key.startswith("_"):
  351. append_hook(hooks, key, getattr(app_hooks, key))
  352. return hooks
  353. def append_hook(target, key, value):
  354. if isinstance(value, dict):
  355. target.setdefault(key, {})
  356. for inkey in value:
  357. append_hook(target[key], inkey, value[inkey])
  358. else:
  359. append_to_list(target, key, value)
  360. def append_to_list(target, key, value):
  361. target.setdefault(key, [])
  362. if not isinstance(value, list):
  363. value = [value]
  364. target[key].extend(value)
  365. if app_name:
  366. hooks = _dict(load_app_hooks(app_name))
  367. else:
  368. hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
  369. if hook:
  370. return hooks.get(hook) or (default if default is not None else [])
  371. else:
  372. return hooks
  373. def setup_module_map():
  374. _cache = cache()
  375. if conf.db_name:
  376. local.app_modules = _cache.get_value("app_modules")
  377. local.module_app = _cache.get_value("module_app")
  378. if not local.app_modules:
  379. local.module_app, local.app_modules = {}, {}
  380. for app in get_all_apps(True):
  381. if app=="webnotes": app="frappe"
  382. local.app_modules.setdefault(app, [])
  383. for module in get_module_list(app):
  384. module = scrub(module)
  385. local.module_app[module] = app
  386. local.app_modules[app].append(module)
  387. if conf.db_name:
  388. _cache.set_value("app_modules", local.app_modules)
  389. _cache.set_value("module_app", local.module_app)
  390. def get_file_items(path, raise_not_found=False, ignore_empty_lines=True):
  391. content = read_file(path, raise_not_found=raise_not_found)
  392. if content:
  393. # \ufeff is no-width-break, \u200b is no-width-space
  394. content = content.replace("\ufeff", "").replace("\u200b", "").strip()
  395. return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))]
  396. else:
  397. return []
  398. def get_file_json(path):
  399. with open(path, 'r') as f:
  400. return json.load(f)
  401. def read_file(path, raise_not_found=False):
  402. from frappe.utils import cstr
  403. if os.path.exists(path):
  404. with open(path, "r") as f:
  405. return cstr(f.read())
  406. elif raise_not_found:
  407. raise IOError("{} Not Found".format(path))
  408. else:
  409. return None
  410. def get_attr(method_string):
  411. modulename = '.'.join(method_string.split('.')[:-1])
  412. methodname = method_string.split('.')[-1]
  413. return getattr(get_module(modulename), methodname)
  414. def call(fn, *args, **kwargs):
  415. if hasattr(fn, 'fnargs'):
  416. fnargs = fn.fnargs
  417. else:
  418. fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
  419. newargs = {}
  420. for a in fnargs:
  421. if a in kwargs:
  422. newargs[a] = kwargs.get(a)
  423. return fn(*args, **newargs)
  424. def make_property_setter(args, ignore_validate=False):
  425. args = _dict(args)
  426. ps = get_doc({
  427. 'doctype': "Property Setter",
  428. 'doctype_or_field': args.doctype_or_field or "DocField",
  429. 'doc_type': args.doctype,
  430. 'field_name': args.fieldname,
  431. 'property': args.property,
  432. 'value': args.value,
  433. 'property_type': args.property_type or "Data",
  434. '__islocal': 1
  435. })
  436. ps.ignore_validate = ignore_validate
  437. ps.insert()
  438. def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
  439. from frappe.core.page.data_import_tool import data_import_tool
  440. data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)
  441. def copy_doc(doc):
  442. """ No_copy fields also get copied."""
  443. import copy
  444. if not isinstance(doc, dict):
  445. d = doc.as_dict()
  446. else:
  447. d = doc
  448. newdoc = get_doc(copy.deepcopy(d))
  449. newdoc.name = None
  450. newdoc.set("__islocal", 1)
  451. newdoc.owner = None
  452. newdoc.creation = None
  453. newdoc.amended_from = None
  454. newdoc.amendment_date = None
  455. for d in newdoc.get_all_children():
  456. d.name = None
  457. d.parent = None
  458. d.set("__islocal", 1)
  459. d.owner = None
  460. d.creation = None
  461. return newdoc
  462. def compare(val1, condition, val2):
  463. import frappe.utils
  464. return frappe.utils.compare(val1, condition, val2)
  465. def respond_as_web_page(title, html, success=None, http_status_code=None):
  466. local.message_title = title
  467. local.message = html
  468. local.message_success = success
  469. local.response['type'] = 'page'
  470. local.response['page_name'] = 'message'
  471. if http_status_code:
  472. local.response['http_status_code'] = http_status_code
  473. def build_match_conditions(doctype, as_condition=True):
  474. import frappe.widgets.reportview
  475. return frappe.widgets.reportview.build_match_conditions(doctype, as_condition)
  476. def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None,
  477. group_by=None, order_by=None, limit_start=0, limit_page_length=None,
  478. as_list=False, debug=False, ignore_permissions=False, user=None):
  479. import frappe.model.db_query
  480. return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters,
  481. fields=fields, docstatus=docstatus, or_filters=or_filters,
  482. group_by=group_by, order_by=order_by, limit_start=limit_start,
  483. limit_page_length=limit_page_length, as_list=as_list, debug=debug,
  484. ignore_permissions=ignore_permissions, user=user)
  485. run_query = get_list
  486. def add_version(doc):
  487. get_doc({
  488. "doctype": "Version",
  489. "ref_doctype": doc.doctype,
  490. "docname": doc.name,
  491. "doclist_json": json.dumps(doc.as_dict(), indent=1, sort_keys=True)
  492. }).insert(ignore_permissions=True)
  493. def get_test_records(doctype):
  494. from frappe.modules import get_doctype_module, get_module_path
  495. path = os.path.join(get_module_path(get_doctype_module(doctype)), "doctype", scrub(doctype), "test_records.json")
  496. if os.path.exists(path):
  497. with open(path, "r") as f:
  498. return json.loads(f.read())
  499. else:
  500. return []
  501. def format_value(value, df, doc=None, currency=None):
  502. import frappe.utils.formatters
  503. return frappe.utils.formatters.format_value(value, df, doc, currency=currency)
  504. def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False):
  505. from frappe.website.render import build_page
  506. local.form_dict.doctype = doctype
  507. local.form_dict.name = name
  508. local.form_dict.format = print_format
  509. local.form_dict.style = style
  510. html = build_page("print")
  511. if as_pdf:
  512. print_settings = db.get_singles_dict("Print Settings")
  513. if int(print_settings.send_print_as_pdf or 0):
  514. from utils.pdf import get_pdf
  515. return get_pdf(html, {"page-size": print_settings.pdf_page_size})
  516. else:
  517. return html
  518. else:
  519. return html
  520. logging_setup_complete = False
  521. def get_logger(module=None):
  522. from frappe.setup_logging import setup_logging
  523. global logging_setup_complete
  524. if not logging_setup_complete:
  525. setup_logging()
  526. logging_setup_complete = True
  527. return logging.getLogger(module or "frappe")