Commit 531a51f7 by McKenzie Welter

Added util method to retrieve individual course_run data from Catalog

parent 53d52964
...@@ -484,9 +484,9 @@ def get_active_web_certificate(course, is_preview_mode=None): ...@@ -484,9 +484,9 @@ def get_active_web_certificate(course, is_preview_mode=None):
return None return None
def get_certificate_template(course_key, mode): def get_certificate_template(course_key, mode, language): # pylint: disable=unused-argument
""" """
Retrieves the custom certificate template based on course_key and mode. Retrieves the custom certificate template based on course_key, mode, and language.
""" """
org_id, template = None, None org_id, template = None, None
# fetch organization of the course # fetch organization of the course
......
...@@ -910,6 +910,7 @@ class CertificateGenerationCourseSetting(TimeStampedModel): ...@@ -910,6 +910,7 @@ class CertificateGenerationCourseSetting(TimeStampedModel):
defaults=default defaults=default
) )
@classmethod
def is_language_specific_templates_enabled_for_course(cls, course_key): def is_language_specific_templates_enabled_for_course(cls, course_key):
"""Check whether language-specific certificates are enabled for a course. """Check whether language-specific certificates are enabled for a course.
......
...@@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse ...@@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse
from django.test.client import Client, RequestFactory from django.test.client import Client, RequestFactory
from django.test.utils import override_settings from django.test.utils import override_settings
from util.date_utils import strftime_localized from util.date_utils import strftime_localized
from mock import patch from mock import Mock, patch
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from certificates.api import get_certificate_url from certificates.api import get_certificate_url
...@@ -884,11 +884,13 @@ class CertificatesViewsTests(CommonCertificatesTestCase): ...@@ -884,11 +884,13 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
@override_settings(LANGUAGE_CODE='fr') @override_settings(LANGUAGE_CODE='fr')
def test_certificate_custom_template_with_org_mode_course(self): @patch('certificates.views.webview.get_course_run_details')
def test_certificate_custom_template_with_org_mode_course(self, mock_get_course_run_details):
""" """
Tests custom template search and rendering. Tests custom template search and rendering.
This test should check template matching when org={org}, course={course}, mode={mode}. This test should check template matching when org={org}, course={course}, mode={mode}.
""" """
mock_get_course_run_details.return_value = {'language': 'en'}
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
self._create_custom_template(org_id=1, mode='honor', course_key=unicode(self.course.id)) self._create_custom_template(org_id=1, mode='honor', course_key=unicode(self.course.id))
self._create_custom_template(org_id=2, mode='honor') self._create_custom_template(org_id=2, mode='honor')
...@@ -913,12 +915,14 @@ class CertificatesViewsTests(CommonCertificatesTestCase): ...@@ -913,12 +915,14 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
self.assertContains(response, 'course name: course_title_0') self.assertContains(response, 'course name: course_title_0')
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
def test_certificate_custom_template_with_org(self): @patch('certificates.views.webview.get_course_run_details')
def test_certificate_custom_template_with_org(self, mock_get_course_run_details):
""" """
Tests custom template search if we have a single template for organization and mode Tests custom template search if we have a single template for organization and mode
with course set to Null. with course set to Null.
This test should check template matching when org={org}, course=Null, mode={mode}. This test should check template matching when org={org}, course=Null, mode={mode}.
""" """
mock_get_course_run_details.return_value = {'language': 'en'}
course = CourseFactory.create( course = CourseFactory.create(
org='cstX', number='cst_22', display_name='custom template course' org='cstX', number='cst_22', display_name='custom template course'
) )
...@@ -940,11 +944,13 @@ class CertificatesViewsTests(CommonCertificatesTestCase): ...@@ -940,11 +944,13 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
self.assertContains(response, 'course name: course_title_0') self.assertContains(response, 'course name: course_title_0')
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
def test_certificate_custom_template_with_organization(self): @patch('certificates.views.webview.get_course_run_details')
def test_certificate_custom_template_with_organization(self, mock_get_course_run_details):
""" """
Tests custom template search when we have a single template for a organization. Tests custom template search when we have a single template for a organization.
This test should check template matching when org={org}, course=Null, mode=null. This test should check template matching when org={org}, course=Null, mode=null.
""" """
mock_get_course_run_details.return_value = {'language': 'en'}
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
self._create_custom_template(org_id=1, mode='honor') self._create_custom_template(org_id=1, mode='honor')
self._create_custom_template(org_id=1, mode='honor', course_key=self.course.id) self._create_custom_template(org_id=1, mode='honor', course_key=self.course.id)
...@@ -962,11 +968,13 @@ class CertificatesViewsTests(CommonCertificatesTestCase): ...@@ -962,11 +968,13 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
def test_certificate_custom_template_with_course_mode(self): @patch('certificates.views.webview.get_course_run_details')
def test_certificate_custom_template_with_course_mode(self, mock_get_course_run_details):
""" """
Tests custom template search if we have a single template for a course mode. Tests custom template search if we have a single template for a course mode.
This test should check template matching when org=null, course=Null, mode={mode}. This test should check template matching when org=null, course=Null, mode={mode}.
""" """
mock_get_course_run_details.return_value = {'language': 'en'}
mode = 'honor' mode = 'honor'
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
self._create_custom_template(mode=mode) self._create_custom_template(mode=mode)
...@@ -982,10 +990,12 @@ class CertificatesViewsTests(CommonCertificatesTestCase): ...@@ -982,10 +990,12 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
self.assertContains(response, 'mode: {}'.format(mode)) self.assertContains(response, 'mode: {}'.format(mode))
@ddt.data(True, False) @ddt.data(True, False)
def test_certificate_custom_template_with_unicode_data(self, custom_certs_enabled): @patch('certificates.views.webview.get_course_run_details')
def test_certificate_custom_template_with_unicode_data(self, custom_certs_enabled, mock_get_course_run_details):
""" """
Tests custom template renders properly with unicode data. Tests custom template renders properly with unicode data.
""" """
mock_get_course_run_details.return_value = {'language': 'en'}
mode = 'honor' mode = 'honor'
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
self._create_custom_template(mode=mode) self._create_custom_template(mode=mode)
...@@ -1014,10 +1024,12 @@ class CertificatesViewsTests(CommonCertificatesTestCase): ...@@ -1014,10 +1024,12 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
self.assertContains(response, 'https://twitter.com/intent/tweet') self.assertContains(response, 'https://twitter.com/intent/tweet')
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
def test_certificate_asset_by_slug(self): @patch('certificates.views.webview.get_course_run_details')
def test_certificate_asset_by_slug(self, mock_get_course_run_details):
""" """
Tests certificate template asset display by slug using static.certificate_asset_url method. Tests certificate template asset display by slug using static.certificate_asset_url method.
""" """
mock_get_course_run_details.return_value = {'language': 'en'}
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
self._create_custom_template(mode='honor') self._create_custom_template(mode='honor')
test_url = get_certificate_url( test_url = get_certificate_url(
......
...@@ -30,6 +30,7 @@ from certificates.api import ( ...@@ -30,6 +30,7 @@ from certificates.api import (
has_html_certificates_enabled has_html_certificates_enabled
) )
from certificates.models import ( from certificates.models import (
CertificateGenerationCourseSetting,
CertificateHtmlViewConfiguration, CertificateHtmlViewConfiguration,
CertificateSocialNetworks, CertificateSocialNetworks,
CertificateStatuses, CertificateStatuses,
...@@ -39,6 +40,7 @@ from courseware.access import has_access ...@@ -39,6 +40,7 @@ from courseware.access import has_access
from courseware.courses import get_course_by_id from courseware.courses import get_course_by_id
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from edxmako.template import Template from edxmako.template import Template
from openedx.core.djangoapps.catalog.utils import get_course_run_details
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.lib.courses import course_image_url from openedx.core.lib.courses import course_image_url
from openedx.core.djangoapps.certificates.api import display_date_for_certificate from openedx.core.djangoapps.certificates.api import display_date_for_certificate
...@@ -224,7 +226,7 @@ def _update_context_with_basic_info(context, course_id, platform_name, configura ...@@ -224,7 +226,7 @@ def _update_context_with_basic_info(context, course_id, platform_name, configura
) )
def _update_course_context(request, context, course, platform_name): def _update_course_context(request, context, course, course_key, platform_name):
""" """
Updates context dictionary with course info. Updates context dictionary with course info.
""" """
...@@ -248,6 +250,11 @@ def _update_course_context(request, context, course, platform_name): ...@@ -248,6 +250,11 @@ def _update_course_context(request, context, course, platform_name):
'{partner_short_name}.').format( '{partner_short_name}.').format(
partner_short_name=context['organization_short_name'], partner_short_name=context['organization_short_name'],
platform_name=platform_name) platform_name=platform_name)
# If language specific templates are enabled for the course, add course_run specific information to the context
if CertificateGenerationCourseSetting.is_language_specific_templates_enabled_for_course(course_key):
fields = ['start', 'end', 'max_effort', 'language']
course_run_data = get_course_run_details(course_key, fields)
context.update(course_run_data)
def _update_social_context(request, context, course, user, user_certificate, platform_name): def _update_social_context(request, context, course, user, user_certificate, platform_name):
...@@ -413,7 +420,7 @@ def _render_certificate_template(request, context, course, user_certificate): ...@@ -413,7 +420,7 @@ def _render_certificate_template(request, context, course, user_certificate):
Picks appropriate certificate templates and renders it. Picks appropriate certificate templates and renders it.
""" """
if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False): if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False):
custom_template = get_certificate_template(course.id, user_certificate.mode) custom_template = get_certificate_template(course.id, user_certificate.mode, context.get('language'))
if custom_template: if custom_template:
template = Template( template = Template(
custom_template, custom_template,
...@@ -571,7 +578,7 @@ def render_html_view(request, user_id, course_id): ...@@ -571,7 +578,7 @@ def render_html_view(request, user_id, course_id):
_update_organization_context(context, course) _update_organization_context(context, course)
# Append course info # Append course info
_update_course_context(request, context, course, platform_name) _update_course_context(request, context, course, course_key, platform_name)
# Append user info # Append user info
_update_context_with_user_info(context, user, user_certificate) _update_context_with_user_info(context, user, user_certificate)
......
...@@ -118,6 +118,8 @@ class CourseRunFactory(DictFactoryBase): ...@@ -118,6 +118,8 @@ class CourseRunFactory(DictFactoryBase):
title = factory.Faker('catch_phrase') title = factory.Faker('catch_phrase')
type = 'verified' type = 'verified'
uuid = factory.Faker('uuid4') uuid = factory.Faker('uuid4')
language = 'en'
max_effort = 5
class CourseFactory(DictFactoryBase): class CourseFactory(DictFactoryBase):
......
...@@ -15,6 +15,7 @@ from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, Pr ...@@ -15,6 +15,7 @@ from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, Pr
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.catalog.utils import ( from openedx.core.djangoapps.catalog.utils import (
get_course_runs, get_course_runs,
get_course_run_details,
get_program_types, get_program_types,
get_programs, get_programs,
get_programs_with_type get_programs_with_type
...@@ -304,3 +305,31 @@ class TestGetCourseRuns(CatalogIntegrationMixin, TestCase): ...@@ -304,3 +305,31 @@ class TestGetCourseRuns(CatalogIntegrationMixin, TestCase):
self.assertTrue(mock_get_edx_api_data.called) self.assertTrue(mock_get_edx_api_data.called)
self.assert_contract(mock_get_edx_api_data.call_args) self.assert_contract(mock_get_edx_api_data.call_args)
self.assertEqual(data, catalog_course_runs) self.assertEqual(data, catalog_course_runs)
@skip_unless_lms
@mock.patch(UTILS_MODULE + '.get_edx_api_data')
class TestGetCourseRunDetails(CatalogIntegrationMixin, TestCase):
"""
Tests covering retrieval of information about a specific course run from the catalog service.
"""
def setUp(self):
super(TestGetCourseRunDetails, self).setUp()
self.catalog_integration = self.create_catalog_integration(cache_ttl=1)
self.user = UserFactory(username=self.catalog_integration.service_username)
def test_get_course_run_details(self, mock_get_edx_api_data):
"""
Test retrieval of details about a specific course run
"""
course_run = CourseRunFactory()
course_run_details = {
'language': course_run['language'],
'start': course_run['start'],
'end': course_run['end'],
'max_effort': course_run['max_effort']
}
mock_get_edx_api_data.return_value = course_run_details
data = get_course_run_details(course_run['key'], ['language', 'start', 'end', 'max_effort'])
self.assertTrue(mock_get_edx_api_data.called)
self.assertEqual(data, course_run_details)
...@@ -185,3 +185,39 @@ def get_course_runs(): ...@@ -185,3 +185,39 @@ def get_course_runs():
course_runs = get_edx_api_data(catalog_integration, 'course_runs', api=api, querystring=querystring) course_runs = get_edx_api_data(catalog_integration, 'course_runs', api=api, querystring=querystring)
return course_runs return course_runs
def get_course_run_details(course_run_key, fields):
"""
Retrieve information about the course run with the given id
Arguments:
course_run_key: key for the course_run about which we are retrieving information
Returns:
dict with language, start date, end date, and max_effort details about specified course run
"""
catalog_integration = CatalogIntegration.current()
course_run_details = dict()
if catalog_integration.enabled:
try:
user = catalog_integration.get_service_user()
except ObjectDoesNotExist:
msg = 'Catalog service user {} does not exist. Data for course_run {} will not be retrieved'.format(
catalog_integration.service_username,
course_run_key
)
logger.error(msg)
return course_run_details
api = create_catalog_api_client(user)
cache_key = '{base}.course_runs'.format(base=catalog_integration.CACHE_KEY)
course_run_details = get_edx_api_data(catalog_integration, 'course_runs', api, resource_id=course_run_key,
cache_key=cache_key, many=False, traverse_pagination=False, fields=fields)
else:
msg = 'Unable to retrieve details about course_run {} because Catalog Integration is not enabled'.format(
course_run_key
)
logger.error(msg)
return course_run_details
...@@ -15,8 +15,20 @@ from openedx.core.lib.token_utils import JwtBuilder ...@@ -15,8 +15,20 @@ from openedx.core.lib.token_utils import JwtBuilder
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def get_fields(fields, response):
"""Extracts desired fields from the API response"""
results = {}
for field in fields:
try:
results[field] = response[field]
# TODO: Determine what exception would be raised here if response does not have the specified field
except:
msg = '{resource} does not have the attribute {field}'.format(resource, field)
log.exception(msg)
def get_edx_api_data(api_config, resource, api, resource_id=None, querystring=None, cache_key=None, many=True, def get_edx_api_data(api_config, resource, api, resource_id=None, querystring=None, cache_key=None, many=True,
traverse_pagination=True): traverse_pagination=True, fields=None):
"""GET data from an edX REST API. """GET data from an edX REST API.
DRY utility for handling caching and pagination. DRY utility for handling caching and pagination.
...@@ -59,7 +71,10 @@ def get_edx_api_data(api_config, resource, api, resource_id=None, querystring=No ...@@ -59,7 +71,10 @@ def get_edx_api_data(api_config, resource, api, resource_id=None, querystring=No
response = endpoint(resource_id).get(**querystring) response = endpoint(resource_id).get(**querystring)
if resource_id is not None: if resource_id is not None:
results = response if fields:
results = get_fields(fields, response)
else:
results = response
elif traverse_pagination: elif traverse_pagination:
results = _traverse_pagination(response, endpoint, querystring, no_data) results = _traverse_pagination(response, endpoint, querystring, no_data)
else: else:
......
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