Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

doc.py 20 KiB

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