fix: Support SQL like LIKE filter
Other changes: - Ignore empty doctype in filter creator - Simplified recorder filter evals
This commit is contained in:
parent
9ad5f662c6
commit
7a5a0c27a2
4 changed files with 38 additions and 43 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue