diff --git a/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py index 4e9275182d32f7224787aade1a61e1b9e9c2d7fb..4195e4a208a62f94d785a8bf74c57ba2534c6693 100644 --- a/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py +++ b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py @@ -970,23 +970,22 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert course_entitlement.enrollment_course_run is not None @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") - def test_enrollment_closed_upgrade_open(self, mock_get_course_runs): + def test_already_enrolled_course_ended(self, mock_get_course_runs): """ - Test that user can still select a session while course enrollment - is closed and upgrade deadline is in future. + Test that already enrolled user can still select a session while + course has ended but upgrade deadline is in future. """ course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values # Setup enrollment period to be in the past utc_now = datetime.now(UTC) - self.course.enrollment_start = utc_now - timedelta(days=15) - self.course.enrollment_end = utc_now - timedelta(days=1) + self.course.start = utc_now - timedelta(days=15) + self.course.end = utc_now - timedelta(days=1) self.course = self.update_course(self.course, self.user.id) CourseOverview.update_select_courses([self.course.id], force_update=True) - assert CourseEnrollment.is_enrollment_closed(self.user, self.course) - assert not CourseEnrollment.is_enrolled(self.user, self.course.id) + CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.AUDIT) url = reverse( self.ENTITLEMENTS_ENROLLMENT_NAMESPACE, @@ -1005,6 +1004,8 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert response.status_code == 201 assert CourseEnrollment.is_enrolled(self.user, self.course.id) + (enrolled_mode, is_active) = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) + assert is_active and (enrolled_mode == course_entitlement.mode) assert course_entitlement.enrollment_course_run is not None @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") diff --git a/common/djangoapps/entitlements/tests/test_utils.py b/common/djangoapps/entitlements/tests/test_utils.py index 0cd598b33f1cfa90198598d689de8bc117a2ee85..b1a91ac72068e11a2d363df38477d73d4b1f7ad4 100644 --- a/common/djangoapps/entitlements/tests/test_utils.py +++ b/common/djangoapps/entitlements/tests/test_utils.py @@ -155,14 +155,15 @@ class TestCourseRunFulfillableForEntitlement(ModuleStoreTestCase): assert not is_course_run_entitlement_fulfillable(course_overview.id, entitlement) - def test_course_run_fulfillable_enrollment_ended_upgrade_open(self): + def test_course_run_fulfillable_already_enrolled_course_ended(self): course_overview = self.create_course( start_from_now=-3, - end_from_now=2, + end_from_now=-1, enrollment_start_from_now=-2, enrollment_end_from_now=-1, ) entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) + CourseEnrollmentFactory.create(user=entitlement.user, course_id=course_overview.id) assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement) diff --git a/common/djangoapps/entitlements/utils.py b/common/djangoapps/entitlements/utils.py index a16bfc52ba602eae8fffb84ff7c6694ad1211244..0b4594e7cff547e4e01f78f0df9181554a641f9b 100644 --- a/common/djangoapps/entitlements/utils.py +++ b/common/djangoapps/entitlements/utils.py @@ -22,9 +22,8 @@ def is_course_run_entitlement_fulfillable( """ Checks that the current run meets the following criteria for an entitlement - 1) Is currently running or start in the future - 2) A User can enroll in or is currently enrolled - 3) A User can upgrade to the entitlement mode + 1) A User can enroll in or is currently enrolled + 2) A User can upgrade to the entitlement mode Arguments: course_run_key (CourseKey): The id of the Course run that is being checked. @@ -43,16 +42,13 @@ def is_course_run_entitlement_fulfillable( )) return False - # Verify that the course is still running - run_start = course_overview.start - run_end = course_overview.end - is_running = run_start and (not run_end or (run_end and (run_end > compare_date))) - - # Verify that the course run can currently be enrolled, explicitly - # not checking for enrollment end date becasue course run is still - # fulfillable if enrollment has ended but VUD is in future + # Verify that the course run can currently be enrolled enrollment_start = course_overview.enrollment_start - can_enroll = not enrollment_start or enrollment_start < compare_date + enrollment_end = course_overview.enrollment_end + can_enroll = ( + (not enrollment_start or enrollment_start < compare_date) + and (not enrollment_end or enrollment_end > compare_date) + ) # Is the user already enrolled in the Course Run is_enrolled = CourseEnrollment.is_enrolled(entitlement.user, course_run_key) @@ -61,4 +57,4 @@ def is_course_run_entitlement_fulfillable( unexpired_paid_modes = [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)] can_upgrade = unexpired_paid_modes and entitlement.mode in unexpired_paid_modes - return is_running and can_upgrade and (is_enrolled or can_enroll) + return course_overview.start and can_upgrade and (is_enrolled or can_enroll) diff --git a/lms/static/js/learner_dashboard/spec/course_card_view_spec.js b/lms/static/js/learner_dashboard/spec/course_card_view_spec.js index b63c91f78532eb089a405afa1bece2eb799de137..c6c4a1029b84c5f6bcdeb428ca8cfe4c475b9a70 100644 --- a/lms/static/js/learner_dashboard/spec/course_card_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/course_card_view_spec.js @@ -169,7 +169,7 @@ describe('Course Card View', () => { ); }); - it('should show a message if an there is an upcoming course run', () => { + it('should show a message if there is an upcoming course run', () => { course.course_runs[0].is_enrollment_open = false; setupView(course, false); diff --git a/lms/static/js/learner_dashboard/views/course_card_view.js b/lms/static/js/learner_dashboard/views/course_card_view.js index 96cfec4157c9931a3bdae7e46a1af0df178f292d..797c48d1af4922bada4e1b4fac7b0126a8e7922b 100644 --- a/lms/static/js/learner_dashboard/views/course_card_view.js +++ b/lms/static/js/learner_dashboard/views/course_card_view.js @@ -46,7 +46,9 @@ class CourseCardView extends Backbone.View { const $upgradeMessage = this.$('.upgrade-message'); const $certStatus = this.$('.certificate-status'); const $expiredNotification = this.$('.expired-notification'); + const courseKey = this.model.get('course_run_key'); const expired = this.model.get('expired'); + const canUpgrade = this.model.get('upgrade_url') && !(expired === true); const courseUUID = this.model.get('uuid'); const containerSelector = `#course-${courseUUID}`; @@ -72,8 +74,7 @@ class CourseCardView extends Backbone.View { enterCourseBtn: `${containerSelector} .view-course-button`, availableSessions: JSON.stringify(this.model.get('course_runs')), entitlementUUID: this.entitlement.uuid, - currentSessionId: this.model.isEnrolledInSession() ? - this.model.get('course_run_key') : null, + currentSessionId: this.model.isEnrolledInSession() && !canUpgrade ? courseKey : null, enrollUrl: this.model.get('enroll_url'), courseHomeUrl: this.model.get('course_url'), expiredAt: this.entitlement.expired_at, @@ -81,7 +82,7 @@ class CourseCardView extends Backbone.View { }); } - if (this.model.get('upgrade_url') && !(expired === true)) { + if (canUpgrade) { this.upgradeMessage = new UpgradeMessageView({ $el: $upgradeMessage, model: this.model, diff --git a/lms/static/js/learner_dashboard/views/course_entitlement_view.js b/lms/static/js/learner_dashboard/views/course_entitlement_view.js index b19ce2f1b3dc786a2df51d7c49e660468e3d97c1..3f2e65f4c28d04a7a8bf6ec34dad9925cd1fb7c3 100644 --- a/lms/static/js/learner_dashboard/views/course_entitlement_view.js +++ b/lms/static/js/learner_dashboard/views/course_entitlement_view.js @@ -141,7 +141,8 @@ class CourseEntitlementView extends Backbone.View { this.trackSessionChange(eventPage, eventAction, prevSession); // With a containing backbone view, we can simply re-render the parent card - if (this.$parentEl) { + if (this.$parentEl && + this.courseCardModel.get('course_run_key') !== this.currentSessionSelection) { this.courseCardModel.updateCourseRun(this.currentSessionSelection); return; } @@ -388,6 +389,7 @@ class CourseEntitlementView extends Backbone.View { sessionData.forEach((session) => { Object.assign(session, { enrollment_end: CourseEntitlementView.formatDate(session.enrollment_end, dateFormat), + session_id: session.session_id ? session.session_id : session.key, session_dates: this.courseCardModel.formatDateString({ start_date: CourseEntitlementView.formatDate(session.start, dateFormat), advertised_start: session.advertised_start, diff --git a/openedx/core/djangoapps/catalog/tests/test_utils.py b/openedx/core/djangoapps/catalog/tests/test_utils.py index 77bffb06c2eca72ddb638301242d3d049e062898..7886b7148c7fb6ee28b81fb223c8b0f65751e7fa 100644 --- a/openedx/core/djangoapps/catalog/tests/test_utils.py +++ b/openedx/core/djangoapps/catalog/tests/test_utils.py @@ -655,14 +655,19 @@ class TestSessionEntitlement(CatalogIntegrationMixin, TestCase): def test_unpublished_sessions_for_entitlement(self, mock_get_edx_api_data): """ Test unpublished course runs are not part of visible session entitlements when the user - is not enrolled. + is not enrolled and upgrade deadline is passed. """ catalog_course_run = CourseRunFactory.create(status=COURSE_UNPUBLISHED) catalog_course = CourseFactory(course_runs=[catalog_course_run]) mock_get_edx_api_data.return_value = catalog_course course_key = CourseKey.from_string(catalog_course_run.get('key')) course_overview = CourseOverviewFactory.create(id=course_key, start=self.tomorrow) - CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, min_price=100, course_id=course_overview.id) + CourseModeFactory.create( + mode_slug=CourseMode.VERIFIED, + min_price=100, + course_id=course_overview.id, + expiration_datetime=now() - timedelta(days=1) + ) entitlement = CourseEntitlementFactory( user=self.user, mode=CourseMode.VERIFIED ) diff --git a/openedx/core/djangoapps/catalog/utils.py b/openedx/core/djangoapps/catalog/utils.py index 60a1cf34c95f3d17e0c7d3b5cf55f31a6de27c67..e87e7442842859f603cf949a32c489ee0e8f48cf 100644 --- a/openedx/core/djangoapps/catalog/utils.py +++ b/openedx/core/djangoapps/catalog/utils.py @@ -553,9 +553,7 @@ def get_fulfillable_course_runs_for_entitlement(entitlement, course_runs): # User is enrolled in the course so we should include it in the list of enrollable sessions always # this will ensure it is available for the UI enrollable_sessions.append(course_run) - elif (course_run.get('status') == COURSE_PUBLISHED and not - is_enrolled_in_mode and - is_course_run_entitlement_fulfillable(course_id, entitlement, search_time)): + elif not is_enrolled_in_mode and is_course_run_entitlement_fulfillable(course_id, entitlement, search_time): enrollable_sessions.append(course_run) enrollable_sessions.sort(key=lambda session: session.get('start'))