Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 30 additions & 0 deletions geonode/documents/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from geonode.base.populate_test_data import create_models
from geonode.base.enumerations import SOURCE_TYPE_REMOTE
from geonode.documents.models import Document
from geonode.metadata.models import SparseField
from geonode.security.registry import permissions_registry

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -622,3 +623,32 @@ def test_either_path_or_url_doc(self):
actual = self.client.post(self.url, data=payload, format="json")
self.assertEqual(400, actual.status_code)
self.assertDictEqual(expected, actual.json())

def test_documents_api_include_i18n(self):
"""
Ensure that the ?include_i18n parameter returns all localized fields in the Documents API response.
"""

_document = Document.objects.first()
# Manually create sparse fields for the document to test prefetching/serialization
SparseField.objects.create(resource=_document, name="title_multilang_en", value="English Doc Title")
SparseField.objects.create(resource=_document, name="title_multilang_it", value="Titolo Documento Italiano")

url = reverse("documents-detail", kwargs={"pk": _document.pk})

with override_settings(
LANGUAGES=[("en", "English"), ("it", "Italiano")],
MULTILANG_FIELDS=["title"],
):
query_params = "include_i18n=true&lang=it"
response = self.client.get(f"{url}?{query_params}", format="json")

self.assertEqual(response.status_code, 200)
data = response.data["document"]

self.assertEqual(data["title"], "Titolo Documento Italiano")

self.assertIn("title_en", data)
self.assertEqual(data["title_en"], "English Doc Title")
self.assertIn("title_it", data)
self.assertEqual(data["title_it"], "Titolo Documento Italiano")
31 changes: 31 additions & 0 deletions geonode/layers/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from geonode.base.populate_test_data import create_models, create_single_dataset
from geonode.layers.models import Attribute, Dataset
from geonode.maps.models import Map, MapLayer
from geonode.metadata.models import SparseField

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -692,3 +693,33 @@ def test_download_api(self):
self.assertEqual(download_url_data["default"], True)
self.assertEqual(download_url_data["ajax_safe"], False)
self.assertEqual(download_url_data["url"], "https://myoriginal.org")

def test_datasets_api_include_i18n(self):
"""
Ensure that the ?include_i18n parameter returns all localized fields in the API response.
"""

_dataset = Dataset.objects.first()

# Manually create the sparse fields required for the test
SparseField.objects.create(resource=_dataset, name="title_multilang_en", value="English Title")
SparseField.objects.create(resource=_dataset, name="title_multilang_it", value="Titolo Italiano")

url = reverse("datasets-detail", kwargs={"pk": _dataset.pk})

with override_settings(
LANGUAGES=[("en", "English"), ("it", "Italiano")],
MULTILANG_FIELDS=["title"],
):
query_params = "include_i18n=true&lang=it"
response = self.client.get(f"{url}?{query_params}", format="json")

self.assertEqual(response.status_code, 200)
data = response.data["dataset"]

self.assertEqual(data["title"], "Titolo Italiano")

self.assertIn("title_en", data)
self.assertEqual(data["title_en"], "English Title")
self.assertIn("title_it", data)
self.assertEqual(data["title_it"], "Titolo Italiano")
27 changes: 22 additions & 5 deletions geonode/metadata/multilang/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.conf import settings
from rest_framework import serializers

from geonode.metadata.multilang import utils as multi


logger = logging.getLogger(__name__)

Expand All @@ -12,12 +14,27 @@ class MultiLangOutputMixin(serializers.BaseSerializer):
def to_representation(self, instance):

representation = super().to_representation(instance)
request = self.context.get("request")
params = getattr(request, "query_params", None) or getattr(request, "GET", {})
include_i18n = params.get("include_i18n", "false").lower() == "true" if request else False
target_lang = multi.get_language(request)

if settings.MULTILANG_FIELDS and hasattr(instance, "_multilang_sparse_prefetch"):
for sparse in instance._multilang_sparse_prefetch:
base_field_name = sparse.name[:-13] # name_multilang_xx
if base_field_name in representation:
logger.debug(f"setting into {instance} field {base_field_name} --> {sparse.value}")
representation[base_field_name] = sparse.value
multilang_field_map = multi.get_all_multilang_fields()

sparse_value_map = {sparse.name: sparse.value for sparse in instance._multilang_sparse_prefetch}

for (base_field_name, lang_code), sparse_field_name in multilang_field_map.items():

if sparse_field_name not in sparse_value_map:
continue

value = sparse_value_map[sparse_field_name]

if lang_code == target_lang:
representation[base_field_name] = value

if include_i18n:
representation[f"{base_field_name}_{lang_code}"] = value

return representation
15 changes: 13 additions & 2 deletions geonode/metadata/multilang/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,26 @@ def get_default_language():

def get_language(request):
if request:
language = request.query_params.get("lang", None) # explicit query param
params = getattr(request, "query_params", None) or getattr(request, "GET", {})
language = params.get("lang", None)

if not language:
language = getattr(request, "LANGUAGE_CODE", None) # LocaleMiddleware
if not language:
language = request.headers.get("Accept-Language", "").split(",")[0]
headers = getattr(request, "headers", {})
language = headers.get("Accept-Language", "").split(",")[0]
else:
language = get_default_language()

if language and "-" in language:
language = language.split("-")[0] # normalize

return language


def get_all_multilang_fields():
return {
(field, lang): get_multilang_field_name(field, lang)
for field in settings.MULTILANG_FIELDS
for lang in get_2letters_languages()
}
15 changes: 12 additions & 3 deletions geonode/metadata/multilang/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@ class MultiLangViewMixin:

def get_queryset(self):
"""Adds a Prefetch to include localized fields in one query."""
lang = multi.get_language(getattr(self, "request", None))
field_names = multi.get_multilang_fields_for_lang(lang)

request = getattr(self, "request", None)
params = getattr(request, "query_params", None) or getattr(request, "GET", {})
include_i18n = params.get("include_i18n", "false").lower() == "true" if request else False
lang = multi.get_language(request)

if include_i18n:
field_map = multi.get_all_multilang_fields()
field_names = list(field_map.values())
else:
field_names = multi.get_multilang_fields_for_lang(lang)

qs = super().get_queryset()

if not field_names:
return qs

# Prefetch all localized rows for this language
# Prefetch the localized rows
prefetch = Prefetch(
"sparsefield_set", # this must match related_name on FK
queryset=SparseField.objects.filter(name__in=field_names),
Expand Down
17 changes: 17 additions & 0 deletions geonode/metadata/tests/test_multilang.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,23 @@ def test_default_preserialization(self):
self.assertEqual("title_fake", instance["title_multilang_it"])
self.assertIsNone(instance["title_multilang_en"])

def test_get_all_multilang_fields_structure(self):
"""
Verify the new dictionary structure and ensure it identifies
all configured field/language combinations.
"""
with override_settings(
LANGUAGES=[("en", "English"), ("it", "Italiano")],
MULTILANG_FIELDS=["title", "abstract"],
):
field_map = multi.get_all_multilang_fields()

self.assertIsInstance(field_map, dict)
self.assertEqual(len(field_map), 4)

self.assertEqual(field_map[("title", "en")], "title_multilang_en")
self.assertEqual(field_map[("abstract", "it")], "abstract_multilang_it")

@patch("geonode.base.models.ResourceBase.get_real_instance_class")
@patch("geonode.indexing.manager.TSVectorIndexManager.update_index")
@patch("geonode.metadata.handlers.sparse.SparseHandler.update_resource")
Expand Down
Loading