Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
06b9c24
feat: add TypeSense backend (WIP)
bradenmacdonald Aug 27, 2025
8e9f170
test: update tests for compatibility with extracted utils
bradenmacdonald Sep 9, 2025
b269ffd
feat: support indexing and filtering on NULL values
bradenmacdonald Sep 10, 2025
602e55d
feat: courseware search working
bradenmacdonald Sep 10, 2025
bc1f9aa
feat: revert all changes to Meilisearch backend
bradenmacdonald Sep 14, 2025
b2bbba1
chore: revert accidental change to python version in requirements.txt
bradenmacdonald Sep 14, 2025
109805b
refactor: simplify how we access settings
bradenmacdonald Sep 14, 2025
3f84bba
fix: excluding values now works with hundreds of values
bradenmacdonald Sep 14, 2025
4d92010
fix: validate data type for numeric range filter
bradenmacdonald Sep 14, 2025
92689a5
fix: fixup for excluding values fix
bradenmacdonald Sep 15, 2025
5ab9fb5
docs: update inline typesense docs
bradenmacdonald Sep 15, 2025
32642c1
feat: Implement faceted search for Typesense backend
bradenmacdonald Sep 15, 2025
bf4b195
docs: clarify numeric range behavior
bradenmacdonald Sep 15, 2025
b70946b
feat: enable stemming for content field
bradenmacdonald Sep 17, 2025
bcff4ae
feat: enable stemming for course info content fields too
bradenmacdonald Sep 19, 2025
34e3687
docs: add a FIXME about UTC offset datetimes
bradenmacdonald Sep 22, 2025
f63f343
fix: make "language" field optional
bradenmacdonald Oct 21, 2025
1b8601b
fix: syntax error in filter_by clause for deleting items
bradenmacdonald Oct 29, 2025
d8418ea
feat: auto-create typesense index when needed
samuelallan72 Jan 20, 2026
3ef9530
fix: remove debugging log message
samuelallan72 Jan 20, 2026
59f30f1
Merge branch 'master' into typesense
samuelallan72 Jan 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ elasticsearch>=7.8.0,<8.0.0
edx-toggles
event-tracking
meilisearch
typesense
3 changes: 3 additions & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pyyaml==6.0.3
# via code-annotations
requests==2.32.5
# via meilisearch
# typesense
six==1.17.0
# via
# edx-ccx-keys
Expand All @@ -141,6 +142,8 @@ stevedore==5.6.0
# edx-opaque-keys
text-unidecode==1.3
# via python-slugify
typesense==1.1.1
# via -r requirements/base.in
typing-extensions==4.15.0
# via
# edx-opaque-keys
Expand Down
5 changes: 5 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ requests==2.32.5
# -r requirements/quality.txt
# -r requirements/testing.txt
# meilisearch
# typesense
six==1.17.0
# via
# -r requirements/quality.txt
Expand Down Expand Up @@ -421,6 +422,10 @@ tomlkit==0.13.3
# pylint
tox==4.32.0
# via -r requirements/ci.txt
typesense==1.1.1
# via
# -r requirements/quality.txt
# -r requirements/testing.txt
typing-extensions==4.15.0
# via
# -r requirements/quality.txt
Expand Down
3 changes: 3 additions & 0 deletions requirements/quality.txt
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ requests==2.32.5
# via
# -r requirements/testing.txt
# meilisearch
# typesense
six==1.17.0
# via
# -r requirements/testing.txt
Expand All @@ -291,6 +292,8 @@ text-unidecode==1.3
# python-slugify
tomlkit==0.13.3
# via pylint
typesense==1.1.1
# via -r requirements/testing.txt
typing-extensions==4.15.0
# via
# -r requirements/testing.txt
Expand Down
3 changes: 3 additions & 0 deletions requirements/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ requests==2.32.5
# via
# -r requirements/base.txt
# meilisearch
# typesense
six==1.17.0
# via
# -r requirements/base.txt
Expand All @@ -244,6 +245,8 @@ text-unidecode==1.3
# via
# -r requirements/base.txt
# python-slugify
typesense==1.1.1
# via -r requirements/base.txt
typing-extensions==4.15.0
# via
# -r requirements/base.txt
Expand Down
114 changes: 111 additions & 3 deletions search/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"""Tests for utility functions in search.utils module."""

"""
Test for the utility functions shared by multiple search engines.
"""
import unittest

from ddt import ddt, data, unpack
import django.test
from django.utils import timezone

from search.utils import normalize_bool
from search.utils import convert_doc_datatypes, normalize_bool, restore_doc_datatypes


@ddt
Expand Down Expand Up @@ -44,3 +48,107 @@ def test_invalid_string_values(self, value):
@unpack
def test_other_types(self, value, expected):
assert normalize_bool(value) is expected


class EngineUtilsTests(django.test.TestCase):
"""
Utils tests.
"""

def test_convert_empty_document(self):
assert not convert_doc_datatypes({})

def test_convert_document_recursive(self):
"""
Test converting a document to Meilisearch/Typesense compatible format
"""
now = timezone.datetime(2025, 8, 25)
document = {
"timestamp": now,
"dict_field": {
"inner_value": timezone.datetime(2024, 1, 1),
},
}
processed = convert_doc_datatypes(document)
assert {
"timestamp": 1756080000.0,
"timestamp__utcoffset": 0,
"dict_field": {
"inner_value": 1704067200.0,
"inner_value__utcoffset": 0,
}
} == processed

def test_convert_datetime_with_tz(self):
"""
Test converting a document to Meilisearch/Typesense compatible format,
including non-UTC datetimes.
"""
# With timezone
document = {
"id": "1",
"dt": timezone.datetime(
2024,
1,
1,
tzinfo=timezone.get_fixed_timezone(timezone.timedelta(seconds=3600)),
),
}
processed = convert_doc_datatypes(document)
assert 1704063600.0 == processed["dt"]
assert 3600 == processed["dt__utcoffset"]
# reverse serialisation
reverse = restore_doc_datatypes(processed)
assert document == reverse

def test_convert_document_with_null(self):
"""
Test converting a document to Meilisearch/Typesense compatible format,
including NULL values
"""
document = {
"foo": "bar",
"count": 17,
"null_value": None,
"dict_value": {
"null_value": None,
"bar": "foo",
},
}
processed = convert_doc_datatypes(document)
assert {
"foo": "bar",
"count": 17,
# null_value removed
"dict_value": {
# null_value removed
"bar": "foo",
},
} == processed

def test_convert_document_with_null_separate(self):
"""
Test converting a document to Typesense compatible format, marking NULL
values using a separate field as recommended at
https://typesense.org/docs/guide/tips-for-searching-common-types-of-data.html#searching-for-null-or-empty-values
"""
document = {
"foo": "bar",
"count": 17,
"null_value": None,
"dict_value": {
"null_value": None,
"bar": "foo",
},
}
processed = convert_doc_datatypes(document, record_nulls=True)
assert {
"foo": "bar",
"count": 17,
"null_value__is_null": True,
"dict_value": {
"null_value__is_null": True,
"bar": "foo",
},
} == processed
assert document == restore_doc_datatypes(processed)
Loading