您最多选择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. from frappe.utils import flt
  9. from frappe import _
  10. class DatabaseQuery(object):
  11. def __init__(self, doctype):
  12. self.doctype = doctype
  13. self.tables = []
  14. self.conditions = []
  15. self.ignore_permissions = False
  16. self.fields = ["name"]
  17. def execute(self, query=None, filters=None, fields=None, docstatus=None,
  18. group_by=None, order_by=None, limit_start=0, limit_page_length=20,
  19. as_list=False, with_childnames=False, debug=False, ignore_permissions=False):
  20. if not frappe.has_permission(self.doctype, "read"):
  21. raise frappe.PermissionError
  22. if fields:
  23. self.fields = fields
  24. self.filters = filters or []
  25. self.docstatus = docstatus or []
  26. self.group_by = group_by
  27. self.order_by = order_by
  28. self.limit_start = limit_start
  29. self.limit_page_length = limit_page_length
  30. self.with_childnames = with_childnames
  31. self.debug = debug
  32. self.as_list = as_list
  33. self.ignore_permissions = ignore_permissions
  34. if query:
  35. return self.run_custom_query(query)
  36. else:
  37. return self.build_and_run()
  38. def build_and_run(self):
  39. args = self.prepare_args()
  40. args.limit = self.add_limit()
  41. query = """select %(fields)s from %(tables)s where %(conditions)s
  42. %(group_by)s order by %(order_by)s %(limit)s""" % args
  43. return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug)
  44. def prepare_args(self):
  45. self.parse_args()
  46. self.extract_tables()
  47. self.remove_user_tags()
  48. self.build_conditions()
  49. args = frappe._dict()
  50. if self.with_childnames:
  51. for t in self.tables:
  52. if t != "`tab" + self.doctype + "`":
  53. self.fields.append(t + ".name as '%s:name'" % t[4:-1])
  54. # query dict
  55. args.tables = ', '.join(self.tables)
  56. args.conditions = ' and '.join(self.conditions)
  57. args.fields = ', '.join(self.fields)
  58. args.order_by = self.order_by or self.tables[0] + '.modified desc'
  59. args.group_by = self.group_by and (" group by " + self.group_by) or ""
  60. self.check_sort_by_table(args.order_by)
  61. return args
  62. def parse_args(self):
  63. if isinstance(self.filters, basestring):
  64. self.filters = json.loads(self.filters)
  65. if isinstance(self.fields, basestring):
  66. self.filters = json.loads(self.fields)
  67. if isinstance(self.filters, dict):
  68. fdict = self.filters
  69. self.filters = []
  70. for key, value in fdict.iteritems():
  71. self.filters.append(self.make_filter_tuple(key, value))
  72. def make_filter_tuple(self, key, value):
  73. if isinstance(value, (list, tuple)):
  74. return [self.doctype, key, value[0], value[1]]
  75. else:
  76. return [self.doctype, key, "=", value]
  77. def extract_tables(self):
  78. """extract tables from fields"""
  79. self.tables = ['`tab' + self.doctype + '`']
  80. # add tables from fields
  81. if self.fields:
  82. for f in self.fields:
  83. if "." not in f: continue
  84. table_name = f.split('.')[0]
  85. if table_name.lower().startswith('group_concat('):
  86. table_name = table_name[13:]
  87. if table_name.lower().startswith('ifnull('):
  88. table_name = table_name[7:]
  89. if not table_name[0]=='`':
  90. table_name = '`' + table_name + '`'
  91. if not table_name in self.tables:
  92. self.append_table(table_name)
  93. def append_table(self, table_name):
  94. self.tables.append(table_name)
  95. doctype = table_name[4:-1]
  96. if (not self.ignore_permissions) and (not frappe.has_permission(doctype)):
  97. raise frappe.PermissionError, doctype
  98. def remove_user_tags(self):
  99. """remove column _user_tags if not in table"""
  100. columns = frappe.db.get_table_columns(self.doctype)
  101. to_remove = []
  102. for fld in self.fields:
  103. for f in ("_user_tags", "_comments"):
  104. if f in fld and not f in columns:
  105. to_remove.append(fld)
  106. for fld in to_remove:
  107. del self.fields[self.fields.index(fld)]
  108. def build_conditions(self):
  109. self.conditions = []
  110. self.add_docstatus_conditions()
  111. self.build_filter_conditions()
  112. # join parent, child tables
  113. for tname in self.tables[1:]:
  114. self.conditions.append(tname + '.parent = ' + self.tables[0] + '.name')
  115. # match conditions
  116. if not self.ignore_permissions:
  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(self.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. for f in self.filters:
  128. if isinstance(f, basestring):
  129. self.conditions.append(f)
  130. else:
  131. f = self.get_filter_tuple(f)
  132. tname = ('`tab' + f[0] + '`')
  133. if not tname in self.tables:
  134. self.append_table(tname)
  135. # prepare in condition
  136. if f[2] in ['in', 'not in']:
  137. opts = f[3]
  138. if not isinstance(opts, (list, tuple)):
  139. opts = f[3].split(",")
  140. opts = ["'" + t.strip().replace("'", "\\'") + "'" for t in opts]
  141. f[3] = "(" + ', '.join(opts) + ")"
  142. self.conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3])
  143. else:
  144. df = frappe.get_meta(f[0]).get("fields", {"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. # explict permissions
  169. restricted_by_user = frappe.permissions.get_user_perms(frappe.get_meta(self.doctype)).restricted
  170. # get restrictions
  171. restrictions = frappe.defaults.get_restrictions()
  172. if restricted_by_user:
  173. self.or_conditions.append('`tab{doctype}`.`owner`="{user}"'.format(doctype=self.doctype,
  174. user=frappe.local.session.user))
  175. self.match_filters["owner"] = frappe.session.user
  176. if restrictions:
  177. self.add_restrictions(restrictions)
  178. if as_condition:
  179. return self.build_match_condition_string()
  180. else:
  181. return self.match_filters
  182. def add_restrictions(self, restrictions):
  183. fields_to_check = frappe.get_meta(self.doctype).get_restricted_fields(restrictions.keys())
  184. if self.doctype in restrictions:
  185. fields_to_check.append(frappe._dict({"fieldname":"name", "options":self.doctype}))
  186. # check in links
  187. for df in fields_to_check:
  188. self.match_conditions.append("""(ifnull(`tab{doctype}`.`{fieldname}`, "")="" or \
  189. `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", {}).get(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 {0} to sort").format(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 ''