✨(backend) handle title extension
Find returns titles with a language extension
ex:
{ title.fr: "rapport d'activité" }
instead of
{ "title": "rapport d'activité" }
Signed-off-by: charles <[email protected]>
This commit is contained in:
@@ -8,7 +8,6 @@ from functools import cache
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.models import Subquery
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
import requests
|
||||
@@ -193,7 +192,7 @@ class BaseDocumentIndexer(ABC):
|
||||
Returns ids of the documents
|
||||
|
||||
Args:
|
||||
q (str): Text search content.
|
||||
q (str): user query.
|
||||
token (str): OIDC Authentication token.
|
||||
visited (list, optional):
|
||||
List of ids of active public documents with LinkTrace
|
||||
@@ -202,7 +201,7 @@ class BaseDocumentIndexer(ABC):
|
||||
The number of results to return.
|
||||
Defaults to 50 if not specified.
|
||||
path (str, optional):
|
||||
The path to filter documents.
|
||||
The parent path to search descendants of.
|
||||
"""
|
||||
nb_results = nb_results or self.search_limit
|
||||
results = self.search_query(
|
||||
@@ -229,11 +228,57 @@ class BaseDocumentIndexer(ABC):
|
||||
"""
|
||||
|
||||
|
||||
class SearchIndexer(BaseDocumentIndexer):
|
||||
class FindDocumentIndexer(BaseDocumentIndexer):
|
||||
"""
|
||||
Document indexer that pushes documents to La Suite Find app.
|
||||
Document indexer that indexes and searches documents with La Suite Find app.
|
||||
"""
|
||||
|
||||
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
||||
def search(self, q, token, visited=(), nb_results=None, path=None):
|
||||
"""format Find search results"""
|
||||
search_results = super().search(q, token, visited, nb_results, path)
|
||||
return [
|
||||
{
|
||||
**hit["_source"],
|
||||
"id": hit["_id"],
|
||||
"title": self.get_title(hit["_source"]),
|
||||
}
|
||||
for hit in search_results
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_title(source):
|
||||
"""
|
||||
Find returns the titles with an extension depending on the language.
|
||||
This function extracts the title in a generic way.
|
||||
|
||||
Handles multiple cases:
|
||||
- Localized title fields like "title.<some_extension>"
|
||||
- Fallback to plain "title" field if localized version not found
|
||||
- Returns empty string if no title field exists
|
||||
|
||||
Args:
|
||||
source (dict): The _source dictionary from a search hit
|
||||
|
||||
Returns:
|
||||
str: The extracted title or empty string if not found
|
||||
|
||||
Example:
|
||||
>>> get_title({"title.fr": "Bonjour", "id": 1})
|
||||
"Bonjour"
|
||||
>>> get_title({"title": "Hello", "id": 1})
|
||||
"Hello"
|
||||
>>> get_title({"id": 1})
|
||||
""
|
||||
"""
|
||||
titles = utils.get_value_by_pattern(source, r"^title\.")
|
||||
for title in titles:
|
||||
if title:
|
||||
return title
|
||||
if "title" in source:
|
||||
return source["title"]
|
||||
return ""
|
||||
|
||||
def serialize_document(self, document, accesses):
|
||||
"""
|
||||
Convert a Document to the JSON format expected by La Suite Find.
|
||||
|
||||
@@ -633,3 +633,51 @@ def test_services_search_indexers_search_nb_results(mock_post, indexer_settings)
|
||||
|
||||
assert args[0] == indexer_settings.SEARCH_INDEXER_QUERY_URL
|
||||
assert kwargs.get("json")["nb_results"] == 109
|
||||
|
||||
|
||||
def test_search_indexer_get_title_with_localized_field():
|
||||
"""Test extracting title from localized title field."""
|
||||
source = {"title.extension": "Bonjour", "id": 1, "content": "test"}
|
||||
result = SearchIndexer.get_title(source)
|
||||
|
||||
assert result == "Bonjour"
|
||||
|
||||
|
||||
def test_search_indexer_get_title_with_multiple_localized_fields():
|
||||
"""Test that first matching localized title is returned."""
|
||||
source = {"title.extension": "Bonjour", "title.en": "Hello", "id": 1}
|
||||
result = SearchIndexer.get_title(source)
|
||||
|
||||
assert result in ["Bonjour", "Hello"]
|
||||
|
||||
|
||||
def test_search_indexer_get_title_fallback_to_plain_title():
|
||||
"""Test fallback to plain 'title' field when no localized field exists."""
|
||||
source = {"title": "Hello World", "id": 1}
|
||||
result = SearchIndexer.get_title(source)
|
||||
|
||||
assert result == "Hello World"
|
||||
|
||||
|
||||
def test_search_indexer_get_title_no_title_field():
|
||||
"""Test that empty string is returned when no title field exists."""
|
||||
source = {"id": 1, "content": "test"}
|
||||
result = SearchIndexer.get_title(source)
|
||||
|
||||
assert result == ""
|
||||
|
||||
|
||||
def test_search_indexer_get_title_with_empty_localized_title():
|
||||
"""Test that fallback works when localized title is empty."""
|
||||
source = {"title.extension": "", "title": "Fallback Title", "id": 1}
|
||||
result = SearchIndexer.get_title(source)
|
||||
|
||||
assert result == "Fallback Title"
|
||||
|
||||
|
||||
def test_search_indexer_get_title_with_multiple_extension():
|
||||
"""Test extracting title from title field with multiple extensions."""
|
||||
source = {"title.extension_1.extension_2": "Bonjour", "id": 1, "content": "test"}
|
||||
result = SearchIndexer.get_title(source)
|
||||
|
||||
assert result == "Bonjour"
|
||||
|
||||
@@ -205,3 +205,38 @@ def test_utils_users_sharing_documents_with_empty_result():
|
||||
|
||||
cached_data = cache.get(cache_key)
|
||||
assert cached_data == {}
|
||||
|
||||
|
||||
def test_utils_get_value_by_pattern_matching_key():
|
||||
"""Test extracting value from a dictionary with a matching key pattern."""
|
||||
data = {"title.extension": "Bonjour", "id": 1, "content": "test"}
|
||||
result = utils.get_value_by_pattern(data, r"^title\.")
|
||||
|
||||
assert set(result) == {"Bonjour"}
|
||||
|
||||
|
||||
def test_utils_get_value_by_pattern_multiple_matches():
|
||||
"""Test that all matching keys are returned."""
|
||||
data = {"title.extension_1": "Bonjour", "title.extension_2": "Hello", "id": 1}
|
||||
result = utils.get_value_by_pattern(data, r"^title\.")
|
||||
|
||||
assert set(result) == {
|
||||
"Bonjour",
|
||||
"Hello",
|
||||
}
|
||||
|
||||
|
||||
def test_utils_get_value_by_pattern_multiple_extensions():
|
||||
"""Test that all matching keys are returned."""
|
||||
data = {"title.extension_1.extension_2": "Bonjour", "id": 1}
|
||||
result = utils.get_value_by_pattern(data, r"^title\.")
|
||||
|
||||
assert set(result) == {"Bonjour"}
|
||||
|
||||
|
||||
def test_utils_get_value_by_pattern_no_match():
|
||||
"""Test that empty list is returned when no key matches the pattern."""
|
||||
data = {"name": "Test", "id": 1}
|
||||
result = utils.get_value_by_pattern(data, r"^title\.")
|
||||
|
||||
assert result == []
|
||||
|
||||
@@ -18,6 +18,27 @@ from core import enums, models
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_value_by_pattern(data, pattern):
|
||||
"""
|
||||
Get all values from keys matching a regex pattern in a dictionary.
|
||||
|
||||
Args:
|
||||
data (dict): Source dictionary to search
|
||||
pattern (str): Regex pattern to match against keys
|
||||
|
||||
Returns:
|
||||
list: List of values for all matching keys, empty list if no matches
|
||||
|
||||
Example:
|
||||
>>> get_value_by_pattern({"title.fr": "Bonjour", "id": 1}, r"^title\\.")
|
||||
["Bonjour"]
|
||||
>>> get_value_by_pattern({"title.fr": "Bonjour", "title.en": "Hello"}, r"^title\\.")
|
||||
["Bonjour", "Hello"]
|
||||
"""
|
||||
regex = re.compile(pattern)
|
||||
return [value for key, value in data.items() if regex.match(key)]
|
||||
|
||||
|
||||
def get_ancestor_to_descendants_map(paths, steplen):
|
||||
"""
|
||||
Given a list of document paths, return a mapping of ancestor_path -> set of descendant_paths.
|
||||
|
||||
Reference in New Issue
Block a user