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 14 KiB

12 years ago
13 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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. """
  23. Get metadata (main doctype with fields and permissions with all table doctypes)
  24. - if exists in cache, get it from cache
  25. - add custom fields
  26. - override properties from PropertySetter
  27. - sort based on prev_field
  28. - optionally, post process (add js, css, select fields), or without
  29. """
  30. from __future__ import unicode_literals
  31. # imports
  32. import webnotes
  33. import webnotes.model
  34. import webnotes.model.doc
  35. import webnotes.model.doclist
  36. from webnotes.utils import cint
  37. doctype_cache = {}
  38. docfield_types = None
  39. def get(doctype, processed=False, cached=True):
  40. """return doclist"""
  41. if cached:
  42. doclist = from_cache(doctype, processed)
  43. if doclist:
  44. if processed:
  45. add_linked_with(doclist)
  46. update_language(doclist)
  47. return DocTypeDocList(doclist)
  48. load_docfield_types()
  49. # main doctype doclist
  50. doclist = get_doctype_doclist(doctype)
  51. # add doctypes of table fields
  52. table_types = [d.options for d in doclist \
  53. if d.doctype=='DocField' and d.fieldtype=='Table']
  54. for table_doctype in table_types:
  55. doclist += get_doctype_doclist(table_doctype)
  56. if processed:
  57. add_code(doctype, doclist)
  58. expand_selects(doclist)
  59. add_print_formats(doclist)
  60. add_search_fields(doclist)
  61. add_workflows(doclist)
  62. # add validators
  63. #add_validators(doctype, doclist)
  64. # add precision
  65. add_precision(doctype, doclist)
  66. to_cache(doctype, processed, doclist)
  67. if processed:
  68. add_linked_with(doclist)
  69. update_language(doclist)
  70. return DocTypeDocList(doclist)
  71. def load_docfield_types():
  72. global docfield_types
  73. docfield_types = dict(webnotes.conn.sql("""select fieldname, fieldtype from tabDocField
  74. where parent='DocField'"""))
  75. def add_workflows(doclist):
  76. from webnotes.model.workflow import get_workflow_name
  77. doctype = doclist[0].name
  78. # get active workflow
  79. workflow_name = get_workflow_name(doctype)
  80. if workflow_name and webnotes.conn.exists("Workflow", workflow_name):
  81. doclist += webnotes.get_doclist("Workflow", workflow_name)
  82. # add workflow states (for icons and style)
  83. for state in map(lambda d: d.state, doclist.get({"doctype":"Workflow Document State"})):
  84. doclist += webnotes.get_doclist("Workflow State", state)
  85. def get_doctype_doclist(doctype):
  86. """get doclist of single doctype"""
  87. doclist = webnotes.get_doclist('DocType', doctype)
  88. add_custom_fields(doctype, doclist)
  89. apply_property_setters(doctype, doclist)
  90. sort_fields(doclist)
  91. return doclist
  92. def sort_fields(doclist):
  93. """sort on basis of previous_field"""
  94. from webnotes.model.doclist import DocList
  95. newlist = DocList([])
  96. pending = filter(lambda d: d.doctype=='DocField', doclist)
  97. maxloops = 20
  98. while (pending and maxloops>0):
  99. maxloops -= 1
  100. for d in pending[:]:
  101. if d.previous_field:
  102. # field already added
  103. for n in newlist:
  104. if n.fieldname==d.previous_field:
  105. newlist.insert(newlist.index(n)+1, d)
  106. pending.remove(d)
  107. break
  108. else:
  109. newlist.append(d)
  110. pending.remove(d)
  111. # recurring at end
  112. if pending:
  113. newlist += pending
  114. # renum
  115. idx = 1
  116. for d in newlist:
  117. d.idx = idx
  118. idx += 1
  119. doclist.get({"doctype":["!=", "DocField"]}).extend(newlist)
  120. def apply_property_setters(doctype, doclist):
  121. from webnotes.utils import cint
  122. for ps in webnotes.conn.sql("""select * from `tabProperty Setter` where
  123. doc_type=%s""", doctype, as_dict=1):
  124. if ps['doctype_or_field']=='DocType':
  125. if ps.get('property_type', None) in ('Int', 'Check'):
  126. ps['value'] = cint(ps['value'])
  127. doclist[0].fields[ps['property']] = ps['value']
  128. else:
  129. docfield = filter(lambda d: d.doctype=="DocField" and d.fieldname==ps['field_name'],
  130. doclist)
  131. if not docfield: continue
  132. if docfield_types.get(ps['property'], None) in ('Int', 'Check'):
  133. ps['value'] = cint(ps['value'])
  134. docfield[0].fields[ps['property']] = ps['value']
  135. def add_custom_fields(doctype, doclist):
  136. try:
  137. res = webnotes.conn.sql("""SELECT * FROM `tabCustom Field`
  138. WHERE dt = %s AND docstatus < 2""", doctype, as_dict=1)
  139. except Exception, e:
  140. if e.args[0]==1146:
  141. return doclist
  142. else:
  143. raise e
  144. for r in res:
  145. custom_field = webnotes.model.doc.Document(fielddata=r)
  146. # convert to DocField
  147. custom_field.fields.update({
  148. 'doctype': 'DocField',
  149. 'parent': doctype,
  150. 'parentfield': 'fields',
  151. 'parenttype': 'DocType',
  152. })
  153. doclist.append(custom_field)
  154. return doclist
  155. def add_linked_with(doclist):
  156. """add list of doctypes this doctype is 'linked' with"""
  157. doctype = doclist[0].name
  158. links = webnotes.conn.sql("""select parent, fieldname from tabDocField
  159. where (fieldtype="Link" and options=%s)
  160. or (fieldtype="Select" and options=%s)""", (doctype, "link:"+ doctype))
  161. links += webnotes.conn.sql("""select dt, fieldname from `tabCustom Field`
  162. where (fieldtype="Link" and options=%s)
  163. or (fieldtype="Select" and options=%s)""", (doctype, "link:"+ doctype))
  164. doclist[0].fields["__linked_with"] = dict(list(set(links)))
  165. def from_cache(doctype, processed):
  166. """ load doclist from cache.
  167. sets flag __from_cache in first doc of doclist if loaded from cache"""
  168. global doctype_cache
  169. # from memory
  170. if not processed and doctype in doctype_cache:
  171. return doctype_cache[doctype]
  172. doclist = webnotes.cache().get_value(cache_name(doctype, processed))
  173. if doclist:
  174. from webnotes.model.doclist import DocList
  175. doclist = DocList([webnotes.model.doc.Document(fielddata=d)
  176. for d in doclist])
  177. doclist[0].fields["__from_cache"] = 1
  178. return doclist
  179. def to_cache(doctype, processed, doclist):
  180. global doctype_cache
  181. webnotes.cache().set_value(cache_name(doctype, processed),
  182. [d.fields for d in doclist])
  183. if not processed:
  184. doctype_cache[doctype] = doclist
  185. def cache_name(doctype, processed):
  186. """returns cache key"""
  187. suffix = ""
  188. if processed:
  189. suffix = ":Raw"
  190. return "doctype:" + doctype + suffix
  191. def clear_cache(doctype=None):
  192. global doctype_cache
  193. def clear_single(dt):
  194. webnotes.cache().delete_value(cache_name(dt, False))
  195. webnotes.cache().delete_value(cache_name(dt, True))
  196. if doctype in doctype_cache:
  197. del doctype_cache[dt]
  198. if doctype:
  199. clear_single(doctype)
  200. # clear all parent doctypes
  201. for dt in webnotes.conn.sql("""select parent from tabDocField
  202. where fieldtype="Table" and options=%s""", doctype):
  203. clear_single(dt[0])
  204. else:
  205. # clear all
  206. for dt in webnotes.conn.sql("""select name from tabDocType"""):
  207. clear_single(dt[0])
  208. def add_code(doctype, doclist):
  209. import os
  210. from webnotes.modules import scrub, get_module_path
  211. doc = doclist[0]
  212. path = os.path.join(get_module_path(doc.module), 'doctype', scrub(doc.name))
  213. def _add_code(fname, fieldname):
  214. fpath = os.path.join(path, fname)
  215. if os.path.exists(fpath):
  216. with open(fpath, 'r') as f:
  217. doc.fields[fieldname] = f.read()
  218. _add_code(scrub(doc.name) + '.js', '__js')
  219. _add_code(scrub(doc.name) + '.css', '__css')
  220. _add_code('%s_list.js' % scrub(doc.name), '__list_js')
  221. _add_code('%s_calendar.js' % scrub(doc.name), '__calendar_js')
  222. _add_code('%s_map.js' % scrub(doc.name), '__map_js')
  223. add_embedded_js(doc)
  224. def add_embedded_js(doc):
  225. """embed all require files"""
  226. import re, os, conf
  227. # custom script
  228. custom = webnotes.conn.get_value("Custom Script", {"dt": doc.name,
  229. "script_type": "Client"}, "script") or ""
  230. doc.fields['__js'] = (doc.fields.get('__js') or '') + '\n' + custom
  231. def _sub(match):
  232. fpath = os.path.join(os.path.dirname(conf.__file__), \
  233. re.search('["\'][^"\']*["\']', match.group(0)).group(0)[1:-1])
  234. if os.path.exists(fpath):
  235. with open(fpath, 'r') as f:
  236. return '\n' + f.read() + '\n'
  237. else:
  238. return '\n// no file "%s" found \n' % fpath
  239. if doc.fields.get('__js'):
  240. doc.fields['__js'] = re.sub('(wn.require\([^\)]*.)', _sub, doc.fields['__js'])
  241. def expand_selects(doclist):
  242. for d in filter(lambda d: d.fieldtype=='Select' \
  243. and (d.options or '').startswith('link:'), doclist):
  244. doctype = d.options.split("\n")[0][5:]
  245. d.link_doctype = doctype
  246. d.options = '\n'.join([''] + [o.name for o in webnotes.conn.sql("""select
  247. name from `tab%s` where docstatus<2 order by name asc""" % doctype, as_dict=1)])
  248. def add_print_formats(doclist):
  249. print_formats = webnotes.conn.sql("""select * FROM `tabPrint Format`
  250. WHERE doc_type=%s AND docstatus<2""", doclist[0].name, as_dict=1)
  251. for pf in print_formats:
  252. doclist.append(webnotes.model.doc.Document('Print Format', fielddata=pf))
  253. def get_property(dt, prop, fieldname=None):
  254. """get a doctype property"""
  255. doctypelist = get(dt)
  256. if fieldname:
  257. field = doctypelist.get_field(fieldname)
  258. return field and field.fields.get(prop) or None
  259. else:
  260. return doctypelist[0].fields.get(prop)
  261. def get_link_fields(doctype):
  262. """get docfields of links and selects with "link:" """
  263. doctypelist = get(doctype)
  264. return doctypelist.get({"fieldtype":"Link"}).extend(doctypelist.get({"fieldtype":"Select",
  265. "options": "^link:"}))
  266. def add_validators(doctype, doclist):
  267. for validator in webnotes.conn.sql("""select name from `tabDocType Validator` where
  268. for_doctype=%s""", doctype, as_dict=1):
  269. doclist.extend(webnotes.get_doclist('DocType Validator', validator.name))
  270. def add_search_fields(doclist):
  271. """add search fields found in the doctypes indicated by link fields' options"""
  272. for lf in doclist.get({"fieldtype": "Link", "options":["!=", "[Select]"]}):
  273. if lf.options:
  274. search_fields = get(lf.options)[0].search_fields
  275. if search_fields:
  276. lf.search_fields = map(lambda sf: sf.strip(), search_fields.split(","))
  277. def update_language(doclist):
  278. """update language"""
  279. if webnotes.lang != 'en':
  280. from webnotes.translate import messages
  281. from webnotes.modules import get_doc_path
  282. # load languages for each doctype
  283. from webnotes.translate import get_lang_data
  284. _messages = {}
  285. for d in doclist:
  286. if d.doctype=='DocType':
  287. _messages.update(get_lang_data(get_doc_path(d.module, d.doctype, d.name),
  288. webnotes.lang, 'doc'))
  289. _messages.update(get_lang_data(get_doc_path(d.module, d.doctype, d.name),
  290. webnotes.lang, 'js'))
  291. doc = doclist[0]
  292. # attach translations to client
  293. doc.fields["__messages"] = _messages
  294. if not webnotes.lang in messages:
  295. messages[webnotes.lang] = webnotes._dict({})
  296. messages[webnotes.lang].update(_messages)
  297. def add_precision(doctype, doclist):
  298. type_precision_map = {
  299. "Currency": 2,
  300. "Float": cint(webnotes.conn.get_default("float_precision")) or 6
  301. }
  302. for df in doclist.get({"doctype": "DocField",
  303. "fieldtype": ["in", type_precision_map.keys()]}):
  304. df.precision = type_precision_map[df.fieldtype]
  305. class DocTypeDocList(webnotes.model.doclist.DocList):
  306. def get_field(self, fieldname, parent=None, parentfield=None):
  307. filters = {"doctype":"DocField"}
  308. if isinstance(fieldname, dict):
  309. filters.update(fieldname)
  310. else:
  311. filters["fieldname"] = fieldname
  312. # if parentfield, get the name of the parent table
  313. if parentfield:
  314. parent = self.get_options(parentfield)
  315. if parent:
  316. filters["parent"] = parent
  317. else:
  318. filters["parent"] = self[0].name
  319. fields = self.get(filters)
  320. if fields:
  321. return fields[0]
  322. def get_fieldnames(self, filters=None):
  323. if not filters: filters = {}
  324. filters.update({"doctype": "DocField", "parent": self[0].name})
  325. return map(lambda df: df.fieldname, self.get(filters))
  326. def get_options(self, fieldname, parent=None, parentfield=None):
  327. return self.get_field(fieldname, parent, parentfield).options
  328. def get_label(self, fieldname, parent=None, parentfield=None):
  329. return self.get_field(fieldname, parent, parentfield).label
  330. def get_table_fields(self):
  331. return self.get({"doctype": "DocField", "fieldtype": "Table"})
  332. def get_precision_map(self, parent=None, parentfield=None):
  333. """get a map of fields of type 'currency' or 'float' with precision values"""
  334. filters = {"doctype": "DocField", "fieldtype": ["in", ["Currency", "Float"]]}
  335. if parentfield:
  336. parent = self.get_options(parentfield)
  337. if parent:
  338. filters["parent"] = parent
  339. else:
  340. filters["parent"] = self[0].name
  341. from webnotes import _dict
  342. return _dict((f.fieldname, f.precision) for f in self.get(filters))
  343. def get_parent_doclist(self):
  344. return webnotes.doclist([self[0]] + self.get({"parent": self[0].name}))
  345. def rename_field(doctype, old_fieldname, new_fieldname, lookup_field=None):
  346. """this function assumes that sync is NOT performed"""
  347. import webnotes.model
  348. doctype_list = get(doctype)
  349. old_field = doctype_list.get_field(lookup_field or old_fieldname)
  350. if not old_field:
  351. print "rename_field: " + (lookup_field or old_fieldname) + " not found."
  352. if old_field.fieldtype == "Table":
  353. # change parentfield of table mentioned in options
  354. webnotes.conn.sql("""update `tab%s` set parentfield=%s
  355. where parentfield=%s""" % (old_field.options.split("\n")[0], "%s", "%s"),
  356. (new_fieldname, old_fieldname))
  357. elif old_field.fieldtype not in webnotes.model.no_value_fields:
  358. # copy
  359. if doctype_list[0].issingle:
  360. webnotes.conn.sql("""update `tabSingles` set field=%s
  361. where doctype=%s and field=%s""",
  362. (new_fieldname, doctype, old_fieldname))
  363. else:
  364. webnotes.conn.sql("""update `tab%s` set `%s`=`%s`""" % \
  365. (doctype, new_fieldname, old_fieldname))