diff --git a/lms/djangoapps/learner_dashboard/views.py b/lms/djangoapps/learner_dashboard/views.py index 83e3e814b387..7f4e43b74ca4 100644 --- a/lms/djangoapps/learner_dashboard/views.py +++ b/lms/djangoapps/learner_dashboard/views.py @@ -34,7 +34,7 @@ def program_listing(request): 'marketing_url': get_program_marketing_url(programs_config), 'nav_hidden': True, 'programs': meter.engaged_programs, - 'progress': meter.progress, + 'progress': meter.progress(), 'show_program_listing': programs_config.enabled, 'uses_pattern_library': True, } @@ -50,7 +50,9 @@ def program_details(request, program_uuid): if not programs_config.enabled: raise Http404 - program_data = get_programs(uuid=program_uuid) + meter = ProgramProgressMeter(request.user, uuid=program_uuid) + program_data = meter.programs[0] + if not program_data: raise Http404 @@ -65,7 +67,6 @@ def program_details(request, program_uuid): } context = { - 'program_data': program_data, 'urls': urls, 'show_program_listing': programs_config.enabled, 'nav_hidden': True, @@ -75,6 +76,16 @@ def program_details(request, program_uuid): } if waffle.switch_is_active('new_program_progress'): + course_progress = meter.progress(programs=[program_data], count_only=False)[0] + program_data.pop('courses') + + context.update({ + 'program_data': program_data, + 'course_progress': course_progress, + }) + return render_to_response('learner_dashboard/program_details_2017.html', context) else: + context.update({'program_data': program_data}) + return render_to_response('learner_dashboard/program_details.html', context) diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index 3a0ad99d8750..8bcddb05c576 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -53,7 +53,7 @@ def _create_enrollments(self, *course_run_ids): def _assert_progress(self, meter, *progresses): """Variadic helper used to verify progress calculations.""" - self.assertEqual(meter.progress, list(progresses)) + self.assertEqual(meter.progress(), list(progresses)) def _attach_detail_url(self, programs): """Add expected detail URLs to a list of program dicts.""" @@ -113,6 +113,39 @@ def test_single_program_engagement(self, mock_get_programs): ) self.assertEqual(meter.completed_programs, []) + def test_course_progress(self, mock_get_programs): + """ + Verify that the progress meter can represent progress in terms of + serialized courses. + """ + course_run_key = generate_course_run_key() + data = [ + ProgramFactory( + courses=[ + CourseFactory(course_runs=[ + CourseRunFactory(key=course_run_key), + ]), + ] + ) + ] + mock_get_programs.return_value = data + + self._create_enrollments(course_run_key) + + meter = ProgramProgressMeter(self.user) + + program = data[0] + expected = [ + ProgressFactory( + uuid=program['uuid'], + completed=[], + in_progress=[program['courses'][0]], + not_started=[] + ) + ] + + self.assertEqual(meter.progress(count_only=False), expected) + def test_mutiple_program_engagement(self, mock_get_programs): """ Verify that correct programs are returned in the correct order when the diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index 7102a5080237..2fe993a1d173 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -57,8 +57,11 @@ class ProgramProgressMeter(object): Keyword Arguments: enrollments (list): List of the user's enrollments. + uuid (str): UUID identifying a specific program. If provided, the meter + will only inspect this one program, not all programs the user may be + engaged with. """ - def __init__(self, user, enrollments=None): + def __init__(self, user, enrollments=None, uuid=None): self.user = user self.enrollments = enrollments or list(CourseEnrollment.enrollments_for_user(self.user)) @@ -67,7 +70,10 @@ def __init__(self, user, enrollments=None): # enrollment.course_id is really a CourseKey (╯ಠ_ಠ)╯︵ ┻━┻ self.course_run_ids = [unicode(e.course_id) for e in self.enrollments] - self.programs = attach_program_detail_url(get_programs()) + if uuid: + self.programs = [get_programs(uuid=uuid)] + else: + self.programs = attach_program_detail_url(get_programs()) def invert_programs(self): """Intersect programs and enrollments. @@ -119,31 +125,39 @@ def engaged_programs(self): return programs - @property - def progress(self): + def progress(self, programs=None, count_only=True): """Gauge a user's progress towards program completion. + Keyword Arguments: + programs (list): Specific list of programs to check the user's progress + against. If left unspecified, self.engaged_programs will be used. + + count_only (bool): Whether or not to return counts of completed, in + progress, and unstarted courses instead of serialized representations + of the courses. + Returns: list of dict, each containing information about a user's progress towards completing a program. """ progress = [] - for program in self.engaged_programs: - completed, in_progress, not_started = 0, 0, 0 + programs = programs or self.engaged_programs + for program in programs: + completed, in_progress, not_started = [], [], [] for course in program['courses']: if self._is_course_complete(course): - completed += 1 + completed.append(course) elif self._is_course_in_progress(course): - in_progress += 1 + in_progress.append(course) else: - not_started += 1 + not_started.append(course) progress.append({ 'uuid': program['uuid'], - 'completed': completed, - 'in_progress': in_progress, - 'not_started': not_started, + 'completed': len(completed) if count_only else completed, + 'in_progress': len(in_progress) if count_only else in_progress, + 'not_started': len(not_started) if count_only else not_started, }) return progress