We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Tree DocTypes: Display Group Node 1st in Link Field search list
Presently in Link Fields when you search tree doctype document name you will see that if the document is a group node it is shown last in the list after the child nodes.

![tdYsDN1](https://user-images.githubusercontent.com/36654812/118114929-e123fb80-b405-11eb-933b-c902d8aca583.png)

Search should generally show the best match first. The current implementation does the opposite.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `frappe/desk/search.py`
Content:
```
1 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
2 # MIT License. See license.txt
3 
4 # Search
5 import frappe, json
6 from frappe.utils import cstr, unique, cint
7 from frappe.permissions import has_permission
8 from frappe import _, is_whitelisted
9 import re
10 import wrapt
11 
12 UNTRANSLATED_DOCTYPES = ["DocType", "Role"]
13 
14 def sanitize_searchfield(searchfield):
15 	blacklisted_keywords = ['select', 'delete', 'drop', 'update', 'case', 'and', 'or', 'like']
16 
17 	def _raise_exception(searchfield):
18 		frappe.throw(_('Invalid Search Field {0}').format(searchfield), frappe.DataError)
19 
20 	if len(searchfield) == 1:
21 		# do not allow special characters to pass as searchfields
22 		regex = re.compile(r'^.*[=;*,\'"$\-+%#@()_].*')
23 		if regex.match(searchfield):
24 			_raise_exception(searchfield)
25 
26 	if len(searchfield) >= 3:
27 
28 		# to avoid 1=1
29 		if '=' in searchfield:
30 			_raise_exception(searchfield)
31 
32 		# in mysql -- is used for commenting the query
33 		elif ' --' in searchfield:
34 			_raise_exception(searchfield)
35 
36 		# to avoid and, or and like
37 		elif any(' {0} '.format(keyword) in searchfield.split() for keyword in blacklisted_keywords):
38 			_raise_exception(searchfield)
39 
40 		# to avoid select, delete, drop, update and case
41 		elif any(keyword in searchfield.split() for keyword in blacklisted_keywords):
42 			_raise_exception(searchfield)
43 
44 		else:
45 			regex = re.compile(r'^.*[=;*,\'"$\-+%#@()].*')
46 			if any(regex.match(f) for f in searchfield.split()):
47 				_raise_exception(searchfield)
48 
49 # this is called by the Link Field
50 @frappe.whitelist()
51 def search_link(doctype, txt, query=None, filters=None, page_length=20, searchfield=None, reference_doctype=None, ignore_user_permissions=False):
52 	search_widget(doctype, txt.strip(), query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions)
53 	frappe.response['results'] = build_for_autosuggest(frappe.response["values"])
54 	del frappe.response["values"]
55 
56 # this is called by the search box
57 @frappe.whitelist()
58 def search_widget(doctype, txt, query=None, searchfield=None, start=0,
59 	page_length=20, filters=None, filter_fields=None, as_dict=False, reference_doctype=None, ignore_user_permissions=False):
60 
61 	start = cint(start)
62 
63 	if isinstance(filters, str):
64 		filters = json.loads(filters)
65 
66 	if searchfield:
67 		sanitize_searchfield(searchfield)
68 
69 	if not searchfield:
70 		searchfield = "name"
71 
72 	standard_queries = frappe.get_hooks().standard_queries or {}
73 
74 	if query and query.split()[0].lower()!="select":
75 		# by method
76 		try:
77 			is_whitelisted(frappe.get_attr(query))
78 			frappe.response["values"] = frappe.call(query, doctype, txt,
79 				searchfield, start, page_length, filters, as_dict=as_dict)
80 		except frappe.exceptions.PermissionError as e:
81 			if frappe.local.conf.developer_mode:
82 				raise e
83 			else:
84 				frappe.respond_as_web_page(title='Invalid Method', html='Method not found',
85 				indicator_color='red', http_status_code=404)
86 			return
87 		except Exception as e:
88 			raise e
89 	elif not query and doctype in standard_queries:
90 		# from standard queries
91 		search_widget(doctype, txt, standard_queries[doctype][0],
92 			searchfield, start, page_length, filters)
93 	else:
94 		meta = frappe.get_meta(doctype)
95 
96 		if query:
97 			frappe.throw(_("This query style is discontinued"))
98 			# custom query
99 			# frappe.response["values"] = frappe.db.sql(scrub_custom_query(query, searchfield, txt))
100 		else:
101 			if isinstance(filters, dict):
102 				filters_items = filters.items()
103 				filters = []
104 				for f in filters_items:
105 					if isinstance(f[1], (list, tuple)):
106 						filters.append([doctype, f[0], f[1][0], f[1][1]])
107 					else:
108 						filters.append([doctype, f[0], "=", f[1]])
109 
110 			if filters==None:
111 				filters = []
112 			or_filters = []
113 
114 
115 			# build from doctype
116 			if txt:
117 				search_fields = ["name"]
118 				if meta.title_field:
119 					search_fields.append(meta.title_field)
120 
121 				if meta.search_fields:
122 					search_fields.extend(meta.get_search_fields())
123 
124 				for f in search_fields:
125 					fmeta = meta.get_field(f.strip())
126 					if (doctype not in UNTRANSLATED_DOCTYPES) and (f == "name" or (fmeta and fmeta.fieldtype in ["Data", "Text", "Small Text", "Long Text",
127 						"Link", "Select", "Read Only", "Text Editor"])):
128 							or_filters.append([doctype, f.strip(), "like", "%{0}%".format(txt)])
129 
130 			if meta.get("fields", {"fieldname":"enabled", "fieldtype":"Check"}):
131 				filters.append([doctype, "enabled", "=", 1])
132 			if meta.get("fields", {"fieldname":"disabled", "fieldtype":"Check"}):
133 				filters.append([doctype, "disabled", "!=", 1])
134 
135 			# format a list of fields combining search fields and filter fields
136 			fields = get_std_fields_list(meta, searchfield or "name")
137 			if filter_fields:
138 				fields = list(set(fields + json.loads(filter_fields)))
139 			formatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields]
140 
141 			# find relevance as location of search term from the beginning of string `name`. used for sorting results.
142 			formatted_fields.append("""locate({_txt}, `tab{doctype}`.`name`) as `_relevance`""".format(
143 				_txt=frappe.db.escape((txt or "").replace("%", "").replace("@", "")), doctype=doctype))
144 
145 
146 			# In order_by, `idx` gets second priority, because it stores link count
147 			from frappe.model.db_query import get_order_by
148 			order_by_based_on_meta = get_order_by(doctype, meta)
149 			# 2 is the index of _relevance column
150 			order_by = "_relevance, {0}, `tab{1}`.idx desc".format(order_by_based_on_meta, doctype)
151 
152 			ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'
153 			ignore_permissions = True if doctype == "DocType" else (cint(ignore_user_permissions) and has_permission(doctype, ptype=ptype))
154 
155 			if doctype in UNTRANSLATED_DOCTYPES:
156 				page_length = None
157 
158 			values = frappe.get_list(doctype,
159 				filters=filters,
160 				fields=formatted_fields,
161 				or_filters=or_filters,
162 				limit_start=start,
163 				limit_page_length=page_length,
164 				order_by=order_by,
165 				ignore_permissions=ignore_permissions,
166 				reference_doctype=reference_doctype,
167 				as_list=not as_dict,
168 				strict=False)
169 
170 			if doctype in UNTRANSLATED_DOCTYPES:
171 				values = tuple([v for v in list(values) if re.search(re.escape(txt)+".*", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)])
172 
173 			# remove _relevance from results
174 			if as_dict:
175 				for r in values:
176 					r.pop("_relevance")
177 				frappe.response["values"] = values
178 			else:
179 				frappe.response["values"] = [r[:-1] for r in values]
180 
181 def get_std_fields_list(meta, key):
182 	# get additional search fields
183 	sflist = ["name"]
184 	if meta.search_fields:
185 		for d in meta.search_fields.split(","):
186 			if d.strip() not in sflist:
187 				sflist.append(d.strip())
188 
189 	if meta.title_field and meta.title_field not in sflist:
190 		sflist.append(meta.title_field)
191 
192 	if key not in sflist:
193 		sflist.append(key)
194 
195 	return sflist
196 
197 def build_for_autosuggest(res):
198 	results = []
199 	for r in res:
200 		out = {"value": r[0], "description": ", ".join(unique(cstr(d) for d in r if d)[1:])}
201 		results.append(out)
202 	return results
203 
204 def scrub_custom_query(query, key, txt):
205 	if '%(key)s' in query:
206 		query = query.replace('%(key)s', key)
207 	if '%s' in query:
208 		query = query.replace('%s', ((txt or '') + '%'))
209 	return query
210 
211 @wrapt.decorator
212 def validate_and_sanitize_search_inputs(fn, instance, args, kwargs):
213 	kwargs.update(dict(zip(fn.__code__.co_varnames, args)))
214 	sanitize_searchfield(kwargs['searchfield'])
215 	kwargs['start'] = cint(kwargs['start'])
216 	kwargs['page_len'] = cint(kwargs['page_len'])
217 
218 	if kwargs['doctype'] and not frappe.db.exists('DocType', kwargs['doctype']):
219 		return []
220 
221 	return fn(**kwargs)
222 
223 
224 @frappe.whitelist()
225 def get_names_for_mentions(search_term):
226 	users_for_mentions = frappe.cache().get_value('users_for_mentions', get_users_for_mentions)
227 	user_groups = frappe.cache().get_value('user_groups', get_user_groups)
228 
229 	filtered_mentions = []
230 	for mention_data in users_for_mentions + user_groups:
231 		if search_term.lower() not in mention_data.value.lower():
232 			continue
233 
234 		mention_data['link'] = frappe.utils.get_url_to_form(
235 			'User Group' if mention_data.get('is_group') else 'User Profile',
236 			mention_data['id']
237 		)
238 
239 		filtered_mentions.append(mention_data)
240 
241 	return sorted(filtered_mentions, key=lambda d: d['value'])
242 
243 def get_users_for_mentions():
244 	return frappe.get_all('User',
245 		fields=['name as id', 'full_name as value'],
246 		filters={
247 			'name': ['not in', ('Administrator', 'Guest')],
248 			'allowed_in_mentions': True,
249 			'user_type': 'System User',
250 		})
251 
252 def get_user_groups():
253 	return frappe.get_all('User Group', fields=['name as id', 'name as value'], update={
254 		'is_group': True
255 	})
256
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/frappe/desk/search.py b/frappe/desk/search.py
--- a/frappe/desk/search.py
+++ b/frappe/desk/search.py
@@ -168,7 +168,18 @@
 				strict=False)
 
 			if doctype in UNTRANSLATED_DOCTYPES:
-				values = tuple([v for v in list(values) if re.search(re.escape(txt)+".*", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)])
+				# Filtering the values array so that query is included in very element
+				values = (
+					v for v in values
+					if re.search(
+						f"{re.escape(txt)}.*", _(v.name if as_dict else v[0]), re.IGNORECASE
+					)
+				)
+
+			# Sorting the values array so that relevant results always come first
+			# This will first bring elements on top in which query is a prefix of element
+			# Then it will bring the rest of the elements and sort them in lexicographical order
+			values = sorted(values, key=lambda x: relevance_sorter(x, txt, as_dict))
 
 			# remove _relevance from results
 			if as_dict:
@@ -208,6 +219,13 @@
 		query = query.replace('%s', ((txt or '') + '%'))
 	return query
 
+def relevance_sorter(key, query, as_dict):
+	value = _(key.name if as_dict else key[0])
+	return (
+		value.lower().startswith(query.lower()) is not True,
+		value
+	)
+
 @wrapt.decorator
 def validate_and_sanitize_search_inputs(fn, instance, args, kwargs):
 	kwargs.update(dict(zip(fn.__code__.co_varnames, args)))

{"golden_diff": "diff --git a/frappe/desk/search.py b/frappe/desk/search.py\n--- a/frappe/desk/search.py\n+++ b/frappe/desk/search.py\n@@ -168,7 +168,18 @@\n \t\t\t\tstrict=False)\n \n \t\t\tif doctype in UNTRANSLATED_DOCTYPES:\n-\t\t\t\tvalues = tuple([v for v in list(values) if re.search(re.escape(txt)+\".*\", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)])\n+\t\t\t\t# Filtering the values array so that query is included in very element\n+\t\t\t\tvalues = (\n+\t\t\t\t\tv for v in values\n+\t\t\t\t\tif re.search(\n+\t\t\t\t\t\tf\"{re.escape(txt)}.*\", _(v.name if as_dict else v[0]), re.IGNORECASE\n+\t\t\t\t\t)\n+\t\t\t\t)\n+\n+\t\t\t# Sorting the values array so that relevant results always come first\n+\t\t\t# This will first bring elements on top in which query is a prefix of element\n+\t\t\t# Then it will bring the rest of the elements and sort them in lexicographical order\n+\t\t\tvalues = sorted(values, key=lambda x: relevance_sorter(x, txt, as_dict))\n \n \t\t\t# remove _relevance from results\n \t\t\tif as_dict:\n@@ -208,6 +219,13 @@\n \t\tquery = query.replace('%s', ((txt or '') + '%'))\n \treturn query\n \n+def relevance_sorter(key, query, as_dict):\n+\tvalue = _(key.name if as_dict else key[0])\n+\treturn (\n+\t\tvalue.lower().startswith(query.lower()) is not True,\n+\t\tvalue\n+\t)\n+\n @wrapt.decorator\n def validate_and_sanitize_search_inputs(fn, instance, args, kwargs):\n \tkwargs.update(dict(zip(fn.__code__.co_varnames, args)))\n", "issue": "Tree DocTypes: Display Group Node 1st in Link Field search list\nPresently in Link Fields when you search tree doctype document name you will see that if the document is a group node it is shown last in the list after the child nodes.\r\n\r\n![tdYsDN1](https://user-images.githubusercontent.com/36654812/118114929-e123fb80-b405-11eb-933b-c902d8aca583.png)\r\n\r\nSearch should generally show the best match first. The current implementation does the opposite.\n", "before_files": [{"content": "# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n# MIT License. See license.txt\n\n# Search\nimport frappe, json\nfrom frappe.utils import cstr, unique, cint\nfrom frappe.permissions import has_permission\nfrom frappe import _, is_whitelisted\nimport re\nimport wrapt\n\nUNTRANSLATED_DOCTYPES = [\"DocType\", \"Role\"]\n\ndef sanitize_searchfield(searchfield):\n\tblacklisted_keywords = ['select', 'delete', 'drop', 'update', 'case', 'and', 'or', 'like']\n\n\tdef _raise_exception(searchfield):\n\t\tfrappe.throw(_('Invalid Search Field {0}').format(searchfield), frappe.DataError)\n\n\tif len(searchfield) == 1:\n\t\t# do not allow special characters to pass as searchfields\n\t\tregex = re.compile(r'^.*[=;*,\\'\"$\\-+%#@()_].*')\n\t\tif regex.match(searchfield):\n\t\t\t_raise_exception(searchfield)\n\n\tif len(searchfield) >= 3:\n\n\t\t# to avoid 1=1\n\t\tif '=' in searchfield:\n\t\t\t_raise_exception(searchfield)\n\n\t\t# in mysql -- is used for commenting the query\n\t\telif ' --' in searchfield:\n\t\t\t_raise_exception(searchfield)\n\n\t\t# to avoid and, or and like\n\t\telif any(' {0} '.format(keyword) in searchfield.split() for keyword in blacklisted_keywords):\n\t\t\t_raise_exception(searchfield)\n\n\t\t# to avoid select, delete, drop, update and case\n\t\telif any(keyword in searchfield.split() for keyword in blacklisted_keywords):\n\t\t\t_raise_exception(searchfield)\n\n\t\telse:\n\t\t\tregex = re.compile(r'^.*[=;*,\\'\"$\\-+%#@()].*')\n\t\t\tif any(regex.match(f) for f in searchfield.split()):\n\t\t\t\t_raise_exception(searchfield)\n\n# this is called by the Link Field\n@frappe.whitelist()\ndef search_link(doctype, txt, query=None, filters=None, page_length=20, searchfield=None, reference_doctype=None, ignore_user_permissions=False):\n\tsearch_widget(doctype, txt.strip(), query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions)\n\tfrappe.response['results'] = build_for_autosuggest(frappe.response[\"values\"])\n\tdel frappe.response[\"values\"]\n\n# this is called by the search box\n@frappe.whitelist()\ndef search_widget(doctype, txt, query=None, searchfield=None, start=0,\n\tpage_length=20, filters=None, filter_fields=None, as_dict=False, reference_doctype=None, ignore_user_permissions=False):\n\n\tstart = cint(start)\n\n\tif isinstance(filters, str):\n\t\tfilters = json.loads(filters)\n\n\tif searchfield:\n\t\tsanitize_searchfield(searchfield)\n\n\tif not searchfield:\n\t\tsearchfield = \"name\"\n\n\tstandard_queries = frappe.get_hooks().standard_queries or {}\n\n\tif query and query.split()[0].lower()!=\"select\":\n\t\t# by method\n\t\ttry:\n\t\t\tis_whitelisted(frappe.get_attr(query))\n\t\t\tfrappe.response[\"values\"] = frappe.call(query, doctype, txt,\n\t\t\t\tsearchfield, start, page_length, filters, as_dict=as_dict)\n\t\texcept frappe.exceptions.PermissionError as e:\n\t\t\tif frappe.local.conf.developer_mode:\n\t\t\t\traise e\n\t\t\telse:\n\t\t\t\tfrappe.respond_as_web_page(title='Invalid Method', html='Method not found',\n\t\t\t\tindicator_color='red', http_status_code=404)\n\t\t\treturn\n\t\texcept Exception as e:\n\t\t\traise e\n\telif not query and doctype in standard_queries:\n\t\t# from standard queries\n\t\tsearch_widget(doctype, txt, standard_queries[doctype][0],\n\t\t\tsearchfield, start, page_length, filters)\n\telse:\n\t\tmeta = frappe.get_meta(doctype)\n\n\t\tif query:\n\t\t\tfrappe.throw(_(\"This query style is discontinued\"))\n\t\t\t# custom query\n\t\t\t# frappe.response[\"values\"] = frappe.db.sql(scrub_custom_query(query, searchfield, txt))\n\t\telse:\n\t\t\tif isinstance(filters, dict):\n\t\t\t\tfilters_items = filters.items()\n\t\t\t\tfilters = []\n\t\t\t\tfor f in filters_items:\n\t\t\t\t\tif isinstance(f[1], (list, tuple)):\n\t\t\t\t\t\tfilters.append([doctype, f[0], f[1][0], f[1][1]])\n\t\t\t\t\telse:\n\t\t\t\t\t\tfilters.append([doctype, f[0], \"=\", f[1]])\n\n\t\t\tif filters==None:\n\t\t\t\tfilters = []\n\t\t\tor_filters = []\n\n\n\t\t\t# build from doctype\n\t\t\tif txt:\n\t\t\t\tsearch_fields = [\"name\"]\n\t\t\t\tif meta.title_field:\n\t\t\t\t\tsearch_fields.append(meta.title_field)\n\n\t\t\t\tif meta.search_fields:\n\t\t\t\t\tsearch_fields.extend(meta.get_search_fields())\n\n\t\t\t\tfor f in search_fields:\n\t\t\t\t\tfmeta = meta.get_field(f.strip())\n\t\t\t\t\tif (doctype not in UNTRANSLATED_DOCTYPES) and (f == \"name\" or (fmeta and fmeta.fieldtype in [\"Data\", \"Text\", \"Small Text\", \"Long Text\",\n\t\t\t\t\t\t\"Link\", \"Select\", \"Read Only\", \"Text Editor\"])):\n\t\t\t\t\t\t\tor_filters.append([doctype, f.strip(), \"like\", \"%{0}%\".format(txt)])\n\n\t\t\tif meta.get(\"fields\", {\"fieldname\":\"enabled\", \"fieldtype\":\"Check\"}):\n\t\t\t\tfilters.append([doctype, \"enabled\", \"=\", 1])\n\t\t\tif meta.get(\"fields\", {\"fieldname\":\"disabled\", \"fieldtype\":\"Check\"}):\n\t\t\t\tfilters.append([doctype, \"disabled\", \"!=\", 1])\n\n\t\t\t# format a list of fields combining search fields and filter fields\n\t\t\tfields = get_std_fields_list(meta, searchfield or \"name\")\n\t\t\tif filter_fields:\n\t\t\t\tfields = list(set(fields + json.loads(filter_fields)))\n\t\t\tformatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields]\n\n\t\t\t# find relevance as location of search term from the beginning of string `name`. used for sorting results.\n\t\t\tformatted_fields.append(\"\"\"locate({_txt}, `tab{doctype}`.`name`) as `_relevance`\"\"\".format(\n\t\t\t\t_txt=frappe.db.escape((txt or \"\").replace(\"%\", \"\").replace(\"@\", \"\")), doctype=doctype))\n\n\n\t\t\t# In order_by, `idx` gets second priority, because it stores link count\n\t\t\tfrom frappe.model.db_query import get_order_by\n\t\t\torder_by_based_on_meta = get_order_by(doctype, meta)\n\t\t\t# 2 is the index of _relevance column\n\t\t\torder_by = \"_relevance, {0}, `tab{1}`.idx desc\".format(order_by_based_on_meta, doctype)\n\n\t\t\tptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'\n\t\t\tignore_permissions = True if doctype == \"DocType\" else (cint(ignore_user_permissions) and has_permission(doctype, ptype=ptype))\n\n\t\t\tif doctype in UNTRANSLATED_DOCTYPES:\n\t\t\t\tpage_length = None\n\n\t\t\tvalues = frappe.get_list(doctype,\n\t\t\t\tfilters=filters,\n\t\t\t\tfields=formatted_fields,\n\t\t\t\tor_filters=or_filters,\n\t\t\t\tlimit_start=start,\n\t\t\t\tlimit_page_length=page_length,\n\t\t\t\torder_by=order_by,\n\t\t\t\tignore_permissions=ignore_permissions,\n\t\t\t\treference_doctype=reference_doctype,\n\t\t\t\tas_list=not as_dict,\n\t\t\t\tstrict=False)\n\n\t\t\tif doctype in UNTRANSLATED_DOCTYPES:\n\t\t\t\tvalues = tuple([v for v in list(values) if re.search(re.escape(txt)+\".*\", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)])\n\n\t\t\t# remove _relevance from results\n\t\t\tif as_dict:\n\t\t\t\tfor r in values:\n\t\t\t\t\tr.pop(\"_relevance\")\n\t\t\t\tfrappe.response[\"values\"] = values\n\t\t\telse:\n\t\t\t\tfrappe.response[\"values\"] = [r[:-1] for r in values]\n\ndef get_std_fields_list(meta, key):\n\t# get additional search fields\n\tsflist = [\"name\"]\n\tif meta.search_fields:\n\t\tfor d in meta.search_fields.split(\",\"):\n\t\t\tif d.strip() not in sflist:\n\t\t\t\tsflist.append(d.strip())\n\n\tif meta.title_field and meta.title_field not in sflist:\n\t\tsflist.append(meta.title_field)\n\n\tif key not in sflist:\n\t\tsflist.append(key)\n\n\treturn sflist\n\ndef build_for_autosuggest(res):\n\tresults = []\n\tfor r in res:\n\t\tout = {\"value\": r[0], \"description\": \", \".join(unique(cstr(d) for d in r if d)[1:])}\n\t\tresults.append(out)\n\treturn results\n\ndef scrub_custom_query(query, key, txt):\n\tif '%(key)s' in query:\n\t\tquery = query.replace('%(key)s', key)\n\tif '%s' in query:\n\t\tquery = query.replace('%s', ((txt or '') + '%'))\n\treturn query\n\n@wrapt.decorator\ndef validate_and_sanitize_search_inputs(fn, instance, args, kwargs):\n\tkwargs.update(dict(zip(fn.__code__.co_varnames, args)))\n\tsanitize_searchfield(kwargs['searchfield'])\n\tkwargs['start'] = cint(kwargs['start'])\n\tkwargs['page_len'] = cint(kwargs['page_len'])\n\n\tif kwargs['doctype'] and not frappe.db.exists('DocType', kwargs['doctype']):\n\t\treturn []\n\n\treturn fn(**kwargs)\n\n\n@frappe.whitelist()\ndef get_names_for_mentions(search_term):\n\tusers_for_mentions = frappe.cache().get_value('users_for_mentions', get_users_for_mentions)\n\tuser_groups = frappe.cache().get_value('user_groups', get_user_groups)\n\n\tfiltered_mentions = []\n\tfor mention_data in users_for_mentions + user_groups:\n\t\tif search_term.lower() not in mention_data.value.lower():\n\t\t\tcontinue\n\n\t\tmention_data['link'] = frappe.utils.get_url_to_form(\n\t\t\t'User Group' if mention_data.get('is_group') else 'User Profile',\n\t\t\tmention_data['id']\n\t\t)\n\n\t\tfiltered_mentions.append(mention_data)\n\n\treturn sorted(filtered_mentions, key=lambda d: d['value'])\n\ndef get_users_for_mentions():\n\treturn frappe.get_all('User',\n\t\tfields=['name as id', 'full_name as value'],\n\t\tfilters={\n\t\t\t'name': ['not in', ('Administrator', 'Guest')],\n\t\t\t'allowed_in_mentions': True,\n\t\t\t'user_type': 'System User',\n\t\t})\n\ndef get_user_groups():\n\treturn frappe.get_all('User Group', fields=['name as id', 'name as value'], update={\n\t\t'is_group': True\n\t})\n", "path": "frappe/desk/search.py"}], "after_files": [{"content": "# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n# MIT License. See license.txt\n\n# Search\nimport frappe, json\nfrom frappe.utils import cstr, unique, cint\nfrom frappe.permissions import has_permission\nfrom frappe import _, is_whitelisted\nimport re\nimport wrapt\n\nUNTRANSLATED_DOCTYPES = [\"DocType\", \"Role\"]\n\ndef sanitize_searchfield(searchfield):\n\tblacklisted_keywords = ['select', 'delete', 'drop', 'update', 'case', 'and', 'or', 'like']\n\n\tdef _raise_exception(searchfield):\n\t\tfrappe.throw(_('Invalid Search Field {0}').format(searchfield), frappe.DataError)\n\n\tif len(searchfield) == 1:\n\t\t# do not allow special characters to pass as searchfields\n\t\tregex = re.compile(r'^.*[=;*,\\'\"$\\-+%#@()_].*')\n\t\tif regex.match(searchfield):\n\t\t\t_raise_exception(searchfield)\n\n\tif len(searchfield) >= 3:\n\n\t\t# to avoid 1=1\n\t\tif '=' in searchfield:\n\t\t\t_raise_exception(searchfield)\n\n\t\t# in mysql -- is used for commenting the query\n\t\telif ' --' in searchfield:\n\t\t\t_raise_exception(searchfield)\n\n\t\t# to avoid and, or and like\n\t\telif any(' {0} '.format(keyword) in searchfield.split() for keyword in blacklisted_keywords):\n\t\t\t_raise_exception(searchfield)\n\n\t\t# to avoid select, delete, drop, update and case\n\t\telif any(keyword in searchfield.split() for keyword in blacklisted_keywords):\n\t\t\t_raise_exception(searchfield)\n\n\t\telse:\n\t\t\tregex = re.compile(r'^.*[=;*,\\'\"$\\-+%#@()].*')\n\t\t\tif any(regex.match(f) for f in searchfield.split()):\n\t\t\t\t_raise_exception(searchfield)\n\n# this is called by the Link Field\n@frappe.whitelist()\ndef search_link(doctype, txt, query=None, filters=None, page_length=20, searchfield=None, reference_doctype=None, ignore_user_permissions=False):\n\tsearch_widget(doctype, txt.strip(), query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions)\n\tfrappe.response['results'] = build_for_autosuggest(frappe.response[\"values\"])\n\tdel frappe.response[\"values\"]\n\n# this is called by the search box\n@frappe.whitelist()\ndef search_widget(doctype, txt, query=None, searchfield=None, start=0,\n\tpage_length=20, filters=None, filter_fields=None, as_dict=False, reference_doctype=None, ignore_user_permissions=False):\n\n\tstart = cint(start)\n\n\tif isinstance(filters, str):\n\t\tfilters = json.loads(filters)\n\n\tif searchfield:\n\t\tsanitize_searchfield(searchfield)\n\n\tif not searchfield:\n\t\tsearchfield = \"name\"\n\n\tstandard_queries = frappe.get_hooks().standard_queries or {}\n\n\tif query and query.split()[0].lower()!=\"select\":\n\t\t# by method\n\t\ttry:\n\t\t\tis_whitelisted(frappe.get_attr(query))\n\t\t\tfrappe.response[\"values\"] = frappe.call(query, doctype, txt,\n\t\t\t\tsearchfield, start, page_length, filters, as_dict=as_dict)\n\t\texcept frappe.exceptions.PermissionError as e:\n\t\t\tif frappe.local.conf.developer_mode:\n\t\t\t\traise e\n\t\t\telse:\n\t\t\t\tfrappe.respond_as_web_page(title='Invalid Method', html='Method not found',\n\t\t\t\tindicator_color='red', http_status_code=404)\n\t\t\treturn\n\t\texcept Exception as e:\n\t\t\traise e\n\telif not query and doctype in standard_queries:\n\t\t# from standard queries\n\t\tsearch_widget(doctype, txt, standard_queries[doctype][0],\n\t\t\tsearchfield, start, page_length, filters)\n\telse:\n\t\tmeta = frappe.get_meta(doctype)\n\n\t\tif query:\n\t\t\tfrappe.throw(_(\"This query style is discontinued\"))\n\t\t\t# custom query\n\t\t\t# frappe.response[\"values\"] = frappe.db.sql(scrub_custom_query(query, searchfield, txt))\n\t\telse:\n\t\t\tif isinstance(filters, dict):\n\t\t\t\tfilters_items = filters.items()\n\t\t\t\tfilters = []\n\t\t\t\tfor f in filters_items:\n\t\t\t\t\tif isinstance(f[1], (list, tuple)):\n\t\t\t\t\t\tfilters.append([doctype, f[0], f[1][0], f[1][1]])\n\t\t\t\t\telse:\n\t\t\t\t\t\tfilters.append([doctype, f[0], \"=\", f[1]])\n\n\t\t\tif filters==None:\n\t\t\t\tfilters = []\n\t\t\tor_filters = []\n\n\n\t\t\t# build from doctype\n\t\t\tif txt:\n\t\t\t\tsearch_fields = [\"name\"]\n\t\t\t\tif meta.title_field:\n\t\t\t\t\tsearch_fields.append(meta.title_field)\n\n\t\t\t\tif meta.search_fields:\n\t\t\t\t\tsearch_fields.extend(meta.get_search_fields())\n\n\t\t\t\tfor f in search_fields:\n\t\t\t\t\tfmeta = meta.get_field(f.strip())\n\t\t\t\t\tif (doctype not in UNTRANSLATED_DOCTYPES) and (f == \"name\" or (fmeta and fmeta.fieldtype in [\"Data\", \"Text\", \"Small Text\", \"Long Text\",\n\t\t\t\t\t\t\"Link\", \"Select\", \"Read Only\", \"Text Editor\"])):\n\t\t\t\t\t\t\tor_filters.append([doctype, f.strip(), \"like\", \"%{0}%\".format(txt)])\n\n\t\t\tif meta.get(\"fields\", {\"fieldname\":\"enabled\", \"fieldtype\":\"Check\"}):\n\t\t\t\tfilters.append([doctype, \"enabled\", \"=\", 1])\n\t\t\tif meta.get(\"fields\", {\"fieldname\":\"disabled\", \"fieldtype\":\"Check\"}):\n\t\t\t\tfilters.append([doctype, \"disabled\", \"!=\", 1])\n\n\t\t\t# format a list of fields combining search fields and filter fields\n\t\t\tfields = get_std_fields_list(meta, searchfield or \"name\")\n\t\t\tif filter_fields:\n\t\t\t\tfields = list(set(fields + json.loads(filter_fields)))\n\t\t\tformatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields]\n\n\t\t\t# find relevance as location of search term from the beginning of string `name`. used for sorting results.\n\t\t\tformatted_fields.append(\"\"\"locate({_txt}, `tab{doctype}`.`name`) as `_relevance`\"\"\".format(\n\t\t\t\t_txt=frappe.db.escape((txt or \"\").replace(\"%\", \"\").replace(\"@\", \"\")), doctype=doctype))\n\n\n\t\t\t# In order_by, `idx` gets second priority, because it stores link count\n\t\t\tfrom frappe.model.db_query import get_order_by\n\t\t\torder_by_based_on_meta = get_order_by(doctype, meta)\n\t\t\t# 2 is the index of _relevance column\n\t\t\torder_by = \"_relevance, {0}, `tab{1}`.idx desc\".format(order_by_based_on_meta, doctype)\n\n\t\t\tptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'\n\t\t\tignore_permissions = True if doctype == \"DocType\" else (cint(ignore_user_permissions) and has_permission(doctype, ptype=ptype))\n\n\t\t\tif doctype in UNTRANSLATED_DOCTYPES:\n\t\t\t\tpage_length = None\n\n\t\t\tvalues = frappe.get_list(doctype,\n\t\t\t\tfilters=filters,\n\t\t\t\tfields=formatted_fields,\n\t\t\t\tor_filters=or_filters,\n\t\t\t\tlimit_start=start,\n\t\t\t\tlimit_page_length=page_length,\n\t\t\t\torder_by=order_by,\n\t\t\t\tignore_permissions=ignore_permissions,\n\t\t\t\treference_doctype=reference_doctype,\n\t\t\t\tas_list=not as_dict,\n\t\t\t\tstrict=False)\n\n\t\t\tif doctype in UNTRANSLATED_DOCTYPES:\n\t\t\t\t# Filtering the values array so that query is included in very element\n\t\t\t\tvalues = (\n\t\t\t\t\tv for v in values\n\t\t\t\t\tif re.search(\n\t\t\t\t\t\tf\"{re.escape(txt)}.*\", _(v.name if as_dict else v[0]), re.IGNORECASE\n\t\t\t\t\t)\n\t\t\t\t)\n\n\t\t\t# Sorting the values array so that relevant results always come first\n\t\t\t# This will first bring elements on top in which query is a prefix of element\n\t\t\t# Then it will bring the rest of the elements and sort them in lexicographical order\n\t\t\tvalues = sorted(values, key=lambda x: relevance_sorter(x, txt, as_dict))\n\n\t\t\t# remove _relevance from results\n\t\t\tif as_dict:\n\t\t\t\tfor r in values:\n\t\t\t\t\tr.pop(\"_relevance\")\n\t\t\t\tfrappe.response[\"values\"] = values\n\t\t\telse:\n\t\t\t\tfrappe.response[\"values\"] = [r[:-1] for r in values]\n\ndef get_std_fields_list(meta, key):\n\t# get additional search fields\n\tsflist = [\"name\"]\n\tif meta.search_fields:\n\t\tfor d in meta.search_fields.split(\",\"):\n\t\t\tif d.strip() not in sflist:\n\t\t\t\tsflist.append(d.strip())\n\n\tif meta.title_field and meta.title_field not in sflist:\n\t\tsflist.append(meta.title_field)\n\n\tif key not in sflist:\n\t\tsflist.append(key)\n\n\treturn sflist\n\ndef build_for_autosuggest(res):\n\tresults = []\n\tfor r in res:\n\t\tout = {\"value\": r[0], \"description\": \", \".join(unique(cstr(d) for d in r if d)[1:])}\n\t\tresults.append(out)\n\treturn results\n\ndef scrub_custom_query(query, key, txt):\n\tif '%(key)s' in query:\n\t\tquery = query.replace('%(key)s', key)\n\tif '%s' in query:\n\t\tquery = query.replace('%s', ((txt or '') + '%'))\n\treturn query\n\ndef relevance_sorter(key, query, as_dict):\n\tvalue = _(key.name if as_dict else key[0])\n\treturn (\n\t\tvalue.lower().startswith(query.lower()) is not True,\n\t\tvalue\n\t)\n\n@wrapt.decorator\ndef validate_and_sanitize_search_inputs(fn, instance, args, kwargs):\n\tkwargs.update(dict(zip(fn.__code__.co_varnames, args)))\n\tsanitize_searchfield(kwargs['searchfield'])\n\tkwargs['start'] = cint(kwargs['start'])\n\tkwargs['page_len'] = cint(kwargs['page_len'])\n\n\tif kwargs['doctype'] and not frappe.db.exists('DocType', kwargs['doctype']):\n\t\treturn []\n\n\treturn fn(**kwargs)\n\n\n@frappe.whitelist()\ndef get_names_for_mentions(search_term):\n\tusers_for_mentions = frappe.cache().get_value('users_for_mentions', get_users_for_mentions)\n\tuser_groups = frappe.cache().get_value('user_groups', get_user_groups)\n\n\tfiltered_mentions = []\n\tfor mention_data in users_for_mentions + user_groups:\n\t\tif search_term.lower() not in mention_data.value.lower():\n\t\t\tcontinue\n\n\t\tmention_data['link'] = frappe.utils.get_url_to_form(\n\t\t\t'User Group' if mention_data.get('is_group') else 'User Profile',\n\t\t\tmention_data['id']\n\t\t)\n\n\t\tfiltered_mentions.append(mention_data)\n\n\treturn sorted(filtered_mentions, key=lambda d: d['value'])\n\ndef get_users_for_mentions():\n\treturn frappe.get_all('User',\n\t\tfields=['name as id', 'full_name as value'],\n\t\tfilters={\n\t\t\t'name': ['not in', ('Administrator', 'Guest')],\n\t\t\t'allowed_in_mentions': True,\n\t\t\t'user_type': 'System User',\n\t\t})\n\ndef get_user_groups():\n\treturn frappe.get_all('User Group', fields=['name as id', 'name as value'], update={\n\t\t'is_group': True\n\t})\n", "path": "frappe/desk/search.py"}]}