Skip to content

Commit

Permalink
temp: just a stash
Browse files Browse the repository at this point in the history
  • Loading branch information
zawan-ila committed Feb 24, 2025
1 parent a14b8af commit 2f72958
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 19 deletions.
21 changes: 21 additions & 0 deletions course_discovery/apps/core/api_client/lms.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,24 @@ def get_course_run_translations(self, course_run_id: str):
except RequestException as e:
logger.exception(f'Failed to fetch translation data for course run [{course_run_id}]: {e}')
return {}

def get_course_run_translations_and_transcriptions(self, course_run_id: str):
"""
Get translation and transcription information for a given course run.
Args:
course_run_id (str): The course run ID to fetch translation information for.
Returns:
dict: A dictionary containing the relevant information or an empty dict on error.
"""
resource = settings.LMS_API_URLS['translations_and_transcriptions']
resource_url = urljoin(self.lms_url, resource)

try:
response = self.client.get(resource_url, params={'course_id': course_run_id})
response.raise_for_status()
return response.json()
except RequestException as e:
logger.exception(f'Failed to fetch data for course run [{course_run_id}]: {e}')
return {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"""
Unit tests for the `update_course_ai_languages` management command.
"""
import datetime
from unittest.mock import patch

from django.core.management import CommandError, call_command
from django.test import TestCase
from django.utils.timezone import now

from course_discovery.apps.course_metadata.models import CourseRun, CourseRunType
from course_discovery.apps.course_metadata.tests.factories import CourseRunFactory, PartnerFactory, SeatFactory


@patch('course_discovery.apps.core.api_client.lms.LMSAPIClient.get_course_run_translations')
class UpdateCourseAiTranslationsTests(TestCase):
"""Test Suite for the update_course_ai_translations management command."""

AI_LANGS_DATA = {
'available_translation_languages': [
{'code': 'fr', 'label': 'French'},
{'code': 'es', 'label': 'Spanish'}
],
'feature_enabled': True
}

AI_LANGS_DATA_WITH_TRANSCRIPTIONS = {
**AI_LANGS_DATA,
'transcription_languages': ['en', 'gf']
}

def setUp(self):
self.partner = PartnerFactory()
self.course_run = CourseRunFactory()

def test_update_course_run_translations(self, mock_get_translations):
"""Test the command with a valid course run and translation data."""
mock_get_translations.return_value = self.TRANSLATION_DATA

call_command('update_course_ai_translations', partner=self.partner.name)

course_run = CourseRun.objects.get(id=self.course_run.id)
self.assertListEqual(
course_run.translation_languages,
self.TRANSLATION_DATA['available_translation_languages']
)

def test_update_course_run_translations_draft(self, mock_get_translations):
"""
Test the command with both draft and non-draft course runs, ensuring that the both draft and non-draft
course runs are updated with the available translation languages.
"""
mock_get_translations.return_value = self.TRANSLATION_DATA
draft_course_run = CourseRunFactory(
draft=True, end=now() + datetime.timedelta(days=10)
)
course_run = CourseRunFactory(draft=False, draft_version_id=draft_course_run.id)

call_command("update_course_ai_translations", partner=self.partner.name)

course_run.refresh_from_db()
self.assertListEqual(
course_run.translation_languages, self.TRANSLATION_DATA["available_translation_languages"],
)

draft_course_run.refresh_from_db()
self.assertListEqual(
draft_course_run.translation_languages, self.TRANSLATION_DATA["available_translation_languages"],
)

def test_command_with_no_translations(self, mock_get_translations):
"""Test the command when no translations are returned for a course run."""
mock_get_translations.return_value = {
**self.TRANSLATION_DATA,
'available_translation_languages': [],
'feature_enabled': False
}

call_command('update_course_ai_translations', partner=self.partner.name)

course_run = CourseRun.objects.get(id=self.course_run.id)
self.assertListEqual(course_run.translation_languages, [])

def test_command_with_active_flag(self, mock_get_translations):
"""Test the command with the active flag filtering active course runs."""
mock_get_translations.return_value = {
**self.TRANSLATION_DATA,
'available_translation_languages': [{'code': 'fr', 'label': 'French'}]
}

active_course_run = CourseRunFactory(end=now() + datetime.timedelta(days=10))
non_active_course_run = CourseRunFactory(end=now() - datetime.timedelta(days=10), translation_languages=[])

call_command('update_course_ai_translations', partner=self.partner.name, active=True)

active_course_run.refresh_from_db()
non_active_course_run.refresh_from_db()

self.assertListEqual(
active_course_run.translation_languages,
[{'code': 'fr', 'label': 'French'}]
)
self.assertListEqual(non_active_course_run.translation_languages, [])

def test_command_with_marketable_flag(self, mock_get_translations):
"""Test the command with the marketable flag filtering marketable course runs."""
mock_get_translations.return_value = {
**self.TRANSLATION_DATA,
'available_translation_languages': [{'code': 'es', 'label': 'Spanish'}]
}

verified_and_audit_type = CourseRunType.objects.get(slug='verified-audit')
verified_and_audit_type.is_marketable = True
verified_and_audit_type.save()

marketable_course_run = CourseRunFactory(
status='published',
slug='test-course-run',
type=verified_and_audit_type
)
seat = SeatFactory(course_run=marketable_course_run)
marketable_course_run.seats.add(seat)

call_command('update_course_ai_translations', partner=self.partner.name, marketable=True)

marketable_course_run.refresh_from_db()
self.assertListEqual(
marketable_course_run.translation_languages,
[{'code': 'es', 'label': 'Spanish'}]
)

def test_command_with_marketable_and_active_flag(self, mock_get_translations):
"""Test the command with the marketable and active flag filtering both marketable and active course runs."""
mock_get_translations.return_value = {
**self.TRANSLATION_DATA,
'available_translation_languages': [{'code': 'fr', 'label': 'French'}]
}

non_active_non_marketable_course_run = CourseRunFactory(
end=now() - datetime.timedelta(days=10), translation_languages=[])
active_non_marketable_course_run = CourseRunFactory(end=now() + datetime.timedelta(days=10))

verified_and_audit_type = CourseRunType.objects.get(slug='verified-audit')
verified_and_audit_type.is_marketable = True
verified_and_audit_type.save()

marketable_non_active_course_run = CourseRunFactory(
status='published',
slug='test-course-run',
type=verified_and_audit_type,
end=now() - datetime.timedelta(days=10), translation_languages=[]
)
seat = SeatFactory(course_run=marketable_non_active_course_run)
marketable_non_active_course_run.seats.add(seat)

call_command('update_course_ai_translations', partner=self.partner.name, marketable=True, active=True)

marketable_non_active_course_run.refresh_from_db()
self.assertListEqual(
marketable_non_active_course_run.translation_languages,
[{'code': 'fr', 'label': 'French'}]
)
self.assertListEqual(
active_non_marketable_course_run.translation_languages,
[{'code': 'fr', 'label': 'French'}]
)
self.assertListEqual(non_active_non_marketable_course_run.translation_languages, [])

def test_command_no_partner(self, _):
"""Test the command raises an error if no valid partner is found."""
with self.assertRaises(CommandError):
call_command('update_course_ai_translations', partner='nonexistent-partner')
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
Management command to fetch translation and transcription information from the LMS and update the CourseRun model.
"""

import logging

from django.conf import settings
from django.core.management.base import BaseCommand, CommandError

from course_discovery.apps.core.api_client.lms import LMSAPIClient
from course_discovery.apps.course_metadata.models import CourseRun, Partner

logger = logging.getLogger(__name__)


class Command(BaseCommand):
help = 'Fetches Content AI Translations and Transcriptions metadata from the LMS and updates the CourseRun model in Discovery.'

def add_arguments(self, parser):
parser.add_argument(
'--partner',
type=str,
default=settings.DEFAULT_PARTNER_ID,
help='Specify the partner name or ID to fetch translations for. '
'Defaults to the partner configured in settings.DEFAULT_PARTNER_ID.',
)
parser.add_argument(
'--active',
action='store_true',
default=False,
help='Only update translations for active course runs. Defaults to False.',
)
parser.add_argument(
'--marketable',
action='store_true',
default=False,
help='Only update translations for marketable course runs. Defaults to False.',
)

def handle(self, *args, **options):
"""
Example usage: ./manage.py update_course_ai_languages --partner=edx --active --marketable
"""
partner_identifier = options.get('partner')
partner = Partner.objects.filter(name__iexact=partner_identifier).first()

if not partner:
raise CommandError('No partner object found. Ensure that the Partner data is correctly configured.')

lms_api_client = LMSAPIClient(partner)

course_runs = CourseRun.objects.all()

if options['active'] and options['marketable']:
course_runs = course_runs.marketable().union(course_runs.active())
elif options['active']:
course_runs = course_runs.active()
elif options['marketable']:
course_runs = course_runs.marketable()

for course_run in course_runs:
try:
translation_data = lms_api_client.get_course_run_translations_and_transcriptions(course_run.key)
available_translation_languages = (
translation_data.get('available_translation_languages', [])
if translation_data.get('feature_enabled', False)
else []
)
available_transcription_languages = translation_data.get('transcription_languages', [])

# Remove any keys other than `code` and `label`
available_translation_languages = [{'code': lang['code'], 'label': lang['label']} for lang in available_translation_languages]

# Add the labels for the codes. Currently we set the code as the label. We will be fixing this shortly
available_transcription_languages = [{'code': lang, 'label': lang} for lang in available_transcription_languages]

course_run.ai_languages = {
"translation_languages": available_translation_languages,
"transcription_languages": available_transcription_languages
}
course_run.save(update_fields=["ai_languages"])

if course_run.draft_version:
course_run.draft_version.ai_languages = course_run.ai_languages
course_run.draft_version.save(update_fields=["ai_languages"])
logger.info(f'Updated ai languages for {course_run.key} (both draft and non-draft versions)')
else:
logger.info(f'Updated ai languages for {course_run.key} (non-draft version only)')
except Exception as e: # pylint: disable=broad-except
logger.error(f'Error processing {course_run.key}: {e}')
Original file line number Diff line number Diff line change
Expand Up @@ -61,35 +61,18 @@ def handle(self, *args, **options):
for course_run in course_runs:
try:
translation_data = lms_api_client.get_course_run_translations(course_run.key)
available_translation_languages = (

course_run.translation_languages = (
translation_data.get('available_translation_languages', [])
if translation_data.get('feature_enabled', False)
else []
)
available_transcription_languages = translation_data.get('transcription_languages', [])

# Remove any keys other than `code` and `label`
available_translation_languages = [{'code': lang['code'], 'label': lang['label']} for lang in available_translation_languages]

# Add the labels for the codes. Currently we set the code as the label. We will be fixing this shortly
available_transcription_languages = [{'code': lang, 'label': lang} for lang in available_transcription_languages]

course_run.translation_languages = available_translation_languages
course_run.save(update_fields=["translation_languages"])

course_run.ai_languages = {
"translation_languages": available_translation_languages,
"transcription_languages": available_transcription_languages
}
course_run.save(update_fields=["ai_languages"])

if course_run.draft_version:
course_run.draft_version.translation_languages = course_run.translation_languages
course_run.draft_version.save(update_fields=["translation_languages"])
logger.info(f'Updated translations for {course_run.key} (both draft and non-draft versions)')

course_run.draft_version.ai_languages = course_run.ai_languages
course_run.draft_version.save(update_fields=["ai_languages"])
else:
logger.info(f'Updated translations for {course_run.key} (non-draft version only)')
except Exception as e: # pylint: disable=broad-except
Expand Down

0 comments on commit 2f72958

Please sign in to comment.