diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 986a97f3c4..0566460dd1 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -933,7 +933,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): _id = 0 for course, program_status in data: - programs[unicode(course)] = { + programs[unicode(course)] = [{ 'id': _id, 'category': self.category, 'organization': {'display_name': 'Test Organization 1', 'key': 'edX'}, @@ -958,7 +958,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): ], 'subtitle': 'sub', 'name': self.program_name - } + }] _id += 1 @@ -975,7 +975,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): """Verify that program data is parsed correctly for a given course""" with patch('student.views.get_programs_for_dashboard') as mock_data: mock_data.return_value = { - u'edx/demox/Run_1': { + u'edx/demox/Run_1': [{ 'id': 0, 'category': self.category, 'organization': {'display_name': 'Test Organization 1', 'key': 'edX'}, @@ -984,7 +984,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): 'course_codes': course_codes, 'subtitle': 'sub', 'name': self.program_name - } + }] } parse_data = _get_course_programs( self.user, [ @@ -998,14 +998,16 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): self.assertEqual( { u'edx/demox/Run_1': { - 'program_id': 0, 'category': 'xseries', - 'course_count': len(course_codes), - 'display_name': self.program_name, - 'program_marketing_url': urljoin( - settings.MKTG_URLS.get('ROOT'), 'xseries' + '/{}' - ).format(marketing_slug), - 'display_category': 'XSeries' + 'display_category': 'XSeries', + 'course_program_list': [{ + 'program_id': 0, + 'course_count': len(course_codes), + 'display_name': self.program_name, + 'program_marketing_url': urljoin( + settings.MKTG_URLS.get('ROOT'), 'xseries' + '/{}' + ).format(marketing_slug) + }] } }, parse_data @@ -1122,8 +1124,9 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): self.create_programs_config() program_data = self._create_program_data([(self.course_1.id, 'active')]) - if key_remove and key_remove in program_data[unicode(self.course_1.id)]: - del program_data[unicode(self.course_1.id)][key_remove] + for program in program_data[unicode(self.course_1.id)]: + if key_remove and key_remove in program: + del program[key_remove] with patch('student.views.get_programs_for_dashboard') as mock_data: mock_data.return_value = program_data @@ -1135,7 +1138,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): log_warn.assert_called_with( 'Program structure is invalid, skipping display: %r', program_data[ unicode(self.course_1.id) - ] + ][0] ) # verify that no programs related upsell messages appear on the # student dashboard. diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index ac082abf19..3d9a67abc9 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -2421,26 +2421,29 @@ def _get_course_programs(user, user_enrolled_courses): # pylint: disable=invali the given user has active enrollments. Returns: - dict, containing programs keyed by course. Empty if programs cannot be retrieved. + dict, containing programs keyed by course. """ course_programs = get_programs_for_dashboard(user, user_enrolled_courses) programs_data = {} - for course_key, program in course_programs.viewitems(): - if program.get('status') == 'active' and program.get('category') == 'xseries': - try: - programs_data[course_key] = { - 'course_count': len(program['course_codes']), - 'display_name': program['name'], - 'category': program.get('category'), - 'program_id': program['id'], - 'program_marketing_url': urljoin( - settings.MKTG_URLS.get('ROOT'), 'xseries' + '/{}' - ).format(program['marketing_slug']), - 'display_category': 'XSeries' - } - except KeyError: - log.warning('Program structure is invalid, skipping display: %r', program) + for course_key, programs in course_programs.viewitems(): + for program in programs: + if program.get('status') == 'active' and program.get('category') == 'xseries': + try: + programs_for_course = programs_data.setdefault(course_key, {}) + programs_for_course.setdefault('course_program_list', []).append({ + 'course_count': len(program['course_codes']), + 'display_name': program['name'], + 'program_id': program['id'], + 'program_marketing_url': urljoin( + settings.MKTG_URLS.get('ROOT'), + 'xseries' + '/{}' + ).format(program['marketing_slug']) + }) + programs_for_course['display_category'] = 'XSeries' + programs_for_course['category'] = program.get('category') + except KeyError: + log.warning('Program structure is invalid, skipping display: %r', program) return programs_data diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index b862f3556a..649e1d9a7e 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -50,7 +50,7 @@ from student.helpers import ( <% mode_class = '' %> % endif
- % if course_program_info and course_program_info['category']=='xseries': + % if course_program_info and course_program_info.get('category')=='xseries':

