fix: Support SQL like LIKE filter

Other changes:
- Ignore empty doctype in filter creator
- Simplified recorder filter evals
This commit is contained in:
Ankush Menat 2023-08-11 21:08:24 +05:30
parent 9ad5f662c6
commit 7a5a0c27a2
4 changed files with 38 additions and 43 deletions

View file

@ -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,14 +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 not compare(value, operator, operand):
return False
return True

View file

@ -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()

View file

@ -174,32 +174,21 @@ class TestFilters(FrappeTestCase):
)
def test_like_not_like(self):
doc = {"doctype": "User", "username": "test_abc"}
self.assertTrue(
evaluate_filters(
doc,
[["username", "like", "test"]],
)
)
self.assertFalse(
evaluate_filters(
doc,
[["username", "like", "user1"]],
)
)
doc = {"doctype": "User", "username": "test_abc", "prefix": "startswith", "suffix": "endswith"}
self.assertFalse(
evaluate_filters(
doc,
[["username", "not like", "test"]],
)
)
self.assertTrue(
evaluate_filters(
doc,
[["username", "not like", "user1"]],
)
)
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):

View file

@ -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,8 +1721,8 @@ operator_map = {
"<=": operator.le,
"not None": lambda a, b: a is not None,
"None": lambda a, b: a is None,
"like": lambda a, b: operator.contains(a.strip("%"), b.strip("%")),
"not like": lambda a, b: not operator.contains(a.strip("%"), b.strip("%")),
"like": sql_like,
"not like": lambda a, b: not sql_like(a, b),
}
@ -1814,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