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.
 
 
 
 
 
 

718 lines
21 KiB

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