feat: Implement feature flag to disable students un-enrollment (#29326)
Implements a feature flag DISABLE_UNENROLLMENT that is used to disable students un-enrollment for all courses. The Unenrollment option should be disabled when this feature is set to True. ref: BB-4951 Co-authored-by: tinumide <tinuade@opencraft.com> Co-authored-by: Tim McCormack <tmccormack@edx.org>
This commit is contained in:
@@ -504,6 +504,18 @@ FEATURES = {
|
||||
# .. toggle_warnings: For consistency in user-experience, keep the value in sync with the setting of the same name
|
||||
# in the LMS and CMS.
|
||||
'MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW': False,
|
||||
|
||||
# .. toggle_name: FEATURES['DISABLE_UNENROLLMENT']
|
||||
# .. toggle_implementation: DjangoSetting
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Set to True to disable self-unenrollments via REST API.
|
||||
# This also hides the "Unenroll" button on the Learner Dashboard.
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2021-10-11
|
||||
# .. toggle_warnings: For consistency in user experience, keep the value in sync with the setting of the same name
|
||||
# in the LMS and CMS.
|
||||
# .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429'
|
||||
'DISABLE_UNENROLLMENT': False,
|
||||
}
|
||||
|
||||
# .. toggle_name: ENABLE_COPPA_COMPLIANCE
|
||||
|
||||
@@ -357,6 +357,22 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase, OpenEdxEventsTestMixin)
|
||||
resp = self._change_enrollment('unenroll', course_id="edx/")
|
||||
assert resp.status_code == 400
|
||||
|
||||
@patch.dict(settings.FEATURES, {'DISABLE_UNENROLLMENT': True})
|
||||
def test_unenroll_when_unenrollment_disabled(self):
|
||||
"""
|
||||
Tests that a user cannot unenroll when unenrollment has been disabled.
|
||||
"""
|
||||
# Enroll the student in the course
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="honor")
|
||||
|
||||
# Attempt to unenroll
|
||||
resp = self._change_enrollment('unenroll')
|
||||
assert resp.status_code == 400
|
||||
|
||||
# Verify that user is still enrolled
|
||||
is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id)
|
||||
assert is_enrolled
|
||||
|
||||
def test_enrollment_limit(self):
|
||||
"""
|
||||
Assert that in a course with max student limit set to 1, we can enroll staff and instructor along with
|
||||
|
||||
@@ -530,6 +530,10 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
|
||||
empty_dashboard_message = configuration_helpers.get_value(
|
||||
'EMPTY_DASHBOARD_MESSAGE', None
|
||||
)
|
||||
disable_unenrollment = configuration_helpers.get_value(
|
||||
'DISABLE_UNENROLLMENT',
|
||||
settings.FEATURES.get('DISABLE_UNENROLLMENT')
|
||||
)
|
||||
|
||||
disable_course_limit = request and 'course_limit' in request.GET
|
||||
course_limit = get_dashboard_course_limit() if not disable_course_limit else None
|
||||
@@ -808,6 +812,7 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
|
||||
# TODO START: clean up as part of REVEM-199 (START)
|
||||
'course_info': get_dashboard_course_info(user, course_enrollments),
|
||||
# TODO START: clean up as part of REVEM-199 (END)
|
||||
'disable_unenrollment': disable_unenrollment,
|
||||
}
|
||||
|
||||
# Include enterprise learner portal metadata and messaging
|
||||
|
||||
@@ -403,6 +403,12 @@ def change_enrollment(request, check_access=True):
|
||||
# Otherwise, there is only one mode available (the default)
|
||||
return HttpResponse()
|
||||
elif action == "unenroll":
|
||||
if configuration_helpers.get_value(
|
||||
"DISABLE_UNENROLLMENT",
|
||||
settings.FEATURES.get("DISABLE_UNENROLLMENT")
|
||||
):
|
||||
return HttpResponseBadRequest(_("Unenrollment is currently disabled"))
|
||||
|
||||
enrollment = CourseEnrollment.get_enrollment(user, course_id)
|
||||
if not enrollment:
|
||||
return HttpResponseBadRequest(_("You are not enrolled in this course"))
|
||||
|
||||
@@ -998,6 +998,18 @@ FEATURES = {
|
||||
# .. toggle_warnings: For consistency in user-experience, keep the value in sync with the setting of the same name
|
||||
# in the LMS and CMS.
|
||||
'MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW': False,
|
||||
|
||||
# .. toggle_name: FEATURES['DISABLE_UNENROLLMENT']
|
||||
# .. toggle_implementation: DjangoSetting
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Set to True to disable self-unenrollments via REST API.
|
||||
# This also hides the "Unenroll" button on the Learner Dashboard.
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2021-10-11
|
||||
# .. toggle_warnings: For consistency in user experience, keep the value in sync with the setting of the same name
|
||||
# in the LMS and CMS.
|
||||
# .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429'
|
||||
'DISABLE_UNENROLLMENT': False,
|
||||
}
|
||||
|
||||
# Specifies extra XBlock fields that should available when requested via the Course Blocks API
|
||||
|
||||
@@ -209,7 +209,12 @@ from common.djangoapps.student.models import CourseEnrollment
|
||||
cert_status = cert_statuses.get(session_id)
|
||||
can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable()
|
||||
partner_managed_enrollment = enrollment.mode == 'masters'
|
||||
can_unenroll = False if partner_managed_enrollment else (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False
|
||||
# checks if we can unenroll based on the value of partner_managed_enrollment
|
||||
can_unenroll_partner_managed_enrollment = False if partner_managed_enrollment else (not cert_status)
|
||||
# checks if we can unenroll based on the value of unfulfilled_entitlement
|
||||
can_unenroll_unfulfilled_entitlement = cert_status.get('can_unenroll') if cert_status and not unfulfilled_entitlement else False
|
||||
# compares the three different parameters by which we can unenroll
|
||||
can_unenroll = (can_unenroll_partner_managed_enrollment or can_unenroll_unfulfilled_entitlement) and not disable_unenrollment
|
||||
credit_status = credit_statuses.get(session_id)
|
||||
course_mode_info = all_course_modes.get(session_id)
|
||||
is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid)
|
||||
|
||||
@@ -255,11 +255,11 @@ from lms.djangoapps.experiments.utils import UPSELL_TRACKING_FLAG
|
||||
% endif
|
||||
|
||||
## We should only show the gear dropdown if the user is able to refund/unenroll from their entitlement
|
||||
## and/or if they have selected a course run and email_settings are enabled
|
||||
## and/or if they have selected a course run, unenrollment is not disabled, and email_settings are enabled
|
||||
## as these are the only actions currently available
|
||||
% if entitlement and (can_refund_entitlement or show_email_settings):
|
||||
<%include file='_dashboard_entitlement_actions.html' args='course_overview=course_overview,entitlement=entitlement,dashboard_index=dashboard_index, can_refund_entitlement=can_refund_entitlement, show_email_settings=show_email_settings'/>
|
||||
% elif not entitlement:
|
||||
% elif not entitlement and (can_unenroll or partner_managed_enrollment or show_email_settings):
|
||||
<div class="wrapper-action-more" data-course-key="${enrollment.course_id}">
|
||||
<button type="button" class="action action-more" id="actions-dropdown-link-${dashboard_index}" aria-haspopup="true" aria-expanded="false" aria-controls="actions-dropdown-${dashboard_index}" data-course-number="${course_overview.number}" data-course-name="${course_overview.display_name_with_default}" data-dashboard-index="${dashboard_index}">
|
||||
<span class="sr">${_('Course options for')}</span>
|
||||
|
||||
Reference in New Issue
Block a user