Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

bean.py 14 KiB

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