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

672 рядки
20 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. if webnotes.model.meta.is_single(self.doctype):
  77. self._loadsingle()
  78. else:
  79. dataset = webnotes.conn.sql('select * from `%s%s` where name="%s"' % (self._prefix, self.doctype, self.name.replace('"', '\"')))
  80. if not dataset:
  81. webnotes.msgprint('%s %s does not exist' % (self.doctype, self.name), 1)
  82. raise Exception, '[WNF] %s %s does not exist' % (self.doctype, self.name)
  83. self._load_values(dataset[0], webnotes.conn.get_description())
  84. # Load Fields from dataset
  85. # ---------------------------------------------------------------------------
  86. def _load_values(self, data, description):
  87. for i in range(len(description)):
  88. v = data[i]
  89. self.fields[description[i][0]] = webnotes.conn.convert_to_simple_type(v)
  90. def _merge_values(self, data, description):
  91. for i in range(len(description)):
  92. v = data[i]
  93. if v: # only if value, over-write
  94. self.fields[description[i][0]] = webnotes.conn.convert_to_simple_type(v)
  95. # Load Single Type
  96. # ---------------------------------------------------------------------------
  97. def _loadsingle(self):
  98. self.name = self.doctype
  99. dataset = webnotes.conn.sql("select field, value from tabSingles where doctype='%s'" % self.doctype)
  100. for d in dataset: self.fields[d[0]] = d[1]
  101. # Setter
  102. # ---------------------------------------------------------------------------
  103. def __setattr__(self, name, value):
  104. # normal attribute
  105. if not self.__dict__.has_key('_Document__initialized'):
  106. self.__dict__[name] = value
  107. elif self.__dict__.has_key(name):
  108. self.__dict__[name] = value
  109. else:
  110. # field attribute
  111. f = self.__dict__['fields']
  112. f[name] = value
  113. # Getter
  114. # ---------------------------------------------------------------------------
  115. def __getattr__(self, name):
  116. if self.__dict__.has_key(name):
  117. return self.__dict__[name]
  118. elif self.fields.has_key(name):
  119. return self.fields[name]
  120. else:
  121. return ''
  122. # Get Amendement number
  123. # ---------------------------------------------------------------------------
  124. def _get_amended_name(self):
  125. am_id = 1
  126. am_prefix = self.amended_from
  127. if webnotes.conn.sql('select amended_from from `tab%s` where name = "%s"' % (self.doctype, self.amended_from))[0][0] or '':
  128. am_id = cint(self.amended_from.split('-')[-1]) + 1
  129. am_prefix = '-'.join(self.amended_from.split('-')[:-1]) # except the last hyphen
  130. self.name = am_prefix + '-' + str(am_id)
  131. # Set Name
  132. # ---------------------------------------------------------------------------
  133. def _set_name(self, autoname, istable):
  134. self.localname = self.name
  135. # get my object
  136. import webnotes.model.code
  137. so = webnotes.model.code.get_server_obj(self, [])
  138. # amendments
  139. if self.amended_from:
  140. self._get_amended_name()
  141. # by method
  142. elif so and hasattr(so, 'autoname'):
  143. r = webnotes.model.code.run_server_obj(so, 'autoname')
  144. if r: return r
  145. # based on a field
  146. elif autoname and autoname.startswith('field:'):
  147. n = self.fields[autoname[6:]]
  148. if not n:
  149. raise Exception, 'Name is required'
  150. self.name = n.strip()
  151. # based on expression
  152. elif autoname and autoname.startswith('eval:'):
  153. doc = self # for setting
  154. self.name = eval(autoname[5:])
  155. # call the method!
  156. elif autoname and autoname!='Prompt':
  157. self.name = make_autoname(autoname, self.doctype)
  158. # given
  159. elif self.fields.get('__newname',''):
  160. self.name = self.fields['__newname']
  161. # default name for table
  162. elif istable:
  163. self.name = make_autoname('#########', self.doctype)
  164. # Validate Name
  165. # ---------------------------------------------------------------------------
  166. def _validate_name(self, case):
  167. if webnotes.conn.sql('select name from `tab%s` where name=%s' % (self.doctype,'%s'), self.name):
  168. raise NameError, 'Name %s already exists' % self.name
  169. # no name
  170. if not self.name: return 'No Name Specified for %s' % self.doctype
  171. # new..
  172. if self.name.startswith('New '+self.doctype):
  173. return 'There were some errors setting the name, please contact the administrator'
  174. if case=='Title Case': self.name = self.name.title()
  175. if case=='UPPER CASE': self.name = self.name.upper()
  176. self.name = self.name.strip() # no leading and trailing blanks
  177. forbidden = ['%', "'", '"', '#', '*', '?', '`']
  178. for f in forbidden:
  179. if f in self.name:
  180. webnotes.msgprint('%s not allowed in ID (name)' % f, raise_exception =1)
  181. # Insert
  182. # ---------------------------------------------------------------------------
  183. def _makenew(self, autoname, istable, case='', make_autoname=1):
  184. # set owner
  185. if not self.owner: self.owner = webnotes.session['user']
  186. # set name
  187. if make_autoname:
  188. self._set_name(autoname, istable)
  189. # validate name
  190. self._validate_name(case)
  191. # insert!
  192. webnotes.conn.sql("""insert into `tab%s` (name, owner, creation, modified, modified_by) values ('%s', '%s', '%s', '%s', '%s')""" % (self.doctype, self.name, webnotes.session['user'], now(), now(), webnotes.session['user']))
  193. # Update Values
  194. # ---------------------------------------------------------------------------
  195. def _update_single(self, link_list):
  196. update_str = ["(%s, 'modified', %s)",]
  197. values = [self.doctype, now()]
  198. webnotes.conn.sql("delete from tabSingles where doctype='%s'" % self.doctype)
  199. for f in self.fields.keys():
  200. if not (f in ('modified', 'doctype', 'name', 'perm', 'localname', 'creation'))\
  201. and (not f.startswith('__')): # fields not saved
  202. # validate links
  203. if link_list and link_list.get(f):
  204. self.fields[f] = self._validate_link(link_list[f][0], self.fields[f])
  205. if self.fields[f]==None:
  206. update_str.append("(%s,%s,NULL)")
  207. values.append(self.doctype)
  208. values.append(f)
  209. else:
  210. update_str.append("(%s,%s,%s)")
  211. values.append(self.doctype)
  212. values.append(f)
  213. values.append(self.fields[f])
  214. webnotes.conn.sql("insert into tabSingles(doctype, field, value) values %s" % (', '.join(update_str)), values)
  215. # Validate Links
  216. # ---------------------------------------------------------------------------
  217. def validate_links(self, link_list):
  218. err_list = []
  219. for f in self.fields.keys():
  220. # validate links
  221. old_val = self.fields[f]
  222. if link_list and link_list.get(f):
  223. self.fields[f] = self._validate_link(link_list[f][0], self.fields[f])
  224. if old_val and not self.fields[f]:
  225. s = link_list[f][1] + ': ' + old_val
  226. err_list.append(s)
  227. return err_list
  228. def make_link_list(self):
  229. res = webnotes.model.meta.get_link_fields(self.doctype)
  230. link_list = {}
  231. for i in res: link_list[i[0]] = (i[1], i[2]) # options, label
  232. return link_list
  233. def _validate_link(self, dt, dn):
  234. if not dt: return dn
  235. if not dn: return None
  236. if dt.lower().startswith('link:'):
  237. dt = dt[5:]
  238. if '\n' in dt:
  239. dt = dt.split('\n')[0]
  240. tmp = webnotes.conn.sql("""SELECT name FROM `tab%s` WHERE name = %s""" % (dt, '%s'), dn)
  241. return tmp and tmp[0][0] or ''# match case
  242. # Update query
  243. # ---------------------------------------------------------------------------
  244. def _update_values(self, issingle, link_list, ignore_fields=0):
  245. if issingle:
  246. self._update_single(link_list)
  247. else:
  248. update_str, values = [], []
  249. # set modified timestamp
  250. self.modified = now()
  251. self.modified_by = webnotes.session['user']
  252. for f in self.fields.keys():
  253. if (not (f in ('doctype', 'name', 'perm', 'localname', 'creation','_user_tags'))) \
  254. and (not f.startswith('__')): # fields not saved
  255. # validate links
  256. if link_list and link_list.get(f):
  257. self.fields[f] = self._validate_link(link_list[f][0], self.fields[f])
  258. if self.fields[f]==None or self.fields[f]=='':
  259. update_str.append("`%s`=NULL" % f)
  260. if ignore_fields:
  261. try: r = webnotes.conn.sql("update `tab%s` set `%s`=NULL where name=%s" % (self.doctype, f, '%s'), self.name)
  262. except: pass
  263. else:
  264. values.append(self.fields[f])
  265. update_str.append("`%s`=%s" % (f, '%s'))
  266. if ignore_fields:
  267. try: r = webnotes.conn.sql("update `tab%s` set `%s`=%s where name=%s" % (self.doctype, f, '%s', '%s'), (self.fields[f], self.name))
  268. except: pass
  269. if values:
  270. if not ignore_fields:
  271. # update all in one query
  272. r = webnotes.conn.sql("update `tab%s` set %s where name='%s'" % (self.doctype, ', '.join(update_str), self.name), values)
  273. # Save values
  274. # ---------------------------------------------------------------------------
  275. def save(self, new=0, check_links=1, ignore_fields=0, make_autoname = 1):
  276. """
  277. Saves the current record in the database. If new = 1, creates a new instance of the record.
  278. Also clears temperory fields starting with `__`
  279. * if check_links is set, it validates all `Link` fields
  280. * if ignore_fields is sets, it does not throw an exception for any field that does not exist in the
  281. database table
  282. """
  283. res = webnotes.model.meta.get_dt_values(self.doctype, 'autoname, issingle, istable, name_case', as_dict=1)
  284. res = res and res[0] or {}
  285. # if required, make new
  286. if new or (not new and self.fields.get('__islocal')) and (not res.get('issingle')):
  287. r = self._makenew(res.get('autoname'), res.get('istable'), res.get('name_case'), make_autoname)
  288. if r:
  289. return r
  290. # save the values
  291. self._update_values(res.get('issingle'), check_links and self.make_link_list() or {}, ignore_fields)
  292. self._clear_temp_fields()
  293. # check permissions
  294. # ---------------------------------------------------------------------------
  295. def _get_perms(self):
  296. if not self._perms:
  297. 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)
  298. def _get_roles(self):
  299. # check if roles match/
  300. if not self._roles:
  301. if webnotes.user:
  302. self._roles = webnotes.user.get_roles()
  303. else:
  304. self._roles = ['Guest']
  305. def _get_user_defaults(self):
  306. if not self._user_defaults:
  307. self._user_defaults = webnotes.user.get_defaults()
  308. def check_perm(self, verbose=0):
  309. import webnotes
  310. # find roles with read access for this record at 0
  311. self._get_perms()
  312. self._get_roles()
  313. self._get_user_defaults()
  314. has_perm, match = 0, []
  315. # loop through everything to find if there is a match
  316. for r in self._perms:
  317. if r[0] in self._roles:
  318. has_perm = 1
  319. if r[1] and match != -1:
  320. match.append(r[1]) # add to match check
  321. else:
  322. match = -1 # has permission and no match, so match not required!
  323. if has_perm and match and match != -1:
  324. for m in match:
  325. if self.fields.get(m, 'no value') in self._user_defaults.get(m, 'no default'):
  326. has_perm = 1
  327. break # permission found! break
  328. else:
  329. has_perm = 0
  330. if verbose:
  331. webnotes.msgprint("Value not allowed: '%s' for '%s'" % (self.fields.get(m, 'no value'), m))
  332. # check for access key
  333. if webnotes.form and webnotes.form.has_key('akey'):
  334. import webnotes.utils.encrypt
  335. if webnotes.utils.encrypt.decrypt(webnotes.form.getvalue('akey')) == self.name:
  336. has_perm = 1
  337. webnotes.response['print_access'] = 1
  338. return has_perm
  339. # Cleanup
  340. # ---------------------------------------------------------------------------
  341. def _clear_temp_fields(self):
  342. # clear temp stuff
  343. keys = self.fields.keys()
  344. for f in keys:
  345. if f.startswith('__'):
  346. del self.fields[f]
  347. # Table methods
  348. # ---------------------------------------------------------------------------
  349. def clear_table(self, doclist, tablefield, save=0):
  350. """
  351. Clears the child records from the given `doclist` for a particular `tablefield`
  352. """
  353. import webnotes.model.doclist
  354. for d in webnotes.model.doclist.getlist(doclist, tablefield):
  355. d.fields['__oldparent'] = d.parent
  356. d.parent = 'old_parent:' + d.parent # for client to send it back while saving
  357. d.docstatus = 2
  358. if save and not d.fields.get('__islocal'):
  359. d.save()
  360. self.fields['__unsaved'] = 1
  361. def addchild(self, fieldname, childtype = '', local=0, doclist=None):
  362. """
  363. Returns a child record of the give `childtype`.
  364. * if local is set, it does not save the record
  365. * if doclist is passed, it append the record to the doclist
  366. """
  367. if not childtype:
  368. childtype = db_getchildtype(self.doctype, fieldname)
  369. d = Document()
  370. d.parent = self.name
  371. d.parenttype = self.doctype
  372. d.parentfield = fieldname
  373. d.doctype = childtype
  374. d.docstatus = 0;
  375. d.name = ''
  376. d.owner = webnotes.session['user']
  377. if local:
  378. d.fields['__islocal'] = '1' # for Client to identify unsaved doc
  379. else:
  380. d.save(new=1)
  381. if doclist != None:
  382. doclist.append(d)
  383. return d
  384. def addchild(parent, fieldname, childtype = '', local=0, doclist=None):
  385. """
  386. Create a child record to the parent doc.
  387. Example::
  388. c = Document('Contact','ABC')
  389. d = addchild(c, 'contact_updates', 'Contact Update', local = 1)
  390. d.last_updated = 'Phone call'
  391. d.save(1)
  392. """
  393. return parent.addchild(fieldname, childtype, local, doclist)
  394. # Remove Child
  395. # ------------
  396. def removechild(d, is_local = 0):
  397. """
  398. Sets the docstatus of the object d to 2 (deleted) and appends an 'old_parent:' to the parent name
  399. """
  400. if not is_local:
  401. set(d, 'docstatus', 2)
  402. set(d, 'parent', 'old_parent:' + d.parent)
  403. else:
  404. d.parent = 'old_parent:' + d.parent
  405. d.docstatus = 2
  406. # Naming
  407. # ------
  408. def make_autoname(key, doctype=''):
  409. """
  410. Creates an autoname from the given key:
  411. **Autoname rules:**
  412. * The key is separated by '.'
  413. * '####' represents a series. The string before this part becomes the prefix:
  414. Example: ABC.#### creates a series ABC0001, ABC0002 etc
  415. * 'MM' represents the current month
  416. * 'YY' and 'YYYY' represent the current year
  417. *Example:*
  418. * DE/./.YY./.MM./.##### will create a series like
  419. DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
  420. """
  421. n = ''
  422. l = key.split('.')
  423. for e in l:
  424. en = ''
  425. if e.startswith('#'):
  426. digits = len(e)
  427. en = getseries(n, digits, doctype)
  428. elif e=='YY':
  429. import time
  430. en = time.strftime('%y')
  431. elif e=='MM':
  432. import time
  433. en = time.strftime('%m')
  434. elif e=='YYYY':
  435. import time
  436. en = time.strftime('%Y')
  437. else: en = e
  438. n+=en
  439. return n
  440. # Get Series for Autoname
  441. # -----------------------
  442. def getseries(key, digits, doctype=''):
  443. # series created ?
  444. if webnotes.conn.sql("select name from tabSeries where name='%s'" % key):
  445. # yes, update it
  446. webnotes.conn.sql("update tabSeries set current = current+1 where name='%s'" % key)
  447. # find the series counter
  448. r = webnotes.conn.sql("select current from tabSeries where name='%s'" % key)
  449. n = r[0][0]
  450. else:
  451. # no, create it
  452. webnotes.conn.sql("insert into tabSeries (name, current) values ('%s', 1)" % key)
  453. n = 1
  454. return ('%0'+str(digits)+'d') % n
  455. # Get Children
  456. # ------------
  457. def getchildren(name, childtype, field='', parenttype='', from_doctype=0, prefix='tab'):
  458. import webnotes
  459. tmp = ''
  460. if field:
  461. tmp = ' and parentfield="%s" ' % field
  462. if parenttype:
  463. tmp = ' and parenttype="%s" ' % parenttype
  464. try:
  465. dataset = webnotes.conn.sql("select * from `%s%s` where parent='%s' %s order by idx" % (prefix, childtype, name, tmp))
  466. desc = webnotes.conn.get_description()
  467. except Exception, e:
  468. if prefix=='arc' and e.args[0]==1146:
  469. return []
  470. else:
  471. raise e
  472. l = []
  473. for i in dataset:
  474. d = Document()
  475. d.doctype = childtype
  476. d._load_values(i, desc)
  477. l.append(d)
  478. return l
  479. # Check if "Guest" is allowed to view this page
  480. # ---------------------------------------------
  481. def check_page_perm(doc):
  482. if doc.name=='Login Page':
  483. return
  484. if doc.publish:
  485. return
  486. if not webnotes.conn.sql("select name from `tabPage Role` where parent=%s and role='Guest'", doc.name):
  487. webnotes.response['exc_type'] = 'PermissionError'
  488. raise Exception, '[WNF] No read permission for %s %s' % ('Page', doc.name)
  489. def get_report_builder_code(doc):
  490. if doc.doctype=='Search Criteria':
  491. from webnotes.model.code import get_code
  492. if doc.standard != 'No':
  493. doc.report_script = get_code(doc.module, 'Search Criteria', doc.name, 'js')
  494. doc.custom_query = get_code(doc.module, 'Search Criteria', doc.name, 'sql')
  495. # called from everywhere
  496. # load a record and its child records and bundle it in a list - doclist
  497. # ---------------------------------------------------------------------
  498. def get(dt, dn='', with_children = 1, from_get_obj = 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. import webnotes.defs
  505. dn = dn or dt
  506. # load the main doc
  507. doc = Document(dt, dn, prefix=prefix)
  508. # check permission - for doctypes, pages
  509. if (dt in ('DocType', 'Page', 'Control Panel', 'Search Criteria')) or (from_get_obj and webnotes.session.get('user') != 'Guest'):
  510. if dt=='Page' and webnotes.session['user'] == 'Guest':
  511. check_page_perm(doc)
  512. else:
  513. if not doc.check_perm():
  514. webnotes.response['exc_type'] = 'PermissionError'
  515. raise webnotes.ValidationError, '[WNF] No read permission for %s %s' % (dt, dn)
  516. # import report_builder code
  517. get_report_builder_code(doc)
  518. if not with_children:
  519. # done
  520. return [doc,]
  521. # get all children types
  522. tablefields = webnotes.model.meta.get_table_fields(dt)
  523. # load chilren
  524. doclist = [doc,]
  525. for t in tablefields:
  526. doclist += getchildren(doc.name, t[0], t[1], dt, prefix=prefix)
  527. return doclist