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

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