Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,18 @@
# .. 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
Expand Down
16 changes: 16 additions & 0 deletions common/djangoapps/student/tests/test_enrollment.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,22 @@ def test_with_invalid_course_id(self):
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
Expand Down
5 changes: 5 additions & 0 deletions common/djangoapps/student/views/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Comment thread
timmc-edx marked this conversation as resolved.
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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions common/djangoapps/student/views/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
12 changes: 12 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,18 @@
# .. 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
Expand Down
7 changes: 6 additions & 1 deletion lms/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,12 @@
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)
Expand Down
4 changes: 2 additions & 2 deletions lms/templates/dashboard/_dashboard_course_listing.html
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,11 @@ <h3 class="course-title" id="course-title-${enrollment.course_id}">
% 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>
Expand Down