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
4 changes: 4 additions & 0 deletions enterprise_catalog/apps/api/v1/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ class SegmentEvents:
AI_CURATIONS_TASK_COMPLETED = 'edx.server.enterprise-catalog.ai-curations.task.completed'
AI_CURATIONS_RESULTS_FOUND = 'edx.server.enterprise-catalog.ai-curations.results-found'
AI_CURATIONS_RESULTS_NOT_FOUND = 'edx.server.enterprise-catalog.ai-curations.results-not-found'


DEFAULT_TRANSLATION_LANGUAGE = 'en'
AVAILABLE_TRANSLATION_LANGUAGES = ['es']
37 changes: 36 additions & 1 deletion enterprise_catalog/apps/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
from re import findall, search

from django.db import IntegrityError, models
from django.db.models import Prefetch
from rest_framework import serializers, status

from enterprise_catalog.apps.academy.models import Academy, Tag
from enterprise_catalog.apps.api.v1.constants import (
AVAILABLE_TRANSLATION_LANGUAGES,
DEFAULT_TRANSLATION_LANGUAGE,
)
from enterprise_catalog.apps.api.v1.utils import (
get_archived_content_count,
get_enterprise_utm_context,
Expand All @@ -23,6 +28,7 @@
from enterprise_catalog.apps.catalog.models import (
CatalogQuery,
ContentMetadata,
ContentTranslation,
EnterpriseCatalog,
)
from enterprise_catalog.apps.catalog.utils import get_content_filter_hash
Expand Down Expand Up @@ -360,6 +366,7 @@ class HighlightedContentSerializer(serializers.ModelSerializer):
Serializer for the `HighlightedContent` model.
"""
aggregation_key = serializers.SerializerMethodField()
title = serializers.SerializerMethodField()

class Meta:
model = HighlightedContent
Expand All @@ -382,6 +389,22 @@ def get_aggregation_key(self, obj):
"""
return obj.aggregation_key

def get_title(self, obj):
"""
Returns the title, preferring translation if language is supported and available.
"""
lang = self.context.get('lang')
title = obj.title

if not lang or lang == DEFAULT_TRANSLATION_LANGUAGE or lang not in AVAILABLE_TRANSLATION_LANGUAGES:
return title

translations = obj.content_metadata.translations.all()
if translations and translations[0].title:
return translations[0].title

return title


class HighlightSetSerializer(serializers.ModelSerializer):
"""
Expand All @@ -406,8 +429,20 @@ def get_highlighted_content(self, obj):
"""
Returns the data for the associated content included in this HighlightSet object.
"""
lang = self.context.get('lang')

qs = obj.highlighted_content.order_by('created').select_related('content_metadata')
return HighlightedContentSerializer(qs, many=True).data

if lang in AVAILABLE_TRANSLATION_LANGUAGES and lang != DEFAULT_TRANSLATION_LANGUAGE:
# Only prefetches translations if a supported non-English language is requested.
qs = qs.prefetch_related(
Prefetch(
'content_metadata__translations',
queryset=ContentTranslation.objects.filter(language_code=lang)
)
)

return HighlightedContentSerializer(qs, many=True, context=self.context).data


class EnterpriseCurationConfigSerializer(serializers.ModelSerializer):
Expand Down
166 changes: 166 additions & 0 deletions enterprise_catalog/apps/api/v1/tests/test_curation_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from enterprise_catalog.apps.catalog.constants import COURSE, PROGRAM
from enterprise_catalog.apps.catalog.tests.factories import (
ContentMetadataFactory,
ContentTranslationFactory,
)
from enterprise_catalog.apps.curation.tests.factories import (
EnterpriseCurationConfigFactory,
Expand Down Expand Up @@ -526,6 +527,171 @@ def test_delete_not_allowed(self, is_catalog_staff, is_role_assigned_via_jwt):
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED


@ddt.ddt
class HighlightSetMultilingualTests(CurationAPITestBase):
"""
Test multilingual support in HighlightSetReadOnlyViewSet.
"""
def setUp(self):
super().setUp()

# Create Spanish translations for the first three content items
self.spanish_titles = [
'Título en Español 1',
'Título en Español 2',
'Título en Español 3',
]
self.translations = []
for idx, content_metadata in enumerate(self.highlighted_content_metadata_one[:3]):
translation = ContentTranslationFactory(
content_metadata=content_metadata,
language_code='es',
title=self.spanish_titles[idx]
)
self.translations.append(translation)

def test_list_with_spanish_language_parameter(self):
"""
Test that requesting highlight sets with lang=es returns Spanish translations.
"""
url = reverse('api:v1:highlight-sets-list') + f'?enterprise_customer={self.enterprise_uuid}&lang=es'
self.set_up_catalog_learner()

response = self.client.get(url)
assert response.status_code == status.HTTP_200_OK

highlight_sets_results = response.json()['results']
assert len(highlight_sets_results) == 1

highlighted_content = highlight_sets_results[0]['highlighted_content']
# First three should have Spanish titles
for idx in range(3):
assert highlighted_content[idx]['title'] == self.spanish_titles[idx]

# Last two should have original English titles (no translation)
for idx in range(3, 5):
original_title = self.highlighted_content_metadata_one[idx].json_metadata['title']
assert highlighted_content[idx]['title'] == original_title

def test_list_with_english_language_parameter(self):
"""
Test that requesting highlight sets with lang=en returns original English titles.
"""
url = reverse('api:v1:highlight-sets-list') + f'?enterprise_customer={self.enterprise_uuid}&lang=en'
self.set_up_catalog_learner()

response = self.client.get(url)
assert response.status_code == status.HTTP_200_OK

highlight_sets_results = response.json()['results']
highlighted_content = highlight_sets_results[0]['highlighted_content']

# All should have original English titles
for idx in range(5):
original_title = self.highlighted_content_metadata_one[idx].json_metadata['title']
assert highlighted_content[idx]['title'] == original_title

def test_list_with_unsupported_language(self):
"""
Test that requesting with an unsupported language defaults to English.
"""
url = reverse('api:v1:highlight-sets-list') + f'?enterprise_customer={self.enterprise_uuid}&lang=fr'
self.set_up_catalog_learner()

response = self.client.get(url)
assert response.status_code == status.HTTP_200_OK

highlight_sets_results = response.json()['results']
highlighted_content = highlight_sets_results[0]['highlighted_content']

# All should have original English titles (default behavior)
for idx in range(5):
original_title = self.highlighted_content_metadata_one[idx].json_metadata['title']
assert highlighted_content[idx]['title'] == original_title

def test_list_without_language_parameter(self):
"""
Test that requesting highlight sets without lang parameter defaults to English.
"""
url = reverse('api:v1:highlight-sets-list') + f'?enterprise_customer={self.enterprise_uuid}'
self.set_up_catalog_learner()

response = self.client.get(url)
assert response.status_code == status.HTTP_200_OK

highlight_sets_results = response.json()['results']
highlighted_content = highlight_sets_results[0]['highlighted_content']

# All should have original English titles (default behavior)
for idx in range(5):
original_title = self.highlighted_content_metadata_one[idx].json_metadata['title']
assert highlighted_content[idx]['title'] == original_title

def test_detail_with_spanish_language_parameter(self):
"""
Test that retrieving a specific highlight set with lang=es returns Spanish translations.
"""
detail_url = reverse('api:v1:highlight-sets-detail', kwargs={'uuid': str(self.highlight_set_one.uuid)})
detail_url += '?lang=es'
self.set_up_catalog_learner()

response = self.client.get(detail_url)
assert response.status_code == status.HTTP_200_OK

highlighted_content = response.json()['highlighted_content']

# First three should have Spanish titles
for idx in range(3):
assert highlighted_content[idx]['title'] == self.spanish_titles[idx]

# Last two should have original English titles (no translation)
for idx in range(3, 5):
original_title = self.highlighted_content_metadata_one[idx].json_metadata['title']
assert highlighted_content[idx]['title'] == original_title

def test_detail_with_unsupported_language(self):
"""
Test that requesting with an unsupported language defaults to English.
"""
detail_url = reverse('api:v1:highlight-sets-detail', kwargs={'uuid': str(self.highlight_set_one.uuid)})
detail_url += '?lang=fr' # French is not in AVAILABLE_TRANSLATION_LANGUAGES
self.set_up_catalog_learner()

response = self.client.get(detail_url)
assert response.status_code == status.HTTP_200_OK

highlighted_content = response.json()['highlighted_content']

# All should have original English titles (default behavior)
for idx in range(5):
original_title = self.highlighted_content_metadata_one[idx].json_metadata['title']
assert highlighted_content[idx]['title'] == original_title

def test_translation_with_empty_title(self):
"""
Test that if a translation exists but has an empty title, it falls back to the original.
"""
# Create a translation with empty title
content_metadata = self.highlighted_content_metadata_one[4]
ContentTranslationFactory(
content_metadata=content_metadata,
language_code='es',
title='' # Empty title
)

detail_url = reverse('api:v1:highlight-sets-detail', kwargs={'uuid': str(self.highlight_set_one.uuid)})
detail_url += '?lang=es'
self.set_up_catalog_learner()

response = self.client.get(detail_url)
assert response.status_code == status.HTTP_200_OK

highlighted_content = response.json()['highlighted_content']
# Should fall back to original title when translation title is empty
original_title = self.highlighted_content_metadata_one[4].json_metadata['title']
assert highlighted_content[4]['title'] == original_title


@ddt.ddt
class HighlightSetViewSetTests(CurationAPITestBase):
"""
Expand Down
Loading