Commit e7f8a592 by Nimisha Asthagiri

Sync course language: Discovery Course Catalog -> LMS Course Overview

parent f1f5a7dd
"""
Sync course runs from catalog service.
"""
from collections import namedtuple
import logging
from django.core.management.base import BaseCommand
......@@ -15,33 +16,30 @@ log = logging.getLogger(__name__)
class Command(BaseCommand):
"""
Purpose is to sync course runs data from catalog service to make it accessible in edx-platform.
It just happens to only be syncing marketing URLs from catalog course runs for now.
"""
help = 'Refresh marketing urls from catalog service.'
def update_course_overviews(self, course_runs):
"""
Refresh marketing urls for the given catalog course runs.
CourseRunField = namedtuple('CourseRunField', 'catalog_name course_overview_name')
course_run_fields = (
CourseRunField(catalog_name='marketing_url', course_overview_name='marketing_url'),
CourseRunField(catalog_name='eligible_for_financial_aid', course_overview_name='eligible_for_financial_aid'),
CourseRunField(catalog_name='content_language', course_overview_name='language'),
)
def handle(self, *args, **options):
log.info('[sync_course_runs] Fetching course runs from catalog service.')
course_runs = get_course_runs()
Arguments:
course_runs: A list containing catalog course runs.
"""
# metrics for observability
# number of catalog course runs retrieved.
catalog_course_runs_retrieved = len(course_runs)
# number of catalog course runs found in course overview.
course_runs_found_in_cache = 0
# number of course overview records actually get updated.
course_metadata_updated = 0
num_runs_found_in_catalog = len(course_runs)
num_runs_found_in_course_overview = 0
num_course_overviews_updated = 0
for course_run in course_runs:
is_course_metadata_updated = False
marketing_url = course_run['marketing_url']
eligible_for_financial_aid = course_run['eligible_for_financial_aid']
course_key = CourseKey.from_string(course_run['key'])
try:
course_overview = CourseOverview.objects.get(id=course_key)
course_runs_found_in_cache += 1
num_runs_found_in_course_overview += 1
except CourseOverview.DoesNotExist:
log.info(
'[sync_course_runs] course overview record not found for course run: %s',
......@@ -49,32 +47,25 @@ class Command(BaseCommand):
)
continue
# Check whether course overview's marketing url is outdated - this saves a db hit.
if course_overview.marketing_url != marketing_url:
course_overview.marketing_url = marketing_url
is_course_metadata_updated = True
# Check whether course overview's eligible for financial aid is outdated
if course_overview.eligible_for_financial_aid != eligible_for_financial_aid:
course_overview.eligible_for_financial_aid = eligible_for_financial_aid
is_course_metadata_updated = True
is_course_metadata_updated = False
for field in self.course_run_fields:
catalog_value = course_run.get(field.catalog_name)
if getattr(course_overview, field.course_overview_name) != catalog_value:
setattr(course_overview, field.course_overview_name, catalog_value)
is_course_metadata_updated = True
if is_course_metadata_updated:
course_overview.save()
course_metadata_updated += 1
return catalog_course_runs_retrieved, course_runs_found_in_cache, course_metadata_updated
def handle(self, *args, **options):
log.info('[sync_course_runs] Fetching course runs from catalog service.')
course_runs = get_course_runs()
course_runs_retrieved, course_runs_found, course_metadata_updated = self.update_course_overviews(course_runs)
num_course_overviews_updated += 1
log.info(
('[sync_course_runs] course runs retrieved: %d, course runs found in course overview: %d,'
' course runs not found in course overview: %d, course overviews metadata updated: %d,'),
course_runs_retrieved,
course_runs_found,
course_runs_retrieved - course_runs_found,
course_metadata_updated,
'[sync_course_runs] '
'course runs found in catalog: %d, '
'course runs found in course overview: %d, '
'course runs not found in course overview: %d, '
'course overviews updated: %d',
num_runs_found_in_catalog,
num_runs_found_in_course_overview,
num_runs_found_in_catalog - num_runs_found_in_course_overview,
num_course_overviews_updated,
)
......@@ -7,6 +7,7 @@ import mock
from django.core.management import call_command
from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory
from openedx.core.djangoapps.catalog.management.commands.sync_course_runs import Command as sync_command
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
......@@ -25,7 +26,7 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
# create mongo course
self.course = CourseFactory.create()
# load this course into course overview
CourseOverview.get_from_id(self.course.id)
self.course_overview = CourseOverview.get_from_id(self.course.id)
# create a catalog course run with the same course id.
self.catalog_course_run = CourseRunFactory(
key=unicode(self.course.id),
......@@ -33,31 +34,34 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
eligible_for_financial_aid=False
)
def get_course_overview_marketing_url(self, course_id):
"""
Get course overview marketing url.
"""
return CourseOverview.objects.get(id=course_id).marketing_url
def test_course_run_sync(self, mock_catalog_course_runs):
"""
Verify on executing management command course overview data is updated
with course run data from course discovery.
"""
mock_catalog_course_runs.return_value = [self.catalog_course_run]
earlier_marketing_url = self.get_course_overview_marketing_url(self.course.id)
course_overview = CourseOverview.objects.get(id=self.course.id)
earlier_eligible_for_financial_aid = course_overview.eligible_for_financial_aid
call_command('sync_course_runs')
course_overview.refresh_from_db()
updated_marketing_url = self.get_course_overview_marketing_url(self.course.id)
updated_eligible_for_financial_aid = course_overview.eligible_for_financial_aid
# Assert that the Marketing URL has changed.
self.assertNotEqual(earlier_marketing_url, updated_marketing_url)
self.assertNotEqual(earlier_eligible_for_financial_aid, updated_eligible_for_financial_aid)
self.assertEqual(updated_marketing_url, 'test_marketing_url')
self.assertEqual(updated_eligible_for_financial_aid, False)
updated_course_overview = CourseOverview.objects.get(id=self.course.id)
# assert fields have updated
for field in sync_command.course_run_fields:
course_overview_field_name = field.course_overview_name
catalog_field_name = field.catalog_name
previous_course_overview_value = getattr(self.course_overview, course_overview_field_name),
updated_course_overview_value = getattr(updated_course_overview, course_overview_field_name)
# course overview value matches catalog value
self.assertEqual(
updated_course_overview_value,
self.catalog_course_run.get(catalog_field_name),
)
# new value doesn't match old value
self.assertNotEqual(
updated_course_overview_value,
previous_course_overview_value,
)
@mock.patch(COMMAND_MODULE + '.log.info')
def test_course_overview_does_not_exist(self, mock_log_info, mock_catalog_course_runs):
......@@ -73,7 +77,7 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
'[sync_course_runs] course overview record not found for course run: %s',
nonexistent_course_run['key'],
)
updated_marketing_url = self.get_course_overview_marketing_url(self.course.id)
updated_marketing_url = CourseOverview.objects.get(id=self.course.id).marketing_url
self.assertEqual(updated_marketing_url, 'test_marketing_url')
@mock.patch(COMMAND_MODULE + '.log.info')
......@@ -81,17 +85,22 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
"""
Verify logging at start and end of the command.
"""
def _assert_logs(num_updates):
mock_log_info.assert_any_call('[sync_course_runs] Fetching course runs from catalog service.')
mock_log_info.assert_any_call(
'[sync_course_runs] course runs found in catalog: %d, course runs found in course overview: %d,'
' course runs not found in course overview: %d, course overviews updated: %d',
3,
1,
2,
num_updates,
)
mock_log_info.reset_mock()
mock_catalog_course_runs.return_value = [self.catalog_course_run, CourseRunFactory(), CourseRunFactory()]
call_command('sync_course_runs')
# Assert the logs at the start of the command.
mock_log_info.assert_any_call('[sync_course_runs] Fetching course runs from catalog service.')
# Assert the log metrics at it's completion.
mock_log_info.assert_any_call(
('[sync_course_runs] course runs retrieved: %d, course runs found in course overview: %d,'
' course runs not found in course overview: %d, course overviews metadata updated: %d,'),
3,
1,
2,
1,
)
_assert_logs(num_updates=1)
call_command('sync_course_runs')
_assert_logs(num_updates=0)
......@@ -17,6 +17,7 @@ from opaque_keys.edx.keys import CourseKey
from config_models.models import ConfigurationModel
from lms.djangoapps import django_comment_client
from openedx.core.djangoapps.catalog.models import CatalogIntegration
from openedx.core.djangoapps.models.course_details import CourseDetails
from static_replace.models import AssetBaseUrlConfig
from xmodule import course_metadata_utils, block_metadata_utils
......@@ -194,7 +195,8 @@ class CourseOverview(TimeStampedModel):
course_overview.course_video_url = CourseDetails.fetch_video_url(course.id)
course_overview.self_paced = course.self_paced
course_overview.language = course.language
if not CatalogIntegration.is_enabled():
course_overview.language = course.language
return course_overview
......
......@@ -17,6 +17,7 @@ from django.utils import timezone
from PIL import Image
from lms.djangoapps.certificates.api import get_active_web_certificate
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.models.course_details import CourseDetails
from openedx.core.lib.courses import course_image_url
from static_replace.models import AssetBaseUrlConfig
......@@ -40,7 +41,7 @@ from ..models import CourseOverview, CourseOverviewImageSet, CourseOverviewImage
@attr(shard=3)
@ddt.ddt
class CourseOverviewTestCase(ModuleStoreTestCase):
class CourseOverviewTestCase(CatalogIntegrationMixin, ModuleStoreTestCase):
"""
Tests for CourseOverview model.
"""
......@@ -270,6 +271,24 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
course = CourseFactory.create(default_store=modulestore_type, run="TestRun", **kwargs)
self.check_course_overview_against_course(course)
@ddt.data(True, False)
def test_language_field(self, catalog_integration_enabled):
"""
Test that the language field is not updated from the modulestore
when catalog integration is enabled. In that case, it gets updated
by the sync_course_runs management command, which synchronizes with
the Catalog service.
"""
self.create_catalog_integration(enabled=catalog_integration_enabled)
course = CourseFactory.create(language='en')
course_overview = CourseOverview.get_from_id(course.id)
if catalog_integration_enabled:
self.assertNotEqual(course_overview.language, course.language)
else:
self.assertEqual(course_overview.language, course.language)
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
def test_get_non_existent_course(self, modulestore_type):
"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment