Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

1115 wiersze
35 KiB

  1. # Copyright (c) 2015, Frappe 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 functools import wraps
  10. import os, importlib, inspect, logging, json
  11. # public
  12. from .exceptions import *
  13. from .utils.jinja import get_jenv, get_template, render_template
  14. __version__ = "6.27.24"
  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, lang=None):
  36. """Returns translated string in current lang, if exists."""
  37. from frappe.translate import get_full_dict
  38. from frappe.utils import cstr
  39. if not lang:
  40. lang = local.lang
  41. # msg should always be unicode
  42. msg = cstr(msg)
  43. return get_full_dict(local.lang).get(msg) or msg
  44. def get_lang_dict(fortype, name=None):
  45. """Returns the translated language dict for the given type and name.
  46. :param fortype: must be one of `doctype`, `page`, `report`, `include`, `jsfile`, `boot`
  47. :param name: name of the document for which assets are to be returned."""
  48. if local.lang=="en":
  49. return {}
  50. from frappe.translate import get_dict
  51. return get_dict(fortype, name)
  52. def set_user_lang(user, user_language=None):
  53. """Guess and set user language for the session. `frappe.local.lang`"""
  54. from frappe.translate import get_user_lang
  55. local.lang = get_user_lang(user)
  56. # local-globals
  57. db = local("db")
  58. conf = local("conf")
  59. form = form_dict = local("form_dict")
  60. request = local("request")
  61. response = local("response")
  62. session = local("session")
  63. user = local("user")
  64. flags = local("flags")
  65. error_log = local("error_log")
  66. debug_log = local("debug_log")
  67. message_log = local("message_log")
  68. lang = local("lang")
  69. def init(site, sites_path=None, new_site=False):
  70. """Initialize frappe for the current site. Reset thread locals `frappe.local`"""
  71. if getattr(local, "initialised", None):
  72. return
  73. if not sites_path:
  74. sites_path = '.'
  75. local.error_log = []
  76. local.message_log = []
  77. local.debug_log = []
  78. local.realtime_log = []
  79. local.flags = _dict({
  80. "ran_schedulers": [],
  81. "redirect_location": "",
  82. "in_install_db": False,
  83. "in_install_app": False,
  84. "in_import": False,
  85. "in_test": False,
  86. "mute_messages": False,
  87. "ignore_links": False,
  88. "mute_emails": False,
  89. "has_dataurl": False,
  90. "new_site": new_site
  91. })
  92. local.rollback_observers = []
  93. local.test_objects = {}
  94. local.site = site
  95. local.sites_path = sites_path
  96. local.site_path = os.path.join(sites_path, site)
  97. local.request_ip = None
  98. local.response = _dict({"docs":[]})
  99. local.task_id = None
  100. local.conf = _dict(get_site_config())
  101. local.lang = local.conf.lang or "en"
  102. local.lang_full_dict = None
  103. local.module_app = None
  104. local.app_modules = None
  105. local.system_settings = None
  106. local.user = None
  107. local.user_perms = None
  108. local.session = None
  109. local.role_permissions = {}
  110. local.valid_columns = {}
  111. local.new_doc_templates = {}
  112. local.jenv = None
  113. local.jloader =None
  114. local.cache = {}
  115. setup_module_map()
  116. local.initialised = True
  117. def connect(site=None, db_name=None):
  118. """Connect to site database instance.
  119. :param site: If site is given, calls `frappe.init`.
  120. :param db_name: Optional. Will use from `site_config.json`."""
  121. from database import Database
  122. if site:
  123. init(site)
  124. local.db = Database(user=db_name or local.conf.db_name)
  125. local.form_dict = _dict()
  126. local.session = _dict()
  127. set_user("Administrator")
  128. def get_site_config(sites_path=None, site_path=None):
  129. """Returns `site_config.json` combined with `sites/common_site_config.json`.
  130. `site_config` is a set of site wide settings like database name, password, email etc."""
  131. config = {}
  132. sites_path = sites_path or getattr(local, "sites_path", None)
  133. site_path = site_path or getattr(local, "site_path", None)
  134. if sites_path:
  135. common_site_config = os.path.join(sites_path, "common_site_config.json")
  136. if os.path.exists(common_site_config):
  137. config.update(get_file_json(common_site_config))
  138. if site_path:
  139. site_config = os.path.join(site_path, "site_config.json")
  140. if os.path.exists(site_config):
  141. config.update(get_file_json(site_config))
  142. elif local.site and not local.flags.new_site:
  143. raise IncorrectSitePath, "{0} does not exist".format(site_config)
  144. return _dict(config)
  145. def destroy():
  146. """Closes connection and releases werkzeug local."""
  147. if db:
  148. db.close()
  149. release_local(local)
  150. # memcache
  151. redis_server = None
  152. def cache():
  153. """Returns memcache connection."""
  154. global redis_server
  155. if not redis_server:
  156. from frappe.utils.redis_wrapper import RedisWrapper
  157. redis_server = RedisWrapper.from_url(conf.get('redis_cache')
  158. or conf.get("cache_redis_server")
  159. or "redis://localhost:11311")
  160. return redis_server
  161. def get_traceback():
  162. """Returns error traceback."""
  163. import utils
  164. return utils.get_traceback()
  165. def errprint(msg):
  166. """Log error. This is sent back as `exc` in response.
  167. :param msg: Message."""
  168. from utils import cstr
  169. if not request or (not "cmd" in local.form_dict):
  170. print cstr(msg)
  171. error_log.append(cstr(msg))
  172. def log(msg):
  173. """Add to `debug_log`.
  174. :param msg: Message."""
  175. if not request:
  176. if conf.get("logging") or False:
  177. print repr(msg)
  178. from utils import cstr
  179. debug_log.append(cstr(msg))
  180. def msgprint(msg, small=0, raise_exception=0, as_table=False):
  181. """Print a message to the user (via HTTP response).
  182. Messages are sent in the `__server_messages` property in the
  183. response JSON and shown in a pop-up / modal.
  184. :param msg: Message.
  185. :param small: [optional] Show as a floating message in the footer.
  186. :param raise_exception: [optional] Raise given exception and show message.
  187. :param as_table: [optional] If `msg` is a list of lists, render as HTML table.
  188. """
  189. from utils import cstr, encode
  190. def _raise_exception():
  191. if raise_exception:
  192. if flags.rollback_on_exception:
  193. db.rollback()
  194. import inspect
  195. if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception):
  196. raise raise_exception, encode(msg)
  197. else:
  198. raise ValidationError, encode(msg)
  199. if flags.mute_messages:
  200. _raise_exception()
  201. return
  202. if as_table and type(msg) in (list, tuple):
  203. 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>'
  204. if flags.print_messages:
  205. print "Message: " + repr(msg).encode("utf-8")
  206. message_log.append((small and '__small:' or '')+cstr(msg or ''))
  207. _raise_exception()
  208. def throw(msg, exc=ValidationError):
  209. """Throw execption and show message (`msgprint`).
  210. :param msg: Message.
  211. :param exc: Exception class. Default `frappe.ValidationError`"""
  212. msgprint(msg, raise_exception=exc)
  213. def create_folder(path, with_init=False):
  214. """Create a folder in the given path and add an `__init__.py` file (optional).
  215. :param path: Folder path.
  216. :param with_init: Create `__init__.py` in the new folder."""
  217. from frappe.utils import touch_file
  218. if not os.path.exists(path):
  219. os.makedirs(path)
  220. if with_init:
  221. touch_file(os.path.join(path, "__init__.py"))
  222. def set_user(username):
  223. """Set current user.
  224. :param username: **User** name to set as current user."""
  225. local.session.user = username
  226. local.session.sid = username
  227. local.cache = {}
  228. local.form_dict = _dict()
  229. local.jenv = None
  230. local.session.data = _dict()
  231. local.role_permissions = {}
  232. local.new_doc_templates = {}
  233. local.user_perms = None
  234. def get_user():
  235. from frappe.utils.user import UserPermissions
  236. if not local.user_perms:
  237. local.user_perms = UserPermissions(local.session.user)
  238. return local.user_perms
  239. def get_roles(username=None):
  240. """Returns roles of current user."""
  241. if not local.session:
  242. return ["Guest"]
  243. if username:
  244. import frappe.utils.user
  245. return frappe.utils.user.get_roles(username)
  246. else:
  247. return get_user().get_roles()
  248. def get_request_header(key, default=None):
  249. """Return HTTP request header.
  250. :param key: HTTP header key.
  251. :param default: Default value."""
  252. return request.headers.get(key, default)
  253. def sendmail(recipients=(), sender="", subject="No Subject", message="No Message",
  254. as_markdown=False, bulk=False, reference_doctype=None, reference_name=None,
  255. unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
  256. attachments=None, content=None, doctype=None, name=None, reply_to=None,
  257. cc=(), show_as_cc=(), message_id=None, in_reply_to=None, as_bulk=False, send_after=None, expose_recipients=False,
  258. bulk_priority=1, communication=None):
  259. """Send email using user's default **Email Account** or global default **Email Account**.
  260. :param recipients: List of recipients.
  261. :param sender: Email sender. Default is current user.
  262. :param subject: Email Subject.
  263. :param message: (or `content`) Email Content.
  264. :param as_markdown: Convert content markdown to HTML.
  265. :param bulk: Send via scheduled email sender **Bulk Email**. Don't send immediately.
  266. :param bulk_priority: Priority for bulk email, default 1.
  267. :param reference_doctype: (or `doctype`) Append as communication to this DocType.
  268. :param reference_name: (or `name`) Append as communication to this document name.
  269. :param unsubscribe_method: Unsubscribe url with options email, doctype, name. e.g. `/api/method/unsubscribe`
  270. :param unsubscribe_params: Unsubscribe paramaters to be loaded on the unsubscribe_method [optional] (dict).
  271. :param attachments: List of attachments.
  272. :param reply_to: Reply-To email id.
  273. :param message_id: Used for threading. If a reply is received to this email, Message-Id is sent back as In-Reply-To in received email.
  274. :param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To.
  275. :param send_after: Send after the given datetime.
  276. :param expose_recipients: Display all recipients in the footer message - "This email was sent to"
  277. :param communication: Communication link to be set in Bulk Email record
  278. """
  279. if bulk or as_bulk:
  280. import frappe.email.bulk
  281. frappe.email.bulk.send(recipients=recipients, sender=sender,
  282. subject=subject, message=content or message,
  283. reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
  284. unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
  285. attachments=attachments, reply_to=reply_to, cc=cc, show_as_cc=show_as_cc, message_id=message_id, in_reply_to=in_reply_to,
  286. send_after=send_after, expose_recipients=expose_recipients, bulk_priority=bulk_priority, communication=communication)
  287. else:
  288. import frappe.email
  289. if as_markdown:
  290. frappe.email.sendmail_md(recipients, sender=sender,
  291. subject=subject, msg=content or message, attachments=attachments, reply_to=reply_to,
  292. cc=cc, message_id=message_id, in_reply_to=in_reply_to)
  293. else:
  294. frappe.email.sendmail(recipients, sender=sender,
  295. subject=subject, msg=content or message, attachments=attachments, reply_to=reply_to,
  296. cc=cc, message_id=message_id, in_reply_to=in_reply_to)
  297. logger = None
  298. whitelisted = []
  299. guest_methods = []
  300. xss_safe_methods = []
  301. def whitelist(allow_guest=False, xss_safe=False):
  302. """
  303. Decorator for whitelisting a function and making it accessible via HTTP.
  304. Standard request will be `/api/method/[path.to.method]`
  305. :param allow_guest: Allow non logged-in user to access this method.
  306. Use as:
  307. @frappe.whitelist()
  308. def myfunc(param1, param2):
  309. pass
  310. """
  311. def innerfn(fn):
  312. global whitelisted, guest_methods, xss_safe_methods
  313. whitelisted.append(fn)
  314. if allow_guest:
  315. guest_methods.append(fn)
  316. if xss_safe:
  317. xss_safe_methods.append(fn)
  318. return fn
  319. return innerfn
  320. def only_for(roles):
  321. """Raise `frappe.PermissionError` if the user does not have any of the given **Roles**.
  322. :param roles: List of roles to check."""
  323. if not isinstance(roles, (tuple, list)):
  324. roles = (roles,)
  325. roles = set(roles)
  326. myroles = set(get_roles())
  327. if not roles.intersection(myroles):
  328. raise PermissionError
  329. def clear_cache(user=None, doctype=None):
  330. """Clear **User**, **DocType** or global cache.
  331. :param user: If user is given, only user cache is cleared.
  332. :param doctype: If doctype is given, only DocType cache is cleared."""
  333. import frappe.sessions
  334. if doctype:
  335. import frappe.model.meta
  336. frappe.model.meta.clear_cache(doctype)
  337. reset_metadata_version()
  338. elif user:
  339. frappe.sessions.clear_cache(user)
  340. else: # everything
  341. import translate
  342. frappe.sessions.clear_cache()
  343. translate.clear_cache()
  344. reset_metadata_version()
  345. frappe.local.cache = {}
  346. for fn in frappe.get_hooks("clear_cache"):
  347. get_attr(fn)()
  348. frappe.local.role_permissions = {}
  349. def has_permission(doctype, ptype="read", doc=None, user=None, verbose=False, throw=False):
  350. """Raises `frappe.PermissionError` if not permitted.
  351. :param doctype: DocType for which permission is to be check.
  352. :param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`.
  353. :param doc: [optional] Checks User permissions for given doc.
  354. :param user: [optional] Check for given user. Default: current user."""
  355. import frappe.permissions
  356. out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user)
  357. if throw and not out:
  358. if doc:
  359. frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name))
  360. else:
  361. frappe.throw(_("No permission for {0}").format(doctype))
  362. return out
  363. def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=False):
  364. """Raises `frappe.PermissionError` if not permitted.
  365. :param doctype: DocType for which permission is to be check.
  366. :param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`.
  367. :param doc: Checks User permissions for given doc.
  368. :param user: [optional] Check for given user. Default: current user."""
  369. if not user:
  370. user = session.user
  371. hooks = (get_hooks("has_website_permission") or {}).get(doctype, [])
  372. if hooks:
  373. if isinstance(doc, basestring):
  374. doc = get_doc(doctype, doc)
  375. for method in hooks:
  376. result = call(get_attr(method), doc=doc, ptype=ptype, user=user, verbose=verbose)
  377. # if even a single permission check is Falsy
  378. if not result:
  379. return False
  380. # else it is Truthy
  381. return True
  382. else:
  383. return False
  384. def is_table(doctype):
  385. """Returns True if `istable` property (indicating child Table) is set for given DocType."""
  386. def get_tables():
  387. return db.sql_list("select name from tabDocType where istable=1")
  388. tables = cache().get_value("is_table", get_tables)
  389. return doctype in tables
  390. def get_precision(doctype, fieldname, currency=None, doc=None):
  391. """Get precision for a given field"""
  392. from frappe.model.meta import get_field_precision
  393. return get_field_precision(get_meta(doctype).get_field(fieldname), doc, currency)
  394. def generate_hash(txt=None, length=None):
  395. """Generates random hash for given text + current timestamp + random string."""
  396. import hashlib, time
  397. from .utils import random_string
  398. digest = hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest()
  399. if length:
  400. digest = digest[:length]
  401. return digest
  402. def reset_metadata_version():
  403. """Reset `metadata_version` (Client (Javascript) build ID) hash."""
  404. v = generate_hash()
  405. cache().set_value("metadata_version", v)
  406. return v
  407. def new_doc(doctype, parent_doc=None, parentfield=None, as_dict=False):
  408. """Returns a new document of the given DocType with defaults set.
  409. :param doctype: DocType of the new document.
  410. :param parent_doc: [optional] add to parent document.
  411. :param parentfield: [optional] add against this `parentfield`."""
  412. from frappe.model.create_new import get_new_doc
  413. return get_new_doc(doctype, parent_doc, parentfield, as_dict=as_dict)
  414. def set_value(doctype, docname, fieldname, value):
  415. """Set document value. Calls `frappe.client.set_value`"""
  416. import frappe.client
  417. return frappe.client.set_value(doctype, docname, fieldname, value)
  418. def get_doc(arg1, arg2=None):
  419. """Return a `frappe.model.document.Document` object of the given type and name.
  420. :param arg1: DocType name as string **or** document JSON.
  421. :param arg2: [optional] Document name as string.
  422. Examples:
  423. # insert a new document
  424. todo = frappe.get_doc({"doctype":"ToDo", "description": "test"})
  425. tood.insert()
  426. # open an existing document
  427. todo = frappe.get_doc("ToDo", "TD0001")
  428. """
  429. import frappe.model.document
  430. return frappe.model.document.get_doc(arg1, arg2)
  431. def get_last_doc(doctype):
  432. """Get last created document of this type."""
  433. d = get_all(doctype, ["name"], order_by="creation desc", limit_page_length=1)
  434. if d:
  435. return get_doc(doctype, d[0].name)
  436. else:
  437. raise DoesNotExistError
  438. def get_single(doctype):
  439. """Return a `frappe.model.document.Document` object of the given Single doctype."""
  440. return get_doc(doctype, doctype)
  441. def get_meta(doctype, cached=True):
  442. """Get `frappe.model.meta.Meta` instance of given doctype name."""
  443. import frappe.model.meta
  444. return frappe.model.meta.get_meta(doctype, cached=cached)
  445. def get_meta_module(doctype):
  446. import frappe.modules
  447. return frappe.modules.load_doctype_module(doctype)
  448. def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False,
  449. ignore_permissions=False, flags=None):
  450. """Delete a document. Calls `frappe.model.delete_doc.delete_doc`.
  451. :param doctype: DocType of document to be delete.
  452. :param name: Name of document to be delete.
  453. :param force: Allow even if document is linked. Warning: This may lead to data integrity errors.
  454. :param ignore_doctypes: Ignore if child table is one of these.
  455. :param for_reload: Call `before_reload` trigger before deleting.
  456. :param ignore_permissions: Ignore user permissions."""
  457. import frappe.model.delete_doc
  458. frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload,
  459. ignore_permissions, flags)
  460. def delete_doc_if_exists(doctype, name, force=0):
  461. """Delete document if exists."""
  462. if db.exists(doctype, name):
  463. delete_doc(doctype, name, force=force)
  464. def reload_doctype(doctype, force=False):
  465. """Reload DocType from model (`[module]/[doctype]/[name]/[name].json`) files."""
  466. reload_doc(scrub(db.get_value("DocType", doctype, "module")), "doctype", scrub(doctype), force=force)
  467. def reload_doc(module, dt=None, dn=None, force=False):
  468. """Reload Document from model (`[module]/[doctype]/[name]/[name].json`) files.
  469. :param module: Module name.
  470. :param dt: DocType name.
  471. :param dn: Document name.
  472. :param force: Reload even if `modified` timestamp matches.
  473. """
  474. import frappe.modules
  475. return frappe.modules.reload_doc(module, dt, dn, force=force)
  476. def rename_doc(doctype, old, new, debug=0, force=False, merge=False, ignore_permissions=False):
  477. """Rename a document. Calls `frappe.model.rename_doc.rename_doc`"""
  478. from frappe.model.rename_doc import rename_doc
  479. return rename_doc(doctype, old, new, force=force, merge=merge, ignore_permissions=ignore_permissions)
  480. def get_module(modulename):
  481. """Returns a module object for given Python module name using `importlib.import_module`."""
  482. return importlib.import_module(modulename)
  483. def scrub(txt):
  484. """Returns sluggified string. e.g. `Sales Order` becomes `sales_order`."""
  485. return txt.replace(' ','_').replace('-', '_').lower()
  486. def unscrub(txt):
  487. """Returns titlified string. e.g. `sales_order` becomes `Sales Order`."""
  488. return txt.replace('_',' ').replace('-', ' ').title()
  489. def get_module_path(module, *joins):
  490. """Get the path of the given module name.
  491. :param module: Module name.
  492. :param *joins: Join additional path elements using `os.path.join`."""
  493. module = scrub(module)
  494. return get_pymodule_path(local.module_app[module] + "." + module, *joins)
  495. def get_app_path(app_name, *joins):
  496. """Return path of given app.
  497. :param app: App name.
  498. :param *joins: Join additional path elements using `os.path.join`."""
  499. return get_pymodule_path(app_name, *joins)
  500. def get_site_path(*joins):
  501. """Return path of current site.
  502. :param *joins: Join additional path elements using `os.path.join`."""
  503. return os.path.join(local.site_path, *joins)
  504. def get_pymodule_path(modulename, *joins):
  505. """Return path of given Python module name.
  506. :param modulename: Python module name.
  507. :param *joins: Join additional path elements using `os.path.join`."""
  508. if not "public" in joins:
  509. joins = [scrub(part) for part in joins]
  510. return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__), *joins)
  511. def get_module_list(app_name):
  512. """Get list of modules for given all via `app/modules.txt`."""
  513. return get_file_items(os.path.join(os.path.dirname(get_module(app_name).__file__), "modules.txt"))
  514. def get_all_apps(with_internal_apps=True, sites_path=None):
  515. """Get list of all apps via `sites/apps.txt`."""
  516. if not sites_path:
  517. sites_path = local.sites_path
  518. apps = get_file_items(os.path.join(sites_path, "apps.txt"), raise_not_found=True)
  519. if with_internal_apps:
  520. for app in get_file_items(os.path.join(local.site_path, "apps.txt")):
  521. if app not in apps:
  522. apps.append(app)
  523. if "frappe" in apps:
  524. apps.remove("frappe")
  525. apps.insert(0, 'frappe')
  526. return apps
  527. def get_installed_apps(sort=False, frappe_last=False):
  528. """Get list of installed apps in current site."""
  529. if getattr(flags, "in_install_db", True):
  530. return []
  531. if not db:
  532. connect()
  533. installed = json.loads(db.get_global("installed_apps") or "[]")
  534. if sort:
  535. installed = [app for app in get_all_apps(True) if app in installed]
  536. if frappe_last:
  537. if 'frappe' in installed:
  538. installed.remove('frappe')
  539. installed.append('frappe')
  540. return installed
  541. def get_hooks(hook=None, default=None, app_name=None):
  542. """Get hooks via `app/hooks.py`
  543. :param hook: Name of the hook. Will gather all hooks for this name and return as a list.
  544. :param default: Default if no hook found.
  545. :param app_name: Filter by app."""
  546. def load_app_hooks(app_name=None):
  547. hooks = {}
  548. for app in [app_name] if app_name else get_installed_apps(sort=True):
  549. app = "frappe" if app=="webnotes" else app
  550. try:
  551. app_hooks = get_module(app + ".hooks")
  552. except ImportError:
  553. if local.flags.in_install_app:
  554. # if app is not installed while restoring
  555. # ignore it
  556. pass
  557. raise
  558. for key in dir(app_hooks):
  559. if not key.startswith("_"):
  560. append_hook(hooks, key, getattr(app_hooks, key))
  561. return hooks
  562. def append_hook(target, key, value):
  563. if isinstance(value, dict):
  564. target.setdefault(key, {})
  565. for inkey in value:
  566. append_hook(target[key], inkey, value[inkey])
  567. else:
  568. append_to_list(target, key, value)
  569. def append_to_list(target, key, value):
  570. target.setdefault(key, [])
  571. if not isinstance(value, list):
  572. value = [value]
  573. target[key].extend(value)
  574. if app_name:
  575. hooks = _dict(load_app_hooks(app_name))
  576. else:
  577. hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
  578. if hook:
  579. return hooks.get(hook) or (default if default is not None else [])
  580. else:
  581. return hooks
  582. def setup_module_map():
  583. """Rebuild map of all modules (internal)."""
  584. _cache = cache()
  585. if conf.db_name:
  586. local.app_modules = _cache.get_value("app_modules")
  587. local.module_app = _cache.get_value("module_app")
  588. if not (local.app_modules and local.module_app):
  589. local.module_app, local.app_modules = {}, {}
  590. for app in get_all_apps(True):
  591. if app=="webnotes": app="frappe"
  592. local.app_modules.setdefault(app, [])
  593. for module in get_module_list(app):
  594. module = scrub(module)
  595. local.module_app[module] = app
  596. local.app_modules[app].append(module)
  597. if conf.db_name:
  598. _cache.set_value("app_modules", local.app_modules)
  599. _cache.set_value("module_app", local.module_app)
  600. def get_file_items(path, raise_not_found=False, ignore_empty_lines=True):
  601. """Returns items from text file as a list. Ignores empty lines."""
  602. import frappe.utils
  603. content = read_file(path, raise_not_found=raise_not_found)
  604. if content:
  605. content = frappe.utils.strip(content)
  606. return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))]
  607. else:
  608. return []
  609. def get_file_json(path):
  610. """Read a file and return parsed JSON object."""
  611. with open(path, 'r') as f:
  612. return json.load(f)
  613. def read_file(path, raise_not_found=False):
  614. """Open a file and return its content as Unicode."""
  615. from frappe.utils import cstr
  616. if isinstance(path, unicode):
  617. path = path.encode("utf-8")
  618. if os.path.exists(path):
  619. with open(path, "r") as f:
  620. return cstr(f.read())
  621. elif raise_not_found:
  622. raise IOError("{} Not Found".format(path))
  623. else:
  624. return None
  625. def get_attr(method_string):
  626. """Get python method object from its name."""
  627. app_name = method_string.split(".")[0]
  628. if not local.flags.in_install and app_name not in get_installed_apps():
  629. throw(_("App {0} is not installed").format(app_name), AppNotInstalledError)
  630. modulename = '.'.join(method_string.split('.')[:-1])
  631. methodname = method_string.split('.')[-1]
  632. return getattr(get_module(modulename), methodname)
  633. def call(fn, *args, **kwargs):
  634. """Call a function and match arguments."""
  635. if hasattr(fn, 'fnargs'):
  636. fnargs = fn.fnargs
  637. else:
  638. fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
  639. newargs = {}
  640. for a in kwargs:
  641. if (a in fnargs) or varkw:
  642. newargs[a] = kwargs.get(a)
  643. if "flags" in newargs:
  644. del newargs["flags"]
  645. return fn(*args, **newargs)
  646. def make_property_setter(args, ignore_validate=False, validate_fields_for_doctype=True):
  647. """Create a new **Property Setter** (for overriding DocType and DocField properties)."""
  648. args = _dict(args)
  649. ps = get_doc({
  650. 'doctype': "Property Setter",
  651. 'doctype_or_field': args.doctype_or_field or "DocField",
  652. 'doc_type': args.doctype,
  653. 'field_name': args.fieldname,
  654. 'property': args.property,
  655. 'value': args.value,
  656. 'property_type': args.property_type or "Data",
  657. '__islocal': 1
  658. })
  659. ps.flags.ignore_validate = ignore_validate
  660. ps.flags.validate_fields_for_doctype = validate_fields_for_doctype
  661. ps.insert()
  662. def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
  663. """Import a file using Data Import Tool."""
  664. from frappe.core.page.data_import_tool import data_import_tool
  665. data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)
  666. def copy_doc(doc, ignore_no_copy=True):
  667. """ No_copy fields also get copied."""
  668. import copy
  669. from frappe.model import optional_fields, default_fields
  670. def remove_no_copy_fields(d):
  671. for df in d.meta.get("fields", {"no_copy": 1}):
  672. if hasattr(d, df.fieldname):
  673. d.set(df.fieldname, None)
  674. fields_to_clear = ['name', 'owner', 'creation', 'modified', 'modified_by']
  675. if not local.flags.in_test:
  676. fields_to_clear.append("docstatus")
  677. if not isinstance(doc, dict):
  678. d = doc.as_dict()
  679. else:
  680. d = doc
  681. newdoc = get_doc(copy.deepcopy(d))
  682. newdoc.set("__islocal", 1)
  683. for fieldname in (fields_to_clear + ['amended_from', 'amendment_date']):
  684. newdoc.set(fieldname, None)
  685. if not ignore_no_copy:
  686. remove_no_copy_fields(newdoc)
  687. for i, d in enumerate(newdoc.get_all_children()):
  688. d.set("__islocal", 1)
  689. for fieldname in fields_to_clear:
  690. d.set(fieldname, None)
  691. if not ignore_no_copy:
  692. remove_no_copy_fields(d)
  693. return newdoc
  694. def compare(val1, condition, val2):
  695. """Compare two values using `frappe.utils.compare`
  696. `condition` could be:
  697. - "^"
  698. - "in"
  699. - "not in"
  700. - "="
  701. - "!="
  702. - ">"
  703. - "<"
  704. - ">="
  705. - "<="
  706. - "not None"
  707. - "None"
  708. """
  709. import frappe.utils
  710. return frappe.utils.compare(val1, condition, val2)
  711. def respond_as_web_page(title, html, success=None, http_status_code=None, context=None):
  712. """Send response as a web page with a message rather than JSON. Used to show permission errors etc.
  713. :param title: Page title and heading.
  714. :param message: Message to be shown.
  715. :param success: Alert message.
  716. :param http_status_code: HTTP status code."""
  717. local.message_title = title
  718. local.message = html
  719. local.message_success = success
  720. local.response['type'] = 'page'
  721. local.response['page_name'] = 'message'
  722. if http_status_code:
  723. local.response['http_status_code'] = http_status_code
  724. if context:
  725. local.response['context'] = context
  726. def build_match_conditions(doctype, as_condition=True):
  727. """Return match (User permissions) for given doctype as list or SQL."""
  728. import frappe.desk.reportview
  729. return frappe.desk.reportview.build_match_conditions(doctype, as_condition)
  730. def get_list(doctype, *args, **kwargs):
  731. """List database query via `frappe.model.db_query`. Will also check for permissions.
  732. :param doctype: DocType on which query is to be made.
  733. :param fields: List of fields or `*`.
  734. :param filters: List of filters (see example).
  735. :param order_by: Order By e.g. `modified desc`.
  736. :param limit_page_start: Start results at record #. Default 0.
  737. :param limit_poge_length: No of records in the page. Default 20.
  738. Example usage:
  739. # simple dict filter
  740. frappe.get_list("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"})
  741. # filter as a list of lists
  742. frappe.get_list("ToDo", fields="*", filters = [["modified", ">", "2014-01-01"]])
  743. # filter as a list of dicts
  744. frappe.get_list("ToDo", fields="*", filters = {"description": ("like", "test%")})
  745. """
  746. import frappe.model.db_query
  747. return frappe.model.db_query.DatabaseQuery(doctype).execute(None, *args, **kwargs)
  748. def get_all(doctype, *args, **kwargs):
  749. """List database query via `frappe.model.db_query`. Will **not** check for conditions.
  750. Parameters are same as `frappe.get_list`
  751. :param doctype: DocType on which query is to be made.
  752. :param fields: List of fields or `*`. Default is: `["name"]`.
  753. :param filters: List of filters (see example).
  754. :param order_by: Order By e.g. `modified desc`.
  755. :param limit_page_start: Start results at record #. Default 0.
  756. :param limit_poge_length: No of records in the page. Default 20.
  757. Example usage:
  758. # simple dict filter
  759. frappe.get_all("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"})
  760. # filter as a list of lists
  761. frappe.get_all("ToDo", fields=["*"], filters = [["modified", ">", "2014-01-01"]])
  762. # filter as a list of dicts
  763. frappe.get_all("ToDo", fields=["*"], filters = {"description": ("like", "test%")})
  764. """
  765. kwargs["ignore_permissions"] = True
  766. if not "limit_page_length" in kwargs:
  767. kwargs["limit_page_length"] = 0
  768. return get_list(doctype, *args, **kwargs)
  769. def get_value(*args, **kwargs):
  770. """Returns a document property or list of properties.
  771. Alias for `frappe.db.get_value`
  772. :param doctype: DocType name.
  773. :param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType.
  774. :param fieldname: Column name.
  775. :param ignore: Don't raise exception if table, column is missing.
  776. :param as_dict: Return values as dict.
  777. :param debug: Print query in error log.
  778. """
  779. return db.get_value(*args, **kwargs)
  780. def add_version(doc):
  781. """Insert a new **Version** of the given document.
  782. A **Version** is a JSON dump of the current document state."""
  783. get_doc({
  784. "doctype": "Version",
  785. "ref_doctype": doc.doctype,
  786. "docname": doc.name,
  787. "doclist_json": as_json(doc.as_dict())
  788. }).insert(ignore_permissions=True)
  789. def as_json(obj, indent=1):
  790. from frappe.utils.response import json_handler
  791. return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler)
  792. def are_emails_muted():
  793. return flags.mute_emails or conf.get("mute_emails") or False
  794. def get_test_records(doctype):
  795. """Returns list of objects from `test_records.json` in the given doctype's folder."""
  796. from frappe.modules import get_doctype_module, get_module_path
  797. path = os.path.join(get_module_path(get_doctype_module(doctype)), "doctype", scrub(doctype), "test_records.json")
  798. if os.path.exists(path):
  799. with open(path, "r") as f:
  800. return json.loads(f.read())
  801. else:
  802. return []
  803. def format_value(value, df, doc=None, currency=None):
  804. """Format value with given field properties.
  805. :param value: Value to be formatted.
  806. :param df: DocField object with properties `fieldtype`, `options` etc."""
  807. import frappe.utils.formatters
  808. return frappe.utils.formatters.format_value(value, df, doc, currency=currency)
  809. def get_print(doctype, name, print_format=None, style=None, html=None, as_pdf=False):
  810. """Get Print Format for given document.
  811. :param doctype: DocType of document.
  812. :param name: Name of document.
  813. :param print_format: Print Format name. Default 'Standard',
  814. :param style: Print Format style.
  815. :param as_pdf: Return as PDF. Default False."""
  816. from frappe.website.render import build_page
  817. from frappe.utils.pdf import get_pdf
  818. local.form_dict.doctype = doctype
  819. local.form_dict.name = name
  820. local.form_dict.format = print_format
  821. local.form_dict.style = style
  822. if not html:
  823. html = build_page("print")
  824. if as_pdf:
  825. return get_pdf(html)
  826. else:
  827. return html
  828. def attach_print(doctype, name, file_name=None, print_format=None, style=None, html=None):
  829. from frappe.utils import scrub_urls
  830. if not file_name: file_name = name
  831. file_name = file_name.replace(' ','').replace('/','-')
  832. print_settings = db.get_singles_dict("Print Settings")
  833. local.flags.ignore_print_permissions = True
  834. if int(print_settings.send_print_as_pdf or 0):
  835. out = {
  836. "fname": file_name + ".pdf",
  837. "fcontent": get_print(doctype, name, print_format=print_format, style=style, html=html, as_pdf=True)
  838. }
  839. else:
  840. out = {
  841. "fname": file_name + ".html",
  842. "fcontent": scrub_urls(get_print(doctype, name, print_format=print_format, style=style, html=html)).encode("utf-8")
  843. }
  844. local.flags.ignore_print_permissions = False
  845. return out
  846. logging_setup_complete = False
  847. def get_logger(module=None, loglevel="DEBUG"):
  848. from frappe.setup_logging import setup_logging
  849. global logging_setup_complete
  850. if not logging_setup_complete:
  851. setup_logging()
  852. logging_setup_complete = True
  853. logger = logging.getLogger(module or "frappe")
  854. logger.setLevel(logging.DEBUG)
  855. return logger
  856. def publish_realtime(*args, **kwargs):
  857. """Publish real-time updates
  858. :param event: Event name, like `task_progress` etc.
  859. :param message: JSON message object. For async must contain `task_id`
  860. :param room: Room in which to publish update (default entire site)
  861. :param user: Transmit to user
  862. :param doctype: Transmit to doctype, docname
  863. :param docname: Transmit to doctype, docname
  864. :param after_commit: (default False) will emit after current transaction is committed
  865. """
  866. import frappe.async
  867. return frappe.async.publish_realtime(*args, **kwargs)
  868. def local_cache(namespace, key, generator, regenerate_if_none=False):
  869. """A key value store for caching within a request
  870. :param namespace: frappe.local.cache[namespace]
  871. :param key: frappe.local.cache[namespace][key] used to retrieve value
  872. :param generator: method to generate a value if not found in store
  873. """
  874. if namespace not in local.cache:
  875. local.cache[namespace] = {}
  876. if key not in local.cache[namespace]:
  877. local.cache[namespace][key] = generator()
  878. elif local.cache[namespace][key]==None and regenerate_if_none:
  879. # if key exists but the previous result was None
  880. local.cache[namespace][key] = generator()
  881. return local.cache[namespace][key]
  882. def get_doctype_app(doctype):
  883. def _get_doctype_app():
  884. doctype_module = local.db.get_value("DocType", doctype, "module")
  885. return local.module_app[scrub(doctype_module)]
  886. return local_cache("doctype_app", doctype, generator=_get_doctype_app)