From d37264ae0c4194187c822c6279e107882d952b69 Mon Sep 17 00:00:00 2001 From: "Albert St. Aubin" Date: Thu, 5 Oct 2017 08:08:09 -0400 Subject: [PATCH 1/3] Bug fix for unpublished courses that users have putchased within a program [LEARNER-2715] --- .../djangoapps/programs/tests/test_utils.py | 42 ++++++++++++----- openedx/core/djangoapps/programs/utils.py | 46 +++++++++++++------ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index 253f75f70d..4747764607 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -550,7 +550,7 @@ class TestProgramProgressMeter(TestCase): self.assertEqual(meter._is_course_complete(course), True) -def _create_course(self, course_price): +def _create_course(self, course_price, course_run_count=1): """ Creates the course in mongo and update it with the instructor data. Also creates catalog course with respect to course run. @@ -558,17 +558,18 @@ def _create_course(self, course_price): Returns: Catalog course dict. """ - course = ModuleStoreCourseFactory() - course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) - course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) - course.instructor_info = self.instructors - course = self.update_course(course, self.user.id) + course_runs = [] + for x in range(course_run_count): + course = ModuleStoreCourseFactory.create(run='Run_' + str(x)) + course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) + course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) + course.instructor_info = self.instructors + course = self.update_course(course, self.user.id) - course_run = CourseRunFactory( - key=unicode(course.id), - seats=[SeatFactory(price=course_price)] - ) - return CourseFactory(course_runs=[course_run]) + run = CourseRunFactory(key=unicode(course.id), seats=[SeatFactory(price=course_price)]) + course_runs.append(run) + + return CourseFactory(course_runs=course_runs) @ddt.ddt @@ -810,6 +811,25 @@ class TestProgramDataExtender(ModuleStoreTestCase): data = ProgramDataExtender(program2, self.user).extend() self.assertTrue(data['is_learner_eligible_for_one_click_purchase']) + def test_learner_eligibility_for_one_click_purchase_with_unpublished(self): + """ + Learner should be eligible for one click purchase if: + - program is eligible for one click purchase + - There are courses remaining that have not been purchased and enrolled in. + """ + course1 = _create_course(self, self.course_price, course_run_count=2) + course2 = _create_course(self, self.course_price) + CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='verified') + course1['course_runs'][0]['status'] = 'unpublished' + program2 = ProgramFactory( + courses=[course1, course2], + is_program_eligible_for_one_click_purchase=True, + applicable_seat_types=['verified'], + ) + data = ProgramDataExtender(program2, self.user).extend() + self.assertEqual(len(data['skus']), 1) + self.assertTrue(data['is_learner_eligible_for_one_click_purchase']) + def test_learner_eligibility_for_one_click_purchase_professional_no_id(self): """ Learner should not be eligible for one click purchase if: diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index fac32029f6..5e2073e9cb 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -463,26 +463,44 @@ class ProgramDataExtender(object): if is_learner_eligible_for_one_click_purchase: for course in self.data['courses']: add_course_sku = False + unpublished_enrollment = False + unpublished_course_runs = filter(lambda run: run['status'] == 'unpublished', course['course_runs']) published_course_runs = filter(lambda run: run['status'] == 'published', course['course_runs']) if len(published_course_runs) == 1: # Look at the course runs for a course and determine if the course SKU should be added. - course_run = published_course_runs[0] - (enrollment_mode, active) = CourseEnrollment.enrollment_mode_for_user( - self.user, - CourseKey.from_string(course_run['key']) - ) - if enrollment_mode is not None and active is not None: - # Check all the applicable seat types - # this will also check for no-id-professional as professional - applicable_seat = any(seat_type in enrollment_mode for seat_type in applicable_seat_types) + if len(unpublished_course_runs) > 0: + for course_run in unpublished_course_runs: + (enrollment_mode, active) = CourseEnrollment.enrollment_mode_for_user( + self.user, + CourseKey.from_string(course_run['key']) + ) - # If no applicable seat is found add the course SKU to the list - if not applicable_seat or not active: + if enrollment_mode is not None and active is not None: + # Check all the applicable seat types + # this will also check for no-id-professional as professional + applicable_seat = any(seat_type in enrollment_mode + for seat_type in applicable_seat_types) + + # If no applicable seat is found add the course SKU to the list + if applicable_seat: + unpublished_enrollment = True + + if not unpublished_enrollment: + course_run = published_course_runs[0] + (enrollment_mode, active) = CourseEnrollment.enrollment_mode_for_user( + self.user, + CourseKey.from_string(course_run['key']) + ) + + if enrollment_mode is not None and active is not None: + applicable_seat = any(seat_type in enrollment_mode for seat_type in applicable_seat_types) + + if not applicable_seat or not active: + add_course_sku = True + else: + # There is no enrollment information for the course add the course SKU add_course_sku = True - else: - # There is no enrollment information for the course add the course SKU - add_course_sku = True if add_course_sku: for seat in published_course_runs[0]['seats']: From a4cb374b16fe25dc43ac059fb8c7ee4026feaae7 Mon Sep 17 00:00:00 2001 From: "Albert St. Aubin" Date: Thu, 5 Oct 2017 09:05:02 -0400 Subject: [PATCH 2/3] PR update --- openedx/core/djangoapps/programs/utils.py | 50 +++++++++++------------ 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index 5e2073e9cb..ff6731fdd3 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -451,6 +451,22 @@ class ProgramDataExtender(object): def _attach_course_run_may_certify(self, run_mode): run_mode['may_certify'] = self.course_overview.may_certify() + def _check_enrollment_for_user(self, course_run): + applicable_seat_types = self.data['applicable_seat_types'] + + (enrollment_mode, active) = CourseEnrollment.enrollment_mode_for_user( + self.user, + CourseKey.from_string(course_run['key']) + ) + + is_paid_seat = False + if enrollment_mode is not None and active is not None: + # Check all the applicable seat types + # this will also check for no-id-professional as professional + is_paid_seat = any(seat_type in enrollment_mode for seat_type in applicable_seat_types) + + return is_paid_seat + def _collect_one_click_purchase_eligibility_data(self): """ Extend the program data with data about learner's eligibility for one click purchase, @@ -467,37 +483,17 @@ class ProgramDataExtender(object): unpublished_course_runs = filter(lambda run: run['status'] == 'unpublished', course['course_runs']) published_course_runs = filter(lambda run: run['status'] == 'published', course['course_runs']) if len(published_course_runs) == 1: - # Look at the course runs for a course and determine if the course SKU should be added. + for course_run in unpublished_course_runs: + is_paid_seat = self._check_enrollment_for_user(course_run) - if len(unpublished_course_runs) > 0: - for course_run in unpublished_course_runs: - (enrollment_mode, active) = CourseEnrollment.enrollment_mode_for_user( - self.user, - CourseKey.from_string(course_run['key']) - ) - - if enrollment_mode is not None and active is not None: - # Check all the applicable seat types - # this will also check for no-id-professional as professional - applicable_seat = any(seat_type in enrollment_mode - for seat_type in applicable_seat_types) - - # If no applicable seat is found add the course SKU to the list - if applicable_seat: - unpublished_enrollment = True + if is_paid_seat: + unpublished_enrollment = True if not unpublished_enrollment: course_run = published_course_runs[0] - (enrollment_mode, active) = CourseEnrollment.enrollment_mode_for_user( - self.user, - CourseKey.from_string(course_run['key']) - ) - - if enrollment_mode is not None and active is not None: - applicable_seat = any(seat_type in enrollment_mode for seat_type in applicable_seat_types) - - if not applicable_seat or not active: - add_course_sku = True + is_paid_seat = self._check_enrollment_for_user(course_run) + if not is_paid_seat: + add_course_sku = True else: # There is no enrollment information for the course add the course SKU add_course_sku = True From 724168d6050e6aa2e42c3d998a07f83e27a4ded0 Mon Sep 17 00:00:00 2001 From: "Albert St. Aubin" Date: Thu, 5 Oct 2017 09:40:30 -0400 Subject: [PATCH 3/3] simplification pr changes --- openedx/core/djangoapps/programs/utils.py | 24 ++++++++--------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index ff6731fdd3..3b9d57fa37 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -460,7 +460,7 @@ class ProgramDataExtender(object): ) is_paid_seat = False - if enrollment_mode is not None and active is not None: + if enrollment_mode is not None and active is not None and active is True: # Check all the applicable seat types # this will also check for no-id-professional as professional is_paid_seat = any(seat_type in enrollment_mode for seat_type in applicable_seat_types) @@ -478,25 +478,17 @@ class ProgramDataExtender(object): bundle_variant = 'full' if is_learner_eligible_for_one_click_purchase: for course in self.data['courses']: - add_course_sku = False - unpublished_enrollment = False - unpublished_course_runs = filter(lambda run: run['status'] == 'unpublished', course['course_runs']) - published_course_runs = filter(lambda run: run['status'] == 'published', course['course_runs']) + add_course_sku = True + course_runs = course.get('course_runs', []) + published_course_runs = filter(lambda run: run['status'] == 'published', course_runs) + if len(published_course_runs) == 1: - for course_run in unpublished_course_runs: + for course_run in course_runs: is_paid_seat = self._check_enrollment_for_user(course_run) if is_paid_seat: - unpublished_enrollment = True - - if not unpublished_enrollment: - course_run = published_course_runs[0] - is_paid_seat = self._check_enrollment_for_user(course_run) - if not is_paid_seat: - add_course_sku = True - else: - # There is no enrollment information for the course add the course SKU - add_course_sku = True + add_course_sku = False + break if add_course_sku: for seat in published_course_runs[0]['seats']: