Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

980 wiersze
31 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. """build query for doclistview and return results"""
  4. from typing import List
  5. import frappe.defaults
  6. from frappe.query_builder.utils import Column
  7. import frappe.share
  8. from frappe import _
  9. import frappe.permissions
  10. from datetime import datetime
  11. import frappe, json, copy, re
  12. from frappe.model import optional_fields
  13. from frappe.model.utils.user_settings import get_user_settings, update_user_settings
  14. from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, get_timespan_date_range
  15. from frappe.model.meta import get_table_columns
  16. from frappe.core.doctype.server_script.server_script_utils import get_server_script_map
  17. class DatabaseQuery(object):
  18. def __init__(self, doctype, user=None):
  19. self.doctype = doctype
  20. self.tables = []
  21. self.conditions = []
  22. self.or_conditions = []
  23. self.fields = None
  24. self.user = user or frappe.session.user
  25. self.ignore_ifnull = False
  26. self.flags = frappe._dict()
  27. self.reference_doctype = None
  28. def execute(self, fields=None, filters=None, or_filters=None,
  29. docstatus=None, group_by=None, order_by="KEEP_DEFAULT_ORDERING", limit_start=False,
  30. limit_page_length=None, as_list=False, with_childnames=False, debug=False,
  31. ignore_permissions=False, user=None, with_comment_count=False,
  32. join='left join', distinct=False, start=None, page_length=None, limit=None,
  33. ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
  34. update=None, add_total_row=None, user_settings=None, reference_doctype=None,
  35. run=True, strict=True, pluck=None, ignore_ddl=False, parent_doctype=None) -> List:
  36. if (
  37. not ignore_permissions
  38. and not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype)
  39. and not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype)
  40. ):
  41. frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
  42. raise frappe.PermissionError(self.doctype)
  43. # filters and fields swappable
  44. # its hard to remember what comes first
  45. if (
  46. isinstance(fields, dict)
  47. or (
  48. fields
  49. and isinstance(fields, list)
  50. and isinstance(fields[0], list)
  51. )
  52. ):
  53. # if fields is given as dict/list of list, its probably filters
  54. filters, fields = fields, filters
  55. elif fields and isinstance(filters, list) \
  56. and len(filters) > 1 and isinstance(filters[0], str):
  57. # if `filters` is a list of strings, its probably fields
  58. filters, fields = fields, filters
  59. if fields:
  60. self.fields = fields
  61. else:
  62. self.fields = [f"`tab{self.doctype}`.`{pluck or 'name'}`"]
  63. if start: limit_start = start
  64. if page_length: limit_page_length = page_length
  65. if limit: limit_page_length = limit
  66. self.filters = filters or []
  67. self.or_filters = or_filters or []
  68. self.docstatus = docstatus or []
  69. self.group_by = group_by
  70. self.order_by = order_by
  71. self.limit_start = cint(limit_start)
  72. self.limit_page_length = cint(limit_page_length) if limit_page_length else None
  73. self.with_childnames = with_childnames
  74. self.debug = debug
  75. self.join = join
  76. self.distinct = distinct
  77. self.as_list = as_list
  78. self.ignore_ifnull = ignore_ifnull
  79. self.flags.ignore_permissions = ignore_permissions
  80. self.user = user or frappe.session.user
  81. self.update = update
  82. self.user_settings_fields = copy.deepcopy(self.fields)
  83. self.run = run
  84. self.strict = strict
  85. self.ignore_ddl = ignore_ddl
  86. # for contextual user permission check
  87. # to determine which user permission is applicable on link field of specific doctype
  88. self.reference_doctype = reference_doctype or self.doctype
  89. if user_settings:
  90. self.user_settings = json.loads(user_settings)
  91. self.columns = self.get_table_columns()
  92. # no table & ignore_ddl, return
  93. if not self.columns: return []
  94. result = self.build_and_run()
  95. if with_comment_count and not as_list and self.doctype:
  96. self.add_comment_count(result)
  97. if save_user_settings:
  98. self.save_user_settings_fields = save_user_settings_fields
  99. self.update_user_settings()
  100. if pluck:
  101. return [d[pluck] for d in result]
  102. return result
  103. def build_and_run(self):
  104. args = self.prepare_args()
  105. args.limit = self.add_limit()
  106. if args.conditions:
  107. args.conditions = "where " + args.conditions
  108. if self.distinct:
  109. args.fields = 'distinct ' + args.fields
  110. args.order_by = '' # TODO: recheck for alternative
  111. # Postgres requires any field that appears in the select clause to also
  112. # appear in the order by and group by clause
  113. if frappe.db.db_type == 'postgres' and args.order_by and args.group_by:
  114. args = self.prepare_select_args(args)
  115. query = """select %(fields)s
  116. from %(tables)s
  117. %(conditions)s
  118. %(group_by)s
  119. %(order_by)s
  120. %(limit)s""" % args
  121. return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug,
  122. update=self.update, ignore_ddl=self.ignore_ddl, run=self.run)
  123. def prepare_args(self):
  124. self.parse_args()
  125. self.sanitize_fields()
  126. self.extract_tables()
  127. self.set_optional_columns()
  128. self.build_conditions()
  129. args = frappe._dict()
  130. if self.with_childnames:
  131. for t in self.tables:
  132. if t != "`tab" + self.doctype + "`":
  133. self.fields.append(t + ".name as '%s:name'" % t[4:-1])
  134. # query dict
  135. args.tables = self.tables[0]
  136. # left join parent, child tables
  137. for child in self.tables[1:]:
  138. parent_name = self.cast_name(f"{self.tables[0]}.name")
  139. args.tables += f" {self.join} {child} on ({child}.parent = {parent_name})"
  140. if self.grouped_or_conditions:
  141. self.conditions.append(f"({' or '.join(self.grouped_or_conditions)})")
  142. args.conditions = ' and '.join(self.conditions)
  143. if self.or_conditions:
  144. args.conditions += (' or ' if args.conditions else "") + \
  145. ' or '.join(self.or_conditions)
  146. self.set_field_tables()
  147. fields = []
  148. # Wrapping fields with grave quotes to allow support for sql keywords
  149. # TODO: Add support for wrapping fields with sql functions and distinct keyword
  150. for field in self.fields:
  151. stripped_field = field.strip().lower()
  152. skip_wrapping = any([
  153. stripped_field.startswith(("`", "*", '"', "'")),
  154. "(" in stripped_field,
  155. "distinct" in stripped_field,
  156. ])
  157. if skip_wrapping:
  158. fields.append(field)
  159. elif "as" in field.lower().split(" "):
  160. col, _, new = field.split()
  161. fields.append(f"`{col}` as {new}")
  162. else:
  163. fields.append(f"`{field}`")
  164. args.fields = ", ".join(fields)
  165. self.set_order_by(args)
  166. self.validate_order_by_and_group_by(args.order_by)
  167. args.order_by = args.order_by and (" order by " + args.order_by) or ""
  168. self.validate_order_by_and_group_by(self.group_by)
  169. args.group_by = self.group_by and (" group by " + self.group_by) or ""
  170. return args
  171. def prepare_select_args(self, args):
  172. order_field = re.sub(r"\ order\ by\ |\ asc|\ ASC|\ desc|\ DESC", "", args.order_by)
  173. if order_field not in args.fields:
  174. extracted_column = order_column = order_field.replace("`", "")
  175. if "." in extracted_column:
  176. extracted_column = extracted_column.split(".")[1]
  177. args.fields += f", MAX({extracted_column}) as `{order_column}`"
  178. args.order_by = args.order_by.replace(order_field, f"`{order_column}`")
  179. return args
  180. def parse_args(self):
  181. """Convert fields and filters from strings to list, dicts"""
  182. if isinstance(self.fields, str):
  183. if self.fields == "*":
  184. self.fields = ["*"]
  185. else:
  186. try:
  187. self.fields = json.loads(self.fields)
  188. except ValueError:
  189. self.fields = [f.strip() for f in self.fields.split(",")]
  190. # remove empty strings / nulls in fields
  191. self.fields = [f for f in self.fields if f]
  192. for filter_name in ["filters", "or_filters"]:
  193. filters = getattr(self, filter_name)
  194. if isinstance(filters, str):
  195. filters = json.loads(filters)
  196. if isinstance(filters, dict):
  197. fdict = filters
  198. filters = []
  199. for key, value in fdict.items():
  200. filters.append(make_filter_tuple(self.doctype, key, value))
  201. setattr(self, filter_name, filters)
  202. def sanitize_fields(self):
  203. '''
  204. regex : ^.*[,();].*
  205. purpose : The regex will look for malicious patterns like `,`, '(', ')', '@', ;' in each
  206. field which may leads to sql injection.
  207. example :
  208. field = "`DocType`.`issingle`, version()"
  209. As field contains `,` and mysql function `version()`, with the help of regex
  210. the system will filter out this field.
  211. '''
  212. sub_query_regex = re.compile("^.*[,();@].*")
  213. blacklisted_keywords = ['select', 'create', 'insert', 'delete', 'drop', 'update', 'case', 'show']
  214. blacklisted_functions = ['concat', 'concat_ws', 'if', 'ifnull', 'nullif', 'coalesce',
  215. 'connection_id', 'current_user', 'database', 'last_insert_id', 'session_user',
  216. 'system_user', 'user', 'version', 'global']
  217. def _raise_exception():
  218. frappe.throw(_('Use of sub-query or function is restricted'), frappe.DataError)
  219. def _is_query(field):
  220. if re.compile(r"^(select|delete|update|drop|create)\s").match(field):
  221. _raise_exception()
  222. elif re.compile(r"\s*[0-9a-zA-z]*\s*( from | group by | order by | where | join )").match(field):
  223. _raise_exception()
  224. for field in self.fields:
  225. if sub_query_regex.match(field):
  226. if any(keyword in field.lower().split() for keyword in blacklisted_keywords):
  227. _raise_exception()
  228. if any(f"({keyword}" in field.lower() for keyword in blacklisted_keywords):
  229. _raise_exception()
  230. if any(f"{keyword}(" in field.lower() for keyword in blacklisted_functions):
  231. _raise_exception()
  232. if '@' in field.lower():
  233. # prevent access to global variables
  234. _raise_exception()
  235. if re.compile(r"[0-9a-zA-Z]+\s*'").match(field):
  236. _raise_exception()
  237. if re.compile(r"[0-9a-zA-Z]+\s*,").match(field):
  238. _raise_exception()
  239. _is_query(field)
  240. if self.strict:
  241. if re.compile(r".*/\*.*").match(field):
  242. frappe.throw(_('Illegal SQL Query'))
  243. if re.compile(r".*\s(union).*\s").match(field.lower()):
  244. frappe.throw(_('Illegal SQL Query'))
  245. def extract_tables(self):
  246. """extract tables from fields"""
  247. self.tables = [f"`tab{self.doctype}`"]
  248. sql_functions = [
  249. "dayofyear(",
  250. "extract(",
  251. "locate(",
  252. "strpos(",
  253. "count(",
  254. "sum(",
  255. "avg(",
  256. ]
  257. # add tables from fields
  258. if self.fields:
  259. for i, field in enumerate(self.fields):
  260. # add cast in locate/strpos
  261. func_found = False
  262. for func in sql_functions:
  263. if func in field.lower():
  264. self.fields[i] = self.cast_name(field, func)
  265. func_found = True
  266. break
  267. if func_found or not ("tab" in field and "." in field):
  268. continue
  269. table_name = field.split('.')[0]
  270. if table_name.lower().startswith('group_concat('):
  271. table_name = table_name[13:]
  272. if not table_name[0]=='`':
  273. table_name = f"`{table_name}`"
  274. if table_name not in self.tables:
  275. self.append_table(table_name)
  276. def cast_name(self, column: str, sql_function: str = "",) -> str:
  277. if frappe.db.db_type == "postgres":
  278. if "name" in column.lower():
  279. if "cast(" not in column.lower() or "::" not in column:
  280. if not sql_function:
  281. return f"cast({column} as varchar)"
  282. elif sql_function == "locate(":
  283. return re.sub(
  284. r'locate\(([^,]+),([^)]+)\)',
  285. r'locate(\1, cast(\2 as varchar))',
  286. column,
  287. flags=re.IGNORECASE
  288. )
  289. elif sql_function == "strpos(":
  290. return re.sub(
  291. r'strpos\(([^,]+),([^)]+)\)',
  292. r'strpos(cast(\1 as varchar), \2)',
  293. column,
  294. flags=re.IGNORECASE
  295. )
  296. elif sql_function == "ifnull(":
  297. return re.sub(
  298. r"ifnull\(([^,]+)",
  299. r"ifnull(cast(\1 as varchar)",
  300. column,
  301. flags=re.IGNORECASE
  302. )
  303. return column
  304. def append_table(self, table_name):
  305. self.tables.append(table_name)
  306. doctype = table_name[4:-1]
  307. ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'
  308. if not self.flags.ignore_permissions and \
  309. not frappe.has_permission(doctype, ptype=ptype, parent_doctype=self.doctype):
  310. frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(doctype))
  311. raise frappe.PermissionError(doctype)
  312. def set_field_tables(self):
  313. '''If there are more than one table, the fieldname must not be ambiguous.
  314. If the fieldname is not explicitly mentioned, set the default table'''
  315. def _in_standard_sql_methods(field):
  316. methods = ('count(', 'avg(', 'sum(', 'extract(', 'dayofyear(')
  317. return field.lower().startswith(methods)
  318. if len(self.tables) > 1:
  319. for idx, field in enumerate(self.fields):
  320. if '.' not in field and not _in_standard_sql_methods(field):
  321. self.fields[idx] = f"{self.tables[0]}.{field}"
  322. def get_table_columns(self):
  323. try:
  324. return get_table_columns(self.doctype)
  325. except frappe.db.TableMissingError:
  326. if self.ignore_ddl:
  327. return None
  328. else:
  329. raise
  330. def set_optional_columns(self):
  331. """Removes optional columns like `_user_tags`, `_comments` etc. if not in table"""
  332. # remove from fields
  333. to_remove = []
  334. for fld in self.fields:
  335. for f in optional_fields:
  336. if f in fld and not f in self.columns:
  337. to_remove.append(fld)
  338. for fld in to_remove:
  339. del self.fields[self.fields.index(fld)]
  340. # remove from filters
  341. to_remove = []
  342. for each in self.filters:
  343. if isinstance(each, str):
  344. each = [each]
  345. for element in each:
  346. if element in optional_fields and element not in self.columns:
  347. to_remove.append(each)
  348. for each in to_remove:
  349. if isinstance(self.filters, dict):
  350. del self.filters[each]
  351. else:
  352. self.filters.remove(each)
  353. def build_conditions(self):
  354. self.conditions = []
  355. self.grouped_or_conditions = []
  356. self.build_filter_conditions(self.filters, self.conditions)
  357. self.build_filter_conditions(self.or_filters, self.grouped_or_conditions)
  358. # match conditions
  359. if not self.flags.ignore_permissions:
  360. match_conditions = self.build_match_conditions()
  361. if match_conditions:
  362. self.conditions.append(f"({match_conditions})")
  363. def build_filter_conditions(self, filters, conditions, ignore_permissions=None):
  364. """build conditions from user filters"""
  365. if ignore_permissions is not None:
  366. self.flags.ignore_permissions = ignore_permissions
  367. if isinstance(filters, dict):
  368. filters = [filters]
  369. for f in filters:
  370. if isinstance(f, str):
  371. conditions.append(f)
  372. else:
  373. conditions.append(self.prepare_filter_condition(f))
  374. def prepare_filter_condition(self, f):
  375. """Returns a filter condition in the format:
  376. ifnull(`tabDocType`.`fieldname`, fallback) operator "value"
  377. """
  378. # TODO: refactor
  379. from frappe.boot import get_additional_filters_from_hooks
  380. additional_filters_config = get_additional_filters_from_hooks()
  381. f = get_filter(self.doctype, f, additional_filters_config)
  382. tname = ('`tab' + f.doctype + '`')
  383. if tname not in self.tables:
  384. self.append_table(tname)
  385. if 'ifnull(' in f.fieldname:
  386. column_name = self.cast_name(f.fieldname, "ifnull(")
  387. else:
  388. column_name = self.cast_name(f"{tname}.`{f.fieldname}`")
  389. if f.operator.lower() in additional_filters_config:
  390. f.update(get_additional_filter_field(additional_filters_config, f, f.value))
  391. meta = frappe.get_meta(f.doctype)
  392. can_be_null = True
  393. # prepare in condition
  394. if f.operator.lower() in ('ancestors of', 'descendants of', 'not ancestors of', 'not descendants of'):
  395. values = f.value or ''
  396. # TODO: handle list and tuple
  397. # if not isinstance(values, (list, tuple)):
  398. # values = values.split(",")
  399. field = meta.get_field(f.fieldname)
  400. ref_doctype = field.options if field else f.doctype
  401. lft, rgt = '', ''
  402. if f.value:
  403. lft, rgt = frappe.db.get_value(ref_doctype, f.value, ["lft", "rgt"])
  404. # Get descendants elements of a DocType with a tree structure
  405. if f.operator.lower() in ('descendants of', 'not descendants of') :
  406. result = frappe.get_all(ref_doctype, filters={
  407. 'lft': ['>', lft],
  408. 'rgt': ['<', rgt]
  409. }, order_by='`lft` ASC')
  410. else :
  411. # Get ancestor elements of a DocType with a tree structure
  412. result = frappe.get_all(ref_doctype, filters={
  413. 'lft': ['<', lft],
  414. 'rgt': ['>', rgt]
  415. }, order_by='`lft` DESC')
  416. fallback = "''"
  417. value = [frappe.db.escape((cstr(v.name) or '').strip(), percent=False) for v in result]
  418. if len(value):
  419. value = f"({', '.join(value)})"
  420. else:
  421. value = "('')"
  422. # changing operator to IN as the above code fetches all the parent / child values and convert into tuple
  423. # which can be directly used with IN operator to query.
  424. f.operator = 'not in' if f.operator.lower() in ('not ancestors of', 'not descendants of') else 'in'
  425. elif f.operator.lower() in ('in', 'not in'):
  426. values = f.value or ''
  427. if isinstance(values, str):
  428. values = values.split(",")
  429. fallback = "''"
  430. value = [frappe.db.escape((cstr(v) or '').strip(), percent=False) for v in values]
  431. if len(value):
  432. value = f"({', '.join(value)})"
  433. else:
  434. value = "('')"
  435. else:
  436. df = meta.get("fields", {"fieldname": f.fieldname})
  437. df = df[0] if df else None
  438. if df and df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"):
  439. can_be_null = False
  440. if f.operator.lower() in ('previous', 'next', 'timespan'):
  441. date_range = get_date_range(f.operator.lower(), f.value)
  442. f.operator = "Between"
  443. f.value = date_range
  444. fallback = "'0001-01-01 00:00:00'"
  445. if f.operator in ('>', '<') and (f.fieldname in ('creation', 'modified')):
  446. value = cstr(f.value)
  447. fallback = "'0001-01-01 00:00:00'"
  448. elif f.operator.lower() in ('between') and \
  449. (f.fieldname in ('creation', 'modified') or
  450. (df and (df.fieldtype=="Date" or df.fieldtype=="Datetime"))):
  451. value = get_between_date_filter(f.value, df)
  452. fallback = "'0001-01-01 00:00:00'"
  453. elif f.operator.lower() == "is":
  454. if f.value == 'set':
  455. f.operator = '!='
  456. elif f.value == 'not set':
  457. f.operator = '='
  458. value = ""
  459. fallback = "''"
  460. can_be_null = True
  461. if 'ifnull' not in column_name.lower():
  462. column_name = f'ifnull({column_name}, {fallback})'
  463. elif df and df.fieldtype=="Date":
  464. value = frappe.db.format_date(f.value)
  465. fallback = "'0001-01-01'"
  466. elif (df and df.fieldtype=="Datetime") or isinstance(f.value, datetime):
  467. value = frappe.db.format_datetime(f.value)
  468. fallback = "'0001-01-01 00:00:00'"
  469. elif df and df.fieldtype=="Time":
  470. value = get_time(f.value).strftime("%H:%M:%S.%f")
  471. fallback = "'00:00:00'"
  472. elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, str) and
  473. (not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])):
  474. value = "" if f.value is None else f.value
  475. fallback = "''"
  476. if f.operator.lower() in ("like", "not like") and isinstance(value, str):
  477. # because "like" uses backslash (\) for escaping
  478. value = value.replace("\\", "\\\\").replace("%", "%%")
  479. elif f.operator == '=' and df and df.fieldtype in ['Link', 'Data']: # TODO: Refactor if possible
  480. value = f.value or "''"
  481. fallback = "''"
  482. elif f.fieldname == 'name':
  483. value = f.value or "''"
  484. fallback = "''"
  485. else:
  486. value = flt(f.value)
  487. fallback = 0
  488. if isinstance(f.value, Column):
  489. can_be_null = False # added to avoid the ifnull/coalesce addition
  490. quote = '"' if frappe.conf.db_type == 'postgres' else "`"
  491. value = f"{tname}.{quote}{f.value.name}{quote}"
  492. # escape value
  493. elif isinstance(value, str) and f.operator.lower() != 'between':
  494. value = f"{frappe.db.escape(value, percent=False)}"
  495. if (
  496. self.ignore_ifnull
  497. or not can_be_null
  498. or (f.value and f.operator.lower() in ('=', 'like'))
  499. or 'ifnull(' in column_name.lower()
  500. ):
  501. if f.operator.lower() == 'like' and frappe.conf.get('db_type') == 'postgres':
  502. f.operator = 'ilike'
  503. condition = f'{column_name} {f.operator} {value}'
  504. else:
  505. condition = f'ifnull({column_name}, {fallback}) {f.operator} {value}'
  506. return condition
  507. def build_match_conditions(self, as_condition=True):
  508. """add match conditions if applicable"""
  509. self.match_filters = []
  510. self.match_conditions = []
  511. only_if_shared = False
  512. if not self.user:
  513. self.user = frappe.session.user
  514. if not self.tables: self.extract_tables()
  515. meta = frappe.get_meta(self.doctype)
  516. role_permissions = frappe.permissions.get_role_permissions(meta, user=self.user)
  517. self.shared = frappe.share.get_shared(self.doctype, self.user)
  518. if (
  519. not meta.istable and
  520. not (role_permissions.get("select") or role_permissions.get("read")) and
  521. not self.flags.ignore_permissions and
  522. not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype)
  523. ):
  524. only_if_shared = True
  525. if not self.shared:
  526. frappe.throw(_("No permission to read {0}").format(self.doctype), frappe.PermissionError)
  527. else:
  528. self.conditions.append(self.get_share_condition())
  529. else:
  530. # skip user perm check if owner constraint is required
  531. if requires_owner_constraint(role_permissions):
  532. self.match_conditions.append(
  533. f"`tab{self.doctype}`.`owner` = {frappe.db.escape(self.user, percent=False)}"
  534. )
  535. # add user permission only if role has read perm
  536. elif role_permissions.get("read") or role_permissions.get("select"):
  537. # get user permissions
  538. user_permissions = frappe.permissions.get_user_permissions(self.user)
  539. self.add_user_permissions(user_permissions)
  540. if as_condition:
  541. conditions = ""
  542. if self.match_conditions:
  543. # will turn out like ((blog_post in (..) and blogger in (...)) or (blog_category in (...)))
  544. conditions = "((" + ") or (".join(self.match_conditions) + "))"
  545. doctype_conditions = self.get_permission_query_conditions()
  546. if doctype_conditions:
  547. conditions += (' and ' + doctype_conditions) if conditions else doctype_conditions
  548. # share is an OR condition, if there is a role permission
  549. if not only_if_shared and self.shared and conditions:
  550. conditions = f"({conditions}) or ({self.get_share_condition()})"
  551. return conditions
  552. else:
  553. return self.match_filters
  554. def get_share_condition(self):
  555. return f"`tab{self.doctype}`.name in ({', '.join(frappe.db.escape(s, percent=False) for s in self.shared)})"
  556. def add_user_permissions(self, user_permissions):
  557. meta = frappe.get_meta(self.doctype)
  558. doctype_link_fields = []
  559. doctype_link_fields = meta.get_link_fields()
  560. # append current doctype with fieldname as 'name' as first link field
  561. doctype_link_fields.append(dict(
  562. options=self.doctype,
  563. fieldname='name',
  564. ))
  565. match_filters = {}
  566. match_conditions = []
  567. for df in doctype_link_fields:
  568. if df.get('ignore_user_permissions'): continue
  569. user_permission_values = user_permissions.get(df.get('options'), {})
  570. if user_permission_values:
  571. docs = []
  572. if frappe.get_system_settings("apply_strict_user_permissions"):
  573. condition = ""
  574. else:
  575. empty_value_condition = f"ifnull(`tab{self.doctype}`.`{df.get('fieldname')}`, '')=''"
  576. condition = empty_value_condition + " or "
  577. for permission in user_permission_values:
  578. if not permission.get('applicable_for'):
  579. docs.append(permission.get('doc'))
  580. # append docs based on user permission applicable on reference doctype
  581. # this is useful when getting list of docs from a link field
  582. # in this case parent doctype of the link
  583. # will be the reference doctype
  584. elif df.get('fieldname') == 'name' and self.reference_doctype:
  585. if permission.get('applicable_for') == self.reference_doctype:
  586. docs.append(permission.get('doc'))
  587. elif permission.get('applicable_for') == self.doctype:
  588. docs.append(permission.get('doc'))
  589. if docs:
  590. values = ", ".join(frappe.db.escape(doc, percent=False) for doc in docs)
  591. condition += f"`tab{self.doctype}`.`{df.get('fieldname')}` in ({values})"
  592. match_conditions.append(f"({condition})")
  593. match_filters[df.get('options')] = docs
  594. if match_conditions:
  595. self.match_conditions.append(" and ".join(match_conditions))
  596. if match_filters:
  597. self.match_filters.append(match_filters)
  598. def get_permission_query_conditions(self):
  599. conditions = []
  600. condition_methods = frappe.get_hooks("permission_query_conditions", {}).get(self.doctype, [])
  601. if condition_methods:
  602. for method in condition_methods:
  603. c = frappe.call(frappe.get_attr(method), self.user)
  604. if c:
  605. conditions.append(c)
  606. permision_script_name = get_server_script_map().get("permission_query", {}).get(self.doctype)
  607. if permision_script_name:
  608. script = frappe.get_doc("Server Script", permision_script_name)
  609. condition = script.get_permission_query_conditions(self.user)
  610. if condition:
  611. conditions.append(condition)
  612. return " and ".join(conditions) if conditions else ""
  613. def set_order_by(self, args):
  614. meta = frappe.get_meta(self.doctype)
  615. if self.order_by and self.order_by != "KEEP_DEFAULT_ORDERING":
  616. args.order_by = self.order_by
  617. else:
  618. args.order_by = ""
  619. # don't add order by from meta if a mysql group function is used without group by clause
  620. group_function_without_group_by = (len(self.fields)==1 and
  621. ( self.fields[0].lower().startswith("count(")
  622. or self.fields[0].lower().startswith("min(")
  623. or self.fields[0].lower().startswith("max(")
  624. ) and not self.group_by)
  625. if not group_function_without_group_by:
  626. sort_field = sort_order = None
  627. if meta.sort_field and ',' in meta.sort_field:
  628. # multiple sort given in doctype definition
  629. # Example:
  630. # `idx desc, modified desc`
  631. # will covert to
  632. # `tabItem`.`idx` desc, `tabItem`.`modified` desc
  633. args.order_by = ', '.join(
  634. f"`tab{self.doctype}`.`{f.split()[0].strip()}` {f.split()[1].strip()}" for f in meta.sort_field.split(',')
  635. )
  636. else:
  637. sort_field = meta.sort_field or 'modified'
  638. sort_order = (meta.sort_field and meta.sort_order) or 'desc'
  639. if self.order_by:
  640. args.order_by = f"`tab{self.doctype}`.`{sort_field or 'modified'}` {sort_order or 'desc'}"
  641. # draft docs always on top
  642. if hasattr(meta, 'is_submittable') and meta.is_submittable:
  643. if self.order_by:
  644. args.order_by = f"`tab{self.doctype}`.docstatus asc, {args.order_by}"
  645. def validate_order_by_and_group_by(self, parameters):
  646. """Check order by, group by so that atleast one column is selected and does not have subquery"""
  647. if not parameters:
  648. return
  649. _lower = parameters.lower()
  650. if 'select' in _lower and 'from' in _lower:
  651. frappe.throw(_('Cannot use sub-query in order by'))
  652. if re.compile(r".*[^a-z0-9-_ ,`'\"\.\(\)].*").match(_lower):
  653. frappe.throw(_('Illegal SQL Query'))
  654. for field in parameters.split(","):
  655. if "." in field and field.strip().startswith("`tab"):
  656. tbl = field.strip().split('.')[0]
  657. if tbl not in self.tables:
  658. if tbl.startswith('`'):
  659. tbl = tbl[4:-1]
  660. frappe.throw(_("Please select atleast 1 column from {0} to sort/group").format(tbl))
  661. def add_limit(self):
  662. if self.limit_page_length:
  663. return 'limit %s offset %s' % (self.limit_page_length, self.limit_start)
  664. else:
  665. return ''
  666. def add_comment_count(self, result):
  667. for r in result:
  668. if not r.name:
  669. continue
  670. r._comment_count = 0
  671. if "_comments" in r:
  672. r._comment_count = len(json.loads(r._comments or "[]"))
  673. def update_user_settings(self):
  674. # update user settings if new search
  675. user_settings = json.loads(get_user_settings(self.doctype))
  676. if hasattr(self, 'user_settings'):
  677. user_settings.update(self.user_settings)
  678. if self.save_user_settings_fields:
  679. user_settings['fields'] = self.user_settings_fields
  680. update_user_settings(self.doctype, user_settings)
  681. def check_parent_permission(parent, child_doctype):
  682. if parent:
  683. # User may pass fake parent and get the information from the child table
  684. if child_doctype and not (
  685. frappe.db.exists('DocField', {'parent': parent, 'options': child_doctype})
  686. or frappe.db.exists('Custom Field', {'dt': parent, 'options': child_doctype})
  687. ):
  688. raise frappe.PermissionError
  689. if frappe.permissions.has_permission(parent):
  690. return
  691. # Either parent not passed or the user doesn't have permission on parent doctype of child table!
  692. raise frappe.PermissionError
  693. def get_order_by(doctype, meta):
  694. order_by = ""
  695. sort_field = sort_order = None
  696. if meta.sort_field and ',' in meta.sort_field:
  697. # multiple sort given in doctype definition
  698. # Example:
  699. # `idx desc, modified desc`
  700. # will covert to
  701. # `tabItem`.`idx` desc, `tabItem`.`modified` desc
  702. order_by = ', '.join(f"`tab{doctype}`.`{f.split()[0].strip()}` {f.split()[1].strip()}" for f in meta.sort_field.split(','))
  703. else:
  704. sort_field = meta.sort_field or 'modified'
  705. sort_order = (meta.sort_field and meta.sort_order) or 'desc'
  706. order_by = f"`tab{doctype}`.`{sort_field or 'modified'}` {sort_order or 'desc'}"
  707. # draft docs always on top
  708. if meta.is_submittable:
  709. order_by = f"`tab{doctype}`.docstatus asc, {order_by}"
  710. return order_by
  711. def is_parent_only_filter(doctype, filters):
  712. #check if filters contains only parent doctype
  713. only_parent_doctype = True
  714. if isinstance(filters, list):
  715. for flt in filters:
  716. if doctype not in flt:
  717. only_parent_doctype = False
  718. if 'Between' in flt:
  719. flt[3] = get_between_date_filter(flt[3])
  720. return only_parent_doctype
  721. def has_any_user_permission_for_doctype(doctype, user, applicable_for):
  722. user_permissions = frappe.permissions.get_user_permissions(user=user)
  723. doctype_user_permissions = user_permissions.get(doctype, [])
  724. for permission in doctype_user_permissions:
  725. if not permission.applicable_for or permission.applicable_for == applicable_for:
  726. return True
  727. return False
  728. def get_between_date_filter(value, df=None):
  729. '''
  730. return the formattted date as per the given example
  731. [u'2017-11-01', u'2017-11-03'] => '2017-11-01 00:00:00.000000' AND '2017-11-04 00:00:00.000000'
  732. '''
  733. from_date = frappe.utils.nowdate()
  734. to_date = frappe.utils.nowdate()
  735. if value and isinstance(value, (list, tuple)):
  736. if len(value) >= 1: from_date = value[0]
  737. if len(value) >= 2: to_date = value[1]
  738. if not df or (df and df.fieldtype == 'Datetime'):
  739. to_date = add_to_date(to_date, days=1)
  740. if df and df.fieldtype == 'Datetime':
  741. data = "'%s' AND '%s'" % (
  742. frappe.db.format_datetime(from_date),
  743. frappe.db.format_datetime(to_date))
  744. else:
  745. data = "'%s' AND '%s'" % (
  746. frappe.db.format_date(from_date),
  747. frappe.db.format_date(to_date))
  748. return data
  749. def get_additional_filter_field(additional_filters_config, f, value):
  750. additional_filter = additional_filters_config[f.operator.lower()]
  751. f = frappe._dict(frappe.get_attr(additional_filter['get_field'])())
  752. if f.query_value:
  753. for option in f.options:
  754. option = frappe._dict(option)
  755. if option.value == value:
  756. f.value = option.query_value
  757. return f
  758. def get_date_range(operator, value):
  759. timespan_map = {
  760. '1 week': 'week',
  761. '1 month': 'month',
  762. '3 months': 'quarter',
  763. '6 months': '6 months',
  764. '1 year': 'year',
  765. }
  766. period_map = {
  767. 'previous': 'last',
  768. 'next': 'next',
  769. }
  770. timespan = period_map[operator] + ' ' + timespan_map[value] if operator != 'timespan' else value
  771. return get_timespan_date_range(timespan)
  772. def requires_owner_constraint(role_permissions):
  773. """Returns True if "select" or "read" isn't available without being creator."""
  774. if not role_permissions.get("has_if_owner_enabled"):
  775. return
  776. if_owner_perms = role_permissions.get("if_owner")
  777. if not if_owner_perms:
  778. return
  779. # has select or read without if owner, no need for constraint
  780. for perm_type in ("select", "read"):
  781. if role_permissions.get(perm_type) and perm_type not in if_owner_perms:
  782. return
  783. # not checking if either select or read if present in if_owner_perms
  784. # because either of those is required to perform a query
  785. return True