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.
 
 
 
 
 
 

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