From 9cd7c93223d7bcbd9f770c98c64f7b1aa4de7046 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Tue, 14 Jun 2016 17:09:07 -0400 Subject: [PATCH] Link program listing cards to detail pages When program detail pages are enabled, cards on the listing page will link to their respective detail pages. Includes extensive cleanup of program listing tests. ECOM-4227. --- common/djangoapps/student/tests/tests.py | 2 +- .../learner_dashboard/tests/test_programs.py | 502 ++++++++++-------- lms/djangoapps/learner_dashboard/views.py | 23 +- .../learner_dashboard/models/program_model.js | 2 +- .../program_card_view_spec.js | 4 +- .../empty_programs_list.underscore | 1 - .../learner_dashboard/program_card.underscore | 7 +- .../djangoapps/credentials/tests/factories.py | 2 +- .../credentials/tests/test_utils.py | 6 +- openedx/core/djangoapps/programs/utils.py | 21 + 10 files changed, 322 insertions(+), 248 deletions(-) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index a16101e928..4094a456ec 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -891,7 +891,7 @@ class AnonymousLookupTable(ModuleStoreTestCase): self.assertEqual(anonymous_id, anonymous_id_for_user(self.user, course2.id, save=False)) -# TODO: Clean up these tests so that they use the ProgramsDataMixin. +# TODO: Clean up these tests so that they use program factories. @attr('shard_3') @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @ddt.ddt diff --git a/lms/djangoapps/learner_dashboard/tests/test_programs.py b/lms/djangoapps/learner_dashboard/tests/test_programs.py index 5843c0b91c..835cf2f45e 100644 --- a/lms/djangoapps/learner_dashboard/tests/test_programs.py +++ b/lms/djangoapps/learner_dashboard/tests/test_programs.py @@ -1,268 +1,324 @@ +# -*- coding: utf-8 -*- """ Unit tests covering the program listing and detail pages. """ -import datetime import json +import re import unittest from urlparse import urljoin from bs4 import BeautifulSoup from django.conf import settings from django.core.urlresolvers import reverse -from django.test import override_settings, TestCase +from django.test import override_settings +from django.utils.text import slugify from edx_oauth2_provider.tests.factories import ClientFactory import httpretty -from opaque_keys.edx import locator from provider.constants import CONFIDENTIAL from openedx.core.djangoapps.credentials.models import CredentialsApiConfig from openedx.core.djangoapps.credentials.tests import factories as credentials_factories -from openedx.core.djangoapps.credentials.tests.mixins import CredentialsDataMixin, CredentialsApiConfigMixin +from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin from openedx.core.djangoapps.programs.models import ProgramsApiConfig -from openedx.core.djangoapps.programs.tests import factories -from openedx.core.djangoapps.programs.tests.mixins import ( - ProgramsApiConfigMixin, - ProgramsDataMixin) -from student.models import CourseEnrollment -from student.tests.factories import UserFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase +from openedx.core.djangoapps.programs.tests import factories as programs_factories +from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin +from openedx.core.djangoapps.programs.utils import get_display_category +from student.tests.factories import UserFactory, CourseEnrollmentFactory +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory +@httpretty.activate +@override_settings(MKTG_URLS={'ROOT': 'https://www.example.com'}) @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') -@override_settings(MKTG_URLS={'ROOT': 'http://edx.org'}) -class TestProgramListing( - ModuleStoreTestCase, - ProgramsApiConfigMixin, - ProgramsDataMixin, - CredentialsDataMixin, - CredentialsApiConfigMixin): - - """ - Unit tests for getting the list of programs enrolled by a logged in user - """ - PASSWORD = 'test' +class TestProgramListing(ProgramsApiConfigMixin, CredentialsApiConfigMixin, SharedModuleStoreTestCase): + """Unit tests for the program listing page.""" + maxDiff = None + password = 'test' url = reverse('program_listing_view') - def setUp(self): - """ - Add a student - """ - super(TestProgramListing, self).setUp() - ClientFactory(name=CredentialsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) - ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) - self.student = UserFactory() + @classmethod + def setUpClass(cls): + super(TestProgramListing, cls).setUpClass() - def _create_course_and_enroll(self, student, org, course, run): - """ - Creates a course and associated enrollment. - """ - course_location = locator.CourseLocator(org, course, run) - course = CourseFactory.create( - org=course_location.org, - number=course_location.course, - run=course_location.run + for name in [ProgramsApiConfig.OAUTH2_CLIENT_NAME, CredentialsApiConfig.OAUTH2_CLIENT_NAME]: + ClientFactory(name=name, client_type=CONFIDENTIAL) + + cls.course = CourseFactory() + organization = programs_factories.Organization() + run_mode = programs_factories.RunMode(course_key=unicode(cls.course.id)) # pylint: disable=no-member + course_code = programs_factories.CourseCode(run_modes=[run_mode]) + + cls.first_program = programs_factories.Program( + organizations=[organization], + course_codes=[course_code] + ) + cls.second_program = programs_factories.Program( + organizations=[organization], + course_codes=[course_code] ) - enrollment = CourseEnrollment.enroll(student, course.id) - enrollment.created = datetime.datetime(2000, 12, 31, 0, 0, 0, 0) - enrollment.save() - def _get_program_url(self, marketing_slug): - """ - Helper function to get the program card url - """ - return urljoin( - settings.MKTG_URLS.get('ROOT'), - 'xseries' + '/{}' - ).format(marketing_slug) + cls.data = sorted([cls.first_program, cls.second_program], key=cls.program_sort_key) - def _setup_and_get_program(self): - """ - The core function to setup the mock program api, - then call the django test client to get the actual program listing page - make sure the request suceeds and make sure x_series_url is on the page - """ - self.mock_programs_api() - self.client.login(username=self.student.username, password=self.PASSWORD) - response = self.client.get(self.url) - x_series_url = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries') - self.assertContains(response, x_series_url) - return response + cls.marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries').rstrip('/') - def _get_program_checklist(self, program_id): - """ - The convenience function to get all the program related page element we would like to check against - """ - return [ - self.PROGRAM_NAMES[program_id], - self._get_program_url(self.PROGRAMS_API_RESPONSE['results'][program_id]['marketing_slug']), - self.PROGRAMS_API_RESPONSE['results'][program_id]['organizations'][0]['display_name'], - ] + def setUp(self): + super(TestProgramListing, self).setUp() - def _assert_progress_data_present(self, response): - """Verify that progress data is present.""" - self.assertContains(response, 'userProgress') + self.user = UserFactory() + self.client.login(username=self.user.username, password=self.password) - @httpretty.activate - def test_get_program_with_no_enrollment(self): + @classmethod + def program_sort_key(cls, program): + """ + Helper function used to sort dictionaries representing programs. + """ + return program['id'] + + def credential_sort_key(self, credential): + """ + Helper function used to sort dictionaries representing credentials. + """ + try: + return credential['certificate_url'] + except KeyError: + return credential['credential_url'] + + def mock_programs_api(self, data): + """Helper for mocking out Programs API URLs.""" + self.assertTrue(httpretty.is_enabled(), msg='httpretty must be enabled to mock Programs API calls.') + + url = ProgramsApiConfig.current().internal_api_url.strip('/') + '/programs/' + body = json.dumps({'results': data}) + + httpretty.register_uri(httpretty.GET, url, body=body, content_type='application/json') + + def mock_credentials_api(self, data): + """Helper for mocking out Credentials API URLs.""" + self.assertTrue(httpretty.is_enabled(), msg='httpretty must be enabled to mock Credentials API calls.') + + url = '{base}/user_credentials/?username={username}'.format( + base=CredentialsApiConfig.current().internal_api_url.strip('/'), + username=self.user.username + ) + body = json.dumps({'results': data}) + + httpretty.register_uri(httpretty.GET, url, body=body, content_type='application/json') + + def load_serialized_data(self, response, key): + """ + Extract and deserialize serialized data from the response. + """ + pattern = re.compile(r'{key}: (?P\[.*\])'.format(key=key)) + match = pattern.search(response.content) + serialized = match.group('data') + + return json.loads(serialized) + + def assert_dict_contains_subset(self, superset, subset): + """ + Verify that the dict superset contains the dict subset. + + Works like assertDictContainsSubset, deprecated since Python 3.2. + See: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertDictContainsSubset. + """ + superset_keys = set(superset.keys()) + subset_keys = set(subset.keys()) + intersection = {key: superset[key] for key in superset_keys & subset_keys} + + self.assertEqual(subset, intersection) + + def test_login_required(self): + """ + Verify that login is required to access the page. + """ self.create_programs_config() - response = self._setup_and_get_program() - for program_element in self._get_program_checklist(0): - self.assertNotContains(response, program_element) - for program_element in self._get_program_checklist(1): - self.assertNotContains(response, program_element) + self.mock_programs_api(self.data) - @httpretty.activate - def test_get_one_program(self): - self.create_programs_config() - self._create_course_and_enroll(self.student, *self.COURSE_KEYS[0].split('/')) - response = self._setup_and_get_program() - for program_element in self._get_program_checklist(0): - self.assertContains(response, program_element) - for program_element in self._get_program_checklist(1): - self.assertNotContains(response, program_element) + self.client.logout() - self._assert_progress_data_present(response) - - @httpretty.activate - def test_get_both_program(self): - self.create_programs_config() - self._create_course_and_enroll(self.student, *self.COURSE_KEYS[0].split('/')) - self._create_course_and_enroll(self.student, *self.COURSE_KEYS[5].split('/')) - response = self._setup_and_get_program() - for program_element in self._get_program_checklist(0): - self.assertContains(response, program_element) - for program_element in self._get_program_checklist(1): - self.assertContains(response, program_element) - - self._assert_progress_data_present(response) - - def test_get_programs_dashboard_not_enabled(self): - self.create_programs_config(program_listing_enabled=False) - self.client.login(username=self.student.username, password=self.PASSWORD) response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - def test_xseries_advertise_disabled(self): - self.create_programs_config(xseries_ad_enabled=False) - self.client.login(username=self.student.username, password=self.PASSWORD) - response = self.client.get(self.url) - x_series_url = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries') - self.assertNotContains(response, x_series_url) - - def test_get_programs_not_logged_in(self): - self.create_programs_config() - response = self.client.get(self.url) - self.assertRedirects( response, '{}?next={}'.format(reverse('signin_user'), self.url) ) - def _expected_progam_credentials_data(self): + self.client.login(username=self.user.username, password=self.password) + + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + + def test_404_if_disabled(self): """ - Dry method for getting expected program credentials response data. + Verify that the page 404s if disabled. """ - return [ - credentials_factories.UserCredential( - id=1, - username='test', - credential=credentials_factories.ProgramCredential() - ), - credentials_factories.UserCredential( - id=2, - username='test', - credential=credentials_factories.ProgramCredential() + self.create_programs_config(program_listing_enabled=False) + + response = self.client.get(self.url) + self.assertEqual(response.status_code, 404) + + def test_empty_state(self): + """ + Verify that the response contains no programs data when no programs are engaged. + """ + self.create_programs_config() + self.mock_programs_api(self.data) + + response = self.client.get(self.url) + self.assertContains(response, 'programsData: []') + + def test_programs_listed(self): + """ + Verify that the response contains accurate programs data when programs are engaged. + """ + self.create_programs_config() + self.mock_programs_api(self.data) + + CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # pylint: disable=no-member + + response = self.client.get(self.url) + actual = self.load_serialized_data(response, 'programsData') + actual = sorted(actual, key=self.program_sort_key) + + for index, actual_program in enumerate(actual): + expected_program = self.data[index] + + self.assert_dict_contains_subset(actual_program, expected_program) + self.assertEqual( + actual_program['display_category'], + get_display_category(expected_program) ) - ] - def _expected_credentials_data(self): - """ Dry method for getting expected credentials.""" - program_credentials_data = self._expected_progam_credentials_data() - return [ - { - 'display_name': self.PROGRAMS_API_RESPONSE['results'][0]['name'], - 'subtitle': self.PROGRAMS_API_RESPONSE['results'][0]['subtitle'], - 'credential_url':program_credentials_data[0]['certificate_url'] - }, - { - 'display_name': self.PROGRAMS_API_RESPONSE['results'][1]['name'], - 'subtitle':self.PROGRAMS_API_RESPONSE['results'][1]['subtitle'], - 'credential_url':program_credentials_data[1]['certificate_url'] - } - ] + def test_toggle_xseries_advertising(self): + """ + Verify that when XSeries advertising is disabled, no link to the marketing site + appears in the response (and vice versa). + """ + # Verify the URL is present when advertising is enabled. + self.create_programs_config() + self.mock_programs_api(self.data) - @httpretty.activate - def test_get_xseries_certificates_with_data(self): + response = self.client.get(self.url) + self.assertContains(response, self.marketing_root) + # Verify the URL is missing when advertising is disabled. + self.create_programs_config(xseries_ad_enabled=False) + + response = self.client.get(self.url) + self.assertNotContains(response, self.marketing_root) + + def test_links_to_detail_pages(self): + """ + Verify that links to detail pages are present when enabled, instead of + links to the marketing site. + """ + self.create_programs_config() + self.mock_programs_api(self.data) + + CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # pylint: disable=no-member + + response = self.client.get(self.url) + actual = self.load_serialized_data(response, 'programsData') + actual = sorted(actual, key=self.program_sort_key) + + for index, actual_program in enumerate(actual): + expected_program = self.data[index] + + base = reverse('program_details_view', args=[expected_program['id']]).rstrip('/') + slug = slugify(expected_program['name']) + self.assertEqual( + actual_program['detail_url'], + '{}/{}'.format(base, slug) + ) + + # Verify that links to the marketing site are present when detail pages are disabled. + self.create_programs_config(program_details_enabled=False) + + response = self.client.get(self.url) + actual = self.load_serialized_data(response, 'programsData') + actual = sorted(actual, key=self.program_sort_key) + + for index, actual_program in enumerate(actual): + expected_program = self.data[index] + + self.assertEqual( + actual_program['detail_url'], + '{}/{}'.format(self.marketing_root, expected_program['marketing_slug']) + ) + + def test_certificates_listed(self): + """ + Verify that the response contains accurate certificate data when certificates are available. + """ self.create_programs_config() self.create_credentials_config(is_learner_issuance_enabled=True) - self.client.login(username=self.student.username, password=self.PASSWORD) + self.mock_programs_api(self.data) - # mock programs and credentials apis - self.mock_programs_api() - self.mock_credentials_api(self.student, data=self.CREDENTIALS_API_RESPONSE, reset_url=False) + first_credential = credentials_factories.UserCredential( + username=self.user.username, + credential=credentials_factories.ProgramCredential( + program_id=self.first_program['id'] + ) + ) + second_credential = credentials_factories.UserCredential( + username=self.user.username, + credential=credentials_factories.ProgramCredential( + program_id=self.second_program['id'] + ) + ) - response = self.client.get(reverse("program_listing_view")) - for certificate in self._expected_credentials_data(): - self.assertContains(response, certificate['display_name']) - self.assertContains(response, certificate['credential_url']) + credentials_data = sorted([first_credential, second_credential], key=self.credential_sort_key) - self.assertContains(response, 'images/xseries-certificate-visual.png') + self.mock_credentials_api(credentials_data) - @httpretty.activate - def test_get_xseries_certificates_without_data(self): + response = self.client.get(self.url) + actual = self.load_serialized_data(response, 'certificatesData') + actual = sorted(actual, key=self.credential_sort_key) - self.create_programs_config() - self.create_credentials_config(is_learner_issuance_enabled=True) + for index, actual_credential in enumerate(actual): + expected_credential = credentials_data[index] - self.client.login(username=self.student.username, password=self.PASSWORD) - - # mock programs and credentials apis - self.mock_programs_api() - self.mock_credentials_api(self.student, data={"results": []}, reset_url=False) - - response = self.client.get(reverse("program_listing_view")) - for certificate in self._expected_credentials_data(): - self.assertNotContains(response, certificate['display_name']) - self.assertNotContains(response, certificate['credential_url']) + self.assertEqual( + # TODO: certificate_url is needlessly transformed to credential_url. (╯°□°)╯︵ ┻━┻ + # Clean this up! + actual_credential['credential_url'], + expected_credential['certificate_url'] + ) @httpretty.activate -@override_settings(MKTG_URLS={'ROOT': 'http://edx.org'}) +@override_settings(MKTG_URLS={'ROOT': 'https://www.example.com'}) @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase): - """ - Unit tests for the program details page - """ + """Unit tests for the program details page.""" program_id = 123 password = 'test' + url = reverse('program_details_view', args=[program_id]) @classmethod def setUpClass(cls): super(TestProgramDetails, cls).setUpClass() - cls.course = CourseFactory() + + ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) + + course = CourseFactory() + organization = programs_factories.Organization() + run_mode = programs_factories.RunMode(course_key=unicode(course.id)) # pylint: disable=no-member + course_code = programs_factories.CourseCode(run_modes=[run_mode]) + + cls.data = programs_factories.Program( + organizations=[organization], + course_codes=[course_code] + ) def setUp(self): super(TestProgramDetails, self).setUp() - self.details_page = reverse('program_details_view', args=[self.program_id]) - self.user = UserFactory() self.client.login(username=self.user.username, password=self.password) - ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) - - self.organization = factories.Organization() - self.run_mode = factories.RunMode(course_key=unicode(self.course.id)) # pylint: disable=no-member - self.course_code = factories.CourseCode(run_modes=[self.run_mode]) - self.data = factories.Program( - organizations=[self.organization], - course_codes=[self.course_code] - ) - - def _mock_programs_api(self, data, status=200): + def mock_programs_api(self, data, status=200): """Helper for mocking out Programs API URLs.""" self.assertTrue(httpretty.is_enabled(), msg='httpretty must be enabled to mock Programs API calls.') @@ -281,15 +337,15 @@ class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase): content_type='application/json', ) - def _assert_program_data_present(self, response): + def assert_program_data_present(self, response): """Verify that program data is present.""" self.assertContains(response, 'programData') self.assertContains(response, 'urls') self.assertContains(response, 'program_listing_url') self.assertContains(response, self.data['name']) - self._assert_programs_tab_present(response) + self.assert_programs_tab_present(response) - def _assert_programs_tab_present(self, response): + def assert_programs_tab_present(self, response): """Verify that the programs tab is present in the nav.""" soup = BeautifulSoup(response.content, 'html.parser') self.assertTrue( @@ -301,20 +357,20 @@ class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase): Verify that login is required to access the page. """ self.create_programs_config() - self._mock_programs_api(self.data) + self.mock_programs_api(self.data) self.client.logout() - response = self.client.get(self.details_page) + response = self.client.get(self.url) self.assertRedirects( response, - '{}?next={}'.format(reverse('signin_user'), self.details_page) + '{}?next={}'.format(reverse('signin_user'), self.url) ) self.client.login(username=self.user.username, password=self.password) - response = self.client.get(self.details_page) - self._assert_program_data_present(response) + response = self.client.get(self.url) + self.assert_program_data_present(response) def test_404_if_disabled(self): """ @@ -322,33 +378,33 @@ class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase): """ self.create_programs_config(program_details_enabled=False) - response = self.client.get(self.details_page) - self.assertEquals(response.status_code, 404) - - def test_page_routing(self): - """Verify that the page can be hit with or without a program name in the URL.""" - self.create_programs_config() - self._mock_programs_api(self.data) - - response = self.client.get(self.details_page) - self._assert_program_data_present(response) - - response = self.client.get(self.details_page + 'program_name/') - self._assert_program_data_present(response) - - response = self.client.get(self.details_page + 'program_name/invalid/') - self.assertEquals(response.status_code, 404) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 404) def test_404_if_no_data(self): """Verify that the page 404s if no program data is found.""" self.create_programs_config() - self._mock_programs_api(self.data, status=404) - response = self.client.get(self.details_page) - self.assertEquals(response.status_code, 404) + self.mock_programs_api(self.data, status=404) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 404) httpretty.reset() - self._mock_programs_api({}) - response = self.client.get(self.details_page) - self.assertEquals(response.status_code, 404) + self.mock_programs_api({}) + response = self.client.get(self.url) + self.assertEqual(response.status_code, 404) + + def test_page_routing(self): + """Verify that the page can be hit with or without a program name in the URL.""" + self.create_programs_config() + self.mock_programs_api(self.data) + + response = self.client.get(self.url) + self.assert_program_data_present(response) + + response = self.client.get(self.url + 'program_name/') + self.assert_program_data_present(response) + + response = self.client.get(self.url + 'program_name/invalid/') + self.assertEqual(response.status_code, 404) diff --git a/lms/djangoapps/learner_dashboard/views.py b/lms/djangoapps/learner_dashboard/views.py index 9b210d8e5c..4ba5cab92c 100644 --- a/lms/djangoapps/learner_dashboard/views.py +++ b/lms/djangoapps/learner_dashboard/views.py @@ -21,28 +21,26 @@ from lms.djangoapps.learner_dashboard.utils import ( @require_GET def view_programs(request): """View programs in which the user is engaged.""" - show_program_listing = ProgramsApiConfig.current().show_program_listing - if not show_program_listing: + programs_config = ProgramsApiConfig.current() + if not programs_config.show_program_listing: raise Http404 meter = utils.ProgramProgressMeter(request.user) programs = meter.engaged_programs # TODO: Pull 'xseries' string from configuration model. - marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries').strip('/') + marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries').rstrip('/') + for program in programs: + program['detail_url'] = utils.get_program_detail_url(program, marketing_root) program['display_category'] = utils.get_display_category(program) - program['marketing_url'] = '{root}/{slug}'.format( - root=marketing_root, - slug=program['marketing_slug'] - ) context = { 'programs': programs, 'progress': meter.progress, - 'xseries_url': marketing_root if ProgramsApiConfig.current().show_xseries_ad else None, + 'xseries_url': marketing_root if programs_config.show_xseries_ad else None, 'nav_hidden': True, - 'show_program_listing': show_program_listing, + 'show_program_listing': programs_config.show_program_listing, 'credentials': get_programs_credentials(request.user, category='xseries'), 'disable_courseware_js': True, 'uses_pattern_library': True @@ -55,8 +53,8 @@ def view_programs(request): @require_GET def program_details(request, program_id): """View details about a specific program.""" - show_program_details = ProgramsApiConfig.current().show_program_details - if not show_program_details: + programs_config = ProgramsApiConfig.current() + if not programs_config.show_program_details: raise Http404 program_data = utils.get_programs(request.user, program_id=program_id) @@ -65,7 +63,6 @@ def program_details(request, program_id): raise Http404 program_data = utils.supplement_program_data(program_data, request.user) - show_program_listing = ProgramsApiConfig.current().show_program_listing urls = { 'program_listing_url': reverse('program_listing_view'), @@ -77,7 +74,7 @@ def program_details(request, program_id): context = { 'program_data': program_data, 'urls': urls, - 'show_program_listing': show_program_listing, + 'show_program_listing': programs_config.show_program_listing, 'nav_hidden': True, 'disable_courseware_js': True, 'uses_pattern_library': True diff --git a/lms/static/js/learner_dashboard/models/program_model.js b/lms/static/js/learner_dashboard/models/program_model.js index 0cfefbf9ab..cd5e5811ee 100644 --- a/lms/static/js/learner_dashboard/models/program_model.js +++ b/lms/static/js/learner_dashboard/models/program_model.js @@ -15,7 +15,7 @@ type: data.display_category + ' Program', subtitle: data.subtitle, organizations: data.organizations, - marketingUrl: data.marketing_url, + detailUrl: data.detail_url, smallBannerUrl: data.banner_image_urls.w348h116, mediumBannerUrl: data.banner_image_urls.w435h145, largeBannerUrl: data.banner_image_urls.w726h242, diff --git a/lms/static/js/spec/learner_dashboard/program_card_view_spec.js b/lms/static/js/spec/learner_dashboard/program_card_view_spec.js index 34b95c2cc3..e179a8e1fb 100644 --- a/lms/static/js/spec/learner_dashboard/program_card_view_spec.js +++ b/lms/static/js/spec/learner_dashboard/program_card_view_spec.js @@ -28,7 +28,7 @@ define([ modified: '2016-03-25T13:45:21.220732Z', marketing_slug: 'p_2?param=haha&test=b', id: 146, - marketing_url: 'http://www.edx.org/xseries/p_2?param=haha&test=b', + detail_url: 'http://courses.edx.org/dashboard/programs/1/foo', banner_image_urls: { w348h116: 'http://www.edx.org/images/test1', w435h145: 'http://www.edx.org/images/test2', @@ -55,7 +55,7 @@ define([ expect($card.find('.title').html().trim()).toEqual(program.name); expect($card.find('.category span').html().trim()).toEqual('XSeries Program'); expect($card.find('.organization').html().trim()).toEqual(program.organizations[0].key); - expect($card.find('.card-link').attr('href')).toEqual(program.marketing_url); + expect($card.find('.card-link').attr('href')).toEqual(program.detail_url); }; beforeEach(function() { diff --git a/lms/templates/learner_dashboard/empty_programs_list.underscore b/lms/templates/learner_dashboard/empty_programs_list.underscore index 2215da497d..77a233ab97 100644 --- a/lms/templates/learner_dashboard/empty_programs_list.underscore +++ b/lms/templates/learner_dashboard/empty_programs_list.underscore @@ -5,4 +5,3 @@ <%- gettext('Explore XSeries Programs') %> - diff --git a/lms/templates/learner_dashboard/program_card.underscore b/lms/templates/learner_dashboard/program_card.underscore index 6387f52216..1c21e7ce51 100644 --- a/lms/templates/learner_dashboard/program_card.underscore +++ b/lms/templates/learner_dashboard/program_card.underscore @@ -1,4 +1,3 @@ - <% } %> - +