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.
 
 
 
 
 
 

696 regels
20 KiB

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