diff --git a/invenio_records_resources/resources/records/args.py b/invenio_records_resources/resources/records/args.py index 7f62174d..6dc1c02b 100644 --- a/invenio_records_resources/resources/records/args.py +++ b/invenio_records_resources/resources/records/args.py @@ -9,6 +9,8 @@ """Schemas for parameter parsing.""" +from invenio_i18n import get_locale + from flask_resources.parsers import MultiDictSchema from marshmallow import fields, post_load, validate @@ -29,3 +31,9 @@ def facets(self, data, original_data=None, **kwargs): for k in set(original_data.keys()) - set(data.keys()): data["facets"][k] = original_data.getlist(k) return data + + @post_load + def inject_locale(self, data, **kwargs): + """Inject locale from request context into the args.""" + data["locale"] = str(get_locale()) + return data diff --git a/invenio_records_resources/services/records/params/querystr.py b/invenio_records_resources/services/records/params/querystr.py index b67833ab..4668911c 100644 --- a/invenio_records_resources/services/records/params/querystr.py +++ b/invenio_records_resources/services/records/params/querystr.py @@ -38,7 +38,7 @@ def apply(self, identity, search, params): raise QuerystringValidationError("Invalid 'suggest' parameter.") if query_str: - query = parser_cls(identity).parse(query_str) + query = parser_cls(identity).parse(query_str, locale=params.get("locale")) search = search.query(query) return search diff --git a/invenio_records_resources/services/records/queryparser/query.py b/invenio_records_resources/services/records/queryparser/query.py index 9c685f59..3b9b60b5 100644 --- a/invenio_records_resources/services/records/queryparser/query.py +++ b/invenio_records_resources/services/records/queryparser/query.py @@ -80,6 +80,7 @@ def __init__(self, identity=None, extra_params=None, tree_transformer_cls=None): # fields is not removed from extra params since if given it must be # used in both querystring and multi match self._fields = self.extra_params.get("fields") or [] + self._dynamic_fields = self.extra_params.pop("dynamic_fields", None) or [] @property def allow_list(self): @@ -117,8 +118,12 @@ def factory(cls, tree_transformer_cls=None, **extra_params): tree_transformer_cls=tree_transformer_cls, ) - def parse(self, query_str): + def parse(self, query_str, locale=None): """Parse the query.""" + if locale and self.extra_params.get("fields", None): + self.extra_params["fields"] += [ + field.format(locale=locale) for field in self._dynamic_fields + ] try: # We parse the Lucene query syntax in Python, so we know upfront # if the syntax is correct before executing it in the search engine diff --git a/invenio_records_resources/services/records/queryparser/suggest.py b/invenio_records_resources/services/records/queryparser/suggest.py index 6510832d..aeac39af 100644 --- a/invenio_records_resources/services/records/queryparser/suggest.py +++ b/invenio_records_resources/services/records/queryparser/suggest.py @@ -53,6 +53,10 @@ def __init__(self, identity=None, extra_params=None, **kwargs): super().__init__(identity=identity, extra_params=extra_params) self.extra_params.setdefault("type", "bool_prefix") - def parse(self, query_str): + def parse(self, query_str, locale=None): """Parse the query.""" + if locale and self.extra_params.get("fields", None): + self.extra_params["fields"] += [ + field.format(locale=locale) for field in self._dynamic_fields + ] return dsl.Q("multi_match", query=query_str, **self.extra_params) diff --git a/tests/resources/test_resource_faceting.py b/tests/resources/test_resource_faceting.py index 787feb8d..2ac8f967 100644 --- a/tests/resources/test_resource_faceting.py +++ b/tests/resources/test_resource_faceting.py @@ -279,7 +279,7 @@ def test_links_keep_facets(client, headers, three_indexed_records): response_links = response.json["links"] expected_links = { "self": ( - "https://127.0.0.1:5000/api/mocks?" + "https://127.0.0.1:5000/api/mocks?locale=en&" "page=1&size=25&sort=newest&type=A%2A%2AB" ), } @@ -293,11 +293,11 @@ def test_links_keep_repeated_facets(client, headers, three_indexed_records): response_links = response.json["links"] expected_links = { "self": ( - "https://127.0.0.1:5000/api/mocks?page=1&size=1&sort=newest" + "https://127.0.0.1:5000/api/mocks?locale=en&page=1&size=1&sort=newest" "&type=B&type=A" ), "next": ( - "https://127.0.0.1:5000/api/mocks?page=2&size=1&sort=newest" + "https://127.0.0.1:5000/api/mocks?locale=en&page=2&size=1&sort=newest" "&type=B&type=A" ), } diff --git a/tests/resources/test_resource_pagination.py b/tests/resources/test_resource_pagination.py index ca16db23..c91e2baa 100644 --- a/tests/resources/test_resource_pagination.py +++ b/tests/resources/test_resource_pagination.py @@ -111,9 +111,9 @@ def test_middle_search_result_has_next_and_prev_links( response_links = response.json["links"] expected_links = { - "self": "https://127.0.0.1:5000/api/mocks?page=2&size=1&sort=newest", - "prev": "https://127.0.0.1:5000/api/mocks?page=1&size=1&sort=newest", - "next": "https://127.0.0.1:5000/api/mocks?page=3&size=1&sort=newest", + "self": "https://127.0.0.1:5000/api/mocks?locale=en&page=2&size=1&sort=newest", + "prev": "https://127.0.0.1:5000/api/mocks?locale=en&page=1&size=1&sort=newest", + "next": "https://127.0.0.1:5000/api/mocks?locale=en&page=3&size=1&sort=newest", } # NOTE: This is done so that we only test for pagination links @@ -129,8 +129,8 @@ def test_first_search_result_has_next_and_no_prev_link( response_links = response.json["links"] expected_links = { - "self": "https://127.0.0.1:5000/api/mocks?page=1&size=1&sort=newest", - "next": "https://127.0.0.1:5000/api/mocks?page=2&size=1&sort=newest", + "self": "https://127.0.0.1:5000/api/mocks?locale=en&page=1&size=1&sort=newest", + "next": "https://127.0.0.1:5000/api/mocks?locale=en&page=2&size=1&sort=newest", } for key, url in expected_links.items(): assert url == response_links[key] @@ -145,8 +145,8 @@ def test_last_search_result_has_prev_link_and_no_next_link( response_links = response.json["links"] expected_links = { - "self": "https://127.0.0.1:5000/api/mocks?page=3&size=1&sort=newest", - "prev": "https://127.0.0.1:5000/api/mocks?page=2&size=1&sort=newest", + "self": "https://127.0.0.1:5000/api/mocks?locale=en&page=3&size=1&sort=newest", + "prev": "https://127.0.0.1:5000/api/mocks?locale=en&page=2&size=1&sort=newest", } for key, url in expected_links.items(): assert url == response_links[key] @@ -161,8 +161,8 @@ def test_beyond_last_search_has_prev_link_and_no_next_link( response_links = response.json["links"] expected_links = { - "self": "https://127.0.0.1:5000/api/mocks?page=4&size=1&sort=newest", - "prev": "https://127.0.0.1:5000/api/mocks?page=3&size=1&sort=newest", + "self": "https://127.0.0.1:5000/api/mocks?locale=en&page=4&size=1&sort=newest", + "prev": "https://127.0.0.1:5000/api/mocks?locale=en&page=3&size=1&sort=newest", } for key, url in expected_links.items(): assert url == response_links[key] @@ -177,7 +177,7 @@ def test_beyond_beyond_last_search_has_no_prev_or_next_link( response_links = response.json["links"] expected_links = { - "self": "https://127.0.0.1:5000/api/mocks?page=5&size=1&sort=newest", + "self": "https://127.0.0.1:5000/api/mocks?locale=en&page=5&size=1&sort=newest", } for key, url in expected_links.items(): assert url == response_links[key] @@ -192,15 +192,15 @@ def test_searchstring_is_preserved(client, headers, three_indexed_records): response_links = response.json["links"] expected_links = { "self": ( - "https://127.0.0.1:5000/api/mocks?page=2&q=test%20foo&size=1" + "https://127.0.0.1:5000/api/mocks?locale=en&page=2&q=test%20foo&size=1" "&sort=bestmatch" ), "prev": ( - "https://127.0.0.1:5000/api/mocks?page=1&q=test%20foo&size=1" + "https://127.0.0.1:5000/api/mocks?locale=en&page=1&q=test%20foo&size=1" "&sort=bestmatch" ), "next": ( - "https://127.0.0.1:5000/api/mocks?page=3&q=test%20foo&size=1" + "https://127.0.0.1:5000/api/mocks?locale=en&page=3&q=test%20foo&size=1" "&sort=bestmatch" ), } diff --git a/tests/resources/test_resource_sorting.py b/tests/resources/test_resource_sorting.py index a0ac66eb..3c794bd1 100644 --- a/tests/resources/test_resource_sorting.py +++ b/tests/resources/test_resource_sorting.py @@ -102,8 +102,12 @@ def test_sort_in_links_no_matter_if_sort_in_url(client, headers, three_indexed_r response_links = response.json["links"] expected_links = { - "self": ("https://127.0.0.1:5000/api/mocks?page=1&size=1&sort=newest"), - "next": ("https://127.0.0.1:5000/api/mocks?page=2&size=1&sort=newest"), + "self": ( + "https://127.0.0.1:5000/api/mocks?locale=en&page=1&size=1&sort=newest" + ), + "next": ( + "https://127.0.0.1:5000/api/mocks?locale=en&page=2&size=1&sort=newest" + ), } # NOTE: This is done so that we only test for pagination links for key, url in expected_links.items(): @@ -113,8 +117,12 @@ def test_sort_in_links_no_matter_if_sort_in_url(client, headers, three_indexed_r response_links = response.json["links"] expected_links = { - "self": ("https://127.0.0.1:5000/api/mocks?page=1&size=1&sort=newest"), - "next": ("https://127.0.0.1:5000/api/mocks?page=2&size=1&sort=newest"), + "self": ( + "https://127.0.0.1:5000/api/mocks?locale=en&page=1&size=1&sort=newest" + ), + "next": ( + "https://127.0.0.1:5000/api/mocks?locale=en&page=2&size=1&sort=newest" + ), } for key, url in expected_links.items(): assert url == response_links[key] @@ -126,7 +134,7 @@ def test_searchstring_is_preserved(client, headers, three_indexed_records): response_links = response.json["links"] expected_links = { "self": ( - "https://127.0.0.1:5000/api/mocks?page=1&q=the%20quick&size=25" + "https://127.0.0.1:5000/api/mocks?locale=en&page=1&q=the%20quick&size=25" "&sort=newest" ), }