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:
Demid
2022-04-22 16:24:23 +03:00
committed by GitHub
parent 0338aab681
commit da4a6d6103
7 changed files with 59 additions and 3 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"))

View File

@@ -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

View File

@@ -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)

View File

@@ -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>