From 0d1f8992bcc1c5e423fd7b7281471de2f1d6ac44 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas Date: Thu, 19 Mar 2026 17:17:33 +0530 Subject: [PATCH 1/6] fix(filter): use JSON encoding for in filter values containing commas --- frappe/model/db_query.py | 6 +++++- frappe/public/js/frappe/ui/filters/filter.js | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index eecacbd790..4c425f1ed6 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -888,7 +888,11 @@ from {tables} if value is None: values = f.value or "" if isinstance(values, str): - values = values.split(",") + try: + parsed = json.loads(values) + values = parsed if isinstance(parsed, list) else [parsed] + except (ValueError, TypeError): + values = values.split(",") fallback = "''" value = [frappe.db.escape((cstr(v) or "").strip(), percent=False) for v in values] diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 40f183450f..7b8c05bf7c 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -203,7 +203,7 @@ frappe.ui.Filter = class { this._filter_value_set = Promise.resolve(); if (["in", "not in"].includes(condition) && Array.isArray(value)) { - value = value.join(","); + value = value.some((v) => String(v).includes(",")) ? JSON.stringify(value) : value.join(","); } if (Array.isArray(value)) { @@ -485,7 +485,12 @@ frappe.ui.filter_utils = { } } else if (["in", "not in"].includes(condition)) { if (val) { - val = val.split(",").map((v) => strip(v)); + try { + const parsed = JSON.parse(val); + val = Array.isArray(parsed) ? parsed : [String(parsed)]; + } catch { + val = val.split(",").map((v) => strip(v)); + } } } else if (frappe.boot.additional_filters_config[condition]) { val = field.value || val; From c18cdfa7323f1dff80d11d474ccf7ab5fe25bdcc Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas Date: Sat, 21 Mar 2026 14:46:16 +0530 Subject: [PATCH 2/6] fix(filter): remove redundant TypeError from except clause --- frappe/model/db_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 4c425f1ed6..124843dfd9 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -891,7 +891,7 @@ from {tables} try: parsed = json.loads(values) values = parsed if isinstance(parsed, list) else [parsed] - except (ValueError, TypeError): + except ValueError: values = values.split(",") fallback = "''" From 71512a7ba210d5b0b1bfc7f94212be3209249f5b Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas Date: Sat, 21 Mar 2026 14:50:37 +0530 Subject: [PATCH 3/6] fix(filter): fix formatting --- frappe/public/js/frappe/ui/filters/filter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 7b8c05bf7c..95ac66013f 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -203,7 +203,9 @@ frappe.ui.Filter = class { this._filter_value_set = Promise.resolve(); if (["in", "not in"].includes(condition) && Array.isArray(value)) { - value = value.some((v) => String(v).includes(",")) ? JSON.stringify(value) : value.join(","); + value = value.some((v) => String(v).includes(",")) + ? JSON.stringify(value) + : value.join(","); } if (Array.isArray(value)) { From d609b8e2cc9d4794b2f3f39d92fa1c3651b0d855 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas Date: Sat, 21 Mar 2026 15:00:59 +0530 Subject: [PATCH 4/6] test: add test cases --- frappe/tests/test_db_query.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index 8246da7d8b..c20b8aa9c1 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -271,6 +271,33 @@ class TestDBQuery(IntegrationTestCase): result in DatabaseQuery("DocType").execute(filters={"name": ["not in", "DocType,DocField"]}) ) + def test_in_filter_json_encoded_values(self): + # JSON-encoded list string should work the same as comma-separated + for result in [{"name": "DocType"}, {"name": "DocField"}]: + self.assertTrue( + result + in DatabaseQuery("DocType").execute(filters={"name": ["in", '["DocType", "DocField"]']}) + ) + + # Values containing commas must not be split + todo = frappe.get_doc( + doctype="ToDo", description="Test, With Comma", allocated_to="Administrator" + ).insert() + try: + results = DatabaseQuery("ToDo").execute( + filters={"description": ["in", '["Test, With Comma"]']}, + fields=["description"], + ) + self.assertIn({"description": "Test, With Comma"}, results) + + results_split = DatabaseQuery("ToDo").execute( + filters={"description": ["in", "Test, With Comma"]}, + fields=["description"], + ) + self.assertNotIn({"description": "Test, With Comma"}, results_split) + finally: + frappe.delete_doc("ToDo", todo.name) + def test_string_as_field(self): self.assertEqual( frappe.get_all("DocType", as_list=True), frappe.get_all("DocType", fields="name", as_list=True) From f4b9c202d601902fe065afe59d7e73b15da76a39 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas Date: Sat, 21 Mar 2026 15:37:52 +0530 Subject: [PATCH 5/6] fix(filter): parse JSON-encoded values in FilterTuple before comma split --- frappe/types/filter.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/types/filter.py b/frappe/types/filter.py index acf9369322..5f3811fd6c 100644 --- a/frappe/types/filter.py +++ b/frappe/types/filter.py @@ -1,3 +1,4 @@ +import json import textwrap from collections import defaultdict from collections.abc import Generator, Iterable, Mapping, Sequence @@ -110,7 +111,11 @@ class FilterTuple(_FilterTuple): # soundness if operator in ("in", "not in") and isinstance(value, str): - value = value.split(",") + try: + parsed = json.loads(value) + value = parsed if isinstance(parsed, list) else [parsed] + except ValueError: + value = value.split(",") _value: Value if isinstance(value, _InputValue): From 83d265a3799517952251fdf6387f6d1750934f1e Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas Date: Sat, 21 Mar 2026 16:36:52 +0530 Subject: [PATCH 6/6] fix(filter): parse JSON-encoded string values before comma split in FilterTuple --- frappe/types/filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/types/filter.py b/frappe/types/filter.py index 5f3811fd6c..197577faa2 100644 --- a/frappe/types/filter.py +++ b/frappe/types/filter.py @@ -113,7 +113,7 @@ class FilterTuple(_FilterTuple): if operator in ("in", "not in") and isinstance(value, str): try: parsed = json.loads(value) - value = parsed if isinstance(parsed, list) else [parsed] + value = parsed if isinstance(parsed, list) else value.split(",") # type: ignore[assignment] except ValueError: value = value.split(",")