選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

288 行
8.8 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. """build query for doclistview and return results"""
  5. import frappe, json
  6. import frappe.defaults
  7. import frappe.permissions
  8. import frappe.model.doctype
  9. from frappe.utils import cstr, flt
  10. class DatabaseQuery(object):
  11. def __init__(self, doctype):
  12. self.doctype = doctype
  13. def execute(self, query=None, filters=None, fields=None, docstatus=None,
  14. group_by=None, order_by=None, limit_start=0, limit_page_length=20,
  15. as_list=False, with_childnames=False, debug=False):
  16. self.fields = fields or ["name"]
  17. self.filters = filters or []
  18. self.docstatus = docstatus or []
  19. self.group_by = group_by
  20. self.order_by = order_by
  21. self.limit_start = limit_start
  22. self.limit_page_length = limit_page_length
  23. self.with_childnames = with_childnames
  24. self.debug = debug
  25. self.as_list = as_list
  26. self.tables = []
  27. self.meta = []
  28. if query:
  29. return self.run_custom_query(query)
  30. else:
  31. return self.build_and_run()
  32. def build_and_run(self):
  33. args = self.prepare_args()
  34. args.limit = self.add_limit()
  35. query = """select %(fields)s from %(tables)s where %(conditions)s
  36. %(group_by)s order by %(order_by)s %(limit)s""" % args
  37. return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug)
  38. def prepare_args(self):
  39. self.parse_args()
  40. self.extract_tables()
  41. self.load_metadata()
  42. self.remove_user_tags()
  43. self.build_conditions()
  44. args = frappe._dict()
  45. if self.with_childnames:
  46. for t in self.tables:
  47. if t != "`tab" + doctype + "`":
  48. fields.append(t + ".name as '%s:name'" % t[4:-1])
  49. # query dict
  50. args.tables = ', '.join(self.tables)
  51. args.conditions = ' and '.join(self.conditions)
  52. args.fields = ', '.join(self.fields)
  53. args.order_by = self.order_by or self.tables[0] + '.modified desc'
  54. args.group_by = self.group_by and (" group by " + group_by) or ""
  55. self.check_sort_by_table(args.order_by)
  56. return args
  57. def parse_args(self):
  58. if isinstance(self.filters, basestring):
  59. self.filters = json.loads(self.filters)
  60. if isinstance(self.fields, basestring):
  61. self.filters = json.loads(self.fields)
  62. if isinstance(self.filters, dict):
  63. fdict = self.filters
  64. self.filters = []
  65. for key, value in fdict.iteritems():
  66. self.filters.append(self.make_filter_tuple(key, value))
  67. def make_filter_tuple(self, key, value):
  68. if isinstance(value, (list, tuple)):
  69. return (self.doctype, key, value[0], value[1])
  70. else:
  71. return (self.doctype, key, "=", value)
  72. def extract_tables(self):
  73. """extract tables from fields"""
  74. self.tables = ['`tab' + self.doctype + '`']
  75. # add tables from fields
  76. if self.fields:
  77. for f in self.fields:
  78. if "." not in f: continue
  79. table_name = f.split('.')[0]
  80. if table_name.lower().startswith('group_concat('):
  81. table_name = table_name[13:]
  82. if table_name.lower().startswith('ifnull('):
  83. table_name = table_name[7:]
  84. if not table_name[0]=='`':
  85. table_name = '`' + table_name + '`'
  86. if not table_name in self.tables:
  87. self.tables.append(table_name)
  88. def load_metadata(self):
  89. """load all doctypes and roles"""
  90. self.meta = {}
  91. for t in self.tables:
  92. if t.startswith('`'):
  93. doctype = t[4:-1]
  94. if self.meta.get(doctype):
  95. continue
  96. if not frappe.has_permission(doctype):
  97. raise frappe.PermissionError, doctype
  98. self.meta[doctype] = frappe.model.doctype.get(doctype)
  99. def remove_user_tags(self):
  100. """remove column _user_tags if not in table"""
  101. columns = frappe.db.get_table_columns(self.doctype)
  102. to_remove = []
  103. for fld in self.fields:
  104. for f in ("_user_tags", "_comments"):
  105. if f in fld and not f in columns:
  106. to_remove.append(fld)
  107. for fld in to_remove:
  108. del self.fields[self.fields.index(fld)]
  109. def build_conditions(self):
  110. self.conditions = []
  111. self.add_docstatus_conditions()
  112. self.build_filter_conditions()
  113. # join parent, child tables
  114. for tname in self.tables[1:]:
  115. self.conditions.append(tname + '.parent = ' + self.tables[0] + '.name')
  116. # match conditions
  117. match_conditions = self.build_match_conditions()
  118. if match_conditions:
  119. self.conditions.append(match_conditions)
  120. def add_docstatus_conditions(self):
  121. if self.docstatus:
  122. self.conditions.append(self.tables[0] + '.docstatus in (' + ','.join(docstatus) + ')')
  123. else:
  124. self.conditions.append(self.tables[0] + '.docstatus < 2')
  125. def build_filter_conditions(self):
  126. """build conditions from user filters"""
  127. doclist = {}
  128. for f in self.filters:
  129. if isinstance(f, basestring):
  130. self.conditions.append(f)
  131. else:
  132. f = self.get_filter_tuple(f)
  133. tname = ('`tab' + f[0] + '`')
  134. if not tname in self.tables:
  135. self.tables.append(tname)
  136. if not tname in self.meta:
  137. self.load_metadata()
  138. # prepare in condition
  139. if f[2] in ['in', 'not in']:
  140. opts = ["'" + t.strip().replace("'", "\\'") + "'" for t in f[3].split(',')]
  141. f[3] = "(" + ', '.join(opts) + ")"
  142. self.conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3])
  143. else:
  144. df = self.meta[f[0]].get({"doctype": "DocField", "fieldname": f[1]})
  145. if f[2] == "like" or (isinstance(f[3], basestring) and
  146. (not df or df[0].fieldtype not in ["Float", "Int", "Currency", "Percent"])):
  147. value, default_val = ("'" + f[3].replace("'", "\\'") + "'"), '""'
  148. else:
  149. value, default_val = flt(f[3]), 0
  150. self.conditions.append('ifnull({tname}.{fname}, {default_val}) {operator} {value}'.format(
  151. tname=tname, fname=f[1], default_val=default_val, operator=f[2],
  152. value=value))
  153. def get_filter_tuple(self, f):
  154. if isinstance(f, dict):
  155. key, value = f.items()[0]
  156. f = self.make_filter_tuple(key, value)
  157. if not isinstance(f, (list, tuple)):
  158. frappe.throw("Filter must be a tuple or list (in a list)")
  159. if len(f) != 4:
  160. frappe.throw("Filter must have 4 values (doctype, fieldname, condition, value): " + str(f))
  161. return f
  162. def build_match_conditions(self, as_condition=True):
  163. """add match conditions if applicable"""
  164. self.match_filters = {}
  165. self.match_conditions = []
  166. self.or_conditions = []
  167. if not self.tables: self.extract_tables()
  168. if not self.meta: self.load_metadata()
  169. # explict permissions
  170. restricted_by_user = frappe.permissions.get_user_perms(self.meta[self.doctype]).restricted
  171. # get restrictions
  172. restrictions = frappe.defaults.get_restrictions()
  173. if restricted_by_user:
  174. self.or_conditions.append('`tab{doctype}`.`owner`="{user}"'.format(doctype=self.doctype,
  175. user=frappe.local.session.user))
  176. self.match_filters["owner"] = frappe.session.user
  177. if restrictions:
  178. self.add_restrictions(restrictions)
  179. if as_condition:
  180. return self.build_match_condition_string()
  181. else:
  182. return self.match_filters
  183. def add_restrictions(self, restrictions):
  184. fields_to_check = self.meta[self.doctype].get_restricted_fields(restrictions.keys())
  185. if self.doctype in restrictions:
  186. fields_to_check.append(frappe._dict({"fieldname":"name", "options":self.doctype}))
  187. # check in links
  188. for df in fields_to_check:
  189. self.match_conditions.append('`tab{doctype}`.{fieldname} in ({values})'.format(doctype=self.doctype,
  190. fieldname=df.fieldname,
  191. values=", ".join([('"'+v.replace('"', '\"')+'"') \
  192. for v in restrictions[df.options]])))
  193. self.match_filters.setdefault(df.fieldname, [])
  194. self.match_filters[df.fieldname]= restrictions[df.options]
  195. def build_match_condition_string(self):
  196. conditions = " and ".join(self.match_conditions)
  197. doctype_conditions = self.get_permission_query_conditions()
  198. if doctype_conditions:
  199. conditions += ' and ' + doctype_conditions if conditions else doctype_conditions
  200. if self.or_conditions:
  201. if conditions:
  202. conditions = '({conditions}) or {or_conditions}'.format(conditions=conditions,
  203. or_conditions = ' or '.join(self.or_conditions))
  204. else:
  205. conditions = " or ".join(self.or_conditions)
  206. return conditions
  207. def get_permission_query_conditions(self):
  208. condition_methods = frappe.get_hooks("permission_query_conditions:" + self.doctype)
  209. if condition_methods:
  210. conditions = []
  211. for method in condition_methods:
  212. c = frappe.get_attr(method)()
  213. if c:
  214. conditions.append(c)
  215. return " and ".join(conditions) if conditions else None
  216. def run_custom_query(self, query):
  217. if '%(key)s' in query:
  218. query = query.replace('%(key)s', 'name')
  219. return frappe.db.sql(query, as_dict = (not self.as_list))
  220. def check_sort_by_table(self, order_by):
  221. if "." in order_by:
  222. tbl = order_by.split('.')[0]
  223. if tbl not in self.tables:
  224. if tbl.startswith('`'):
  225. tbl = tbl[4:-1]
  226. frappe.throw("Please select atleast 1 column from '%s' to sort" % tbl)
  227. def add_limit(self):
  228. if self.limit_page_length:
  229. return 'limit %s, %s' % (self.limit_start, self.limit_page_length)
  230. else:
  231. return ''