From eff50e1cd3926a4e80b8155c871a941bce8fb689 Mon Sep 17 00:00:00 2001 From: Maxim Sysoev Date: Sat, 2 Mar 2024 15:59:30 +0200 Subject: [PATCH] fix: filter Implementation is set operator (#25182) * Implementation is set operator. fix issue #25180 * Refactored filtrer operator `is`, Add tests * fix: Correct implementation for `is set` --------- Co-authored-by: Ankush Menat --- frappe/tests/test_utils.py | 11 +++++++++-- frappe/utils/data.py | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index 5d7ca03d3d..824eaeac1c 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -186,12 +186,14 @@ class TestFilters(FrappeTestCase): ) ) - def test_like_not_like(self): + def test_filter_evaluation(self): doc = { "doctype": "User", "username": "test_abc", "prefix": "startswith", "suffix": "endswith", + "empty": None, + "number": 0, } test_cases = [ @@ -203,10 +205,15 @@ class TestFilters(FrappeTestCase): ([["prefix", "not like", "end%"]], True), ([["suffix", "like", "%with"]], True), ([["suffix", "not like", "%end"]], True), + ([["suffix", "is", "set"]], True), + ([["suffix", "is", "not set"]], False), + ([["empty", "is", "set"]], False), + ([["empty", "is", "not set"]], True), + ([["number", "is", "set"]], True), ] for filter, expected_result in test_cases: - self.assertEqual(evaluate_filters(doc, filter), expected_result) + self.assertEqual(evaluate_filters(doc, filter), expected_result, msg=f"{filter}") class TestMoney(FrappeTestCase): diff --git a/frappe/utils/data.py b/frappe/utils/data.py index d537839e37..a094157ac1 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1930,6 +1930,25 @@ def sql_like(value: str, pattern: str) -> bool: return pattern in value +def filter_operator_is(value: str, pattern: str) -> bool: + """Operator `is` can have two values: 'set' or 'not set'.""" + pattern = pattern.lower() + + def is_set(): + if value is None: + return False + elif isinstance(value, str) and not value: + return False + return True + + if pattern == "set": + return is_set() + elif pattern == "not set": + return not is_set() + else: + frappe.throw(frappe._(f"Invalid argument for operator 'IS': {pattern}")) + + operator_map = { # startswith "^": lambda a, b: (a or "").startswith(b), @@ -1947,6 +1966,7 @@ operator_map = { "None": lambda a, b: a is None, "like": sql_like, "not like": lambda a, b: not sql_like(a, b), + "is": filter_operator_is, }