You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

486 regels
14 KiB

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