Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions lms/djangoapps/learner_dashboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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

Expand All @@ -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,
Expand All @@ -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)
35 changes: 34 additions & 1 deletion openedx/core/djangoapps/programs/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand Down
38 changes: 26 additions & 12 deletions openedx/core/djangoapps/programs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down