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

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