diff --git a/frappe/core/doctype/recorder/recorder.py b/frappe/core/doctype/recorder/recorder.py index 4aab095914..c8ca1cc798 100644 --- a/frappe/core/doctype/recorder/recorder.py +++ b/frappe/core/doctype/recorder/recorder.py @@ -4,7 +4,7 @@ import frappe from frappe.model.document import Document from frappe.recorder import get as get_recorder_data -from frappe.utils import cint, compare, make_filter_dict +from frappe.utils import cint, evaluate_filters, make_filter_dict class Recorder(Document): @@ -47,7 +47,7 @@ class Recorder(Document): order_by_statment = order_by_statment.split(".")[1] if " " in order_by_statment: - sort_key, sort_order = order_by_statment.split(" ") + sort_key, sort_order = order_by_statment.split(" ", 1) else: sort_key = order_by_statment sort_order = "desc" @@ -63,9 +63,9 @@ class Recorder(Document): @staticmethod def get_filtered_requests(args): - filters = make_filter_dict(args.get("filters")) + filters = args.get("filters") requests = [serialize_request(request) for request in get_recorder_data()] - return [req for req in requests if _evaluate_filters(req, filters)] + return [req for req in requests if evaluate_filters(req, filters)] @staticmethod def get_stats(args): @@ -100,20 +100,3 @@ def serialize_request(request): ) return request - - -def _evaluate_filters(row, filters) -> bool: - for field in filters: - value = row[field] - operand = filters[field][1] - operator = filters[field][0] - - if operator == "like": - operator = "in" # python equivalent. - operand = operand.strip("%") - # Swap because like is "reverse IN" - value, operand = operand, value - - if not compare(value, operator, operand): - return False - return True diff --git a/frappe/core/doctype/recorder/test_recorder.py b/frappe/core/doctype/recorder/test_recorder.py index d0dfc3827b..aad47cadf5 100644 --- a/frappe/core/doctype/recorder/test_recorder.py +++ b/frappe/core/doctype/recorder/test_recorder.py @@ -15,6 +15,9 @@ class TestRecorder(FrappeTestCase): def setUp(self): self.start_recoder() + def tearDown(self) -> None: + frappe.recorder.stop() + def start_recoder(self): frappe.recorder.stop() frappe.recorder.delete() diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index 2a32c15ce5..3f091db809 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -173,6 +173,23 @@ class TestFilters(FrappeTestCase): ) ) + def test_like_not_like(self): + doc = {"doctype": "User", "username": "test_abc", "prefix": "startswith", "suffix": "endswith"} + + test_cases = [ + ([["username", "like", "test"]], True), + ([["username", "like", "user1"]], False), + ([["username", "not like", "test"]], False), + ([["username", "not like", "user1"]], True), + ([["prefix", "like", "start%"]], True), + ([["prefix", "not like", "end%"]], True), + ([["suffix", "like", "%with"]], True), + ([["suffix", "not like", "%end"]], True), + ] + + for filter, expected_result in test_cases: + self.assertEqual(evaluate_filters(doc, filter), expected_result) + class TestMoney(FrappeTestCase): def test_money_in_words(self): diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 6974d6d636..f7c6bf59de 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1692,6 +1692,20 @@ def get_url_to_report_with_filters(name, filters, report_type=None, doctype=None return get_url(uri=f"/app/query-report/{quoted(name)}?{filters}") +def sql_like(value: str, pattern: str) -> bool: + if not isinstance(pattern, str) and isinstance(value, str): + return False + if pattern.startswith("%") and pattern.endswith("%"): + return pattern.strip("%") in value + elif pattern.startswith("%"): + return value.endswith(pattern.lstrip("%")) + elif pattern.endswith("%"): + return value.startswith(pattern.rstrip("%")) + else: + # assume default as wrapped in '%' + return pattern in value + + operator_map = { # startswith "^": lambda a, b: (a or "").startswith(b), @@ -1707,6 +1721,8 @@ operator_map = { "<=": operator.le, "not None": lambda a, b: a is not None, "None": lambda a, b: a is None, + "like": sql_like, + "not like": lambda a, b: not sql_like(a, b), } @@ -1812,7 +1828,7 @@ def get_filter(doctype: str, f: dict | list | tuple, filters_config=None) -> "fr break try: - df = frappe.get_meta(f.doctype).get_field(f.fieldname) + df = frappe.get_meta(f.doctype).get_field(f.fieldname) if f.doctype else None except frappe.exceptions.DoesNotExistError: df = None