Skip to content

Commit 65e8602

Browse files
committed
✨ Add BaseQueryPreprocessor for customizable search query preprocessing
1 parent fec96a8 commit 65e8602

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed

froide/helper/search/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ def get_default_ngram_analyzer():
5555
)
5656

5757

58+
def get_default_query_preprocessor():
59+
from froide.helper.search.filters import BaseQueryPreprocessor
60+
61+
return BaseQueryPreprocessor()
62+
63+
5864
def get_func(config_name, default_func):
5965
def get_it():
6066
from django.conf import settings
@@ -73,3 +79,4 @@ def get_it():
7379
get_search_analyzer = get_func("search_analyzer", get_default_text_analyzer)
7480
get_search_quote_analyzer = get_func("search_quote_analyzer", get_default_text_analyzer)
7581
get_ngram_analyzer = get_func("ngram_analyzer", get_default_ngram_analyzer)
82+
get_query_preprocessor = get_func("query_preprocessor", get_default_query_preprocessor)

froide/helper/search/filters.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import django_filters
55
from elasticsearch_dsl.query import Q
66

7+
from froide.helper.search import get_query_preprocessor
8+
79

810
class BaseSearchFilterSet(django_filters.FilterSet):
911
query_fields = ["content"]
@@ -19,6 +21,7 @@ class BaseSearchFilterSet(django_filters.FilterSet):
1921
def __init__(self, *args, **kwargs):
2022
self.facet_config = kwargs.pop("facet_config", {})
2123
self.view = kwargs.pop("view", None)
24+
self.query_preprocessor = get_query_preprocessor()
2225
super().__init__(*args, **kwargs)
2326

2427
def apply_filter(self, qs, name, *args, **kwargs):
@@ -42,13 +45,29 @@ def filter_queryset(self, queryset):
4245

4346
def auto_query(self, qs, name, value):
4447
if value:
48+
query = self.query_preprocessor.prepare_query(value)
4549
return qs.set_query(
4650
Q(
4751
"simple_query_string",
48-
query=value,
52+
query=query,
4953
fields=self.query_fields,
5054
default_operator="and",
5155
lenient=True,
5256
)
5357
)
5458
return qs
59+
60+
61+
class BaseQueryPreprocessor:
62+
"""
63+
Base class that can be overridden for custom search query preprocessing.
64+
"""
65+
66+
def prepare_query(self, text: str):
67+
"""
68+
Preprocess the given search query text and return the processed text.
69+
70+
This method can be overridden in subclasses to implement custom
71+
preprocessing logic.
72+
"""
73+
return text

froide/helper/tests/test_search.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from unittest.mock import MagicMock
2+
3+
from froide.helper.search.filters import BaseSearchFilterSet
4+
5+
6+
class DummyModel:
7+
pass
8+
9+
10+
class DummyQS:
11+
def __init__(self):
12+
self.model = DummyModel()
13+
self.query = None
14+
15+
def set_query(self, q):
16+
self.query = q
17+
return self
18+
19+
20+
class TestBaseSearchFilterSetQueryPreprocessing:
21+
def test_auto_query_without_query_value(self):
22+
qs = DummyQS()
23+
fs = BaseSearchFilterSet(queryset=qs)
24+
25+
result = fs.auto_query(qs, "q", "")
26+
27+
assert result is qs
28+
assert result.query is None
29+
30+
def test_auto_query_with_default_query_preprocessor(self):
31+
qs = DummyQS()
32+
fs = BaseSearchFilterSet(queryset=qs)
33+
34+
result = fs.auto_query(qs, "q", "test query")
35+
36+
assert result is qs
37+
assert result.query is not None
38+
assert result.query.query == "test query"
39+
40+
def test_auto_query_with_custom_query_preprocessor(self, monkeypatch):
41+
# Mock custom query preprocessor.
42+
mock_preprocessor = MagicMock()
43+
mock_preprocessor.prepare_query.return_value = "processed query"
44+
monkeypatch.setattr(
45+
"froide.helper.search.filters.get_query_preprocessor",
46+
lambda: mock_preprocessor,
47+
)
48+
49+
qs = DummyQS()
50+
fs = BaseSearchFilterSet(queryset=qs)
51+
52+
result = fs.auto_query(qs, "q", "original query")
53+
54+
assert result.query.query == "processed query"
55+
mock_preprocessor.prepare_query.assert_called_once_with("original query")

0 commit comments

Comments
 (0)