Skip to content

Commit e122374

Browse files
committed
Included analyzer and search_analyzer as new fields; included more tests and documentation
1 parent 7625617 commit e122374

File tree

4 files changed

+75
-6
lines changed

4 files changed

+75
-6
lines changed

django_mongodb_backend/indexes.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,21 @@ class SearchIndex(Index):
109109
suffix = "six"
110110
_error_id_prefix = "django_mongodb_backend.indexes.SearchIndex"
111111

112-
def __init__(self, *, fields=(), field_mappings=None, name=None):
112+
def __init__(
113+
self, *, fields=(), field_mappings=None, name=None, analyzer=None, search_analyzer=None
114+
):
113115
if field_mappings and not isinstance(field_mappings, dict):
114116
raise ValueError(
115117
"field_mappings must be a dictionary mapping field names to their "
116118
"Atlas Search field mappings."
117119
)
120+
if analyzer is not None and not isinstance(analyzer, str):
121+
raise ValueError(f"Analyzer must be a string. got type: {type(analyzer)}")
122+
if search_analyzer is not None and not isinstance(search_analyzer, str):
123+
raise ValueError(f"search_analyzer must be a string. got type: {type(search_analyzer)}")
118124
self.field_mappings = field_mappings
125+
self.analyzer = analyzer
126+
self.search_analyzer = search_analyzer
119127
if field_mappings:
120128
if fields:
121129
raise ValueError("Cannot provide fields and fields_mappings")
@@ -126,6 +134,10 @@ def deconstruct(self):
126134
path, args, kwargs = super().deconstruct()
127135
if self.field_mappings is not None:
128136
kwargs["field_mappings"] = self.field_mappings
137+
if self.analyzer is not None:
138+
kwargs["analyzer"] = self.analyzer
139+
if self.search_analyzer is not None:
140+
kwargs["search_analyzer"] = self.search_analyzer
129141
return path, args, kwargs
130142

131143
def check(self, model, connection):
@@ -175,8 +187,14 @@ def get_pymongo_index_model(
175187
field = model._meta.get_field(field_name)
176188
type_ = self.search_index_data_types(field.db_type(schema_editor.connection))
177189
fields[field_path] = {"type": type_}
190+
analyzers = {}
191+
if self.analyzer is not None:
192+
analyzers["analyzer"] = self.analyzer
193+
if self.search_analyzer is not None:
194+
analyzers["searchAnalyzer"] = self.search_analyzer
178195
return SearchIndexModel(
179-
definition={"mappings": {"dynamic": False, "fields": fields}}, name=self.name
196+
definition={"mappings": {"dynamic": False, "fields": fields, **analyzers}},
197+
name=self.name,
180198
)
181199

182200

docs/ref/models/indexes.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ minutes, depending on the size of the collection.
2626
``SearchIndex``
2727
---------------
2828

29-
.. class:: SearchIndex(fields=(), field_mappings=None, name=None)
29+
.. class:: SearchIndex(fields=(), field_mappings=None, name=None, analyzer=None, search_analyzer=None)
3030

3131
Creates a basic :doc:`search index <atlas:atlas-search/index-definitions>`
3232
on the given field(s).
@@ -46,9 +46,17 @@ minutes, depending on the size of the collection.
4646
(substituting the name of your model as well as a different list index if
4747
your model has multiple indexes).
4848

49+
Use ``analyzer`` or ``search_analyzer`` to configure the
50+
indexing and searching analyzer, respectively, for
51+
the search index definition. If these fields are not provided,
52+
they will default to ``lucene.standard`` at the server level.
53+
(See ``definition["mappings"]["analyzer"]``
54+
and ``definition["mappings"]["searchAnalyzer"]``
55+
in the :ref:`atlas:fts-static-mapping-example`).
56+
4957
.. versionchanged:: 5.2.2
5058

51-
The ``fields_mappings`` argument was added.
59+
The ``fields_mappings``, ``analyzer``, and ``search_analyzer`` arguments were added.
5260

5361
``VectorSearchIndex``
5462
---------------------

docs/releases/5.2.x.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Django MongoDB Backend 5.2.x
1010
New features
1111
------------
1212

13-
- Added the ``field_mappings`` argument to :class:`.SearchIndex` to allow
14-
creating advanced indexes.
13+
- Added the ``field_mappings``, ``analyzer``, and ``search_analyzer``
14+
arguments to :class:`.SearchIndex` to allow creating advanced indexes.
1515

1616
Bug fixes
1717
---------

tests/indexes_/test_search_indexes.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ def test_field_mappings_type(self):
6666
with self.assertRaisesMessage(ValueError, msg):
6767
SearchIndex(field_mappings={"foo"})
6868

69+
def test_analyzer_type(self):
70+
msg = "analyzer must be a string. got type: <class 'int'>"
71+
with self.assertRaisesMessage(ValueError, msg):
72+
SearchIndex(analyzer=42)
73+
74+
def test_search_analyzer_type(self):
75+
msg = "search_analyzer must be a string. got type: <class 'list'>"
76+
with self.assertRaisesMessage(ValueError, msg):
77+
SearchIndex(search_analyzer=["foo"])
78+
6979

7080
class VectorSearchIndexTests(SimpleTestCase):
7181
def test_no_init_args(self):
@@ -197,6 +207,39 @@ def test_valid_fields(self):
197207
with connection.schema_editor() as editor:
198208
editor.remove_index(index=index, model=SearchIndexTestModel)
199209

210+
def test_analyzer_inclusion(self):
211+
index = SearchIndex(
212+
name="recent_test_idx",
213+
fields=["char"],
214+
analyzer="lucene.simple",
215+
search_analyzer="lucene.simple",
216+
)
217+
with connection.schema_editor() as editor:
218+
editor.add_index(index=index, model=SearchIndexTestModel)
219+
try:
220+
index_info = connection.introspection.get_constraints(
221+
cursor=None,
222+
table_name=SearchIndexTestModel._meta.db_table,
223+
)
224+
expected_options = {
225+
"dynamic": False,
226+
"analyzer": "lucene.simple",
227+
"searchAnalyzer": "lucene.simple",
228+
"fields": {
229+
"char": {
230+
"indexOptions": "offsets",
231+
"norms": "include",
232+
"store": True,
233+
"type": "string",
234+
}
235+
},
236+
}
237+
self.assertCountEqual(index_info[index.name]["columns"], index.fields)
238+
self.assertEqual(index_info[index.name]["options"], expected_options)
239+
finally:
240+
with connection.schema_editor() as editor:
241+
editor.remove_index(index=index, model=SearchIndexTestModel)
242+
200243

201244
@skipUnlessDBFeature("supports_atlas_search")
202245
class VectorSearchIndexSchemaTests(SchemaAssertionMixin, TestCase):

0 commit comments

Comments
 (0)