Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

704 строки
21 KiB

  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. from __future__ import unicode_literals
  23. """
  24. Contains the Document class representing an object / record
  25. """
  26. _toc = ["webnotes.model.doc.Document"]
  27. import webnotes
  28. import webnotes.model.meta
  29. from webnotes.utils import *
  30. valid_fields_map = {}
  31. class Document:
  32. """
  33. The wn(meta-data)framework equivalent of a Database Record.
  34. Stores,Retrieves,Updates the record in the corresponding table.
  35. Runs the triggers required.
  36. The `Document` class represents the basic Object-Relational Mapper (ORM). The object type is defined by
  37. `DocType` and the object ID is represented by `name`::
  38. Please note the anamoly in the Web Notes Framework that `ID` is always called as `name`
  39. If both `doctype` and `name` are specified in the constructor, then the object is loaded from the database.
  40. If only `doctype` is given, then the object is not loaded
  41. If `fielddata` is specfied, then the object is created from the given dictionary.
  42. **Note 1:**
  43. The getter and setter of the object are overloaded to map to the fields of the object that
  44. are loaded when it is instantiated.
  45. For example: doc.name will be the `name` field and doc.owner will be the `owner` field
  46. **Note 2 - Standard Fields:**
  47. * `name`: ID / primary key
  48. * `owner`: creator of the record
  49. * `creation`: datetime of creation
  50. * `modified`: datetime of last modification
  51. * `modified_by` : last updating user
  52. * `docstatus` : Status 0 - Saved, 1 - Submitted, 2- Cancelled
  53. * `parent` : if child (table) record, this represents the parent record
  54. * `parenttype` : type of parent record (if any)
  55. * `parentfield` : table fieldname of parent record (if any)
  56. * `idx` : Index (sequence) of the child record
  57. """
  58. def __init__(self, doctype = None, name = None, fielddata = None, prefix='tab'):
  59. self._roles = []
  60. self._perms = []
  61. self._user_defaults = {}
  62. self._prefix = prefix
  63. if isinstance(doctype, dict):
  64. fielddata = doctype
  65. doctype = None
  66. if fielddata:
  67. self.fields = webnotes._dict(fielddata)
  68. else:
  69. self.fields = webnotes._dict()
  70. if not self.fields.has_key('name'):
  71. self.fields['name']='' # required on save
  72. if not self.fields.has_key('doctype'):
  73. self.fields['doctype']='' # required on save
  74. if not self.fields.has_key('owner'):
  75. self.fields['owner']='' # required on save
  76. if doctype:
  77. self.fields['doctype'] = doctype
  78. if name:
  79. self.fields['name'] = name
  80. self.__initialized = 1
  81. if (doctype and name):
  82. self._loadfromdb(doctype, name)
  83. else:
  84. if not fielddata:
  85. self.fields['__islocal'] = 1
  86. if not self.fields.docstatus:
  87. self.fields.docstatus = 0
  88. def __nonzero__(self):
  89. return True
  90. def __str__(self):
  91. return str(self.fields)
  92. def __repr__(self):
  93. return repr(self.fields)
  94. def __unicode__(self):
  95. return unicode(self.fields)
  96. def __eq__(self, other):
  97. if isinstance(other, Document):
  98. return self.fields == other.fields
  99. else:
  100. return False
  101. def __getstate__(self):
  102. return self.fields
  103. def __setstate__(self, d):
  104. self.fields = d
  105. def encode(self, encoding='utf-8'):
  106. """convert all unicode values to utf-8"""
  107. for key in self.fields:
  108. if isinstance(self.fields[key], unicode):
  109. self.fields[key] = self.fields[key].encode(encoding)
  110. def _loadfromdb(self, doctype = None, name = None):
  111. if name: self.name = name
  112. if doctype: self.doctype = doctype
  113. is_single = False
  114. try:
  115. is_single = webnotes.model.meta.is_single(self.doctype)
  116. except Exception, e:
  117. pass
  118. if is_single:
  119. self._loadsingle()
  120. else:
  121. dataset = webnotes.conn.sql('select * from `%s%s` where name="%s"' % (self._prefix, self.doctype, self.name.replace('"', '\"')))
  122. if not dataset:
  123. raise Exception, '[WNF] %s %s does not exist' % (self.doctype, self.name)
  124. self._load_values(dataset[0], webnotes.conn.get_description())
  125. def _load_values(self, data, description):
  126. if '__islocal' in self.fields:
  127. del self.fields['__islocal']
  128. for i in range(len(description)):
  129. v = data[i]
  130. self.fields[description[i][0]] = webnotes.conn.convert_to_simple_type(v)
  131. def _merge_values(self, data, description):
  132. for i in range(len(description)):
  133. v = data[i]
  134. if v: # only if value, over-write
  135. self.fields[description[i][0]] = webnotes.conn.convert_to_simple_type(v)
  136. def _loadsingle(self):
  137. self.name = self.doctype
  138. self.fields.update(getsingle(self.doctype))
  139. def __setattr__(self, name, value):
  140. # normal attribute
  141. if not self.__dict__.has_key('_Document__initialized'):
  142. self.__dict__[name] = value
  143. elif self.__dict__.has_key(name):
  144. self.__dict__[name] = value
  145. else:
  146. # field attribute
  147. f = self.__dict__['fields']
  148. f[name] = value
  149. def __getattr__(self, name):
  150. if self.__dict__.has_key(name):
  151. return self.__dict__[name]
  152. elif self.fields.has_key(name):
  153. return self.fields[name]
  154. else:
  155. return ''
  156. def _get_amended_name(self):
  157. am_id = 1
  158. am_prefix = self.amended_from
  159. if webnotes.conn.sql('select amended_from from `tab%s` where name = "%s"' % (self.doctype, self.amended_from))[0][0] or '':
  160. am_id = cint(self.amended_from.split('-')[-1]) + 1
  161. am_prefix = '-'.join(self.amended_from.split('-')[:-1]) # except the last hyphen
  162. self.name = am_prefix + '-' + str(am_id)
  163. def _set_name(self, autoname, istable):
  164. self.localname = self.name
  165. # get my object
  166. import webnotes.model.code
  167. so = webnotes.model.code.get_server_obj(self, [])
  168. # amendments
  169. if self.amended_from:
  170. self._get_amended_name()
  171. # by method
  172. elif so and hasattr(so, 'autoname'):
  173. r = webnotes.model.code.run_server_obj(so, 'autoname')
  174. if r: return r
  175. # based on a field
  176. elif autoname and autoname.startswith('field:'):
  177. n = self.fields[autoname[6:]]
  178. if not n:
  179. raise Exception, 'Name is required'
  180. self.name = n.strip()
  181. elif autoname and autoname.startswith("naming_series:"):
  182. if not self.naming_series:
  183. # pick default naming series
  184. from webnotes.model.doctype import get_property
  185. self.naming_series = get_property(self.doctype, "options", "naming_series")
  186. if not self.naming_series:
  187. webnotes.msgprint(webnotes._("Naming Series mandatory"), raise_exception=True)
  188. self.naming_series = self.naming_series.split("\n")
  189. self.naming_series = self.naming_series[0] or self.naming_series[1]
  190. self.name = make_autoname(self.naming_series+'.#####')
  191. # based on expression
  192. elif autoname and autoname.startswith('eval:'):
  193. doc = self # for setting
  194. self.name = eval(autoname[5:])
  195. # call the method!
  196. elif autoname and autoname!='Prompt':
  197. self.name = make_autoname(autoname, self.doctype)
  198. # given
  199. elif self.fields.get('__newname',''):
  200. self.name = self.fields['__newname']
  201. # default name for table
  202. elif istable:
  203. self.name = make_autoname('#########', self.doctype)
  204. # unable to determine a name, use a serial number!
  205. if not self.name:
  206. self.name = make_autoname('#########', self.doctype)
  207. def _insert(self, autoname, istable, case='', make_autoname=1, keep_timestamps=False):
  208. # set name
  209. if make_autoname:
  210. self._set_name(autoname, istable)
  211. # validate name
  212. self.name = validate_name(self.doctype, self.name, case)
  213. # insert!
  214. if not keep_timestamps:
  215. if not self.owner:
  216. self.owner = webnotes.session['user']
  217. self.modified_by = webnotes.session['user']
  218. if not self.creation:
  219. self.creation = self.modified = now()
  220. else:
  221. self.modified = now()
  222. webnotes.conn.sql("insert into `tab%(doctype)s`" % self.fields \
  223. + """ (name, owner, creation, modified, modified_by)
  224. values (%(name)s, %(owner)s, %(creation)s, %(modified)s,
  225. %(modified_by)s)""", self.fields)
  226. def _update_single(self, link_list):
  227. update_str = ["(%s, 'modified', %s)",]
  228. values = [self.doctype, now()]
  229. webnotes.conn.sql("delete from tabSingles where doctype='%s'" % self.doctype)
  230. for f in self.fields.keys():
  231. if not (f in ('modified', 'doctype', 'name', 'perm', 'localname', 'creation'))\
  232. and (not f.startswith('__')): # fields not saved
  233. # validate links
  234. if link_list and link_list.get(f):
  235. self.fields[f] = self._validate_link(link_list[f][0], self.fields[f])
  236. if self.fields[f]==None:
  237. update_str.append("(%s,%s,NULL)")
  238. values.append(self.doctype)
  239. values.append(f)
  240. else:
  241. update_str.append("(%s,%s,%s)")
  242. values.append(self.doctype)
  243. values.append(f)
  244. values.append(self.fields[f])
  245. webnotes.conn.sql("insert into tabSingles(doctype, field, value) values %s" % (', '.join(update_str)), values)
  246. def validate_links(self, link_list):
  247. err_list = []
  248. for f in self.fields.keys():
  249. # validate links
  250. old_val = self.fields[f]
  251. if link_list and link_list.get(f):
  252. self.fields[f] = self._validate_link(link_list[f][0], self.fields[f])
  253. if old_val and not self.fields[f]:
  254. s = link_list[f][1] + ': ' + old_val
  255. err_list.append(s)
  256. return err_list
  257. def make_link_list(self):
  258. res = webnotes.model.meta.get_link_fields(self.doctype)
  259. link_list = {}
  260. for i in res: link_list[i[0]] = (i[1], i[2]) # options, label
  261. return link_list
  262. def _validate_link(self, dt, dn):
  263. if not dt: return dn
  264. if not dn: return None
  265. if dt=="[Select]": return dn
  266. if dt.lower().startswith('link:'):
  267. dt = dt[5:]
  268. if '\n' in dt:
  269. dt = dt.split('\n')[0]
  270. tmp = webnotes.conn.sql("""SELECT name FROM `tab%s`
  271. WHERE name = %s""" % (dt, '%s'), dn)
  272. return tmp and tmp[0][0] or ''# match case
  273. def _update_values(self, issingle, link_list, ignore_fields=0, keep_timestamps=False):
  274. if issingle:
  275. self._update_single(link_list)
  276. else:
  277. update_str, values = [], []
  278. # set modified timestamp
  279. if self.modified and not keep_timestamps:
  280. self.modified = now()
  281. self.modified_by = webnotes.session['user']
  282. fields_list = ignore_fields and self.get_valid_fields() or self.fields.keys()
  283. for f in fields_list:
  284. if (not (f in ('doctype', 'name', 'perm', 'localname',
  285. 'creation','_user_tags', "file_list"))) and (not f.startswith('__')):
  286. # fields not saved
  287. # validate links
  288. if link_list and link_list.get(f):
  289. self.fields[f] = self._validate_link(link_list[f][0],
  290. self.fields.get(f))
  291. if self.fields.get(f) is None or self.fields.get(f)=='':
  292. update_str.append("`%s`=NULL" % f)
  293. else:
  294. values.append(self.fields.get(f))
  295. update_str.append("`%s`=%s" % (f, '%s'))
  296. if values:
  297. values.append(self.name)
  298. r = webnotes.conn.sql("update `tab%s` set %s where name=%s" % \
  299. (self.doctype, ', '.join(update_str), "%s"), values)
  300. def get_valid_fields(self):
  301. global valid_fields_map
  302. if not valid_fields_map.get(self.doctype):
  303. import webnotes.model.doctype
  304. if cint(webnotes.conn.get_value("DocType", self.doctype, "issingle")):
  305. doctypelist = webnotes.model.doctype.get(self.doctype)
  306. valid_fields_map[self.doctype] = doctypelist.get_fieldnames({
  307. "fieldtype": ["not in", webnotes.model.no_value_fields]})
  308. else:
  309. valid_fields_map[self.doctype] = \
  310. webnotes.conn.get_table_columns(self.doctype)
  311. return valid_fields_map.get(self.doctype)
  312. def save(self, new=0, check_links=1, ignore_fields=0, make_autoname=1,
  313. keep_timestamps=False):
  314. res = webnotes.model.meta.get_dt_values(self.doctype,
  315. 'autoname, issingle, istable, name_case', as_dict=1)
  316. res = res and res[0] or {}
  317. if new:
  318. self.fields["__islocal"] = 1
  319. # add missing parentinfo (if reqd)
  320. if self.parent and not (self.parenttype and self.parentfield):
  321. self.update_parentinfo()
  322. if self.parent and not self.idx:
  323. self.set_idx()
  324. # if required, make new
  325. if self.fields.get('__islocal') and (not res.get('issingle')):
  326. r = self._insert(res.get('autoname'), res.get('istable'), res.get('name_case'),
  327. make_autoname, keep_timestamps = keep_timestamps)
  328. if r:
  329. return r
  330. else:
  331. if not res.get('issingle') and not webnotes.conn.exists(self.doctype, self.name):
  332. webnotes.msgprint("""This document was updated before your change. Please refresh before saving.""", raise_exception=1)
  333. # save the values
  334. self._update_values(res.get('issingle'),
  335. check_links and self.make_link_list() or {}, ignore_fields=ignore_fields,
  336. keep_timestamps=keep_timestamps)
  337. self._clear_temp_fields()
  338. def insert(self):
  339. self.fields['__islocal'] = 1
  340. self.save()
  341. return self
  342. def update_parentinfo(self):
  343. """update parent type and parent field, if not explicitly specified"""
  344. tmp = webnotes.conn.sql("""select parent, fieldname from tabDocField
  345. where fieldtype='Table' and options=%s""", self.doctype)
  346. if len(tmp)==0:
  347. raise Exception, 'Incomplete parent info in child table (%s, %s)' \
  348. % (self.doctype, self.fields.get('name', '[new]'))
  349. elif len(tmp)>1:
  350. raise Exception, 'Ambiguous parent info (%s, %s)' \
  351. % (self.doctype, self.fields.get('name', '[new]'))
  352. else:
  353. self.parenttype = tmp[0][0]
  354. self.parentfield = tmp[0][1]
  355. def set_idx(self):
  356. """set idx"""
  357. self.idx = (webnotes.conn.sql("""select max(idx) from `tab%s`
  358. where parent=%s and parentfield=%s""" % (self.doctype, '%s', '%s'),
  359. (self.parent, self.parentfield))[0][0] or 0) + 1
  360. def _clear_temp_fields(self):
  361. # clear temp stuff
  362. keys = self.fields.keys()
  363. for f in keys:
  364. if f.startswith('__'):
  365. del self.fields[f]
  366. def clear_table(self, doclist, tablefield, save=0):
  367. """
  368. Clears the child records from the given `doclist` for a particular `tablefield`
  369. """
  370. from webnotes.model.utils import getlist
  371. table_list = getlist(doclist, tablefield)
  372. delete_list = [d.name for d in table_list]
  373. if delete_list:
  374. #filter doclist
  375. doclist = filter(lambda d: d.name not in delete_list, doclist)
  376. # delete from db
  377. webnotes.conn.sql("""\
  378. delete from `tab%s`
  379. where parent=%s and parenttype=%s"""
  380. % (table_list[0].doctype, '%s', '%s'),
  381. (self.name, self.doctype))
  382. self.fields['__unsaved'] = 1
  383. return webnotes.doclist(doclist)
  384. def addchild(self, fieldname, childtype = '', doclist=None):
  385. """
  386. Returns a child record of the give `childtype`.
  387. * if local is set, it does not save the record
  388. * if doclist is passed, it append the record to the doclist
  389. """
  390. from webnotes.model.doc import Document
  391. d = Document()
  392. d.parent = self.name
  393. d.parenttype = self.doctype
  394. d.parentfield = fieldname
  395. d.doctype = childtype
  396. d.docstatus = 0;
  397. d.name = ''
  398. d.owner = webnotes.session['user']
  399. d.fields['__islocal'] = 1 # for Client to identify unsaved doc
  400. if doclist != None:
  401. doclist.append(d)
  402. if doclist:
  403. d.idx = max([(d.idx or 0) for d in doclist if d.doctype==childtype]) + 1
  404. return d
  405. def get_values(self):
  406. """get non-null fields dict withouth standard fields"""
  407. from webnotes.model import default_fields
  408. ret = {}
  409. for key in self.fields:
  410. if key not in default_fields and self.fields[key]:
  411. ret[key] = self.fields[key]
  412. return ret
  413. def addchild(parent, fieldname, childtype = '', doclist=None):
  414. """
  415. Create a child record to the parent doc.
  416. Example::
  417. c = Document('Contact','ABC')
  418. d = addchild(c, 'contact_updates', 'Contact Update')
  419. d.last_updated = 'Phone call'
  420. d.save(1)
  421. """
  422. return parent.addchild(fieldname, childtype, doclist)
  423. def make_autoname(key, doctype=''):
  424. """
  425. Creates an autoname from the given key:
  426. **Autoname rules:**
  427. * The key is separated by '.'
  428. * '####' represents a series. The string before this part becomes the prefix:
  429. Example: ABC.#### creates a series ABC0001, ABC0002 etc
  430. * 'MM' represents the current month
  431. * 'YY' and 'YYYY' represent the current year
  432. *Example:*
  433. * DE/./.YY./.MM./.##### will create a series like
  434. DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
  435. """
  436. n = ''
  437. l = key.split('.')
  438. series_set = False
  439. for e in l:
  440. en = ''
  441. if e.startswith('#'):
  442. if not series_set:
  443. digits = len(e)
  444. en = getseries(n, digits, doctype)
  445. series_set = True
  446. elif e=='YY':
  447. import time
  448. en = time.strftime('%y')
  449. elif e=='MM':
  450. import time
  451. en = time.strftime('%m')
  452. elif e=='YYYY':
  453. import time
  454. en = time.strftime('%Y')
  455. else: en = e
  456. n+=en
  457. return n
  458. def getseries(key, digits, doctype=''):
  459. # series created ?
  460. if webnotes.conn.sql("select name from tabSeries where name='%s'" % key):
  461. # yes, update it
  462. webnotes.conn.sql("update tabSeries set current = current+1 where name='%s'" % key)
  463. # find the series counter
  464. r = webnotes.conn.sql("select current from tabSeries where name='%s'" % key)
  465. n = r[0][0]
  466. else:
  467. # no, create it
  468. webnotes.conn.sql("insert into tabSeries (name, current) values ('%s', 1)" % key)
  469. n = 1
  470. return ('%0'+str(digits)+'d') % n
  471. def getchildren(name, childtype, field='', parenttype='', from_doctype=0, prefix='tab'):
  472. import webnotes
  473. from webnotes.model.doclist import DocList
  474. condition = ""
  475. values = []
  476. if field:
  477. condition += ' and parentfield=%s '
  478. values.append(field)
  479. if parenttype:
  480. condition += ' and parenttype=%s '
  481. values.append(parenttype)
  482. dataset = webnotes.conn.sql("""select * from `%s%s` where parent=%s %s order by idx""" \
  483. % (prefix, childtype, "%s", condition), tuple([name]+values))
  484. desc = webnotes.conn.get_description()
  485. l = DocList()
  486. for i in dataset:
  487. d = Document()
  488. d.doctype = childtype
  489. d._load_values(i, desc)
  490. l.append(d)
  491. return l
  492. def check_page_perm(doc):
  493. if doc.name=='Login Page':
  494. return
  495. if doc.publish:
  496. return
  497. if not webnotes.conn.sql("select name from `tabPage Role` where parent=%s and role='Guest'", doc.name):
  498. webnotes.response['403'] = 1
  499. raise webnotes.PermissionError, '[WNF] No read permission for %s %s' % ('Page', doc.name)
  500. def get_report_builder_code(doc):
  501. if doc.doctype=='Search Criteria':
  502. from webnotes.model.code import get_code
  503. if doc.standard != 'No':
  504. doc.report_script = get_code(doc.module, 'Search Criteria', doc.name, 'js')
  505. doc.custom_query = get_code(doc.module, 'Search Criteria', doc.name, 'sql')
  506. def get(dt, dn='', with_children = 1, from_controller = 0, prefix = 'tab'):
  507. """
  508. Returns a doclist containing the main record and all child records
  509. """
  510. import webnotes
  511. import webnotes.model
  512. from webnotes.model.doclist import DocList
  513. dn = dn or dt
  514. # load the main doc
  515. doc = Document(dt, dn, prefix=prefix)
  516. if dt=='Page' and webnotes.session['user'] == 'Guest':
  517. check_page_perm(doc)
  518. if not with_children:
  519. # done
  520. return DocList([doc,])
  521. # get all children types
  522. tablefields = webnotes.model.meta.get_table_fields(dt)
  523. # load chilren
  524. doclist = DocList([doc,])
  525. for t in tablefields:
  526. doclist += getchildren(doc.name, t[0], t[1], dt, prefix=prefix)
  527. # import report_builder code
  528. if not from_controller:
  529. get_report_builder_code(doc)
  530. return doclist
  531. def getsingle(doctype):
  532. """get single doc as dict"""
  533. dataset = webnotes.conn.sql("select field, value from tabSingles where doctype=%s", doctype)
  534. return dict(dataset)
  535. def copy_common_fields(from_doc, to_doc):
  536. from webnotes.model import default_fields
  537. doctype_list = webnotes.get_doctype(to_doc.doctype)
  538. for fieldname, value in from_doc.fields.items():
  539. if fieldname in default_fields:
  540. continue
  541. if doctype_list.get_field(fieldname) and to_doc.fields[fieldname] != value:
  542. to_doc.fields[fieldname] = value
  543. def validate_name(doctype, name, case=None, merge=False):
  544. if not merge:
  545. if webnotes.conn.sql('select name from `tab%s` where name=%s' % (doctype,'%s'), name):
  546. raise NameError, 'Name %s already exists' % name
  547. # no name
  548. if not name: return 'No Name Specified for %s' % doctype
  549. # new..
  550. if name.startswith('New '+doctype):
  551. raise NameError, 'There were some errors setting the name, please contact the administrator'
  552. if case=='Title Case': name = name.title()
  553. if case=='UPPER CASE': name = name.upper()
  554. name = name.strip() # no leading and trailing blanks
  555. forbidden = ['%', "'", '"', '#', '*', '?', '`']
  556. for f in forbidden:
  557. if f in name:
  558. webnotes.msgprint('%s not allowed in ID (name)' % f, raise_exception =1)
  559. return name