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.
 
 
 
 
 
 

293 line
7.4 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. out = frappe.response
  6. from frappe.utils import cint
  7. import frappe.defaults
  8. def get_sql_tables(q):
  9. if q.find('WHERE') != -1:
  10. tl = q.split('FROM')[1].split('WHERE')[0].split(',')
  11. elif q.find('GROUP BY') != -1:
  12. tl = q.split('FROM')[1].split('GROUP BY')[0].split(',')
  13. else:
  14. tl = q.split('FROM')[1].split('ORDER BY')[0].split(',')
  15. return [t.strip().strip('`')[3:] for t in tl]
  16. def get_parent_dt(dt):
  17. pdt = ''
  18. if frappe.db.sql('select name from `tabDocType` where istable=1 and name=%s', dt):
  19. import frappe.model.meta
  20. return frappe.model.meta.get_parent_dt(dt)
  21. return pdt
  22. def get_sql_meta(tl):
  23. std_columns = {
  24. 'owner':('Owner', '', '', '100'),
  25. 'creation':('Created on', 'Date', '', '100'),
  26. 'modified':('Last modified on', 'Date', '', '100'),
  27. 'modified_by':('Modified By', '', '', '100')
  28. }
  29. meta = {}
  30. for dt in tl:
  31. meta[dt] = std_columns.copy()
  32. # for table doctype, the ID is the parent id
  33. pdt = get_parent_dt(dt)
  34. if pdt:
  35. meta[dt]['parent'] = ('ID', 'Link', pdt, '200')
  36. # get the field properties from DocField
  37. res = frappe.db.sql("select fieldname, label, fieldtype, options, width \
  38. from tabDocField where parent=%s", dt)
  39. for r in res:
  40. if r[0]:
  41. meta[dt][r[0]] = (r[1], r[2], r[3], r[4]);
  42. # name
  43. meta[dt]['name'] = ('ID', 'Link', dt, '200')
  44. return meta
  45. def add_match_conditions(q, tl):
  46. from frappe.widgets.reportview import build_match_conditions
  47. sl = []
  48. for dt in tl:
  49. s = build_match_conditions(dt)
  50. if s:
  51. sl.append(s)
  52. # insert the conditions
  53. if sl:
  54. condition_st = q.find('WHERE')!=-1 and ' AND ' or ' WHERE '
  55. condition_end = q.find('ORDER BY')!=-1 and 'ORDER BY' or 'LIMIT'
  56. condition_end = q.find('GROUP BY')!=-1 and 'GROUP BY' or condition_end
  57. if q.find('ORDER BY')!=-1 or q.find('LIMIT')!=-1 or q.find('GROUP BY')!=-1: # if query continues beyond conditions
  58. q = q.split(condition_end)
  59. q = q[0] + condition_st + '(' + ' OR '.join(sl) + ') ' + condition_end + q[1]
  60. else:
  61. q = q + condition_st + '(' + ' OR '.join(sl) + ')'
  62. return q
  63. def exec_report(code, res, colnames=[], colwidths=[], coltypes=[], coloptions=[], filter_values={}, query='', from_export=0):
  64. col_idx, i, out, style, header_html, footer_html, page_template = {}, 0, None, [], '', '', ''
  65. for c in colnames:
  66. col_idx[c] = i
  67. i+=1
  68. # load globals (api)
  69. from frappe import *
  70. from frappe.utils import *
  71. from frappe.model.doc import *
  72. set = frappe.db.set
  73. get_value = frappe.db.get_value
  74. convert_to_lists = frappe.db.convert_to_lists
  75. NEWLINE = '\n'
  76. exec str(code)
  77. if out!=None:
  78. res = out
  79. return res, style, header_html, footer_html, page_template
  80. def guess_type(m):
  81. """
  82. Returns fieldtype depending on the MySQLdb Description
  83. """
  84. import MySQLdb
  85. if m in MySQLdb.NUMBER:
  86. return 'Currency'
  87. elif m in MySQLdb.DATE:
  88. return 'Date'
  89. else:
  90. return 'Data'
  91. def build_description_simple():
  92. colnames, coltypes, coloptions, colwidths = [], [], [], []
  93. for m in frappe.db.get_description():
  94. colnames.append(m[0])
  95. coltypes.append(guess_type[m[0]])
  96. coloptions.append('')
  97. colwidths.append('100')
  98. return colnames, coltypes, coloptions, colwidths
  99. def build_description_standard(meta, tl):
  100. desc = frappe.db.get_description()
  101. colnames, coltypes, coloptions, colwidths = [], [], [], []
  102. # merged metadata - used if we are unable to
  103. # get both the table name and field name from
  104. # the description - in case of joins
  105. merged_meta = {}
  106. for d in meta:
  107. merged_meta.update(meta[d])
  108. for f in desc:
  109. fn, dt = f[0], ''
  110. if '.' in fn:
  111. dt, fn = fn.split('.')
  112. if (not dt) and merged_meta.get(fn):
  113. # no "AS" given, find type from merged description
  114. desc = merged_meta[fn]
  115. colnames.append(desc[0] or fn)
  116. coltypes.append(desc[1] or '')
  117. coloptions.append(desc[2] or '')
  118. colwidths.append(desc[3] or '100')
  119. elif meta.get(dt,{}).has_key(fn):
  120. # type specified for a multi-table join
  121. # usually from Report Builder
  122. desc = meta[dt][fn]
  123. colnames.append(desc[0] or fn)
  124. coltypes.append(desc[1] or '')
  125. coloptions.append(desc[2] or '')
  126. colwidths.append(desc[3] or '100')
  127. else:
  128. # nothing found
  129. # guess
  130. colnames.append(fn)
  131. coltypes.append(guess_type(f[1]))
  132. coloptions.append('')
  133. colwidths.append('100')
  134. return colnames, coltypes, coloptions, colwidths
  135. @frappe.whitelist()
  136. def runquery(q='', ret=0, from_export=0):
  137. import frappe.utils
  138. formatted = cint(frappe.form_dict.get('formatted'))
  139. # CASE A: Simple Query
  140. # --------------------
  141. if frappe.form_dict.get('simple_query') or frappe.form_dict.get('is_simple'):
  142. if not q: q = frappe.form_dict.get('simple_query') or frappe.form_dict.get('query')
  143. if q.split()[0].lower() != 'select':
  144. raise Exception, 'Query must be a SELECT'
  145. as_dict = cint(frappe.form_dict.get('as_dict'))
  146. res = frappe.db.sql(q, as_dict = as_dict, as_list = not as_dict, formatted=formatted)
  147. # build colnames etc from metadata
  148. colnames, coltypes, coloptions, colwidths = [], [], [], []
  149. # CASE B: Standard Query
  150. # -----------------------
  151. else:
  152. if not q: q = frappe.form_dict.get('query')
  153. tl = get_sql_tables(q)
  154. meta = get_sql_meta(tl)
  155. q = add_match_conditions(q, tl)
  156. # replace special variables
  157. q = q.replace('__user', frappe.session.user)
  158. q = q.replace('__today', frappe.utils.nowdate())
  159. res = frappe.db.sql(q, as_list=1, formatted=formatted)
  160. colnames, coltypes, coloptions, colwidths = build_description_standard(meta, tl)
  161. # run server script
  162. # -----------------
  163. style, header_html, footer_html, page_template = '', '', '', ''
  164. out['colnames'] = colnames
  165. out['coltypes'] = coltypes
  166. out['coloptions'] = coloptions
  167. out['colwidths'] = colwidths
  168. out['header_html'] = header_html
  169. out['footer_html'] = footer_html
  170. out['page_template'] = page_template
  171. if style:
  172. out['style'] = style
  173. # just the data - return
  174. if ret==1:
  175. return res
  176. out['values'] = res
  177. # return num of entries
  178. qm = frappe.form_dict.get('query_max') or ''
  179. if qm and qm.strip():
  180. if qm.split()[0].lower() != 'select':
  181. raise Exception, 'Query (Max) must be a SELECT'
  182. if not frappe.form_dict.get('simple_query'):
  183. qm = add_match_conditions(qm, tl)
  184. out['n_values'] = frappe.utils.cint(frappe.db.sql(qm)[0][0])
  185. @frappe.whitelist()
  186. def runquery_csv():
  187. global out
  188. # run query
  189. res = runquery(from_export = 1)
  190. q = frappe.form_dict.get('query')
  191. rep_name = frappe.form_dict.get('report_name')
  192. if not frappe.form_dict.get('simple_query'):
  193. # Report Name
  194. if not rep_name:
  195. rep_name = get_sql_tables(q)[0]
  196. if not rep_name: rep_name = 'DataExport'
  197. # Headings
  198. heads = []
  199. rows = [[rep_name], out['colnames']] + out['values']
  200. from cStringIO import StringIO
  201. import csv
  202. f = StringIO()
  203. writer = csv.writer(f)
  204. for r in rows:
  205. # encode only unicode type strings and not int, floats etc.
  206. writer.writerow(map(lambda v: isinstance(v, unicode) and v.encode('utf-8') or v, r))
  207. f.seek(0)
  208. out['result'] = unicode(f.read(), 'utf-8')
  209. out['type'] = 'csv'
  210. out['doctype'] = rep_name
  211. def add_limit_to_query(query, args):
  212. """
  213. Add limit condition to query
  214. can be used by methods called in listing to add limit condition
  215. """
  216. if args.get('limit_page_length'):
  217. query += """
  218. limit %(limit_start)s, %(limit_page_length)s"""
  219. import frappe.utils
  220. args['limit_start'] = frappe.utils.cint(args.get('limit_start'))
  221. args['limit_page_length'] = frappe.utils.cint(args.get('limit_page_length'))
  222. return query, args