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.

doctype.py 13 KiB

13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. # TODO:
  23. # Patch: Remove DocFormat
  24. # imports
  25. from __future__ import unicode_literals
  26. import webnotes
  27. import webnotes.model
  28. import webnotes.model.doc
  29. from webnotes.utils.cache import CacheItem
  30. class _DocType:
  31. """
  32. The _DocType object is created internally using the module's `get` method.
  33. """
  34. def __init__(self, name):
  35. self.name = name
  36. def make_doclist(self, form=1, force=False):
  37. """
  38. """
  39. # do not load from cache if auto cache clear is enabled
  40. import conf
  41. if hasattr(conf, 'auto_cache_clear'):
  42. force = not conf.auto_cache_clear
  43. if form and not force:
  44. cached_doclist = self.load_from_cache()
  45. if cached_doclist: return cached_doclist
  46. # Get parent doc and its fields
  47. doclist = webnotes.model.doc.get('DocType', self.name, 1)
  48. doclist += self.get_custom_fields(self.name)
  49. if form:
  50. table_fields = [t[0] for t in self.get_table_fields(doclist)]
  51. # for each unique table
  52. for t in list(set(table_fields)):
  53. # Get child doc and its fields
  54. table_doclist = webnotes.model.doc.get('DocType', t, 1)
  55. table_doclist += self.get_custom_fields(t)
  56. doclist += table_doclist
  57. self.apply_property_setters(doclist)
  58. if form:
  59. self.load_select_options(doclist)
  60. self.add_code(doclist[0])
  61. self.load_print_formats(doclist)
  62. self.insert_into_cache(doclist)
  63. return doclist
  64. def get_custom_fields(self, doc_type):
  65. """
  66. Gets a list of custom field docs masked as type DocField
  67. """
  68. from webnotes.model.doclist import DocList
  69. custom_doclist = DocList()
  70. res = webnotes.conn.sql("""SELECT * FROM `tabCustom Field`
  71. WHERE dt = %s AND docstatus < 2""", doc_type, as_dict=1)
  72. for r in res:
  73. # Cheat! Mask Custom Field as DocField
  74. custom_field = webnotes.model.doc.Document(fielddata=r)
  75. self.mask_custom_field(custom_field, doc_type)
  76. custom_doclist.append(custom_field)
  77. return custom_doclist
  78. def mask_custom_field(self, custom_field, doc_type):
  79. """
  80. Masks doctype and parent related properties of Custom Field as that
  81. of DocField
  82. """
  83. custom_field.fields.update({
  84. 'doctype': 'DocField',
  85. 'parent': doc_type,
  86. 'parentfield': 'fields',
  87. 'parenttype': 'DocType',
  88. })
  89. def get_table_fields(self, doclist):
  90. """
  91. Returns [[options, fieldname]] of fields of type 'Table'
  92. """
  93. table_fields = []
  94. for d in doclist:
  95. if d.doctype=='DocField' and d.fieldtype == 'Table':
  96. table_fields.append([d.options, d.fieldname])
  97. return table_fields
  98. def apply_property_setters(self, doclist):
  99. """
  100. """
  101. property_dict, doc_type_list = self.get_property_setters(doclist)
  102. for d in doclist:
  103. self.update_field_properties(d, property_dict)
  104. self.apply_previous_field_properties(doclist, property_dict,
  105. doc_type_list)
  106. def get_property_setters(self, doclist):
  107. """
  108. Returns a dict of property setter lists and doc_type_list
  109. """
  110. from webnotes.utils import cstr
  111. property_dict = {}
  112. doc_type_list = list(set(
  113. d.doctype=='DocType' and d.name or d.parent
  114. for d in doclist))
  115. in_string = '", "'.join(doc_type_list)
  116. for ps in webnotes.conn.sql("""\
  117. SELECT doc_type, field_name, property, property_type, value
  118. FROM `tabProperty Setter`
  119. WHERE doc_type IN ("%s")""" % in_string, as_dict=1):
  120. property_dict.setdefault(ps.get('doc_type'),
  121. {}).setdefault(cstr(ps.get('field_name')), []).append(ps)
  122. return property_dict, doc_type_list
  123. def update_field_properties(self, d, property_dict):
  124. """
  125. apply properties except previous_field ones
  126. """
  127. from webnotes.utils import cstr
  128. # get property setters for a given doctype's fields
  129. doctype_property_dict = (d.doctype=='DocField' and property_dict.get(d.parent) or
  130. property_dict.get(d.name))
  131. if not (doctype_property_dict and doctype_property_dict.get(cstr(d.fieldname))): return
  132. from webnotes.utils import cint
  133. prop_updates = []
  134. for prop in doctype_property_dict.get(cstr(d.fieldname)):
  135. if prop.get('property')=='previous_field': continue
  136. if prop.get('property_type') == 'Check' or \
  137. prop.get('value') in ['0', '1']:
  138. prop_updates.append([prop.get('property'), cint(prop.get('value'))])
  139. else:
  140. prop_updates.append([prop.get('property'), prop.get('value')])
  141. prop_updates and d.fields.update(dict(prop_updates))
  142. def apply_previous_field_properties(self, doclist, property_dict,
  143. doc_type_list):
  144. """
  145. """
  146. prev_field_dict = self.get_previous_field_properties(property_dict)
  147. if not prev_field_dict: return
  148. for doc_type in doc_type_list:
  149. docfields = self.get_sorted_docfields(doclist, doc_type)
  150. docfields = self.sort_docfields(doc_type, docfields, prev_field_dict)
  151. if docfields: self.change_idx(doclist, docfields, doc_type)
  152. def get_previous_field_properties(self, property_dict):
  153. """
  154. setup prev_field_dict
  155. """
  156. from webnotes.utils import cstr
  157. doctype_prev_field_list = []
  158. for doc_type in property_dict:
  159. prev_field_list = []
  160. for prop_list in property_dict.get(doc_type).values():
  161. for prop in prop_list:
  162. if prop.get('property') == 'previous_field':
  163. prev_field_list.append([prop.get('value'),
  164. prop.get('field_name')])
  165. break
  166. if not prev_field_list: continue
  167. doctype_prev_field_list.append([doc_type, dict(prev_field_list)])
  168. if not doctype_prev_field_list: return
  169. return dict(doctype_prev_field_list)
  170. def get_sorted_docfields(self, doclist, doc_type):
  171. """
  172. get a sorted list of docfield names
  173. """
  174. sorted_list = sorted([
  175. d for d in doclist
  176. if d.doctype == 'DocField'
  177. and d.parent == doc_type
  178. ], key=lambda df: df.idx)
  179. return [d.fieldname for d in sorted_list]
  180. def sort_docfields(self, doc_type, docfields, prev_field_dict):
  181. """
  182. """
  183. temp_dict = prev_field_dict.get(doc_type)
  184. if not temp_dict: return
  185. prev_field = 'None' in temp_dict and 'None' or docfields[0]
  186. i = 0
  187. while temp_dict:
  188. get_next_docfield = True
  189. cur_field = temp_dict.get(prev_field)
  190. if cur_field and cur_field in docfields:
  191. try:
  192. del temp_dict[prev_field]
  193. if prev_field in docfields:
  194. docfields.remove(cur_field)
  195. docfields.insert(docfields.index(prev_field) + 1,
  196. cur_field)
  197. elif prev_field == 'None':
  198. docfields.remove(cur_field)
  199. docfields.insert(0, cur_field)
  200. except ValueError:
  201. pass
  202. if cur_field in temp_dict:
  203. prev_field = cur_field
  204. get_next_docfield = False
  205. if get_next_docfield:
  206. i += 1
  207. if i>=len(docfields): break
  208. prev_field = docfields[i]
  209. keys, vals = temp_dict.keys(), temp_dict.values()
  210. if prev_field in vals:
  211. i -= 1
  212. prev_field = keys[vals.index(prev_field)]
  213. return docfields
  214. def change_idx(self, doclist, docfields, doc_type):
  215. for d in doclist:
  216. if d.fieldname and d.fieldname in docfields and d.parent == doc_type:
  217. d.idx = docfields.index(d.fieldname) + 1
  218. def add_code(self, doc):
  219. """add js, css code"""
  220. import os
  221. from webnotes.modules import scrub, get_module_path
  222. import conf
  223. module_path = get_module_path(doc.module)
  224. path = os.path.join(module_path, 'doctype', scrub(doc.name))
  225. def _add_code(fname, fieldname):
  226. fpath = os.path.join(path, fname)
  227. if os.path.exists(fpath):
  228. with open(fpath, 'r') as f:
  229. doc.fields[fieldname] = f.read()
  230. _add_code(scrub(doc.name) + '.js', '__js')
  231. _add_code(scrub(doc.name) + '.css', '__css')
  232. _add_code('%s_list.js' % scrub(doc.name), '__listjs')
  233. _add_code('help.md', 'description')
  234. # custom script
  235. from webnotes.model.code import get_custom_script
  236. custom = get_custom_script(doc.name, 'Client') or ''
  237. doc.fields['__js'] = doc.fields.setdefault('__js', '') + '\n' + custom
  238. # embed all require files
  239. import re
  240. def _sub(match):
  241. fpath = os.path.join(os.path.dirname(conf.__file__),
  242. re.search('["\'][^"\']*["\']', match.group(0)).group(0)[1:-1])
  243. if os.path.exists(fpath):
  244. with open(fpath, 'r') as f:
  245. return '\n' + f.read() + '\n'
  246. else:
  247. return '\n// no file "%s" found \n' % fpath
  248. if doc.fields.get('__js'):
  249. doc.fields['__js'] = re.sub('(wn.require\([^\)]*.)', _sub, doc.fields['__js'])
  250. def load_select_options(self, doclist):
  251. """
  252. Loads Select options for 'Select' fields
  253. with link: as start of options
  254. """
  255. for d in doclist:
  256. if (d.doctype == 'DocField' and d.fieldtype == 'Select' and
  257. d.options and d.options[:5].lower() == 'link:'):
  258. # Get various options
  259. opt_list = self._get_select_options(d)
  260. opt_list = [''] + [o[0] or '' for o in opt_list]
  261. d.options = "\n".join(opt_list)
  262. def _get_select_options(self, d):
  263. """
  264. Queries and returns select options
  265. (called by load_select_options)
  266. """
  267. op = d.options.split('\n')
  268. if len(op) > 1 and op[1][:4].lower() == 'sql:':
  269. # Execute the sql query
  270. query = op[1][4:].replace('__user',
  271. webnotes.session.get('user'))
  272. else:
  273. # Extract DocType and Conditions
  274. # and execute the resulting query
  275. dt = op[0][5:].strip()
  276. cond_list = [cond.replace('__user',
  277. webnotes.session.get('user')) for cond in op[1:]]
  278. query = """\
  279. SELECT name FROM `tab%s`
  280. WHERE %s docstatus!=2
  281. ORDER BY name ASC""" % (dt,
  282. cond_list and (" AND ".join(cond_list) + " AND ") or "")
  283. try:
  284. opt_list = webnotes.conn.sql(query)
  285. except:
  286. # WARNING: Exception suppressed
  287. opt_list = []
  288. return opt_list
  289. def load_print_formats(self, doclist):
  290. """
  291. Load Print Formats in doclist
  292. """
  293. # TODO: Process Print Formats for $import
  294. # to deprecate code in print_format.py
  295. # if this is implemented, clear CacheItem on saving print format
  296. print_formats = webnotes.conn.sql("""\
  297. SELECT * FROM `tabPrint Format`
  298. WHERE doc_type=%s AND docstatus<2""", doclist[0].fields.get('name'),
  299. as_dict=1)
  300. for pf in print_formats:
  301. if not pf: continue
  302. print_format_doc = webnotes.model.doc.Document('Print Format', fielddata=pf)
  303. doclist.append(print_format_doc)
  304. def load_from_cache(self):
  305. import json
  306. json_doclist = CacheItem(self.name).get()
  307. if json_doclist:
  308. return [webnotes.model.doc.Document(fielddata=d)
  309. for d in json.loads(json_doclist)]
  310. def insert_into_cache(self, doclist):
  311. import json
  312. json_doclist = json.dumps([d.fields for d in doclist])
  313. CacheItem(self.name).set(json_doclist)
  314. def get(dt, form=1, force=False):
  315. """
  316. Load "DocType" - called by form builder, report buider and from code.py (when there is no cache)
  317. """
  318. if not dt: return []
  319. doclist = _DocType(dt).make_doclist(form, force)
  320. return doclist
  321. # Deprecate after import_docs rewrite
  322. def get_field_property(dt, fieldname, property):
  323. """
  324. get a field property, override it from property setter if specified
  325. """
  326. field = webnotes.conn.sql("""
  327. select name, `%s`
  328. from tabDocField
  329. where parent=%s and fieldname=%s""" % (property, '%s', '%s'), (dt, fieldname))
  330. prop = webnotes.conn.sql("""
  331. select value
  332. from `tabProperty Setter`
  333. where doc_type=%s and field_name=%s and property=%s""", (dt, fieldname, property))
  334. if prop:
  335. return prop[0][0]
  336. else:
  337. return field[0][1]
  338. def get_property(dt, property, fieldname=None):
  339. """
  340. get a doctype property, override it from property setter if specified
  341. """
  342. if fieldname:
  343. prop = webnotes.conn.sql("""
  344. select value
  345. from `tabProperty Setter`
  346. where doc_type=%s and field_name=%s
  347. and property=%s""", (dt, fieldname, property))
  348. if prop:
  349. return prop[0][0]
  350. else:
  351. val = webnotes.conn.sql("""\
  352. SELECT %s FROM `tabDocField`
  353. WHERE parent = %s AND fieldname = %s""" % \
  354. (property, '%s', '%s'), (dt, fieldname))
  355. if val and val[0][0]: return val[0][0] or ''
  356. else:
  357. prop = webnotes.conn.sql("""
  358. select value
  359. from `tabProperty Setter`
  360. where doc_type=%s and doctype_or_field='DocType'
  361. and property=%s""", (dt, property))
  362. if prop:
  363. return prop[0][0]
  364. else:
  365. return webnotes.conn.get_value('DocType', dt, property)
  366. # Test Cases
  367. import unittest
  368. class DocTypeTest(unittest.TestCase):
  369. def setUp(self):
  370. self.name = 'Sales Order'
  371. self.dt = _DocType(self.name)
  372. def tearDown(self):
  373. webnotes.conn.rollback()
  374. def test_make_doclist(self):
  375. doclist = self.dt.make_doclist()
  376. for d in doclist:
  377. print d.idx, d.doctype, d.name, d.parent
  378. if not d.doctype: print d.fields
  379. #print "--", d.name, "--"
  380. #print d.doctype
  381. self.assertTrue(doclist)
  382. def test_get_custom_fields(self):
  383. return
  384. doclist = self.dt.get_custom_fields(self.name)
  385. for d in doclist:
  386. print "--", d.name, "--"
  387. print d.fields
  388. self.assertTrue(doclist)