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.
 
 
 
 
 
 

371 lines
9.5 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. """build query for doclistview and return results"""
  5. import frappe, json
  6. from six.moves import range
  7. import frappe.permissions
  8. import MySQLdb
  9. from frappe.model.db_query import DatabaseQuery
  10. from frappe import _
  11. from six import text_type, string_types
  12. @frappe.whitelist()
  13. def get():
  14. args = get_form_params()
  15. data = compress(execute(**args), args = args)
  16. return data
  17. def execute(doctype, *args, **kwargs):
  18. return DatabaseQuery(doctype).execute(*args, **kwargs)
  19. def get_form_params():
  20. """Stringify GET request parameters."""
  21. data = frappe._dict(frappe.local.form_dict)
  22. del data["cmd"]
  23. if "csrf_token" in data:
  24. del data["csrf_token"]
  25. if isinstance(data.get("filters"), string_types):
  26. data["filters"] = json.loads(data["filters"])
  27. if isinstance(data.get("fields"), string_types):
  28. data["fields"] = json.loads(data["fields"])
  29. if isinstance(data.get("docstatus"), string_types):
  30. data["docstatus"] = json.loads(data["docstatus"])
  31. if isinstance(data.get("save_user_settings"), string_types):
  32. data["save_user_settings"] = json.loads(data["save_user_settings"])
  33. else:
  34. data["save_user_settings"] = True
  35. doctype = data["doctype"]
  36. fields = data["fields"]
  37. for field in fields:
  38. key = field.split(" as ")[0]
  39. if "." in key:
  40. parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`")
  41. else:
  42. parenttype = data.doctype
  43. fieldname = fieldname.strip("`")
  44. df = frappe.get_meta(parenttype).get_field(fieldname)
  45. report_hide = df.report_hide if df else None
  46. # remove the field from the query if the report hide flag is set
  47. if report_hide:
  48. fields.remove(field)
  49. # queries must always be server side
  50. data.query = None
  51. return data
  52. def compress(data, args = {}):
  53. """separate keys and values"""
  54. from frappe.desk.query_report import add_total_row
  55. if not data: return data
  56. values = []
  57. keys = data[0].keys()
  58. for row in data:
  59. new_row = []
  60. for key in keys:
  61. new_row.append(row[key])
  62. values.append(new_row)
  63. if args.get("add_total_row"):
  64. meta = frappe.get_meta(args.doctype)
  65. values = add_total_row(values, keys, meta)
  66. return {
  67. "keys": keys,
  68. "values": values
  69. }
  70. @frappe.whitelist()
  71. def save_report():
  72. """save report"""
  73. data = frappe.local.form_dict
  74. if frappe.db.exists('Report', data['name']):
  75. d = frappe.get_doc('Report', data['name'])
  76. else:
  77. d = frappe.new_doc('Report')
  78. d.report_name = data['name']
  79. d.ref_doctype = data['doctype']
  80. d.report_type = "Report Builder"
  81. d.json = data['json']
  82. frappe.get_doc(d).save()
  83. frappe.msgprint(_("{0} is saved").format(d.name))
  84. return d.name
  85. @frappe.whitelist()
  86. def export_query():
  87. """export from report builder"""
  88. form_params = get_form_params()
  89. form_params["limit_page_length"] = None
  90. form_params["as_list"] = True
  91. doctype = form_params.doctype
  92. add_totals_row = None
  93. file_format_type = form_params["file_format_type"]
  94. del form_params["doctype"]
  95. del form_params["file_format_type"]
  96. if 'add_totals_row' in form_params and form_params['add_totals_row']=='1':
  97. add_totals_row = 1
  98. del form_params["add_totals_row"]
  99. frappe.permissions.can_export(doctype, raise_exception=True)
  100. if 'selected_items' in form_params:
  101. si = json.loads(frappe.form_dict.get('selected_items'))
  102. form_params["filters"] = {"name": ("in", si)}
  103. del form_params["selected_items"]
  104. db_query = DatabaseQuery(doctype)
  105. ret = db_query.execute(**form_params)
  106. if add_totals_row:
  107. ret = append_totals_row(ret)
  108. data = [['Sr'] + get_labels(db_query.fields, doctype)]
  109. for i, row in enumerate(ret):
  110. data.append([i+1] + list(row))
  111. if file_format_type == "CSV":
  112. # convert to csv
  113. import csv
  114. from six import StringIO
  115. f = StringIO()
  116. writer = csv.writer(f)
  117. for r in data:
  118. # encode only unicode type strings and not int, floats etc.
  119. writer.writerow(map(lambda v: isinstance(v, text_type) and v.encode('utf-8') or v, r))
  120. f.seek(0)
  121. frappe.response['result'] = text_type(f.read(), 'utf-8')
  122. frappe.response['type'] = 'csv'
  123. frappe.response['doctype'] = doctype
  124. elif file_format_type == "Excel":
  125. from frappe.utils.xlsxutils import make_xlsx
  126. xlsx_file = make_xlsx(data, doctype)
  127. frappe.response['filename'] = doctype + '.xlsx'
  128. frappe.response['filecontent'] = xlsx_file.getvalue()
  129. frappe.response['type'] = 'binary'
  130. def append_totals_row(data):
  131. if not data:
  132. return data
  133. data = list(data)
  134. totals = []
  135. totals.extend([""]*len(data[0]))
  136. for row in data:
  137. for i in range(len(row)):
  138. if isinstance(row[i], (float, int)):
  139. totals[i] = (totals[i] or 0) + row[i]
  140. data.append(totals)
  141. return data
  142. def get_labels(fields, doctype):
  143. """get column labels based on column names"""
  144. labels = []
  145. for key in fields:
  146. key = key.split(" as ")[0]
  147. if "." in key:
  148. parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`")
  149. else:
  150. parenttype = doctype
  151. fieldname = fieldname.strip("`")
  152. df = frappe.get_meta(parenttype).get_field(fieldname)
  153. label = df.label if df else fieldname.title()
  154. if label in labels:
  155. label = doctype + ": " + label
  156. labels.append(label)
  157. return labels
  158. @frappe.whitelist()
  159. def delete_items():
  160. """delete selected items"""
  161. import json
  162. il = json.loads(frappe.form_dict.get('items'))
  163. doctype = frappe.form_dict.get('doctype')
  164. for i, d in enumerate(il):
  165. try:
  166. frappe.delete_doc(doctype, d)
  167. if len(il) >= 5:
  168. frappe.publish_realtime("progress",
  169. dict(progress=[i+1, len(il)], title=_('Deleting {0}').format(doctype)),
  170. user=frappe.session.user)
  171. except Exception:
  172. pass
  173. @frappe.whitelist()
  174. def get_sidebar_stats(stats, doctype, filters=[]):
  175. cat_tags = frappe.db.sql("""select tag.parent as category, tag.tag_name as tag
  176. from `tabTag Doc Category` as docCat
  177. INNER JOIN tabTag as tag on tag.parent = docCat.parent
  178. where docCat.tagdoc=%s
  179. ORDER BY tag.parent asc,tag.idx""",doctype,as_dict=1)
  180. return {"defined_cat":cat_tags, "stats":get_stats(stats, doctype, filters)}
  181. @frappe.whitelist()
  182. def get_stats(stats, doctype, filters=[]):
  183. """get tag info"""
  184. import json
  185. tags = json.loads(stats)
  186. if filters:
  187. filters = json.loads(filters)
  188. stats = {}
  189. try:
  190. columns = frappe.db.get_table_columns(doctype)
  191. except MySQLdb.OperationalError:
  192. # raised when _user_tags column is added on the fly
  193. columns = []
  194. for tag in tags:
  195. if not tag in columns: continue
  196. try:
  197. tagcount = frappe.get_list(doctype, fields=[tag, "count(*)"],
  198. #filters=["ifnull(`%s`,'')!=''" % tag], group_by=tag, as_list=True)
  199. filters = filters + ["ifnull(`%s`,'')!=''" % tag], group_by = tag, as_list = True)
  200. if tag=='_user_tags':
  201. stats[tag] = scrub_user_tags(tagcount)
  202. stats[tag].append([_("No Tags"), frappe.get_list(doctype,
  203. fields=[tag, "count(*)"],
  204. filters=filters +["({0} = ',' or {0} is null)".format(tag)], as_list=True)[0][1]])
  205. else:
  206. stats[tag] = tagcount
  207. except frappe.SQLError:
  208. # does not work for child tables
  209. pass
  210. except MySQLdb.OperationalError:
  211. # raised when _user_tags column is added on the fly
  212. pass
  213. return stats
  214. @frappe.whitelist()
  215. def get_filter_dashboard_data(stats, doctype, filters=[]):
  216. """get tags info"""
  217. import json
  218. tags = json.loads(stats)
  219. if filters:
  220. filters = json.loads(filters)
  221. stats = {}
  222. columns = frappe.db.get_table_columns(doctype)
  223. for tag in tags:
  224. if not tag["name"] in columns: continue
  225. tagcount = []
  226. if tag["type"] not in ['Date', 'Datetime']:
  227. tagcount = frappe.get_list(doctype,
  228. fields=[tag["name"], "count(*)"],
  229. filters = filters + ["ifnull(`%s`,'')!=''" % tag["name"]],
  230. group_by = tag["name"],
  231. as_list = True)
  232. if tag["type"] not in ['Check','Select','Date','Datetime','Int',
  233. 'Float','Currency','Percent'] and tag['name'] not in ['docstatus']:
  234. stats[tag["name"]] = list(tagcount)
  235. if stats[tag["name"]]:
  236. data =["No Data", frappe.get_list(doctype,
  237. fields=[tag["name"], "count(*)"],
  238. filters=filters + ["({0} = '' or {0} is null)".format(tag["name"])],
  239. as_list=True)[0][1]]
  240. if data and data[1]!=0:
  241. stats[tag["name"]].append(data)
  242. else:
  243. stats[tag["name"]] = tagcount
  244. return stats
  245. def scrub_user_tags(tagcount):
  246. """rebuild tag list for tags"""
  247. rdict = {}
  248. tagdict = dict(tagcount)
  249. for t in tagdict:
  250. if not t:
  251. continue
  252. alltags = t.split(',')
  253. for tag in alltags:
  254. if tag:
  255. if not tag in rdict:
  256. rdict[tag] = 0
  257. rdict[tag] += tagdict[t]
  258. rlist = []
  259. for tag in rdict:
  260. rlist.append([tag, rdict[tag]])
  261. return rlist
  262. # used in building query in queries.py
  263. def get_match_cond(doctype):
  264. cond = DatabaseQuery(doctype).build_match_conditions()
  265. return ((' and ' + cond) if cond else "").replace("%", "%%")
  266. def build_match_conditions(doctype, as_condition=True):
  267. match_conditions = DatabaseQuery(doctype).build_match_conditions(as_condition=as_condition)
  268. if as_condition:
  269. return match_conditions.replace("%", "%%")
  270. else:
  271. return match_conditions
  272. def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with_match_conditions=False):
  273. if isinstance(filters, string_types):
  274. filters = json.loads(filters)
  275. if filters:
  276. flt = filters
  277. if isinstance(filters, dict):
  278. filters = filters.items()
  279. flt = []
  280. for f in filters:
  281. if isinstance(f[1], string_types) and f[1][0] == '!':
  282. flt.append([doctype, f[0], '!=', f[1][1:]])
  283. else:
  284. flt.append([doctype, f[0], '=', f[1]])
  285. query = DatabaseQuery(doctype)
  286. query.filters = flt
  287. query.conditions = conditions
  288. if with_match_conditions:
  289. query.build_match_conditions()
  290. query.build_filter_conditions(flt, conditions, ignore_permissions)
  291. cond = ' and ' + ' and '.join(query.conditions)
  292. else:
  293. cond = ''
  294. return cond