Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 
 

1395 righe
43 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, print_function
  8. from six import iteritems, text_type
  9. from werkzeug.local import Local, release_local
  10. import os, sys, importlib, inspect, json
  11. # public
  12. from .exceptions import *
  13. from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template
  14. __version__ = '8.7.1'
  15. __title__ = "Frappe Framework"
  16. local = Local()
  17. class _dict(dict):
  18. """dict like object that exposes keys as attributes"""
  19. def __getattr__(self, key):
  20. ret = self.get(key)
  21. if not ret and key.startswith("__"):
  22. raise AttributeError()
  23. return ret
  24. def __setattr__(self, key, value):
  25. self[key] = value
  26. def __getstate__(self):
  27. return self
  28. def __setstate__(self, d):
  29. self.update(d)
  30. def update(self, d):
  31. """update and return self -- the missing dict feature in python"""
  32. super(_dict, self).update(d)
  33. return self
  34. def copy(self):
  35. return _dict(dict(self).copy())
  36. def _(msg, lang=None):
  37. """Returns translated string in current lang, if exists."""
  38. from frappe.translate import get_full_dict
  39. if not hasattr(local, 'lang'):
  40. local.lang = lang or 'en'
  41. if not lang:
  42. lang = local.lang
  43. # msg should always be unicode
  44. msg = as_unicode(msg).strip()
  45. # return lang_full_dict according to lang passed parameter
  46. return get_full_dict(lang).get(msg) or msg
  47. def as_unicode(text, encoding='utf-8'):
  48. '''Convert to unicode if required'''
  49. if isinstance(text, text_type):
  50. return text
  51. elif text==None:
  52. return ''
  53. elif isinstance(text, basestring):
  54. return text_type(text, encoding)
  55. else:
  56. return text_type(text)
  57. def get_lang_dict(fortype, name=None):
  58. """Returns the translated language dict for the given type and name.
  59. :param fortype: must be one of `doctype`, `page`, `report`, `include`, `jsfile`, `boot`
  60. :param name: name of the document for which assets are to be returned."""
  61. from frappe.translate import get_dict
  62. return get_dict(fortype, name)
  63. def set_user_lang(user, user_language=None):
  64. """Guess and set user language for the session. `frappe.local.lang`"""
  65. from frappe.translate import get_user_lang
  66. local.lang = get_user_lang(user)
  67. # local-globals
  68. db = local("db")
  69. conf = local("conf")
  70. form = form_dict = local("form_dict")
  71. request = local("request")
  72. response = local("response")
  73. session = local("session")
  74. user = local("user")
  75. flags = local("flags")
  76. error_log = local("error_log")
  77. debug_log = local("debug_log")
  78. message_log = local("message_log")
  79. lang = local("lang")
  80. def init(site, sites_path=None, new_site=False):
  81. """Initialize frappe for the current site. Reset thread locals `frappe.local`"""
  82. if getattr(local, "initialised", None):
  83. return
  84. if not sites_path:
  85. sites_path = '.'
  86. local.error_log = []
  87. local.message_log = []
  88. local.debug_log = []
  89. local.realtime_log = []
  90. local.flags = _dict({
  91. "ran_schedulers": [],
  92. "currently_saving": [],
  93. "redirect_location": "",
  94. "in_install_db": False,
  95. "in_install_app": False,
  96. "in_import": False,
  97. "in_test": False,
  98. "mute_messages": False,
  99. "ignore_links": False,
  100. "mute_emails": False,
  101. "has_dataurl": False,
  102. "new_site": new_site
  103. })
  104. local.rollback_observers = []
  105. local.test_objects = {}
  106. local.site = site
  107. local.sites_path = sites_path
  108. local.site_path = os.path.join(sites_path, site)
  109. local.request_ip = None
  110. local.response = _dict({"docs":[]})
  111. local.task_id = None
  112. local.conf = _dict(get_site_config())
  113. local.lang = local.conf.lang or "en"
  114. local.lang_full_dict = None
  115. local.module_app = None
  116. local.app_modules = None
  117. local.system_settings = _dict()
  118. local.user = None
  119. local.user_perms = None
  120. local.session = None
  121. local.role_permissions = {}
  122. local.valid_columns = {}
  123. local.new_doc_templates = {}
  124. local.link_count = {}
  125. local.jenv = None
  126. local.jloader =None
  127. local.cache = {}
  128. local.meta_cache = {}
  129. local.form_dict = _dict()
  130. local.session = _dict()
  131. setup_module_map()
  132. local.initialised = True
  133. def connect(site=None, db_name=None):
  134. """Connect to site database instance.
  135. :param site: If site is given, calls `frappe.init`.
  136. :param db_name: Optional. Will use from `site_config.json`."""
  137. from database import Database
  138. if site:
  139. init(site)
  140. local.db = Database(user=db_name or local.conf.db_name)
  141. set_user("Administrator")
  142. def get_site_config(sites_path=None, site_path=None):
  143. """Returns `site_config.json` combined with `sites/common_site_config.json`.
  144. `site_config` is a set of site wide settings like database name, password, email etc."""
  145. config = {}
  146. sites_path = sites_path or getattr(local, "sites_path", None)
  147. site_path = site_path or getattr(local, "site_path", None)
  148. if sites_path:
  149. common_site_config = os.path.join(sites_path, "common_site_config.json")
  150. if os.path.exists(common_site_config):
  151. config.update(get_file_json(common_site_config))
  152. if site_path:
  153. site_config = os.path.join(site_path, "site_config.json")
  154. if os.path.exists(site_config):
  155. config.update(get_file_json(site_config))
  156. elif local.site and not local.flags.new_site:
  157. print("{0} does not exist".format(local.site))
  158. sys.exit(1)
  159. #raise IncorrectSitePath, "{0} does not exist".format(site_config)
  160. return _dict(config)
  161. def get_conf(site=None):
  162. if hasattr(local, 'conf'):
  163. return local.conf
  164. else:
  165. # if no site, get from common_site_config.json
  166. with init_site(site):
  167. return local.conf
  168. class init_site:
  169. def __init__(self, site=None):
  170. '''If site==None, initialize it for empty site ('') to load common_site_config.json'''
  171. self.site = site or ''
  172. def __enter__(self):
  173. init(self.site)
  174. return local
  175. def __exit__(self, type, value, traceback):
  176. destroy()
  177. def destroy():
  178. """Closes connection and releases werkzeug local."""
  179. if db:
  180. db.close()
  181. release_local(local)
  182. # memcache
  183. redis_server = None
  184. def cache():
  185. """Returns memcache connection."""
  186. global redis_server
  187. if not redis_server:
  188. from frappe.utils.redis_wrapper import RedisWrapper
  189. redis_server = RedisWrapper.from_url(conf.get('redis_cache')
  190. or "redis://localhost:11311")
  191. return redis_server
  192. def get_traceback():
  193. """Returns error traceback."""
  194. import utils
  195. return utils.get_traceback()
  196. def errprint(msg):
  197. """Log error. This is sent back as `exc` in response.
  198. :param msg: Message."""
  199. msg = as_unicode(msg)
  200. if not request or (not "cmd" in local.form_dict) or conf.developer_mode:
  201. print(msg.encode('utf-8'))
  202. error_log.append(msg)
  203. def log(msg):
  204. """Add to `debug_log`.
  205. :param msg: Message."""
  206. if not request:
  207. if conf.get("logging") or False:
  208. print(repr(msg))
  209. debug_log.append(as_unicode(msg))
  210. def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False):
  211. """Print a message to the user (via HTTP response).
  212. Messages are sent in the `__server_messages` property in the
  213. response JSON and shown in a pop-up / modal.
  214. :param msg: Message.
  215. :param title: [optional] Message title.
  216. :param raise_exception: [optional] Raise given exception and show message.
  217. :param as_table: [optional] If `msg` is a list of lists, render as HTML table.
  218. """
  219. from utils import encode
  220. out = _dict(message=msg)
  221. def _raise_exception():
  222. if raise_exception:
  223. if flags.rollback_on_exception:
  224. db.rollback()
  225. import inspect
  226. if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception):
  227. raise raise_exception(encode(msg))
  228. else:
  229. raise ValidationError(encode(msg))
  230. if flags.mute_messages:
  231. _raise_exception()
  232. return
  233. if as_table and type(msg) in (list, tuple):
  234. out.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>'
  235. if flags.print_messages and out.msg:
  236. print("Message: " + repr(out.msg).encode("utf-8"))
  237. if title:
  238. out.title = title
  239. if not indicator and raise_exception:
  240. indicator = 'red'
  241. if indicator:
  242. out.indicator = indicator
  243. if alert:
  244. out.alert = 1
  245. message_log.append(json.dumps(out))
  246. _raise_exception()
  247. def clear_messages():
  248. local.message_log = []
  249. def throw(msg, exc=ValidationError, title=None):
  250. """Throw execption and show message (`msgprint`).
  251. :param msg: Message.
  252. :param exc: Exception class. Default `frappe.ValidationError`"""
  253. msgprint(msg, raise_exception=exc, title=title, indicator='red')
  254. def emit_js(js, user=False, **kwargs):
  255. from frappe.async import publish_realtime
  256. if user == False:
  257. user = session.user
  258. publish_realtime('eval_js', js, user=user, **kwargs)
  259. def create_folder(path, with_init=False):
  260. """Create a folder in the given path and add an `__init__.py` file (optional).
  261. :param path: Folder path.
  262. :param with_init: Create `__init__.py` in the new folder."""
  263. from frappe.utils import touch_file
  264. if not os.path.exists(path):
  265. os.makedirs(path)
  266. if with_init:
  267. touch_file(os.path.join(path, "__init__.py"))
  268. def set_user(username):
  269. """Set current user.
  270. :param username: **User** name to set as current user."""
  271. local.session.user = username
  272. local.session.sid = username
  273. local.cache = {}
  274. local.form_dict = _dict()
  275. local.jenv = None
  276. local.session.data = _dict()
  277. local.role_permissions = {}
  278. local.new_doc_templates = {}
  279. local.user_perms = None
  280. def get_user():
  281. from frappe.utils.user import UserPermissions
  282. if not local.user_perms:
  283. local.user_perms = UserPermissions(local.session.user)
  284. return local.user_perms
  285. def get_roles(username=None):
  286. """Returns roles of current user."""
  287. if not local.session:
  288. return ["Guest"]
  289. if username:
  290. import frappe.permissions
  291. return frappe.permissions.get_roles(username)
  292. else:
  293. return get_user().get_roles()
  294. def get_request_header(key, default=None):
  295. """Return HTTP request header.
  296. :param key: HTTP header key.
  297. :param default: Default value."""
  298. return request.headers.get(key, default)
  299. def sendmail(recipients=[], sender="", subject="No Subject", message="No Message",
  300. as_markdown=False, delayed=True, reference_doctype=None, reference_name=None,
  301. unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
  302. attachments=None, content=None, doctype=None, name=None, reply_to=None,
  303. cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
  304. send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False,
  305. inline_images=None, template=None, args=None, header=None):
  306. """Send email using user's default **Email Account** or global default **Email Account**.
  307. :param recipients: List of recipients.
  308. :param sender: Email sender. Default is current user.
  309. :param subject: Email Subject.
  310. :param message: (or `content`) Email Content.
  311. :param as_markdown: Convert content markdown to HTML.
  312. :param delayed: Send via scheduled email sender **Email Queue**. Don't send immediately. Default is true
  313. :param send_priority: Priority for Email Queue, default 1.
  314. :param reference_doctype: (or `doctype`) Append as communication to this DocType.
  315. :param reference_name: (or `name`) Append as communication to this document name.
  316. :param unsubscribe_method: Unsubscribe url with options email, doctype, name. e.g. `/api/method/unsubscribe`
  317. :param unsubscribe_params: Unsubscribe paramaters to be loaded on the unsubscribe_method [optional] (dict).
  318. :param attachments: List of attachments.
  319. :param reply_to: Reply-To Email Address.
  320. :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.
  321. :param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To.
  322. :param send_after: Send after the given datetime.
  323. :param expose_recipients: Display all recipients in the footer message - "This email was sent to"
  324. :param communication: Communication link to be set in Email Queue record
  325. :param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id
  326. :param template: Name of html template from templates/emails folder
  327. :param args: Arguments for rendering the template
  328. :param header: Append header in email
  329. """
  330. text_content = None
  331. if template:
  332. message, text_content = get_email_from_template(template, args)
  333. message = content or message
  334. if as_markdown:
  335. from markdown2 import markdown
  336. message = markdown(message)
  337. if not delayed:
  338. now = True
  339. import email.queue
  340. email.queue.send(recipients=recipients, sender=sender,
  341. subject=subject, message=message, text_content=text_content,
  342. reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
  343. unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
  344. attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to,
  345. send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority,
  346. communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification,
  347. inline_images=inline_images, header=header)
  348. whitelisted = []
  349. guest_methods = []
  350. xss_safe_methods = []
  351. def whitelist(allow_guest=False, xss_safe=False):
  352. """
  353. Decorator for whitelisting a function and making it accessible via HTTP.
  354. Standard request will be `/api/method/[path.to.method]`
  355. :param allow_guest: Allow non logged-in user to access this method.
  356. Use as:
  357. @frappe.whitelist()
  358. def myfunc(param1, param2):
  359. pass
  360. """
  361. def innerfn(fn):
  362. global whitelisted, guest_methods, xss_safe_methods
  363. whitelisted.append(fn)
  364. if allow_guest:
  365. guest_methods.append(fn)
  366. if xss_safe:
  367. xss_safe_methods.append(fn)
  368. return fn
  369. return innerfn
  370. def only_for(roles):
  371. """Raise `frappe.PermissionError` if the user does not have any of the given **Roles**.
  372. :param roles: List of roles to check."""
  373. if local.flags.in_test:
  374. return
  375. if not isinstance(roles, (tuple, list)):
  376. roles = (roles,)
  377. roles = set(roles)
  378. myroles = set(get_roles())
  379. if not roles.intersection(myroles):
  380. raise PermissionError
  381. def clear_cache(user=None, doctype=None):
  382. """Clear **User**, **DocType** or global cache.
  383. :param user: If user is given, only user cache is cleared.
  384. :param doctype: If doctype is given, only DocType cache is cleared."""
  385. import frappe.sessions
  386. if doctype:
  387. import frappe.model.meta
  388. frappe.model.meta.clear_cache(doctype)
  389. reset_metadata_version()
  390. elif user:
  391. frappe.sessions.clear_cache(user)
  392. else: # everything
  393. import translate
  394. frappe.sessions.clear_cache()
  395. translate.clear_cache()
  396. reset_metadata_version()
  397. clear_domainification_cache()
  398. local.cache = {}
  399. local.new_doc_templates = {}
  400. for fn in get_hooks("clear_cache"):
  401. get_attr(fn)()
  402. local.role_permissions = {}
  403. def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False):
  404. """Raises `frappe.PermissionError` if not permitted.
  405. :param doctype: DocType for which permission is to be check.
  406. :param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`.
  407. :param doc: [optional] Checks User permissions for given doc.
  408. :param user: [optional] Check for given user. Default: current user."""
  409. if not doctype and doc:
  410. doctype = doc.doctype
  411. import frappe.permissions
  412. out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user)
  413. if throw and not out:
  414. if doc:
  415. frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name))
  416. else:
  417. frappe.throw(_("No permission for {0}").format(doctype))
  418. return out
  419. def has_website_permission(doc=None, ptype='read', user=None, verbose=False, doctype=None):
  420. """Raises `frappe.PermissionError` if not permitted.
  421. :param doctype: DocType for which permission is to be check.
  422. :param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`.
  423. :param doc: Checks User permissions for given doc.
  424. :param user: [optional] Check for given user. Default: current user."""
  425. if not user:
  426. user = session.user
  427. if doc:
  428. if isinstance(doc, basestring):
  429. doc = get_doc(doctype, doc)
  430. doctype = doc.doctype
  431. if doc.flags.ignore_permissions:
  432. return True
  433. # check permission in controller
  434. if hasattr(doc, 'has_website_permission'):
  435. return doc.has_website_permission(ptype, verbose=verbose)
  436. hooks = (get_hooks("has_website_permission") or {}).get(doctype, [])
  437. if hooks:
  438. for method in hooks:
  439. result = call(method, doc=doc, ptype=ptype, user=user, verbose=verbose)
  440. # if even a single permission check is Falsy
  441. if not result:
  442. return False
  443. # else it is Truthy
  444. return True
  445. else:
  446. return False
  447. def is_table(doctype):
  448. """Returns True if `istable` property (indicating child Table) is set for given DocType."""
  449. def get_tables():
  450. return db.sql_list("select name from tabDocType where istable=1")
  451. tables = cache().get_value("is_table", get_tables)
  452. return doctype in tables
  453. def get_precision(doctype, fieldname, currency=None, doc=None):
  454. """Get precision for a given field"""
  455. from frappe.model.meta import get_field_precision
  456. return get_field_precision(get_meta(doctype).get_field(fieldname), doc, currency)
  457. def generate_hash(txt=None, length=None):
  458. """Generates random hash for given text + current timestamp + random string."""
  459. import hashlib, time
  460. from .utils import random_string
  461. digest = hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest()
  462. if length:
  463. digest = digest[:length]
  464. return digest
  465. def reset_metadata_version():
  466. """Reset `metadata_version` (Client (Javascript) build ID) hash."""
  467. v = generate_hash()
  468. cache().set_value("metadata_version", v)
  469. return v
  470. def new_doc(doctype, parent_doc=None, parentfield=None, as_dict=False):
  471. """Returns a new document of the given DocType with defaults set.
  472. :param doctype: DocType of the new document.
  473. :param parent_doc: [optional] add to parent document.
  474. :param parentfield: [optional] add against this `parentfield`."""
  475. from frappe.model.create_new import get_new_doc
  476. return get_new_doc(doctype, parent_doc, parentfield, as_dict=as_dict)
  477. def set_value(doctype, docname, fieldname, value=None):
  478. """Set document value. Calls `frappe.client.set_value`"""
  479. import frappe.client
  480. return frappe.client.set_value(doctype, docname, fieldname, value)
  481. def get_doc(arg1, arg2=None):
  482. """Return a `frappe.model.document.Document` object of the given type and name.
  483. :param arg1: DocType name as string **or** document JSON.
  484. :param arg2: [optional] Document name as string.
  485. Examples:
  486. # insert a new document
  487. todo = frappe.get_doc({"doctype":"ToDo", "description": "test"})
  488. tood.insert()
  489. # open an existing document
  490. todo = frappe.get_doc("ToDo", "TD0001")
  491. """
  492. import frappe.model.document
  493. return frappe.model.document.get_doc(arg1, arg2)
  494. def get_last_doc(doctype):
  495. """Get last created document of this type."""
  496. d = get_all(doctype, ["name"], order_by="creation desc", limit_page_length=1)
  497. if d:
  498. return get_doc(doctype, d[0].name)
  499. else:
  500. raise DoesNotExistError
  501. def get_single(doctype):
  502. """Return a `frappe.model.document.Document` object of the given Single doctype."""
  503. return get_doc(doctype, doctype)
  504. def get_meta(doctype, cached=True):
  505. """Get `frappe.model.meta.Meta` instance of given doctype name."""
  506. import frappe.model.meta
  507. return frappe.model.meta.get_meta(doctype, cached=cached)
  508. def get_meta_module(doctype):
  509. import frappe.modules
  510. return frappe.modules.load_doctype_module(doctype)
  511. def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False,
  512. ignore_permissions=False, flags=None):
  513. """Delete a document. Calls `frappe.model.delete_doc.delete_doc`.
  514. :param doctype: DocType of document to be delete.
  515. :param name: Name of document to be delete.
  516. :param force: Allow even if document is linked. Warning: This may lead to data integrity errors.
  517. :param ignore_doctypes: Ignore if child table is one of these.
  518. :param for_reload: Call `before_reload` trigger before deleting.
  519. :param ignore_permissions: Ignore user permissions."""
  520. import frappe.model.delete_doc
  521. frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload,
  522. ignore_permissions, flags)
  523. def delete_doc_if_exists(doctype, name, force=0):
  524. """Delete document if exists."""
  525. if db.exists(doctype, name):
  526. delete_doc(doctype, name, force=force)
  527. def reload_doctype(doctype, force=False, reset_permissions=False):
  528. """Reload DocType from model (`[module]/[doctype]/[name]/[name].json`) files."""
  529. reload_doc(scrub(db.get_value("DocType", doctype, "module")), "doctype", scrub(doctype),
  530. force=force, reset_permissions=reset_permissions)
  531. def reload_doc(module, dt=None, dn=None, force=False, reset_permissions=False):
  532. """Reload Document from model (`[module]/[doctype]/[name]/[name].json`) files.
  533. :param module: Module name.
  534. :param dt: DocType name.
  535. :param dn: Document name.
  536. :param force: Reload even if `modified` timestamp matches.
  537. """
  538. import frappe.modules
  539. return frappe.modules.reload_doc(module, dt, dn, force=force, reset_permissions=reset_permissions)
  540. def rename_doc(*args, **kwargs):
  541. """Rename a document. Calls `frappe.model.rename_doc.rename_doc`"""
  542. from frappe.model.rename_doc import rename_doc
  543. return rename_doc(*args, **kwargs)
  544. def get_module(modulename):
  545. """Returns a module object for given Python module name using `importlib.import_module`."""
  546. return importlib.import_module(modulename)
  547. def scrub(txt):
  548. """Returns sluggified string. e.g. `Sales Order` becomes `sales_order`."""
  549. return txt.replace(' ','_').replace('-', '_').lower()
  550. def unscrub(txt):
  551. """Returns titlified string. e.g. `sales_order` becomes `Sales Order`."""
  552. return txt.replace('_',' ').replace('-', ' ').title()
  553. def get_module_path(module, *joins):
  554. """Get the path of the given module name.
  555. :param module: Module name.
  556. :param *joins: Join additional path elements using `os.path.join`."""
  557. module = scrub(module)
  558. return get_pymodule_path(local.module_app[module] + "." + module, *joins)
  559. def get_app_path(app_name, *joins):
  560. """Return path of given app.
  561. :param app: App name.
  562. :param *joins: Join additional path elements using `os.path.join`."""
  563. return get_pymodule_path(app_name, *joins)
  564. def get_site_path(*joins):
  565. """Return path of current site.
  566. :param *joins: Join additional path elements using `os.path.join`."""
  567. return os.path.join(local.site_path, *joins)
  568. def get_pymodule_path(modulename, *joins):
  569. """Return path of given Python module name.
  570. :param modulename: Python module name.
  571. :param *joins: Join additional path elements using `os.path.join`."""
  572. if not "public" in joins:
  573. joins = [scrub(part) for part in joins]
  574. return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__), *joins)
  575. def get_module_list(app_name):
  576. """Get list of modules for given all via `app/modules.txt`."""
  577. return get_file_items(os.path.join(os.path.dirname(get_module(app_name).__file__), "modules.txt"))
  578. def get_all_apps(with_internal_apps=True, sites_path=None):
  579. """Get list of all apps via `sites/apps.txt`."""
  580. if not sites_path:
  581. sites_path = local.sites_path
  582. apps = get_file_items(os.path.join(sites_path, "apps.txt"), raise_not_found=True)
  583. if with_internal_apps:
  584. for app in get_file_items(os.path.join(local.site_path, "apps.txt")):
  585. if app not in apps:
  586. apps.append(app)
  587. if "frappe" in apps:
  588. apps.remove("frappe")
  589. apps.insert(0, 'frappe')
  590. return apps
  591. def get_installed_apps(sort=False, frappe_last=False):
  592. """Get list of installed apps in current site."""
  593. if getattr(flags, "in_install_db", True):
  594. return []
  595. if not db:
  596. connect()
  597. installed = json.loads(db.get_global("installed_apps") or "[]")
  598. if sort:
  599. installed = [app for app in get_all_apps(True) if app in installed]
  600. if frappe_last:
  601. if 'frappe' in installed:
  602. installed.remove('frappe')
  603. installed.append('frappe')
  604. return installed
  605. def get_doc_hooks():
  606. '''Returns hooked methods for given doc. It will expand the dict tuple if required.'''
  607. if not hasattr(local, 'doc_events_hooks'):
  608. hooks = get_hooks('doc_events', {})
  609. out = {}
  610. for key, value in iteritems(hooks):
  611. if isinstance(key, tuple):
  612. for doctype in key:
  613. append_hook(out, doctype, value)
  614. else:
  615. append_hook(out, key, value)
  616. local.doc_events_hooks = out
  617. return local.doc_events_hooks
  618. def get_hooks(hook=None, default=None, app_name=None):
  619. """Get hooks via `app/hooks.py`
  620. :param hook: Name of the hook. Will gather all hooks for this name and return as a list.
  621. :param default: Default if no hook found.
  622. :param app_name: Filter by app."""
  623. def load_app_hooks(app_name=None):
  624. hooks = {}
  625. for app in [app_name] if app_name else get_installed_apps(sort=True):
  626. app = "frappe" if app=="webnotes" else app
  627. try:
  628. app_hooks = get_module(app + ".hooks")
  629. except ImportError:
  630. if local.flags.in_install_app:
  631. # if app is not installed while restoring
  632. # ignore it
  633. pass
  634. print('Could not find app "{0}"'.format(app_name))
  635. if not request:
  636. sys.exit(1)
  637. raise
  638. for key in dir(app_hooks):
  639. if not key.startswith("_"):
  640. append_hook(hooks, key, getattr(app_hooks, key))
  641. return hooks
  642. if app_name:
  643. hooks = _dict(load_app_hooks(app_name))
  644. else:
  645. hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
  646. if hook:
  647. return hooks.get(hook) or (default if default is not None else [])
  648. else:
  649. return hooks
  650. def append_hook(target, key, value):
  651. '''appends a hook to the the target dict.
  652. If the hook key, exists, it will make it a key.
  653. If the hook value is a dict, like doc_events, it will
  654. listify the values against the key.
  655. '''
  656. if isinstance(value, dict):
  657. # dict? make a list of values against each key
  658. target.setdefault(key, {})
  659. for inkey in value:
  660. append_hook(target[key], inkey, value[inkey])
  661. else:
  662. # make a list
  663. target.setdefault(key, [])
  664. if not isinstance(value, list):
  665. value = [value]
  666. target[key].extend(value)
  667. def setup_module_map():
  668. """Rebuild map of all modules (internal)."""
  669. _cache = cache()
  670. if conf.db_name:
  671. local.app_modules = _cache.get_value("app_modules")
  672. local.module_app = _cache.get_value("module_app")
  673. if not (local.app_modules and local.module_app):
  674. local.module_app, local.app_modules = {}, {}
  675. for app in get_all_apps(True):
  676. if app=="webnotes": app="frappe"
  677. local.app_modules.setdefault(app, [])
  678. for module in get_module_list(app):
  679. module = scrub(module)
  680. local.module_app[module] = app
  681. local.app_modules[app].append(module)
  682. if conf.db_name:
  683. _cache.set_value("app_modules", local.app_modules)
  684. _cache.set_value("module_app", local.module_app)
  685. def get_file_items(path, raise_not_found=False, ignore_empty_lines=True):
  686. """Returns items from text file as a list. Ignores empty lines."""
  687. import frappe.utils
  688. content = read_file(path, raise_not_found=raise_not_found)
  689. if content:
  690. content = frappe.utils.strip(content)
  691. return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))]
  692. else:
  693. return []
  694. def get_file_json(path):
  695. """Read a file and return parsed JSON object."""
  696. with open(path, 'r') as f:
  697. return json.load(f)
  698. def read_file(path, raise_not_found=False):
  699. """Open a file and return its content as Unicode."""
  700. if isinstance(path, text_type):
  701. path = path.encode("utf-8")
  702. if os.path.exists(path):
  703. with open(path, "r") as f:
  704. return as_unicode(f.read())
  705. elif raise_not_found:
  706. raise IOError("{} Not Found".format(path))
  707. else:
  708. return None
  709. def get_attr(method_string):
  710. """Get python method object from its name."""
  711. app_name = method_string.split(".")[0]
  712. if not local.flags.in_install and app_name not in get_installed_apps():
  713. throw(_("App {0} is not installed").format(app_name), AppNotInstalledError)
  714. modulename = '.'.join(method_string.split('.')[:-1])
  715. methodname = method_string.split('.')[-1]
  716. return getattr(get_module(modulename), methodname)
  717. def call(fn, *args, **kwargs):
  718. """Call a function and match arguments."""
  719. if isinstance(fn, basestring):
  720. fn = get_attr(fn)
  721. if hasattr(fn, 'fnargs'):
  722. fnargs = fn.fnargs
  723. else:
  724. fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
  725. newargs = {}
  726. for a in kwargs:
  727. if (a in fnargs) or varkw:
  728. newargs[a] = kwargs.get(a)
  729. if "flags" in newargs:
  730. del newargs["flags"]
  731. return fn(*args, **newargs)
  732. def make_property_setter(args, ignore_validate=False, validate_fields_for_doctype=True):
  733. """Create a new **Property Setter** (for overriding DocType and DocField properties).
  734. If doctype is not specified, it will create a property setter for all fields with the
  735. given fieldname"""
  736. args = _dict(args)
  737. if not args.doctype_or_field:
  738. args.doctype_or_field = 'DocField'
  739. if not args.property_type:
  740. args.property_type = db.get_value('DocField',
  741. {'parent': 'DocField', 'fieldname': args.property}, 'fieldtype') or 'Data'
  742. if not args.doctype:
  743. doctype_list = db.sql_list('select distinct parent from tabDocField where fieldname=%s', args.fieldname)
  744. else:
  745. doctype_list = [args.doctype]
  746. for doctype in doctype_list:
  747. if not args.property_type:
  748. args.property_type = db.get_value('DocField',
  749. {'parent': doctype, 'fieldname': args.fieldname}, 'fieldtype') or 'Data'
  750. ps = get_doc({
  751. 'doctype': "Property Setter",
  752. 'doctype_or_field': args.doctype_or_field,
  753. 'doc_type': doctype,
  754. 'field_name': args.fieldname,
  755. 'property': args.property,
  756. 'value': args.value,
  757. 'property_type': args.property_type or "Data",
  758. '__islocal': 1
  759. })
  760. ps.flags.ignore_validate = ignore_validate
  761. ps.flags.validate_fields_for_doctype = validate_fields_for_doctype
  762. ps.validate_fieldtype_change()
  763. ps.insert()
  764. def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
  765. """Import a file using Data Import Tool."""
  766. from frappe.core.page.data_import_tool import data_import_tool
  767. data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)
  768. def copy_doc(doc, ignore_no_copy=True):
  769. """ No_copy fields also get copied."""
  770. import copy
  771. def remove_no_copy_fields(d):
  772. for df in d.meta.get("fields", {"no_copy": 1}):
  773. if hasattr(d, df.fieldname):
  774. d.set(df.fieldname, None)
  775. fields_to_clear = ['name', 'owner', 'creation', 'modified', 'modified_by']
  776. if not local.flags.in_test:
  777. fields_to_clear.append("docstatus")
  778. if not isinstance(doc, dict):
  779. d = doc.as_dict()
  780. else:
  781. d = doc
  782. newdoc = get_doc(copy.deepcopy(d))
  783. newdoc.set("__islocal", 1)
  784. for fieldname in (fields_to_clear + ['amended_from', 'amendment_date']):
  785. newdoc.set(fieldname, None)
  786. if not ignore_no_copy:
  787. remove_no_copy_fields(newdoc)
  788. for i, d in enumerate(newdoc.get_all_children()):
  789. d.set("__islocal", 1)
  790. for fieldname in fields_to_clear:
  791. d.set(fieldname, None)
  792. if not ignore_no_copy:
  793. remove_no_copy_fields(d)
  794. return newdoc
  795. def compare(val1, condition, val2):
  796. """Compare two values using `frappe.utils.compare`
  797. `condition` could be:
  798. - "^"
  799. - "in"
  800. - "not in"
  801. - "="
  802. - "!="
  803. - ">"
  804. - "<"
  805. - ">="
  806. - "<="
  807. - "not None"
  808. - "None"
  809. """
  810. import frappe.utils
  811. return frappe.utils.compare(val1, condition, val2)
  812. def respond_as_web_page(title, html, success=None, http_status_code=None,
  813. context=None, indicator_color=None, primary_action='/', primary_label = None, fullpage=False):
  814. """Send response as a web page with a message rather than JSON. Used to show permission errors etc.
  815. :param title: Page title and heading.
  816. :param message: Message to be shown.
  817. :param success: Alert message.
  818. :param http_status_code: HTTP status code
  819. :param context: web template context
  820. :param indicator_color: color of indicator in title
  821. :param primary_action: route on primary button (default is `/`)
  822. :param primary_label: label on primary button (defaut is "Home")
  823. :param fullpage: hide header / footer"""
  824. local.message_title = title
  825. local.message = html
  826. local.response['type'] = 'page'
  827. local.response['route'] = 'message'
  828. if http_status_code:
  829. local.response['http_status_code'] = http_status_code
  830. if not context:
  831. context = {}
  832. if not indicator_color:
  833. if success:
  834. indicator_color = 'green'
  835. elif http_status_code and http_status_code > 300:
  836. indicator_color = 'red'
  837. else:
  838. indicator_color = 'blue'
  839. context['indicator_color'] = indicator_color
  840. context['primary_label'] = primary_label
  841. context['primary_action'] = primary_action
  842. context['error_code'] = http_status_code
  843. context['fullpage'] = fullpage
  844. local.response['context'] = context
  845. def redirect_to_message(title, html, http_status_code=None, context=None, indicator_color=None):
  846. """Redirects to /message?id=random
  847. Similar to respond_as_web_page, but used to 'redirect' and show message pages like success, failure, etc. with a detailed message
  848. :param title: Page title and heading.
  849. :param message: Message to be shown.
  850. :param http_status_code: HTTP status code.
  851. Example Usage:
  852. frappe.redirect_to_message(_('Thank you'), "<div><p>You will receive an email at test@example.com</p></div>")
  853. """
  854. message_id = generate_hash(length=8)
  855. message = {
  856. 'context': context or {},
  857. 'http_status_code': http_status_code or 200
  858. }
  859. message['context'].update({
  860. 'header': title,
  861. 'title': title,
  862. 'message': html
  863. })
  864. if indicator_color:
  865. message['context'].update({
  866. "indicator_color": indicator_color
  867. })
  868. cache().set_value("message_id:{0}".format(message_id), message, expires_in_sec=60)
  869. location = '/message?id={0}'.format(message_id)
  870. if not getattr(local, 'is_ajax', False):
  871. local.response["type"] = "redirect"
  872. local.response["location"] = location
  873. else:
  874. return location
  875. def build_match_conditions(doctype, as_condition=True):
  876. """Return match (User permissions) for given doctype as list or SQL."""
  877. import frappe.desk.reportview
  878. return frappe.desk.reportview.build_match_conditions(doctype, as_condition)
  879. def get_list(doctype, *args, **kwargs):
  880. """List database query via `frappe.model.db_query`. Will also check for permissions.
  881. :param doctype: DocType on which query is to be made.
  882. :param fields: List of fields or `*`.
  883. :param filters: List of filters (see example).
  884. :param order_by: Order By e.g. `modified desc`.
  885. :param limit_page_start: Start results at record #. Default 0.
  886. :param limit_poge_length: No of records in the page. Default 20.
  887. Example usage:
  888. # simple dict filter
  889. frappe.get_list("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"})
  890. # filter as a list of lists
  891. frappe.get_list("ToDo", fields="*", filters = [["modified", ">", "2014-01-01"]])
  892. # filter as a list of dicts
  893. frappe.get_list("ToDo", fields="*", filters = {"description": ("like", "test%")})
  894. """
  895. import frappe.model.db_query
  896. return frappe.model.db_query.DatabaseQuery(doctype).execute(None, *args, **kwargs)
  897. def get_all(doctype, *args, **kwargs):
  898. """List database query via `frappe.model.db_query`. Will **not** check for conditions.
  899. Parameters are same as `frappe.get_list`
  900. :param doctype: DocType on which query is to be made.
  901. :param fields: List of fields or `*`. Default is: `["name"]`.
  902. :param filters: List of filters (see example).
  903. :param order_by: Order By e.g. `modified desc`.
  904. :param limit_page_start: Start results at record #. Default 0.
  905. :param limit_poge_length: No of records in the page. Default 20.
  906. Example usage:
  907. # simple dict filter
  908. frappe.get_all("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"})
  909. # filter as a list of lists
  910. frappe.get_all("ToDo", fields=["*"], filters = [["modified", ">", "2014-01-01"]])
  911. # filter as a list of dicts
  912. frappe.get_all("ToDo", fields=["*"], filters = {"description": ("like", "test%")})
  913. """
  914. kwargs["ignore_permissions"] = True
  915. if not "limit_page_length" in kwargs:
  916. kwargs["limit_page_length"] = 0
  917. return get_list(doctype, *args, **kwargs)
  918. def get_value(*args, **kwargs):
  919. """Returns a document property or list of properties.
  920. Alias for `frappe.db.get_value`
  921. :param doctype: DocType name.
  922. :param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType.
  923. :param fieldname: Column name.
  924. :param ignore: Don't raise exception if table, column is missing.
  925. :param as_dict: Return values as dict.
  926. :param debug: Print query in error log.
  927. """
  928. return db.get_value(*args, **kwargs)
  929. def as_json(obj, indent=1):
  930. from frappe.utils.response import json_handler
  931. return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler)
  932. def are_emails_muted():
  933. from utils import cint
  934. return flags.mute_emails or cint(conf.get("mute_emails") or 0) or False
  935. def get_test_records(doctype):
  936. """Returns list of objects from `test_records.json` in the given doctype's folder."""
  937. from frappe.modules import get_doctype_module, get_module_path
  938. path = os.path.join(get_module_path(get_doctype_module(doctype)), "doctype", scrub(doctype), "test_records.json")
  939. if os.path.exists(path):
  940. with open(path, "r") as f:
  941. return json.loads(f.read())
  942. else:
  943. return []
  944. def format_value(*args, **kwargs):
  945. """Format value with given field properties.
  946. :param value: Value to be formatted.
  947. :param df: (Optional) DocField object with properties `fieldtype`, `options` etc."""
  948. import frappe.utils.formatters
  949. return frappe.utils.formatters.format_value(*args, **kwargs)
  950. def format(*args, **kwargs):
  951. """Format value with given field properties.
  952. :param value: Value to be formatted.
  953. :param df: (Optional) DocField object with properties `fieldtype`, `options` etc."""
  954. import frappe.utils.formatters
  955. return frappe.utils.formatters.format_value(*args, **kwargs)
  956. def get_print(doctype=None, name=None, print_format=None, style=None, html=None, as_pdf=False, doc=None, output = None):
  957. """Get Print Format for given document.
  958. :param doctype: DocType of document.
  959. :param name: Name of document.
  960. :param print_format: Print Format name. Default 'Standard',
  961. :param style: Print Format style.
  962. :param as_pdf: Return as PDF. Default False."""
  963. from frappe.website.render import build_page
  964. from frappe.utils.pdf import get_pdf
  965. local.form_dict.doctype = doctype
  966. local.form_dict.name = name
  967. local.form_dict.format = print_format
  968. local.form_dict.style = style
  969. local.form_dict.doc = doc
  970. if not html:
  971. html = build_page("printview")
  972. if as_pdf:
  973. return get_pdf(html, output = output)
  974. else:
  975. return html
  976. def attach_print(doctype, name, file_name=None, print_format=None, style=None, html=None, doc=None):
  977. from frappe.utils import scrub_urls
  978. if not file_name: file_name = name
  979. file_name = file_name.replace(' ','').replace('/','-')
  980. print_settings = db.get_singles_dict("Print Settings")
  981. local.flags.ignore_print_permissions = True
  982. if int(print_settings.send_print_as_pdf or 0):
  983. out = {
  984. "fname": file_name + ".pdf",
  985. "fcontent": get_print(doctype, name, print_format=print_format, style=style, html=html, as_pdf=True, doc=doc)
  986. }
  987. else:
  988. out = {
  989. "fname": file_name + ".html",
  990. "fcontent": scrub_urls(get_print(doctype, name, print_format=print_format, style=style, html=html, doc=doc)).encode("utf-8")
  991. }
  992. local.flags.ignore_print_permissions = False
  993. return out
  994. def publish_progress(*args, **kwargs):
  995. """Show the user progress for a long request
  996. :param percent: Percent progress
  997. :param title: Title
  998. :param doctype: Optional, for DocType
  999. :param name: Optional, for Document name
  1000. """
  1001. import frappe.async
  1002. return frappe.async.publish_progress(*args, **kwargs)
  1003. def publish_realtime(*args, **kwargs):
  1004. """Publish real-time updates
  1005. :param event: Event name, like `task_progress` etc.
  1006. :param message: JSON message object. For async must contain `task_id`
  1007. :param room: Room in which to publish update (default entire site)
  1008. :param user: Transmit to user
  1009. :param doctype: Transmit to doctype, docname
  1010. :param docname: Transmit to doctype, docname
  1011. :param after_commit: (default False) will emit after current transaction is committed
  1012. """
  1013. import frappe.async
  1014. return frappe.async.publish_realtime(*args, **kwargs)
  1015. def local_cache(namespace, key, generator, regenerate_if_none=False):
  1016. """A key value store for caching within a request
  1017. :param namespace: frappe.local.cache[namespace]
  1018. :param key: frappe.local.cache[namespace][key] used to retrieve value
  1019. :param generator: method to generate a value if not found in store
  1020. """
  1021. if namespace not in local.cache:
  1022. local.cache[namespace] = {}
  1023. if key not in local.cache[namespace]:
  1024. local.cache[namespace][key] = generator()
  1025. elif local.cache[namespace][key]==None and regenerate_if_none:
  1026. # if key exists but the previous result was None
  1027. local.cache[namespace][key] = generator()
  1028. return local.cache[namespace][key]
  1029. def enqueue(*args, **kwargs):
  1030. '''
  1031. Enqueue method to be executed using a background worker
  1032. :param method: method string or method object
  1033. :param queue: (optional) should be either long, default or short
  1034. :param timeout: (optional) should be set according to the functions
  1035. :param event: this is passed to enable clearing of jobs from queues
  1036. :param async: (optional) if async=False, the method is executed immediately, else via a worker
  1037. :param job_name: (optional) can be used to name an enqueue call, which can be used to prevent duplicate calls
  1038. :param kwargs: keyword arguments to be passed to the method
  1039. '''
  1040. import frappe.utils.background_jobs
  1041. return frappe.utils.background_jobs.enqueue(*args, **kwargs)
  1042. def get_doctype_app(doctype):
  1043. def _get_doctype_app():
  1044. doctype_module = local.db.get_value("DocType", doctype, "module")
  1045. return local.module_app[scrub(doctype_module)]
  1046. return local_cache("doctype_app", doctype, generator=_get_doctype_app)
  1047. loggers = {}
  1048. log_level = None
  1049. def logger(module=None, with_more_info=True):
  1050. '''Returns a python logger that uses StreamHandler'''
  1051. from frappe.utils.logger import get_logger
  1052. return get_logger(module or 'default', with_more_info=with_more_info)
  1053. def log_error(message=None, title=None):
  1054. '''Log error to Error Log'''
  1055. get_doc(dict(doctype='Error Log', error=str(message or get_traceback()),
  1056. method=title)).insert(ignore_permissions=True)
  1057. def get_desk_link(doctype, name):
  1058. return '<a href="#Form/{0}/{1}" style="font-weight: bold;">{2} {1}</a>'.format(doctype, name, _(doctype))
  1059. def bold(text):
  1060. return '<b>{0}</b>'.format(text)
  1061. def safe_eval(code, eval_globals=None, eval_locals=None):
  1062. '''A safer `eval`'''
  1063. whitelisted_globals = {
  1064. "int": int,
  1065. "float": float,
  1066. "long": long,
  1067. "round": round
  1068. }
  1069. if '__' in code:
  1070. throw('Illegal rule {0}. Cannot use "__"'.format(bold(code)))
  1071. if not eval_globals:
  1072. eval_globals = {}
  1073. eval_globals['__builtins__'] = {}
  1074. eval_globals.update(whitelisted_globals)
  1075. return eval(code, eval_globals, eval_locals)
  1076. def get_active_domains():
  1077. """ get the domains set in the Domain Settings as active domain """
  1078. active_domains = cache().hget("domains", "active_domains") or None
  1079. if active_domains is None:
  1080. domains = get_all("Has Domain", filters={ "parent": "Domain Settings" },
  1081. fields=["domain"], distinct=True)
  1082. active_domains = [row.get("domain") for row in domains]
  1083. active_domains.append("")
  1084. cache().hset("domains", "active_domains", active_domains)
  1085. return active_domains
  1086. def get_active_modules():
  1087. """ get the active modules from Module Def"""
  1088. active_modules = cache().hget("modules", "active_modules") or None
  1089. if active_modules is None:
  1090. domains = get_active_domains()
  1091. modules = get_all("Module Def", filters={"restrict_to_domain": ("in", domains)})
  1092. active_modules = [module.name for module in modules]
  1093. cache().hset("modules", "active_modules", active_modules)
  1094. return active_modules
  1095. def clear_domainification_cache():
  1096. _cache = cache()
  1097. _cache.delete_key("domains", "active_domains")
  1098. _cache.delete_key("modules", "active_modules")
  1099. def get_system_settings(key):
  1100. if not local.system_settings.has_key(key):
  1101. local.system_settings.update({key: db.get_single_value('System Settings', key)})
  1102. return local.system_settings.get(key)