Use course run marketing URLs from the catalog service on program detail page
Part of ECOM-4566.
This commit is contained in:
14
openedx/core/djangoapps/catalog/tests/factories.py
Normal file
14
openedx/core/djangoapps/catalog/tests/factories.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Factories for generating fake catalog data."""
|
||||
import factory
|
||||
from factory.fuzzy import FuzzyText
|
||||
|
||||
|
||||
class CourseRun(factory.Factory):
|
||||
"""
|
||||
Factory for stubbing CourseRun resources from the catalog API.
|
||||
"""
|
||||
class Meta(object):
|
||||
model = dict
|
||||
|
||||
key = FuzzyText(prefix='org/', suffix='/run')
|
||||
marketing_url = FuzzyText(prefix='https://www.example.com/marketing/')
|
||||
120
openedx/core/djangoapps/catalog/tests/test_utils.py
Normal file
120
openedx/core/djangoapps/catalog/tests/test_utils.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Tests covering utilities for integrating with the catalog service."""
|
||||
import ddt
|
||||
from django.test import TestCase
|
||||
import mock
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.catalog import utils
|
||||
from openedx.core.djangoapps.catalog.tests import factories, mixins
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
UTILS_MODULE = 'openedx.core.djangoapps.catalog.utils'
|
||||
|
||||
|
||||
@mock.patch(UTILS_MODULE + '.get_edx_api_data')
|
||||
class TestGetCourseRun(mixins.CatalogIntegrationMixin, TestCase):
|
||||
"""Tests covering retrieval of course runs from the catalog service."""
|
||||
def setUp(self):
|
||||
super(TestGetCourseRun, self).setUp()
|
||||
|
||||
self.user = UserFactory()
|
||||
self.course_key = CourseKey.from_string('foo/bar/baz')
|
||||
self.catalog_integration = self.create_catalog_integration()
|
||||
|
||||
def assert_contract(self, call_args):
|
||||
"""Verify that API data retrieval utility is used correctly."""
|
||||
args, kwargs = call_args
|
||||
|
||||
for arg in (self.catalog_integration, self.user, 'course_runs'):
|
||||
self.assertIn(arg, args)
|
||||
|
||||
self.assertEqual(kwargs['resource_id'], unicode(self.course_key))
|
||||
self.assertEqual(kwargs['api']._store['base_url'], self.catalog_integration.internal_api_url) # pylint: disable=protected-access
|
||||
|
||||
return args, kwargs
|
||||
|
||||
def test_get_course_run(self, mock_get_catalog_data):
|
||||
course_run = factories.CourseRun()
|
||||
mock_get_catalog_data.return_value = course_run
|
||||
|
||||
data = utils.get_course_run(self.course_key, self.user)
|
||||
|
||||
self.assert_contract(mock_get_catalog_data.call_args)
|
||||
self.assertEqual(data, course_run)
|
||||
|
||||
def test_course_run_unavailable(self, mock_get_catalog_data):
|
||||
mock_get_catalog_data.return_value = []
|
||||
|
||||
data = utils.get_course_run(self.course_key, self.user)
|
||||
|
||||
self.assert_contract(mock_get_catalog_data.call_args)
|
||||
self.assertEqual(data, {})
|
||||
|
||||
def test_cache_disabled(self, mock_get_catalog_data):
|
||||
utils.get_course_run(self.course_key, self.user)
|
||||
|
||||
_, kwargs = self.assert_contract(mock_get_catalog_data.call_args)
|
||||
|
||||
self.assertIsNone(kwargs['cache_key'])
|
||||
|
||||
def test_cache_enabled(self, mock_get_catalog_data):
|
||||
catalog_integration = self.create_catalog_integration(cache_ttl=1)
|
||||
|
||||
utils.get_course_run(self.course_key, self.user)
|
||||
|
||||
_, kwargs = mock_get_catalog_data.call_args
|
||||
|
||||
self.assertEqual(kwargs['cache_key'], catalog_integration.CACHE_KEY)
|
||||
|
||||
|
||||
@mock.patch(UTILS_MODULE + '.get_course_run')
|
||||
@mock.patch(UTILS_MODULE + '.strip_querystring')
|
||||
class TestGetRunMarketingUrl(TestCase):
|
||||
"""Tests covering retrieval of course run marketing URLs."""
|
||||
def setUp(self):
|
||||
super(TestGetRunMarketingUrl, self).setUp()
|
||||
|
||||
self.course_key = CourseKey.from_string('foo/bar/baz')
|
||||
self.user = UserFactory()
|
||||
|
||||
def test_get_run_marketing_url(self, mock_strip, mock_get_course_run):
|
||||
course_run = factories.CourseRun()
|
||||
mock_get_course_run.return_value = course_run
|
||||
mock_strip.return_value = course_run['marketing_url']
|
||||
|
||||
url = utils.get_run_marketing_url(self.course_key, self.user)
|
||||
|
||||
self.assertTrue(mock_strip.called)
|
||||
self.assertEqual(url, course_run['marketing_url'])
|
||||
|
||||
def test_marketing_url_empty(self, mock_strip, mock_get_course_run):
|
||||
course_run = factories.CourseRun()
|
||||
course_run['marketing_url'] = ''
|
||||
mock_get_course_run.return_value = course_run
|
||||
|
||||
url = utils.get_run_marketing_url(self.course_key, self.user)
|
||||
|
||||
self.assertFalse(mock_strip.called)
|
||||
self.assertEqual(url, None)
|
||||
|
||||
def test_marketing_url_missing(self, mock_strip, mock_get_course_run):
|
||||
mock_get_course_run.return_value = {}
|
||||
|
||||
url = utils.get_run_marketing_url(self.course_key, self.user)
|
||||
|
||||
self.assertFalse(mock_strip.called)
|
||||
self.assertEqual(url, None)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestStripQuerystring(TestCase):
|
||||
"""Tests covering querystring stripping."""
|
||||
bare_url = 'https://www.example.com/path'
|
||||
|
||||
@ddt.data(
|
||||
bare_url,
|
||||
bare_url + '?foo=bar&baz=qux',
|
||||
)
|
||||
def test_strip_querystring(self, url):
|
||||
self.assertEqual(utils.strip_querystring(url), self.bare_url)
|
||||
69
openedx/core/djangoapps/catalog/utils.py
Normal file
69
openedx/core/djangoapps/catalog/utils.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""Helper functions for working with the catalog service."""
|
||||
from urlparse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
|
||||
from openedx.core.djangoapps.catalog.models import CatalogIntegration
|
||||
from openedx.core.lib.edx_api_utils import get_edx_api_data
|
||||
from openedx.core.lib.token_utils import JwtBuilder
|
||||
|
||||
|
||||
def get_course_run(course_key, user):
|
||||
"""Get a course run's data from the course catalog service.
|
||||
|
||||
Arguments:
|
||||
course_key (CourseKey): Course key object identifying the run whose data we want.
|
||||
user (User): The user to authenticate as when making requests to the catalog service.
|
||||
|
||||
Returns:
|
||||
dict, empty if no data could be retrieved.
|
||||
"""
|
||||
catalog_integration = CatalogIntegration.current()
|
||||
|
||||
scopes = ['email', 'profile']
|
||||
expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION
|
||||
jwt = JwtBuilder(user).build_token(scopes, expires_in)
|
||||
api = EdxRestApiClient(catalog_integration.internal_api_url, jwt=jwt)
|
||||
|
||||
data = get_edx_api_data(
|
||||
catalog_integration,
|
||||
user,
|
||||
'course_runs',
|
||||
resource_id=unicode(course_key),
|
||||
cache_key=catalog_integration.CACHE_KEY if catalog_integration.is_cache_enabled else None,
|
||||
api=api,
|
||||
)
|
||||
|
||||
return data if data else {}
|
||||
|
||||
|
||||
def get_run_marketing_url(course_key, user):
|
||||
"""Get a course run's marketing URL from the course catalog service.
|
||||
|
||||
Arguments:
|
||||
course_key (CourseKey): Course key object identifying the run whose marketing URL we want.
|
||||
user (User): The user to authenticate as when making requests to the catalog service.
|
||||
|
||||
Returns:
|
||||
string, the marketing URL, or None if no URL is available.
|
||||
"""
|
||||
course_run = get_course_run(course_key, user)
|
||||
marketing_url = course_run.get('marketing_url')
|
||||
|
||||
if marketing_url:
|
||||
# This URL may include unwanted UTM parameters in the querystring.
|
||||
# For more, see https://en.wikipedia.org/wiki/UTM_parameters.
|
||||
return strip_querystring(marketing_url)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def strip_querystring(url):
|
||||
"""Strip the querystring from the provided URL.
|
||||
|
||||
urlparse's ParseResult is a subclass of namedtuple. _replace is part of namedtuple's
|
||||
public API: https://docs.python.org/2/library/collections.html#collections.somenamedtuple._replace.
|
||||
The name starts with an underscore to prevent conflicts with field names.
|
||||
"""
|
||||
return urlparse(url)._replace(query='').geturl() # pylint: disable=no-member
|
||||
@@ -36,7 +36,8 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
CERTIFICATES_API_MODULE = 'lms.djangoapps.certificates.api'
|
||||
ECOMMERCE_URL_ROOT = 'http://example-ecommerce.com'
|
||||
ECOMMERCE_URL_ROOT = 'https://example-ecommerce.com'
|
||||
MARKETING_URL = 'https://www.example.com/marketing/path'
|
||||
|
||||
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@@ -677,6 +678,7 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
|
||||
@ddt.ddt
|
||||
@override_settings(ECOMMERCE_PUBLIC_URL_ROOT=ECOMMERCE_URL_ROOT)
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@mock.patch(UTILS_MODULE + '.get_run_marketing_url', mock.Mock(return_value=MARKETING_URL))
|
||||
class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
"""Tests of the utility function used to supplement program data."""
|
||||
maxDiff = None
|
||||
@@ -719,7 +721,7 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
is_course_ended=self.course.end < timezone.now(),
|
||||
is_enrolled=False,
|
||||
is_enrollment_open=True,
|
||||
marketing_url=None,
|
||||
marketing_url=MARKETING_URL,
|
||||
start_date=strftime_localized(self.course.start, 'SHORT_DATE'),
|
||||
upgrade_url=None,
|
||||
),
|
||||
|
||||
@@ -13,6 +13,7 @@ import pytz
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.certificates import api as certificate_api
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from openedx.core.djangoapps.catalog.utils import get_run_marketing_url
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.lib.edx_api_utils import get_edx_api_data
|
||||
@@ -392,8 +393,7 @@ def supplement_program_data(program_data, user):
|
||||
'is_course_ended': is_course_ended,
|
||||
'is_enrolled': is_enrolled,
|
||||
'is_enrollment_open': is_enrollment_open,
|
||||
# TODO: Not currently available on LMS.
|
||||
'marketing_url': None,
|
||||
'marketing_url': get_run_marketing_url(course_key, user),
|
||||
'start_date': start_date_string,
|
||||
'upgrade_url': upgrade_url,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user