Skip to content

Commit

Permalink
Allow entitlements to be used past course has ended.
Browse files Browse the repository at this point in the history
Allow entitlements to be used past the course has ended but
upgrade deadline is still in future for already enrolled
learners.

PROD-1497
  • Loading branch information
waheedahmed committed Aug 24, 2020
1 parent 3a3d3a8 commit 13b3764
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 32 deletions.
15 changes: 8 additions & 7 deletions common/djangoapps/entitlements/rest_api/v1/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -970,23 +970,22 @@ def test_user_already_enrolled(self, mock_get_course_runs):
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,
Expand All @@ -1005,6 +1004,8 @@ def test_enrollment_closed_upgrade_open(self, mock_get_course_runs):

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")
Expand Down
5 changes: 3 additions & 2 deletions common/djangoapps/entitlements/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,15 @@ def test_course_run_not_fulfillable_upgrade_ended(self):

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)
22 changes: 9 additions & 13 deletions common/djangoapps/entitlements/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions lms/static/js/learner_dashboard/views/course_card_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;

Expand All @@ -72,16 +74,15 @@ 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,
daysUntilExpiration: this.entitlement.days_until_expiration,
});
}

if (this.model.get('upgrade_url') && !(expired === true)) {
if (canUpgrade) {
this.upgradeMessage = new UpgradeMessageView({
$el: $upgradeMessage,
model: this.model,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 7 additions & 2 deletions openedx/core/djangoapps/catalog/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,14 +655,19 @@ def test_unpublished_sessions_for_entitlement_when_enrolled(self, mock_get_edx_a
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
)
Expand Down
4 changes: 1 addition & 3 deletions openedx/core/djangoapps/catalog/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down

0 comments on commit 13b3764

Please sign in to comment.