diff --git a/common/djangoapps/entitlements/tests/test_utils.py b/common/djangoapps/entitlements/tests/test_utils.py index a61476847cf950d3006bb26ad57252d7d07dd22b..39ab4c5f38be39ede3934fd5394930c789390c84 100644 --- a/common/djangoapps/entitlements/tests/test_utils.py +++ b/common/djangoapps/entitlements/tests/test_utils.py @@ -139,6 +139,20 @@ class TestCourseRunFulfillableForEntitlement(ModuleStoreTestCase): assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement) + def test_course_run_fulfillable_enrollment_ended_upgrade_open(self): + course_overview = self.create_course( + start_from_now=-3, + end_from_now=2, + enrollment_start_from_now=-2, + enrollment_end_from_now=-1, + ) + + entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED) + # Enroll User in the Course, but do not update the entitlement + CourseEnrollmentFactory.create(user=entitlement.user, course_id=course_overview.id) + + assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement) + def test_course_run_not_fulfillable_upgrade_ended(self): course_overview = self.create_course( start_from_now=-3, diff --git a/common/djangoapps/entitlements/utils.py b/common/djangoapps/entitlements/utils.py index 881d38c092d102b7e1eafc72ff10a24ff3e48621..84b5c5b13f22281749378eea5595c9a371f5e2cf 100644 --- a/common/djangoapps/entitlements/utils.py +++ b/common/djangoapps/entitlements/utils.py @@ -8,16 +8,21 @@ from django.utils import timezone from course_modes.models import CourseMode from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from student.models import CourseEnrollment log = logging.getLogger("common.entitlements.utils") -def is_course_run_entitlement_fulfillable(course_run_key, entitlement, compare_date=timezone.now()): +def is_course_run_entitlement_fulfillable( + course_run_key, + entitlement, + compare_date=timezone.now(), +): """ 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 + 2) A User can enroll in or is currently enrolled 3) A User can upgrade to the entitlement mode Arguments: @@ -50,8 +55,11 @@ def is_course_run_entitlement_fulfillable(course_run_key, entitlement, compare_d 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) + # Ensure the course run is upgradeable and the mode matches the entitlement's mode 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 can_enroll + return is_running and can_upgrade and (is_enrolled or can_enroll) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 42b8d95605d22f538e1125e9f725b8218181fbe5..8327a34cdfec46ebf1be5137b1bd7283e109ce54 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -1270,7 +1270,7 @@ class CourseEnrollment(models.Model): Args: user (User): The user associated with the enrollment. - course_id (CourseKey): The key of the course associated with the enrollment. + course_key (CourseKey): The key of the course associated with the enrollment. Returns: Course enrollment object or None diff --git a/lms/djangoapps/support/static/support/jsx/entitlements/components/EntitlementForm/index.jsx b/lms/djangoapps/support/static/support/jsx/entitlements/components/EntitlementForm/index.jsx index 7bba4c712c10389894837c3ad1bd092168ac0e3c..4f9ee42c5ec71e7ad017491e3399d8c1200e0927 100644 --- a/lms/djangoapps/support/static/support/jsx/entitlements/components/EntitlementForm/index.jsx +++ b/lms/djangoapps/support/static/support/jsx/entitlements/components/EntitlementForm/index.jsx @@ -94,6 +94,7 @@ class EntitlementForm extends React.Component { { label: '--', value: '' }, { label: 'Verified', value: 'verified' }, { label: 'Professional', value: 'professional' }, + { label: 'No ID Professional', value: 'no-id-professional' }, ]} onChange={this.handleModeChange} />