Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

663 рядки
20 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. from frappe import _, msgprint
  6. from frappe.utils import flt, cint, cstr, now
  7. from frappe.model.base_document import BaseDocument, get_controller
  8. from frappe.model.naming import set_new_name
  9. from werkzeug.exceptions import NotFound, Forbidden
  10. import hashlib
  11. # once_only validation
  12. # methods
  13. def get_doc(arg1, arg2=None):
  14. """returns a frappe.model.Document object.
  15. :param arg1: Document dict or DocType name.
  16. :param arg2: [optional] document name.
  17. There are two ways to call `get_doc`
  18. # will fetch the latest user object (with child table) from the database
  19. user = get_doc("User", "test@example.com")
  20. # create a new object
  21. user = get_doc({
  22. "doctype":"User"
  23. "email_id": "test@example.com",
  24. "user_roles: [
  25. {"role": "System Manager"}
  26. ]
  27. })
  28. """
  29. if isinstance(arg1, BaseDocument):
  30. return arg1
  31. elif isinstance(arg1, basestring):
  32. doctype = arg1
  33. else:
  34. doctype = arg1.get("doctype")
  35. controller = get_controller(doctype)
  36. if controller:
  37. return controller(arg1, arg2)
  38. raise ImportError, arg1
  39. class Document(BaseDocument):
  40. """All controllers inherit from `Document`."""
  41. def __init__(self, arg1, arg2=None):
  42. """Constructor.
  43. :param arg1: DocType name as string or document **dict**
  44. :param arg2: Document name, if `arg1` is DocType name.
  45. If DocType name and document name are passed, the object will load
  46. all values (including child documents) from the database.
  47. """
  48. self.doctype = self.name = None
  49. if arg1 and isinstance(arg1, basestring):
  50. if not arg2:
  51. # single
  52. self.doctype = self.name = arg1
  53. else:
  54. self.doctype = arg1
  55. if isinstance(arg2, dict):
  56. # filter
  57. self.name = frappe.db.get_value(arg1, arg2, "name")
  58. if self.name is None:
  59. frappe.throw(_("{0} {1} not found").format(_(arg1), arg2), frappe.DoesNotExistError)
  60. else:
  61. self.name = arg2
  62. self.load_from_db()
  63. elif isinstance(arg1, dict):
  64. super(Document, self).__init__(arg1)
  65. self.init_valid_columns()
  66. else:
  67. # incorrect arguments. let's not proceed.
  68. raise frappe.DataError("Document({0}, {1})".format(arg1, arg2))
  69. self.dont_update_if_missing = []
  70. self.flags = frappe._dict()
  71. def load_from_db(self):
  72. """Load document and children from database and create properties
  73. from fields"""
  74. if not getattr(self, "_metaclass", False) and self.meta.issingle:
  75. single_doc = frappe.db.get_singles_dict(self.doctype)
  76. if not single_doc:
  77. single_doc = frappe.new_doc(self.doctype).as_dict()
  78. single_doc["name"] = self.doctype
  79. del single_doc["__islocal"]
  80. self.update(single_doc)
  81. self.init_valid_columns()
  82. self._fix_numeric_types()
  83. else:
  84. d = frappe.db.get_value(self.doctype, self.name, "*", as_dict=1)
  85. if not d:
  86. frappe.throw(_("{0} {1} not found").format(_(self.doctype), self.name), frappe.DoesNotExistError)
  87. self.update(d)
  88. if self.name=="DocType" and self.doctype=="DocType":
  89. from frappe.model.meta import doctype_table_fields
  90. table_fields = doctype_table_fields
  91. else:
  92. table_fields = self.meta.get_table_fields()
  93. for df in table_fields:
  94. children = frappe.db.get_values(df.options,
  95. {"parent": self.name, "parenttype": self.doctype, "parentfield": df.fieldname},
  96. "*", as_dict=True, order_by="idx asc")
  97. if children:
  98. self.set(df.fieldname, children)
  99. else:
  100. self.set(df.fieldname, [])
  101. def check_permission(self, permtype, permlabel=None):
  102. """Raise `frappe.PermissionError` if not permitted"""
  103. if not self.has_permission(permtype):
  104. self.raise_no_permission_to(permlabel or permtype)
  105. def has_permission(self, permtype="read", verbose=False):
  106. """Call `frappe.has_permission` if `self.flags.ignore_permissions`
  107. is not set.
  108. :param permtype: one of `read`, `write`, `submit`, `cancel`, `delete`"""
  109. if self.flags.ignore_permissions:
  110. return True
  111. return frappe.has_permission(self.doctype, permtype, self, verbose=verbose)
  112. def raise_no_permission_to(self, perm_type):
  113. """Raise `frappe.PermissionError`."""
  114. raise frappe.PermissionError("No permission to {} {} {}".format(perm_type, self.doctype, self.name or ""))
  115. def insert(self, ignore_permissions=None):
  116. """Insert the document in the database (as a new document).
  117. This will check for user permissions and execute `before_insert`,
  118. `validate`, `on_update`, `after_insert` methods if they are written.
  119. :param ignore_permissions: Do not check permissions if True."""
  120. if self.flags.in_print:
  121. return
  122. if ignore_permissions!=None:
  123. self.flags.ignore_permissions = ignore_permissions
  124. self.set("__islocal", True)
  125. self.check_permission("create")
  126. self._set_defaults()
  127. self._set_docstatus_user_and_timestamp()
  128. self.check_if_latest()
  129. self.run_method("before_insert")
  130. self.set_new_name()
  131. self.set_parent_in_children()
  132. self.set("__in_insert", True)
  133. self.run_before_save_methods()
  134. self._validate()
  135. self.delete_key("__in_insert")
  136. # run validate, on update etc.
  137. # parent
  138. if getattr(self.meta, "issingle", 0):
  139. self.update_single(self.get_valid_dict())
  140. else:
  141. self.db_insert()
  142. # children
  143. for d in self.get_all_children():
  144. d.db_insert()
  145. self.run_method("after_insert")
  146. self.set("__in_insert", True)
  147. self.run_post_save_methods()
  148. self.delete_key("__in_insert")
  149. return self
  150. def save(self, ignore_permissions=None):
  151. """Save the current document in the database in the **DocType**'s table or
  152. `tabSingles` (for single types).
  153. This will check for user permissions and execute
  154. `validate` before updating, `on_update` after updating triggers.
  155. :param ignore_permissions: Do not check permissions if True."""
  156. if self.flags.in_print:
  157. return
  158. if ignore_permissions!=None:
  159. self.flags.ignore_permissions = ignore_permissions
  160. if self.get("__islocal") or not self.get("name"):
  161. self.insert()
  162. return
  163. self.check_permission("write", "save")
  164. self._set_docstatus_user_and_timestamp()
  165. self.check_if_latest()
  166. self.set_parent_in_children()
  167. self.run_before_save_methods()
  168. if self._action != "cancel":
  169. self._validate()
  170. if self._action == "update_after_submit":
  171. self.validate_update_after_submit()
  172. # parent
  173. if self.meta.issingle:
  174. self.update_single(self.get_valid_dict())
  175. else:
  176. self.db_update()
  177. # children
  178. child_map = {}
  179. ignore_children_type = self.flags.ignore_children_type or []
  180. for d in self.get_all_children():
  181. d.db_update()
  182. child_map.setdefault(d.doctype, []).append(d.name)
  183. for df in self.meta.get_table_fields():
  184. if df.options not in ignore_children_type:
  185. cnames = child_map.get(df.options) or []
  186. if cnames:
  187. frappe.db.sql("""delete from `tab%s` where parent=%s and parenttype=%s and
  188. name not in (%s)""" % (df.options, '%s', '%s', ','.join(['%s'] * len(cnames))),
  189. tuple([self.name, self.doctype] + cnames))
  190. else:
  191. frappe.db.sql("""delete from `tab%s` where parent=%s and parenttype=%s""" \
  192. % (df.options, '%s', '%s'), (self.name, self.doctype))
  193. self.run_post_save_methods()
  194. return self
  195. def set_new_name(self):
  196. """Calls `frappe.naming.se_new_name` for parent and child docs."""
  197. set_new_name(self)
  198. # set name for children
  199. for d in self.get_all_children():
  200. set_new_name(d)
  201. def update_single(self, d):
  202. """Updates values for Single type Document in `tabSingles`."""
  203. frappe.db.sql("""delete from tabSingles where doctype=%s""", self.doctype)
  204. for field, value in d.iteritems():
  205. if field != "doctype":
  206. frappe.db.sql("""insert into tabSingles(doctype, field, value)
  207. values (%s, %s, %s)""", (self.doctype, field, value))
  208. def _set_docstatus_user_and_timestamp(self):
  209. self._original_modified = self.modified
  210. self.modified = now()
  211. self.modified_by = frappe.session.user
  212. if not self.creation:
  213. self.creation = self.modified
  214. if not self.owner:
  215. self.owner = self.modified_by
  216. if self.docstatus==None:
  217. self.docstatus=0
  218. for d in self.get_all_children():
  219. d.docstatus = self.docstatus
  220. d.modified = self.modified
  221. d.modified_by = self.modified_by
  222. if not d.owner:
  223. d.owner = self.owner
  224. if not d.creation:
  225. d.creation = self.creation
  226. def _validate(self):
  227. self._validate_mandatory()
  228. self._validate_links()
  229. self._validate_selects()
  230. self._validate_constants()
  231. for d in self.get_all_children():
  232. d._validate_selects()
  233. d._validate_constants()
  234. self._extract_images_from_text_editor()
  235. def _set_defaults(self):
  236. if frappe.flags.in_import:
  237. return
  238. new_doc = frappe.new_doc(self.doctype)
  239. self.update_if_missing(new_doc)
  240. # children
  241. for df in self.meta.get_table_fields():
  242. new_doc = frappe.new_doc(df.options)
  243. value = self.get(df.fieldname)
  244. if isinstance(value, list):
  245. for d in value:
  246. d.update_if_missing(new_doc)
  247. def check_if_latest(self):
  248. """Checks if `modified` timestamp provided by document being updated is same as the
  249. `modified` timestamp in the database. If there is a different, the document has been
  250. updated in the database after the current copy was read. Will throw an error if
  251. timestamps don't match.
  252. Will also validate document transitions (Save > Submit > Cancel) calling
  253. `self.check_docstatus_transition`."""
  254. conflict = False
  255. self._action = "save"
  256. if not self.get('__islocal'):
  257. if self.meta.issingle:
  258. modified = frappe.db.get_value(self.doctype, self.name, "modified")
  259. if cstr(modified) and cstr(modified) != cstr(self._original_modified):
  260. conflict = True
  261. else:
  262. tmp = frappe.db.get_value(self.doctype, self.name,
  263. ["modified", "docstatus"], as_dict=True)
  264. if not tmp:
  265. frappe.throw(_("Record does not exist"))
  266. modified = cstr(tmp.modified)
  267. if modified and modified != cstr(self._original_modified):
  268. conflict = True
  269. self.check_docstatus_transition(tmp.docstatus)
  270. if conflict:
  271. frappe.msgprint(_("Error: Document has been modified after you have opened it") \
  272. + (" (%s, %s). " % (modified, self.modified)) \
  273. + _("Please refresh to get the latest document."),
  274. raise_exception=frappe.TimestampMismatchError)
  275. else:
  276. self.check_docstatus_transition(0)
  277. def check_docstatus_transition(self, docstatus):
  278. """Ensures valid `docstatus` transition.
  279. Valid transitions are (number in brackets is `docstatus`):
  280. - Save (0) > Save (0)
  281. - Save (0) > Submit (1)
  282. - Submit (1) > Submit (1)
  283. - Submit (1) > Cancel (2)"""
  284. if not self.docstatus:
  285. self.docstatus = 0
  286. if docstatus==0:
  287. if self.docstatus==0:
  288. self._action = "save"
  289. elif self.docstatus==1:
  290. self._action = "submit"
  291. self.check_permission("submit")
  292. else:
  293. raise frappe.DocstatusTransitionError, _("Cannot change docstatus from 0 to 2")
  294. elif docstatus==1:
  295. if self.docstatus==1:
  296. self._action = "update_after_submit"
  297. self.check_permission("submit")
  298. elif self.docstatus==2:
  299. self._action = "cancel"
  300. self.check_permission("cancel")
  301. else:
  302. raise frappe.DocstatusTransitionError, _("Cannot change docstatus from 1 to 0")
  303. elif docstatus==2:
  304. raise frappe.ValidationError, _("Cannot edit cancelled document")
  305. def set_parent_in_children(self):
  306. """Updates `parent` and `parenttype` property in all children."""
  307. for d in self.get_all_children():
  308. d.parent = self.name
  309. d.parenttype = self.doctype
  310. def validate_update_after_submit(self):
  311. if self.flags.ignore_validate_update_after_submit:
  312. return
  313. self._validate_update_after_submit()
  314. for d in self.get_all_children():
  315. d._validate_update_after_submit()
  316. # TODO check only allowed values are updated
  317. def _validate_mandatory(self):
  318. if self.flags.ignore_mandatory:
  319. return
  320. missing = self._get_missing_mandatory_fields()
  321. for d in self.get_all_children():
  322. missing.extend(d._get_missing_mandatory_fields())
  323. if not missing:
  324. return
  325. for fieldname, msg in missing:
  326. msgprint(msg)
  327. if frappe.flags.print_messages:
  328. print self.as_dict()
  329. raise frappe.MandatoryError(", ".join((each[0] for each in missing)))
  330. def _validate_links(self):
  331. if self.flags.ignore_links:
  332. return
  333. invalid_links, cancelled_links = self.get_invalid_links()
  334. for d in self.get_all_children():
  335. result = d.get_invalid_links(is_submittable=self.meta.is_submittable)
  336. invalid_links.extend(result[0])
  337. cancelled_links.extend(result[1])
  338. if invalid_links:
  339. msg = ", ".join((each[2] for each in invalid_links))
  340. frappe.throw(_("Could not find {0}").format(msg),
  341. frappe.LinkValidationError)
  342. if cancelled_links:
  343. msg = ", ".join((each[2] for each in cancelled_links))
  344. frappe.throw(_("Cannot link cancelled document: {0}").format(msg),
  345. frappe.CancelledLinkError)
  346. def get_all_children(self, parenttype=None):
  347. """Returns all children documents from **Table** type field in a list."""
  348. ret = []
  349. for df in self.meta.get("fields", {"fieldtype": "Table"}):
  350. if parenttype:
  351. if df.options==parenttype:
  352. return self.get(df.fieldname)
  353. value = self.get(df.fieldname)
  354. if isinstance(value, list):
  355. ret.extend(value)
  356. return ret
  357. def _extract_images_from_text_editor(self):
  358. from frappe.utils.file_manager import extract_images_from_html
  359. if self.doctype != "DocType":
  360. for df in self.meta.get("fields", {"fieldtype":"Text Editor"}):
  361. extract_images_from_html(self, df.fieldname)
  362. def run_method(self, method, *args, **kwargs):
  363. """run standard triggers, plus those in hooks"""
  364. if "flags" in kwargs:
  365. del kwargs["flags"]
  366. if hasattr(self, method) and hasattr(getattr(self, method), "__call__"):
  367. fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs)
  368. else:
  369. # hack! to run hooks even if method does not exist
  370. fn = lambda self, *args, **kwargs: None
  371. fn.__name__ = method.encode("utf-8")
  372. return Document.hook(fn)(self, *args, **kwargs)
  373. @staticmethod
  374. def whitelist(f):
  375. f.whitelisted = True
  376. return f
  377. @whitelist.__func__
  378. def submit(self):
  379. """Submit the document. Sets `docstatus` = 1, then saves."""
  380. self.docstatus = 1
  381. self.save()
  382. @whitelist.__func__
  383. def cancel(self):
  384. """Cancel the document. Sets `docstatus` = 2, then saves."""
  385. self.docstatus = 2
  386. self.save()
  387. def delete(self):
  388. """Delete document."""
  389. frappe.delete_doc(self.doctype, self.name, flags=self.flags)
  390. def run_before_save_methods(self):
  391. """Run standard methods before `INSERT` or `UPDATE`. Standard Methods are:
  392. - `validate`, `before_save` for **Save**.
  393. - `validate`, `before_submit` for **Submit**.
  394. - `before_cancel` for **Cancel**
  395. - `before_update_after_submit` for **Update after Submit**"""
  396. if self.flags.ignore_validate:
  397. return
  398. if self._action=="save":
  399. self.run_method("validate")
  400. self.run_method("before_save")
  401. elif self._action=="submit":
  402. self.run_method("validate")
  403. self.run_method("before_submit")
  404. elif self._action=="cancel":
  405. self.run_method("before_cancel")
  406. elif self._action=="update_after_submit":
  407. self.run_method("before_update_after_submit")
  408. def run_post_save_methods(self):
  409. """Run standard methods after `INSERT` or `UPDATE`. Standard Methods are:
  410. - `on_update` for **Save**.
  411. - `on_update`, `on_submit` for **Submit**.
  412. - `on_cancel` for **Cancel**
  413. - `update_after_submit` for **Update after Submit**"""
  414. if self._action=="save":
  415. self.run_method("on_update")
  416. elif self._action=="submit":
  417. self.run_method("on_update")
  418. self.run_method("on_submit")
  419. self.add_comment("Submitted")
  420. elif self._action=="cancel":
  421. self.run_method("on_cancel")
  422. self.check_no_back_links_exist()
  423. self.add_comment("Cancelled")
  424. elif self._action=="update_after_submit":
  425. self.run_method("on_update_after_submit")
  426. def check_no_back_links_exist(self):
  427. """Check if document links to any active document before Cancel."""
  428. from frappe.model.delete_doc import check_if_doc_is_linked
  429. if not self.flags.ignore_links:
  430. check_if_doc_is_linked(self, method="Cancel")
  431. @staticmethod
  432. def whitelist(f):
  433. """Decorator: Whitelist method to be called remotely via REST API."""
  434. f.whitelisted = True
  435. return f
  436. @staticmethod
  437. def hook(f):
  438. """Decorator: Make method `hookable` (i.e. extensible by another app).
  439. Note: If each hooked method returns a value (dict), then all returns are
  440. collated in one dict and returned. Ideally, don't return values in hookable
  441. methods, set properties in the document."""
  442. def add_to_return_value(self, new_return_value):
  443. if isinstance(new_return_value, dict):
  444. if not self.get("_return_value"):
  445. self._return_value = {}
  446. self._return_value.update(new_return_value)
  447. else:
  448. self._return_value = new_return_value or self.get("_return_value")
  449. def compose(fn, *hooks):
  450. def runner(self, method, *args, **kwargs):
  451. add_to_return_value(self, fn(self, *args, **kwargs))
  452. for f in hooks:
  453. add_to_return_value(self, f(self, method, *args, **kwargs))
  454. return self._return_value
  455. return runner
  456. def composer(self, *args, **kwargs):
  457. hooks = []
  458. method = f.__name__
  459. doc_events = frappe.get_hooks("doc_events", {})
  460. for handler in doc_events.get(self.doctype, {}).get(method, []) \
  461. + doc_events.get("*", {}).get(method, []):
  462. hooks.append(frappe.get_attr(handler))
  463. composed = compose(f, *hooks)
  464. return composed(self, method, *args, **kwargs)
  465. return composer
  466. def is_whitelisted(self, method):
  467. fn = getattr(self, method, None)
  468. if not fn:
  469. raise NotFound("Method {0} not found".format(method))
  470. elif not getattr(fn, "whitelisted", False):
  471. raise Forbidden("Method {0} not whitelisted".format(method))
  472. def validate_value(self, fieldname, condition, val2, doc=None, raise_exception=None):
  473. """Check that value of fieldname should be 'condition' val2
  474. else throw Exception."""
  475. error_condition_map = {
  476. "in": _("one of"),
  477. "not in": _("none of"),
  478. "^": _("beginning with"),
  479. }
  480. if not doc:
  481. doc = self
  482. df = doc.meta.get_field(fieldname)
  483. val1 = doc.get(fieldname)
  484. if df.fieldtype in ("Currency", "Float", "Percent"):
  485. val1 = flt(val1, self.precision(df.fieldname, doc.parentfield or None))
  486. val2 = flt(val2, self.precision(df.fieldname, doc.parentfield or None))
  487. elif df.fieldtype in ("Int", "Check"):
  488. val1 = cint(val1)
  489. val2 = cint(val2)
  490. elif df.fieldtype in ("Data", "Text", "Small Text", "Long Text",
  491. "Text Editor", "Select", "Link", "Dynamic Link"):
  492. val1 = cstr(val1)
  493. val2 = cstr(val2)
  494. if not frappe.compare(val1, condition, val2):
  495. label = doc.meta.get_label(fieldname)
  496. condition_str = error_condition_map.get(condition, condition)
  497. if doc.parentfield:
  498. msg = _("Incorrect value in row {0}: {1} must be {2} {3}".format(doc.idx, label, condition_str, val2))
  499. else:
  500. msg = _("Incorrect value: {0} must be {1} {2}".format(label, condition_str, val2))
  501. # raise passed exception or True
  502. msgprint(msg, raise_exception=raise_exception or True)
  503. def validate_table_has_rows(self, parentfield, raise_exception=None):
  504. """Raise exception if Table field is empty."""
  505. if not (isinstance(self.get(parentfield), list) and len(self.get(parentfield)) > 0):
  506. label = self.meta.get_label(parentfield)
  507. frappe.throw(_("Table {0} cannot be empty").format(label), raise_exception or frappe.EmptyTableError)
  508. def round_floats_in(self, doc, fieldnames=None):
  509. """Round floats for all `Currency`, `Float`, `Percent` fields for the given doc.
  510. :param doc: Document whose numeric properties are to be rounded.
  511. :param fieldnames: [Optional] List of fields to be rounded."""
  512. if not fieldnames:
  513. fieldnames = (df.fieldname for df in
  514. doc.meta.get("fields", {"fieldtype": ["in", ["Currency", "Float", "Percent"]]}))
  515. for fieldname in fieldnames:
  516. doc.set(fieldname, flt(doc.get(fieldname), self.precision(fieldname, doc.parentfield)))
  517. def get_url(self):
  518. """Returns Desk URL for this document. `/desk#Form/{doctype}/{name}`"""
  519. return "/desk#Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name)
  520. def add_comment(self, comment_type, text=None):
  521. """Add a comment to this document.
  522. :param comment_type: e.g. `Comment`. See Comment for more info."""
  523. comment = frappe.get_doc({
  524. "doctype":"Comment",
  525. "comment_by": frappe.session.user,
  526. "comment_type": comment_type,
  527. "comment_doctype": self.doctype,
  528. "comment_docname": self.name,
  529. "comment": text or _(comment_type)
  530. }).insert(ignore_permissions=True)
  531. return comment
  532. def get_signature(self):
  533. """Returns signature (hash) for private URL."""
  534. return hashlib.sha224(self.creation).hexdigest()