${_("{category} Program Course").format(category=course_program_info['display_category'])}

@@ -345,8 +345,10 @@ from student.helpers import (
%endif - % if course_program_info and course_program_info['category']=='xseries': - <%include file = "_dashboard_xseries_info.html" args="course_program_info=course_program_info, enrollment_mode=enrollment.mode" /> + % if course_program_info and course_program_info.get('category')=='xseries': + %for program_data in course_program_info.get('course_program_list', []): + <%include file = "_dashboard_xseries_info.html" args="program_data=program_data, enrollment_mode=enrollment.mode, display_category=course_program_info['display_category']" /> + %endfor % endif % if is_course_blocked: diff --git a/lms/templates/dashboard/_dashboard_xseries_info.html b/lms/templates/dashboard/_dashboard_xseries_info.html index 40c0e08ccd..5f52f36727 100644 --- a/lms/templates/dashboard/_dashboard_xseries_info.html +++ b/lms/templates/dashboard/_dashboard_xseries_info.html @@ -1,36 +1,34 @@ -<%page args="course_program_info, enrollment_mode" /> +<%page args="program_data, enrollment_mode, display_category" /> <%! from django.utils.translation import ugettext as _ %> <%namespace name='static' file='../static_content.html'/> -
-
-
-

- ${_("{category} Program: Interested in more courses in this subject?").format(category=course_program_info['display_category'])} -

-

- ${_("This course is 1 of {course_count} courses in the {link_start}{program_display_name}{link_end} {program_category}.").format( - course_count=course_program_info['course_count'], - link_start=''.format(course_program_info['program_marketing_url']), - link_end='', - program_display_name=course_program_info['display_name'], - program_category=course_program_info['display_category'], - )} -

+
+
+
+

+ ${_("{category} Program: Interested in more courses in this subject?").format(category=display_category)} +

+

+ ${_("This course is 1 of {course_count} courses in the {link_start}{program_display_name}{link_end} {program_category}.").format( + course_count=program_data['course_count'], + link_start=''.format(program_data['program_marketing_url']), + link_end='', + program_display_name=program_data['display_name'], + program_category=display_category, + )} +

+
+ <% + xseries_btn_class = "xseries-border-btn" + if enrollment_mode == "verified": + xseries_btn_class = "xseries-base-btn"; + %> + + + ${_("View {category} Details").format(category=display_category)} +
- <% - xseries_btn_class = "xseries-border-btn" - if enrollment_mode == "verified": - xseries_btn_class = "xseries-base-btn"; - %> - - - - ${_("View {category} Details").format(category=course_program_info['display_category'])} - -
-
diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index 5a8c624f2b..9703420448 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -107,7 +107,7 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, for course_code in program['course_codes']: for run in course_code['run_modes']: course_key = run['course_key'] - expected[course_key] = program + expected.setdefault(course_key, []).append(program) self.assertEqual(actual, expected) diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index 4a52401dce..1744375643 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -61,7 +61,7 @@ def get_programs_for_dashboard(user, course_keys): # Reindex the result returned by the Programs API from: # program -> course code -> course run # to: - # course run -> program + # course run -> program_array # Ignore course runs not present in the user's active enrollments. for program in programs: try: @@ -69,7 +69,7 @@ def get_programs_for_dashboard(user, course_keys): for run in course_code['run_modes']: course_key = run['course_key'] if course_key in course_keys: - course_programs[course_key] = program + course_programs.setdefault(course_key, []).append(program) except KeyError: log.exception('Unable to parse Programs API response: %r', program)