您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

674 行
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. from werkzeug.exceptions import NotFound
  10. from MySQLdb import ProgrammingError as SQLError
  11. import os, sys, importlib, inspect
  12. import json
  13. from .exceptions import *
  14. from frappe.__version__ import __version__
  15. local = Local()
  16. class _dict(dict):
  17. """dict like object that exposes keys as attributes"""
  18. def __getattr__(self, key):
  19. ret = self.get(key)
  20. if not ret and key.startswith("__"):
  21. raise AttributeError()
  22. return ret
  23. def __setattr__(self, key, value):
  24. self[key] = value
  25. def __getstate__(self):
  26. return self
  27. def __setstate__(self, d):
  28. self.update(d)
  29. def update(self, d):
  30. """update and return self -- the missing dict feature in python"""
  31. super(_dict, self).update(d)
  32. return self
  33. def copy(self):
  34. return _dict(dict(self).copy())
  35. def _(msg):
  36. """translate object in current lang, if exists"""
  37. if local.lang == "en":
  38. return msg
  39. from frappe.translate import get_full_dict
  40. return get_full_dict(local.lang).get(msg, msg)
  41. def get_lang_dict(fortype, name=None):
  42. if local.lang=="en":
  43. return {}
  44. from frappe.translate import get_dict
  45. return get_dict(fortype, name)
  46. def set_user_lang(user, user_language=None):
  47. from frappe.translate import get_user_lang
  48. local.lang = get_user_lang(user)
  49. # local-globals
  50. db = local("db")
  51. conf = local("conf")
  52. form = form_dict = local("form_dict")
  53. request = local("request")
  54. request_method = local("request_method")
  55. response = local("response")
  56. session = local("session")
  57. user = local("user")
  58. flags = local("flags")
  59. error_log = local("error_log")
  60. debug_log = local("debug_log")
  61. message_log = local("message_log")
  62. lang = local("lang")
  63. def init(site, sites_path=None):
  64. if getattr(local, "initialised", None):
  65. return
  66. if not sites_path:
  67. sites_path = '.'
  68. local.error_log = []
  69. local.message_log = []
  70. local.debug_log = []
  71. local.flags = _dict({})
  72. local.rollback_observers = []
  73. local.test_objects = {}
  74. local.site = site
  75. local.sites_path = sites_path
  76. local.site_path = os.path.join(sites_path, site)
  77. local.request_method = request.method if request else 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:
  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.session.data = {}
  172. local.user = User(username)
  173. local.role_permissions = {}
  174. def get_request_header(key, default=None):
  175. return request.headers.get(key, default)
  176. def sendmail(recipients=(), sender="", subject="No Subject", message="No Message",
  177. as_markdown=False, bulk=False):
  178. if bulk:
  179. import frappe.utils.email_lib.bulk
  180. frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender,
  181. subject=subject, message=message, add_unsubscribe_link=False)
  182. else:
  183. import frappe.utils.email_lib
  184. if as_markdown:
  185. frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message)
  186. else:
  187. frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message)
  188. logger = None
  189. whitelisted = []
  190. guest_methods = []
  191. def whitelist(allow_guest=False):
  192. """
  193. decorator for whitelisting a function
  194. Note: if the function is allowed to be accessed by a guest user,
  195. it must explicitly be marked as allow_guest=True
  196. for specific roles, set allow_roles = ['Administrator'] etc.
  197. """
  198. def innerfn(fn):
  199. global whitelisted, guest_methods
  200. whitelisted.append(fn)
  201. if allow_guest:
  202. guest_methods.append(fn)
  203. return fn
  204. return innerfn
  205. def only_for(roles):
  206. if not isinstance(roles, (tuple, list)):
  207. roles = (roles,)
  208. roles = set(roles)
  209. myroles = set(get_roles())
  210. if not roles.intersection(myroles):
  211. raise PermissionError
  212. def clear_cache(user=None, doctype=None):
  213. """clear cache"""
  214. import frappe.sessions
  215. if doctype:
  216. import frappe.model.meta
  217. frappe.model.meta.clear_cache(doctype)
  218. reset_metadata_version()
  219. elif user:
  220. frappe.sessions.clear_cache(user)
  221. else: # everything
  222. import translate
  223. frappe.sessions.clear_cache()
  224. translate.clear_cache()
  225. reset_metadata_version()
  226. frappe.local.role_permissions = {}
  227. def get_roles(username=None):
  228. if not local.session:
  229. return ["Guest"]
  230. return get_user(username).get_roles()
  231. def get_user(username):
  232. from frappe.utils.user import User
  233. if not username or username == local.session.user:
  234. return local.user
  235. else:
  236. return User(username)
  237. def has_permission(doctype, ptype="read", doc=None, user=None):
  238. import frappe.permissions
  239. return frappe.permissions.has_permission(doctype, ptype, doc, user=user)
  240. def is_table(doctype):
  241. tables = cache().get_value("is_table")
  242. if tables==None:
  243. tables = db.sql_list("select name from tabDocType where ifnull(istable,0)=1")
  244. cache().set_value("is_table", tables)
  245. return doctype in tables
  246. def clear_perms(doctype):
  247. db.sql("""delete from tabDocPerm where parent=%s""", doctype)
  248. def reset_perms(doctype):
  249. clear_perms(doctype)
  250. reload_doc(db.get_value("DocType", doctype, "module"),
  251. "DocType", doctype, force=True)
  252. def generate_hash(txt=None):
  253. """Generates random hash for session id"""
  254. import hashlib, time
  255. return hashlib.sha224((txt or "") + repr(time.time())).hexdigest()
  256. def reset_metadata_version():
  257. v = generate_hash()
  258. cache().set_value("metadata_version", v)
  259. return v
  260. def new_doc(doctype, parent_doc=None, parentfield=None):
  261. from frappe.model.create_new import get_new_doc
  262. return get_new_doc(doctype, parent_doc, parentfield)
  263. def set_value(doctype, docname, fieldname, value):
  264. import frappe.client
  265. return frappe.client.set_value(doctype, docname, fieldname, value)
  266. def get_doc(arg1, arg2=None):
  267. import frappe.model.document
  268. return frappe.model.document.get_doc(arg1, arg2)
  269. def get_meta(doctype, cached=True):
  270. import frappe.model.meta
  271. return frappe.model.meta.get_meta(doctype, cached=cached)
  272. def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False):
  273. import frappe.model.delete_doc
  274. if not ignore_doctypes:
  275. ignore_doctypes = []
  276. if isinstance(name, list):
  277. for n in name:
  278. frappe.model.delete_doc.delete_doc(doctype, n, force, ignore_doctypes, for_reload, ignore_permissions)
  279. else:
  280. frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions)
  281. def delete_doc_if_exists(doctype, name):
  282. if db.exists(doctype, name):
  283. delete_doc(doctype, name)
  284. def reload_doc(module, dt=None, dn=None, force=False):
  285. import frappe.modules
  286. return frappe.modules.reload_doc(module, dt, dn, force=force)
  287. def rename_doc(doctype, old, new, debug=0, force=False, merge=False, ignore_permissions=False):
  288. from frappe.model.rename_doc import rename_doc
  289. return rename_doc(doctype, old, new, force=force, merge=merge, ignore_permissions=ignore_permissions)
  290. def insert(doclist):
  291. import frappe.model
  292. return frappe.model.insert(doclist)
  293. def get_module(modulename):
  294. return importlib.import_module(modulename)
  295. def scrub(txt):
  296. return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower()
  297. def unscrub(txt):
  298. return txt.replace('_',' ').replace('-', ' ').title()
  299. def get_module_path(module, *joins):
  300. module = scrub(module)
  301. return get_pymodule_path(local.module_app[module] + "." + module, *joins)
  302. def get_app_path(app_name, *joins):
  303. return get_pymodule_path(app_name, *joins)
  304. def get_site_path(*joins):
  305. return os.path.join(local.site_path, *joins)
  306. def get_pymodule_path(modulename, *joins):
  307. joins = [scrub(part) for part in joins]
  308. return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__), *joins)
  309. def get_module_list(app_name):
  310. return get_file_items(os.path.join(os.path.dirname(get_module(app_name).__file__), "modules.txt"))
  311. def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None):
  312. if not sites_path:
  313. sites_path = local.sites_path
  314. apps = get_file_items(os.path.join(sites_path, "apps.txt"), raise_not_found=True)
  315. if with_internal_apps:
  316. apps.extend(get_file_items(os.path.join(local.site_path, "apps.txt")))
  317. if with_frappe:
  318. apps.insert(0, 'frappe')
  319. return apps
  320. def get_installed_apps():
  321. if getattr(flags, "in_install_db", True):
  322. return []
  323. installed = json.loads(db.get_global("installed_apps") or "[]")
  324. return installed
  325. @whitelist()
  326. def get_versions():
  327. versions = {}
  328. for app in get_installed_apps():
  329. versions[app] = {
  330. "title": get_hooks("app_title", app_name=app),
  331. "description": get_hooks("app_description", app_name=app)
  332. }
  333. try:
  334. versions[app]["version"] = get_attr(app + ".__version__")
  335. except AttributeError:
  336. versions[app]["version"] = '0.0.1'
  337. return versions
  338. def get_hooks(hook=None, default=None, app_name=None):
  339. def load_app_hooks(app_name=None):
  340. hooks = {}
  341. for app in [app_name] if app_name else get_installed_apps():
  342. app = "frappe" if app=="webnotes" else app
  343. app_hooks = get_module(app + ".hooks")
  344. for key in dir(app_hooks):
  345. if not key.startswith("_"):
  346. append_hook(hooks, key, getattr(app_hooks, key))
  347. return hooks
  348. def append_hook(target, key, value):
  349. if isinstance(value, dict):
  350. target.setdefault(key, {})
  351. for inkey in value:
  352. append_hook(target[key], inkey, value[inkey])
  353. else:
  354. append_to_list(target, key, value)
  355. def append_to_list(target, key, value):
  356. target.setdefault(key, [])
  357. if not isinstance(value, list):
  358. value = [value]
  359. target[key].extend(value)
  360. if app_name:
  361. hooks = _dict(load_app_hooks(app_name))
  362. else:
  363. hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
  364. if hook:
  365. return hooks.get(hook) or (default if default is not None else [])
  366. else:
  367. return hooks
  368. def setup_module_map():
  369. _cache = cache()
  370. if conf.db_name:
  371. local.app_modules = _cache.get_value("app_modules")
  372. local.module_app = _cache.get_value("module_app")
  373. if not local.app_modules:
  374. local.module_app, local.app_modules = {}, {}
  375. for app in get_all_apps(True):
  376. if app=="webnotes": app="frappe"
  377. local.app_modules.setdefault(app, [])
  378. for module in get_module_list(app):
  379. module = scrub(module)
  380. local.module_app[module] = app
  381. local.app_modules[app].append(module)
  382. if conf.db_name:
  383. _cache.set_value("app_modules", local.app_modules)
  384. _cache.set_value("module_app", local.module_app)
  385. def get_file_items(path, raise_not_found=False):
  386. content = read_file(path, raise_not_found=raise_not_found)
  387. if content:
  388. return [p.strip() for p in content.splitlines() if p.strip() and not p.startswith("#")]
  389. else:
  390. return []
  391. def get_file_json(path):
  392. with open(path, 'r') as f:
  393. return json.load(f)
  394. def read_file(path, raise_not_found=False):
  395. from frappe.utils import cstr
  396. if os.path.exists(path):
  397. with open(path, "r") as f:
  398. return cstr(f.read())
  399. elif raise_not_found:
  400. raise IOError("{} Not Found".format(path))
  401. else:
  402. return None
  403. def get_attr(method_string):
  404. modulename = '.'.join(method_string.split('.')[:-1])
  405. methodname = method_string.split('.')[-1]
  406. return getattr(get_module(modulename), methodname)
  407. def call(fn, *args, **kwargs):
  408. if hasattr(fn, 'fnargs'):
  409. fnargs = fn.fnargs
  410. else:
  411. fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
  412. newargs = {}
  413. for a in fnargs:
  414. if a in kwargs:
  415. newargs[a] = kwargs.get(a)
  416. return fn(*args, **newargs)
  417. def make_property_setter(args, ignore_validate=False):
  418. args = _dict(args)
  419. ps = get_doc({
  420. 'doctype': "Property Setter",
  421. 'doctype_or_field': args.doctype_or_field or "DocField",
  422. 'doc_type': args.doctype,
  423. 'field_name': args.fieldname,
  424. 'property': args.property,
  425. 'value': args.value,
  426. 'property_type': args.property_type or "Data",
  427. '__islocal': 1
  428. })
  429. ps.ignore_validate = ignore_validate
  430. ps.insert()
  431. def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
  432. from frappe.core.page.data_import_tool import data_import_tool
  433. data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)
  434. def copy_doc(doc):
  435. """ No_copy fields also get copied."""
  436. import copy
  437. if not isinstance(doc, dict):
  438. d = doc.as_dict()
  439. else:
  440. d = doc
  441. newdoc = get_doc(copy.deepcopy(d))
  442. newdoc.name = None
  443. newdoc.set("__islocal", 1)
  444. newdoc.owner = None
  445. newdoc.creation = None
  446. newdoc.amended_from = None
  447. newdoc.amendment_date = None
  448. for d in newdoc.get_all_children():
  449. d.name = None
  450. d.parent = None
  451. d.set("__islocal", 1)
  452. d.owner = None
  453. d.creation = None
  454. return newdoc
  455. def compare(val1, condition, val2):
  456. import frappe.utils
  457. return frappe.utils.compare(val1, condition, val2)
  458. def respond_as_web_page(title, html, success=None, http_status_code=None):
  459. local.message_title = title
  460. local.message = html
  461. local.message_success = success
  462. local.response['type'] = 'page'
  463. local.response['page_name'] = 'message'
  464. if http_status_code:
  465. local.response['http_status_code'] = http_status_code
  466. def build_match_conditions(doctype, as_condition=True):
  467. import frappe.widgets.reportview
  468. return frappe.widgets.reportview.build_match_conditions(doctype, as_condition)
  469. def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None,
  470. group_by=None, order_by=None, limit_start=0, limit_page_length=None,
  471. as_list=False, debug=False, ignore_permissions=False, user=None):
  472. import frappe.model.db_query
  473. return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters,
  474. fields=fields, docstatus=docstatus, or_filters=or_filters,
  475. group_by=group_by, order_by=order_by, limit_start=limit_start,
  476. limit_page_length=limit_page_length, as_list=as_list, debug=debug,
  477. ignore_permissions=ignore_permissions, user=user)
  478. run_query = get_list
  479. def get_jenv():
  480. if not local.jenv:
  481. from jinja2 import Environment, DebugUndefined
  482. import frappe.utils
  483. # frappe will be loaded last, so app templates will get precedence
  484. jenv = Environment(loader = get_jloader(), undefined=DebugUndefined)
  485. set_filters(jenv)
  486. jenv.globals.update({
  487. "frappe": sys.modules[__name__],
  488. "frappe.utils": frappe.utils,
  489. "_": _
  490. })
  491. local.jenv = jenv
  492. return local.jenv
  493. def get_jloader():
  494. if not local.jloader:
  495. from jinja2 import ChoiceLoader, PackageLoader
  496. apps = get_installed_apps()
  497. apps.remove("frappe")
  498. local.jloader = ChoiceLoader([PackageLoader(app, ".") \
  499. for app in apps + ["frappe"]])
  500. return local.jloader
  501. def set_filters(jenv):
  502. from frappe.utils import global_date_format
  503. from frappe.website.utils import get_hex_shade
  504. from markdown2 import markdown
  505. from json import dumps
  506. jenv.filters["global_date_format"] = global_date_format
  507. jenv.filters["markdown"] = markdown
  508. jenv.filters["json"] = dumps
  509. jenv.filters["get_hex_shade"] = get_hex_shade
  510. # load jenv_filters from hooks.py
  511. for app in get_all_apps(True):
  512. for jenv_filter in (get_hooks(app_name=app).jenv_filter or []):
  513. filter_name, filter_function = jenv_filter.split(":")
  514. jenv.filters[filter_name] = get_attr(filter_function)
  515. def get_template(path):
  516. return get_jenv().get_template(path)
  517. def render_template(template, context):
  518. from jinja2 import Template
  519. template = Template(template)
  520. return template.render(**context)
  521. def get_website_route(doctype, name):
  522. return db.get_value("Website Route", {"ref_doctype": doctype, "docname": name})
  523. def add_version(doc):
  524. get_doc({
  525. "doctype": "Version",
  526. "ref_doctype": doc.doctype,
  527. "docname": doc.name,
  528. "doclist_json": json.dumps(doc.as_dict(), indent=1, sort_keys=True)
  529. }).insert(ignore_permissions=True)
  530. def get_test_records(doctype):
  531. from frappe.modules import get_doctype_module, get_module_path
  532. path = os.path.join(get_module_path(get_doctype_module(doctype)), "doctype", scrub(doctype), "test_records.json")
  533. if os.path.exists(path):
  534. with open(path, "r") as f:
  535. return json.loads(f.read())
  536. else:
  537. return []