Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

522 Zeilen
15 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. """
  5. Transactions are defined as collection of classes, a Bean represents collection of Document
  6. objects for a transaction with main and children.
  7. Group actions like save, etc are performed on doclists
  8. """
  9. import frappe
  10. from frappe import _, msgprint
  11. from frappe.utils import cint, cstr, flt
  12. from frappe.model.doc import Document
  13. import frappe.permissions
  14. class DocstatusTransitionError(frappe.ValidationError): pass
  15. class BeanPermissionError(frappe.ValidationError): pass
  16. class TimestampMismatchError(frappe.ValidationError): pass
  17. class Bean:
  18. """
  19. Collection of Documents with one parent and multiple children
  20. """
  21. def __init__(self, dt=None, dn=None):
  22. self.obj = None
  23. self.ignore_permissions = False
  24. self.ignore_children_type = []
  25. self.ignore_links = False
  26. self.ignore_validate = False
  27. self.ignore_fields = False
  28. self.ignore_mandatory = False
  29. self.ignore_restrictions = False
  30. if isinstance(dt, basestring) and not dn:
  31. dn = dt
  32. if dt and dn:
  33. if isinstance(dn, dict):
  34. dn = frappe.db.get_value(dt, dn, "name")
  35. if dn is None:
  36. raise frappe.DoesNotExistError
  37. self.load_from_db(dt, dn)
  38. elif isinstance(dt, list):
  39. self.set_doclist(dt)
  40. elif isinstance(dt, dict):
  41. self.set_doclist([dt])
  42. def load_from_db(self, dt=None, dn=None):
  43. """
  44. Load doclist from dt
  45. """
  46. if not dt: dt = self.doc.doctype
  47. if not dn: dn = self.doc.name
  48. doc = Document(dt, dn)
  49. # get all children types
  50. tablefields = frappe.model.meta.get_table_fields(dt)
  51. # load chilren
  52. doclist = frappe.doclist([doc,])
  53. self.set_doclist(doclist)
  54. def __iter__(self):
  55. return self.doclist.__iter__()
  56. @property
  57. def meta(self):
  58. if not hasattr(self, "_meta"):
  59. self._meta = frappe.get_doctype(self.doc.doctype)
  60. return self._meta
  61. def from_compressed(self, data, docname):
  62. from frappe.model.utils import expand
  63. self.set_doclist(expand(data))
  64. def set_doclist(self, doclist):
  65. for i, d in enumerate(doclist):
  66. if isinstance(d, dict):
  67. doclist[i] = Document(fielddata=d)
  68. self.doclist = frappe.doclist(doclist)
  69. self.doc = self.doclist[0]
  70. if self.doc.meta.issingle:
  71. self.doc.cast_floats_and_ints()
  72. if self.obj:
  73. self.obj.doclist = self.doclist
  74. self.obj.doc = self.doc
  75. def make_controller(self):
  76. if not self.doc.doctype:
  77. raise frappe.DataError("Bean doctype not specified")
  78. if self.obj:
  79. # update doclist before running any method
  80. self.obj.doclist = self.doclist
  81. return self.obj
  82. self.obj = frappe.get_obj(doc=self.doc, doclist=self.doclist)
  83. self.obj.bean = self
  84. self.controller = self.obj
  85. return self.obj
  86. def get_controller(self):
  87. return self.make_controller()
  88. def to_dict(self):
  89. return [d.fields for d in self.doclist]
  90. def check_if_latest(self, method="save"):
  91. from frappe.model.meta import is_single
  92. conflict = False
  93. if not cint(self.doc.fields.get('__islocal')):
  94. if is_single(self.doc.doctype):
  95. modified = frappe.db.get_value(self.doc.doctype, self.doc.name, "modified")
  96. if isinstance(modified, list):
  97. modified = modified[0]
  98. if cstr(modified) and cstr(modified) != cstr(self.doc.modified):
  99. conflict = True
  100. else:
  101. tmp = frappe.db.sql("""select modified, docstatus from `tab%s`
  102. where name=%s for update"""
  103. % (self.doc.doctype, '%s'), self.doc.name, as_dict=True)
  104. if not tmp:
  105. frappe.msgprint("""This record does not exist. Please refresh.""", raise_exception=1)
  106. modified = cstr(tmp[0].modified)
  107. if modified and modified != cstr(self.doc.modified):
  108. conflict = True
  109. self.check_docstatus_transition(tmp[0].docstatus, method)
  110. if conflict:
  111. frappe.msgprint(_("Error: Document has been modified after you have opened it") \
  112. + (" (%s, %s). " % (modified, self.doc.modified)) \
  113. + _("Please refresh to get the latest document."), raise_exception=TimestampMismatchError)
  114. def check_docstatus_transition(self, db_docstatus, method):
  115. valid = {
  116. "save": [0,0],
  117. "submit": [0,1],
  118. "cancel": [1,2],
  119. "update_after_submit": [1,1]
  120. }
  121. labels = {
  122. 0: _("Draft"),
  123. 1: _("Submitted"),
  124. 2: _("Cancelled")
  125. }
  126. if not hasattr(self, "to_docstatus"):
  127. self.to_docstatus = 0
  128. if method != "runserverobj" and [db_docstatus, self.to_docstatus] != valid[method]:
  129. frappe.msgprint(_("Cannot change from") + ": " + labels[db_docstatus] + " > " + \
  130. labels[self.to_docstatus], raise_exception=DocstatusTransitionError)
  131. def update_timestamps_and_docstatus(self):
  132. from frappe.utils import now
  133. ts = now()
  134. user = frappe.__dict__.get('session', {}).get('user') or 'Administrator'
  135. for d in self.doclist:
  136. if self.doc.fields.get('__islocal'):
  137. if not d.owner:
  138. d.owner = user
  139. if not d.creation:
  140. d.creation = ts
  141. d.modified_by = user
  142. d.modified = ts
  143. if d.docstatus != 2 and self.to_docstatus >= int(d.docstatus): # don't update deleted
  144. d.docstatus = self.to_docstatus
  145. def prepare_for_save(self, method):
  146. self.check_if_latest(method)
  147. self.update_timestamps_and_docstatus()
  148. self.update_parent_info()
  149. if self.doc.fields.get("__islocal"):
  150. # set name before validate
  151. self.run_method('before_set_name')
  152. self.doc.set_new_name(self)
  153. self.run_method('before_insert')
  154. if method != "cancel":
  155. self.extract_images_from_text_editor()
  156. def update_parent_info(self):
  157. idx_map = {}
  158. is_local = cint(self.doc.fields.get("__islocal"))
  159. if not frappe.flags.in_import:
  160. parentfields = [d.fieldname for d in self.meta.get({"doctype": "DocField", "fieldtype": "Table"})]
  161. for i, d in enumerate(self.doclist[1:]):
  162. if d.parentfield:
  163. if not frappe.flags.in_import:
  164. if not d.parentfield in parentfields:
  165. frappe.msgprint("Bad parentfield %s" % d.parentfield,
  166. raise_exception=True)
  167. d.parenttype = self.doc.doctype
  168. d.parent = self.doc.name
  169. if not d.idx:
  170. d.idx = idx_map.setdefault(d.parentfield, 0) + 1
  171. else:
  172. d.idx = cint(d.idx)
  173. if is_local:
  174. # if parent is new, all children should be new
  175. d.fields["__islocal"] = 1
  176. d.name = None
  177. idx_map[d.parentfield] = d.idx
  178. def run_method(self, method, *args, **kwargs):
  179. if not args:
  180. args = []
  181. self.make_controller()
  182. def add_to_response(out, new_response):
  183. if isinstance(new_response, dict):
  184. out.update(new_response)
  185. if hasattr(self.controller, method):
  186. add_to_response(frappe.local.response,
  187. frappe.call(getattr(self.controller, method), *args, **kwargs))
  188. self.set_doclist(self.controller.doclist)
  189. args = [self, method] + list(args)
  190. for handler in frappe.get_hooks("bean_event:" + self.doc.doctype + ":" + method) \
  191. + frappe.get_hooks("bean_event:*:" + method):
  192. add_to_response(frappe.local.response, frappe.call(frappe.get_attr(handler), *args, **kwargs))
  193. return frappe.local.response
  194. def get_attr(self, method):
  195. self.make_controller()
  196. return getattr(self.controller, method, None)
  197. def insert(self, ignore_permissions=None):
  198. if ignore_permissions:
  199. self.ignore_permissions = True
  200. self.doc.fields["__islocal"] = 1
  201. self.set_defaults()
  202. if frappe.flags.in_test:
  203. if self.meta.get_field("naming_series"):
  204. self.doc.naming_series = "_T-" + self.doc.doctype + "-"
  205. return self.save()
  206. def insert_or_update(self):
  207. if self.doc.name and frappe.db.exists(self.doc.doctype, self.doc.name):
  208. return self.save()
  209. else:
  210. return self.insert()
  211. def set_defaults(self):
  212. if frappe.flags.in_import:
  213. return
  214. new_docs = {}
  215. new_doclist = []
  216. for d in self.doclist:
  217. if not d.doctype in new_docs:
  218. new_docs[d.doctype] = frappe.new_doc(d.doctype)
  219. newd = frappe.doc(new_docs[d.doctype].fields.copy())
  220. newd.fields.update(d.fields)
  221. new_doclist.append(newd)
  222. self.set_doclist(new_doclist)
  223. def has_read_perm(self):
  224. return self.has_permission("read")
  225. def has_permission(self, permtype):
  226. if self.ignore_permissions:
  227. return True
  228. if self.doc.parent and self.doc.parenttype:
  229. return frappe.has_permission(self.doc.parenttype, permtype,
  230. frappe.doc(self.doc.parenttype, self.doc.parent))
  231. else:
  232. return frappe.has_permission(self.doc.doctype, permtype, self.doc)
  233. def save(self, check_links=1, ignore_permissions=None):
  234. if ignore_permissions!=None:
  235. self.ignore_permissions = ignore_permissions
  236. perm_to_check = "write"
  237. if self.doc.fields.get("__islocal"):
  238. perm_to_check = "create"
  239. if not self.doc.owner:
  240. self.doc.owner = frappe.session.user
  241. self.to_docstatus = 0
  242. self.prepare_for_save("save")
  243. # check permissions after preparing for save, since name might be required
  244. if self.has_permission(perm_to_check):
  245. if not self.ignore_validate:
  246. self.run_method('validate')
  247. self.validate_doclist()
  248. self.save_main()
  249. self.save_children()
  250. self.run_method('on_update')
  251. if perm_to_check=="create":
  252. self.run_method("after_insert")
  253. else:
  254. self.no_permission_to(_(perm_to_check.title()))
  255. return self
  256. def submit(self):
  257. if self.has_permission("submit"):
  258. self.to_docstatus = 1
  259. self.prepare_for_save("submit")
  260. self.run_method('validate')
  261. self.validate_doclist()
  262. self.save_main()
  263. self.save_children()
  264. self.run_method('on_update')
  265. self.run_method('on_submit')
  266. else:
  267. self.no_permission_to(_("Submit"))
  268. return self
  269. def cancel(self):
  270. if self.has_permission("cancel"):
  271. self.to_docstatus = 2
  272. self.prepare_for_save("cancel")
  273. self.run_method('before_cancel')
  274. self.save_main()
  275. self.save_children()
  276. self.run_method('on_cancel')
  277. self.check_no_back_links_exist()
  278. else:
  279. self.no_permission_to(_("Cancel"))
  280. return self
  281. def update_after_submit(self):
  282. if self.doc.docstatus != 1:
  283. frappe.msgprint("Only to called after submit", raise_exception=1)
  284. if self.has_permission("write"):
  285. self.to_docstatus = 1
  286. self.prepare_for_save("update_after_submit")
  287. self.run_method('validate')
  288. self.run_method('before_update_after_submit')
  289. self.validate_doclist()
  290. self.save_main()
  291. self.save_children()
  292. self.run_method('on_update_after_submit')
  293. else:
  294. self.no_permission_to(_("Update"))
  295. return self
  296. def save_main(self):
  297. try:
  298. self.doc.save(check_links = False, ignore_fields = self.ignore_fields)
  299. except NameError, e:
  300. frappe.msgprint('%s "%s" already exists' % (self.doc.doctype, self.doc.name))
  301. # prompt if cancelled
  302. if frappe.db.get_value(self.doc.doctype, self.doc.name, 'docstatus')==2:
  303. frappe.msgprint('[%s "%s" has been cancelled]' % (self.doc.doctype, self.doc.name))
  304. frappe.errprint(frappe.utils.get_traceback())
  305. raise
  306. def save_children(self):
  307. child_map = {}
  308. for d in self.doclist[1:]:
  309. if d.fields.get("parent") or d.fields.get("parentfield"):
  310. d.parent = self.doc.name # rename if reqd
  311. d.parenttype = self.doc.doctype
  312. d.save(check_links=False, ignore_fields = self.ignore_fields)
  313. child_map.setdefault(d.doctype, []).append(d.name)
  314. # delete all children in database that are not in the child_map
  315. # get all children types
  316. tablefields = frappe.model.meta.get_table_fields(self.doc.doctype)
  317. for dt in tablefields:
  318. if dt[0] not in self.ignore_children_type:
  319. cnames = child_map.get(dt[0]) or []
  320. if cnames:
  321. frappe.db.sql("""delete from `tab%s` where parent=%s and parenttype=%s and
  322. name not in (%s)""" % (dt[0], '%s', '%s', ','.join(['%s'] * len(cnames))),
  323. tuple([self.doc.name, self.doc.doctype] + cnames))
  324. else:
  325. frappe.db.sql("""delete from `tab%s` where parent=%s and parenttype=%s""" \
  326. % (dt[0], '%s', '%s'), (self.doc.name, self.doc.doctype))
  327. def delete(self):
  328. frappe.delete_doc(self.doc.doctype, self.doc.name)
  329. def no_permission_to(self, ptype):
  330. frappe.msgprint(("%s (%s): " % (self.doc.name, _(self.doc.doctype))) + \
  331. _("No Permission to ") + ptype, raise_exception=BeanPermissionError)
  332. def check_no_back_links_exist(self):
  333. from frappe.model.delete_doc import check_if_doc_is_linked
  334. check_if_doc_is_linked(self.doc.doctype, self.doc.name, method="Cancel")
  335. def check_mandatory(self):
  336. if self.ignore_mandatory:
  337. return
  338. missing = []
  339. for doc in self.doclist:
  340. for df in self.meta:
  341. if df.doctype=="DocField" and df.reqd and df.parent==doc.doctype and df.fieldname!="naming_series":
  342. msg = ""
  343. if df.fieldtype == "Table":
  344. if not self.get(df.fieldname):
  345. msg = _("Error") + ": " + _("Data missing in table") + ": " + _(df.label)
  346. elif doc.fields.get(df.fieldname) is None:
  347. msg = _("Error") + ": "
  348. if doc.parentfield:
  349. msg += _("Row") + (" # %s: " % (doc.idx,))
  350. msg += _("Value missing for") + ": " + _(df.label)
  351. if msg:
  352. missing.append([msg, df.fieldname])
  353. if missing:
  354. for msg, fieldname in missing:
  355. msgprint(msg)
  356. raise frappe.MandatoryError, ", ".join([fieldname for msg, fieldname in missing])
  357. def extract_images_from_text_editor(self):
  358. from frappe.utils.file_manager import extract_images_from_html
  359. if self.doc.doctype != "DocType":
  360. for df in self.meta.get({"doctype": "DocField", "parent": self.doc.doctype, "fieldtype":"Text Editor"}):
  361. extract_images_from_html(self.doc, df.fieldname)
  362. def validate_doclist(self):
  363. self.check_mandatory()
  364. self.validate_restrictions()
  365. self.check_links()
  366. def check_links(self):
  367. if self.ignore_links:
  368. return
  369. ref, err_list = {}, []
  370. for d in self.doclist:
  371. if not ref.get(d.doctype):
  372. ref[d.doctype] = d.make_link_list()
  373. err_list += d.validate_links(ref[d.doctype])
  374. if err_list:
  375. frappe.msgprint("""[Link Validation] Could not find the following values: %s.
  376. Please correct and resave. Document Not Saved.""" % ', '.join(err_list), raise_exception=1)
  377. def validate_restrictions(self):
  378. if self.ignore_restrictions:
  379. return
  380. has_restricted_data = False
  381. for d in self.doclist:
  382. if not frappe.permissions.has_unrestricted_access(d):
  383. has_restricted_data = True
  384. if has_restricted_data:
  385. raise BeanPermissionError
  386. def clone(source_wrapper):
  387. """ make a clone of a document"""
  388. if isinstance(source_wrapper, list):
  389. source_wrapper = Bean(source_wrapper)
  390. new_wrapper = Bean(source_wrapper.doclist.copy())
  391. if new_wrapper.doc.fields.get("amended_from"):
  392. new_wrapper.doc.fields["amended_from"] = None
  393. if new_wrapper.doc.fields.get("amendment_date"):
  394. new_wrapper.doc.fields["amendment_date"] = None
  395. for d in new_wrapper.doclist:
  396. d.fields.update({
  397. "name": None,
  398. "__islocal": 1,
  399. "docstatus": 0,
  400. })
  401. return new_wrapper
  402. # for bc
  403. def getlist(doclist, parentfield):
  404. import frappe.model.utils
  405. return frappe.model.utils.getlist(doclist, parentfield)
  406. def copy_doclist(doclist, no_copy = []):
  407. """
  408. Make a copy of the doclist
  409. """
  410. import frappe.model.utils
  411. return frappe.model.utils.copy_doclist(doclist, no_copy)