Merge pull request #32595 from openedx/ea/ent-7031
feature: upgrade course enrollment from audit to verified
This commit is contained in:
@@ -52,3 +52,7 @@ class EnrollmentApiLoadError(CourseEnrollmentError):
|
||||
class InvalidEnrollmentAttribute(CourseEnrollmentError):
|
||||
"""Enrollment Attributes could not be validated"""
|
||||
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
||||
|
||||
|
||||
class CourseEnrollmentNotUpdatableError(CourseEnrollmentError):
|
||||
"""The requested enrollment could not be updated."""
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"""
|
||||
Test the enterprise support utils.
|
||||
"""
|
||||
|
||||
import ddt
|
||||
from unittest import mock
|
||||
from unittest.case import TestCase
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.test import override_settings
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.course_groups.cohorts import CourseUserGroup
|
||||
@@ -15,8 +16,10 @@ from openedx.features.enterprise_support.enrollments.exceptions import (
|
||||
CourseIdMissingException,
|
||||
UserDoesNotExistException
|
||||
)
|
||||
from openedx.features.enterprise_support.enrollments.utils import lms_enroll_user_in_course
|
||||
|
||||
from openedx.features.enterprise_support.enrollments.utils import (
|
||||
lms_enroll_user_in_course,
|
||||
lms_update_or_create_enrollment,
|
||||
)
|
||||
COURSE_STRING = 'course-v1:OpenEdX+OutlineCourse+Run3'
|
||||
ENTERPRISE_UUID = 'enterprise_uuid'
|
||||
COURSE_ID = CourseKey.from_string(COURSE_STRING)
|
||||
@@ -26,6 +29,7 @@ COURSE_MODE = 'verified'
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@ddt.ddt
|
||||
class EnrollmentUtilsTest(TestCase):
|
||||
"""
|
||||
Test enterprise support utils.
|
||||
@@ -37,104 +41,222 @@ class EnrollmentUtilsTest(TestCase):
|
||||
self.a_user.id = USER_ID
|
||||
self.a_user.username = USERNAME
|
||||
|
||||
def test_validation_of_inputs_course_id(self):
|
||||
with self.assertRaises(CourseIdMissingException):
|
||||
lms_enroll_user_in_course(USERNAME, None, COURSE_MODE, ENTERPRISE_UUID)
|
||||
def run_test_with_setting(
|
||||
self,
|
||||
setting,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false,
|
||||
):
|
||||
"""
|
||||
Run a test with a setting.
|
||||
"""
|
||||
with override_settings(
|
||||
ENABLE_ENTERPRISE_BACKEND_EMET_AUTO_UPGRADE_ENROLLMENT_MODE=setting
|
||||
):
|
||||
if setting:
|
||||
return test_function_true(mock_update_create_enroll)
|
||||
return test_function_false(mock_enroll_user)
|
||||
|
||||
def test_validation_of_inputs_user_not_provided(self):
|
||||
with self.assertRaises(UserDoesNotExistException):
|
||||
lms_enroll_user_in_course(
|
||||
None,
|
||||
COURSE_ID,
|
||||
COURSE_MODE,
|
||||
ENTERPRISE_UUID,
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_enroll_user_in_course')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_update_or_create_enrollment')
|
||||
@ddt.data(True, False)
|
||||
def test_validation_of_inputs_course_id(self, setting_value, mock_update_create_enroll, mock_enroll_user):
|
||||
test_function_true = lambda mock_fn: lms_update_or_create_enrollment(
|
||||
USERNAME, None, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
|
||||
)
|
||||
test_function_false = lambda mock_fn: lms_enroll_user_in_course(USERNAME, None, COURSE_MODE, ENTERPRISE_UUID)
|
||||
with self.assertRaises(CourseIdMissingException):
|
||||
self.run_test_with_setting(
|
||||
setting_value,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false
|
||||
)
|
||||
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_enroll_user_in_course')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_update_or_create_enrollment')
|
||||
@ddt.data(True, False)
|
||||
def test_validation_of_inputs_user_not_provided(self, setting_value, mock_update_create_enroll, mock_enroll_user):
|
||||
test_function_true = lambda mock_fn: lms_update_or_create_enrollment(
|
||||
None, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
|
||||
)
|
||||
test_function_false = lambda mock_fn: lms_enroll_user_in_course(None, COURSE_ID, COURSE_MODE, ENTERPRISE_UUID)
|
||||
with self.assertRaises(UserDoesNotExistException):
|
||||
self.run_test_with_setting(
|
||||
setting_value,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false
|
||||
)
|
||||
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_enroll_user_in_course')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_update_or_create_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
|
||||
def test_validation_of_inputs_user_not_found(self, mock_tx, mock_user_model):
|
||||
@ddt.data(True, False)
|
||||
def test_validation_of_inputs_user_not_found(
|
||||
self,
|
||||
setting_value,
|
||||
mock_tx,
|
||||
mock_user_model,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user
|
||||
):
|
||||
mock_tx.return_value.atomic.side_effect = None
|
||||
mock_user_model.side_effect = ObjectDoesNotExist()
|
||||
test_function_true = lambda mock_fn: lms_update_or_create_enrollment(
|
||||
USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
|
||||
)
|
||||
test_function_false = lambda mock_fn: lms_enroll_user_in_course(
|
||||
USERNAME,
|
||||
COURSE_ID,
|
||||
COURSE_MODE,
|
||||
ENTERPRISE_UUID
|
||||
)
|
||||
with self.assertRaises(UserDoesNotExistException):
|
||||
lms_enroll_user_in_course(
|
||||
USERNAME,
|
||||
COURSE_ID,
|
||||
COURSE_MODE,
|
||||
ENTERPRISE_UUID,
|
||||
self.run_test_with_setting(
|
||||
setting_value,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false
|
||||
)
|
||||
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_enroll_user_in_course')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_update_or_create_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
|
||||
@ddt.data(True, False)
|
||||
def test_course_enrollment_error_raises(
|
||||
self,
|
||||
setting_value,
|
||||
mock_tx,
|
||||
mock_user_model,
|
||||
mock_get_enrollment_api,
|
||||
mock_add_enrollment_api,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user
|
||||
):
|
||||
enrollment_response = {'mode': COURSE_MODE, 'is_active': True}
|
||||
test_function_true = lambda mock_fn: lms_update_or_create_enrollment(
|
||||
USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
|
||||
)
|
||||
test_function_false = lambda mock_fn: lms_enroll_user_in_course(
|
||||
USERNAME,
|
||||
COURSE_ID,
|
||||
COURSE_MODE,
|
||||
ENTERPRISE_UUID
|
||||
)
|
||||
|
||||
mock_add_enrollment_api.side_effect = CourseEnrollmentError("test")
|
||||
mock_tx.return_value.atomic.side_effect = None
|
||||
|
||||
mock_get_enrollment_api.return_value = enrollment_response
|
||||
|
||||
mock_user_model.return_value = self.a_user
|
||||
|
||||
enrollment_response = {'mode': COURSE_MODE, 'is_active': True} if not setting_value else None
|
||||
mock_get_enrollment_api.return_value = enrollment_response
|
||||
with self.assertRaises(CourseEnrollmentError):
|
||||
lms_enroll_user_in_course(USERNAME, COURSE_ID, COURSE_MODE, ENTERPRISE_UUID)
|
||||
mock_get_enrollment_api.assert_called_once()
|
||||
self.run_test_with_setting(
|
||||
setting_value,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false
|
||||
)
|
||||
mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
|
||||
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_enroll_user_in_course')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_update_or_create_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
|
||||
@ddt.data(True, False)
|
||||
def test_course_group_error_raises(
|
||||
self,
|
||||
setting_value,
|
||||
mock_tx,
|
||||
mock_user_model,
|
||||
mock_get_enrollment_api,
|
||||
mock_add_enrollment_api,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user
|
||||
):
|
||||
enrollment_response = {'mode': COURSE_MODE, 'is_active': True}
|
||||
|
||||
test_function_true = lambda mock_fn: lms_update_or_create_enrollment(
|
||||
USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
|
||||
)
|
||||
test_function_false = lambda mock_fn: lms_enroll_user_in_course(
|
||||
USERNAME,
|
||||
COURSE_ID,
|
||||
COURSE_MODE,
|
||||
ENTERPRISE_UUID
|
||||
)
|
||||
mock_add_enrollment_api.side_effect = CourseUserGroup.DoesNotExist()
|
||||
mock_tx.return_value.atomic.side_effect = None
|
||||
|
||||
mock_get_enrollment_api.return_value = enrollment_response
|
||||
|
||||
mock_user_model.return_value = self.a_user
|
||||
|
||||
enrollment_response = {'mode': COURSE_MODE, 'is_active': True} if not setting_value else None
|
||||
mock_get_enrollment_api.return_value = enrollment_response
|
||||
with self.assertRaises(CourseUserGroup.DoesNotExist):
|
||||
lms_enroll_user_in_course(USERNAME, COURSE_ID, COURSE_MODE, ENTERPRISE_UUID)
|
||||
mock_get_enrollment_api.assert_called_once()
|
||||
self.run_test_with_setting(
|
||||
setting_value,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false
|
||||
)
|
||||
mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
|
||||
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_enroll_user_in_course')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_update_or_create_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
|
||||
@ddt.data(True, False)
|
||||
def test_calls_enrollment_and_cohort_apis(
|
||||
self,
|
||||
setting,
|
||||
mock_tx,
|
||||
mock_user_model,
|
||||
mock_get_enrollment_api,
|
||||
mock_add_enrollment_api,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
):
|
||||
|
||||
expected_response = {'a': 'value'}
|
||||
test_function_true = lambda mock_fn: lms_update_or_create_enrollment(
|
||||
USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
|
||||
)
|
||||
test_function_false = lambda mock_fn: lms_enroll_user_in_course(
|
||||
USERNAME,
|
||||
COURSE_ID,
|
||||
COURSE_MODE,
|
||||
ENTERPRISE_UUID
|
||||
)
|
||||
expected_response = {'mode': COURSE_MODE, 'is_active': True}
|
||||
enrollment_response = {'mode': COURSE_MODE, 'is_active': True}
|
||||
|
||||
mock_add_enrollment_api.return_value = expected_response
|
||||
mock_tx.return_value.atomic.side_effect = None
|
||||
|
||||
mock_get_enrollment_api.return_value = enrollment_response
|
||||
|
||||
mock_user_model.return_value = self.a_user
|
||||
|
||||
response = lms_enroll_user_in_course(USERNAME, COURSE_ID, COURSE_MODE, ENTERPRISE_UUID)
|
||||
|
||||
if setting:
|
||||
mock_get_enrollment_api.return_value = None
|
||||
else:
|
||||
mock_get_enrollment_api.return_value = enrollment_response
|
||||
response = self.run_test_with_setting(
|
||||
setting,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false
|
||||
)
|
||||
assert response == expected_response
|
||||
mock_add_enrollment_api.assert_called_once_with(
|
||||
USERNAME,
|
||||
@@ -144,22 +266,35 @@ class EnrollmentUtilsTest(TestCase):
|
||||
enrollment_attributes=None,
|
||||
enterprise_uuid=ENTERPRISE_UUID,
|
||||
)
|
||||
|
||||
mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
|
||||
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_enroll_user_in_course')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.lms_update_or_create_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
|
||||
@ddt.data(True, False)
|
||||
def test_existing_enrollment_does_not_fail(
|
||||
self,
|
||||
setting_value,
|
||||
mock_tx,
|
||||
mock_user_model,
|
||||
mock_get_enrollment_api,
|
||||
mock_add_enrollment_api,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
):
|
||||
|
||||
expected_response = None
|
||||
test_function_true = lambda mock_fn: lms_update_or_create_enrollment(
|
||||
USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
|
||||
)
|
||||
test_function_false = lambda mock_fn: lms_enroll_user_in_course(
|
||||
USERNAME,
|
||||
COURSE_ID,
|
||||
COURSE_MODE,
|
||||
ENTERPRISE_UUID
|
||||
)
|
||||
expected_response = {'mode': COURSE_MODE, 'is_active': True}
|
||||
enrollment_response = {'mode': COURSE_MODE, 'is_active': True}
|
||||
|
||||
mock_add_enrollment_api.side_effect = CourseEnrollmentExistsError("test", {})
|
||||
@@ -169,16 +304,97 @@ class EnrollmentUtilsTest(TestCase):
|
||||
|
||||
mock_user_model.return_value = self.a_user
|
||||
|
||||
response = lms_enroll_user_in_course(USERNAME, COURSE_ID, COURSE_MODE, ENTERPRISE_UUID)
|
||||
response = self.run_test_with_setting(
|
||||
setting_value,
|
||||
mock_update_create_enroll,
|
||||
mock_enroll_user,
|
||||
test_function_true,
|
||||
test_function_false
|
||||
)
|
||||
if setting_value:
|
||||
mock_add_enrollment_api.assert_not_called()
|
||||
assert response == expected_response
|
||||
else:
|
||||
mock_add_enrollment_api.assert_called_once_with(
|
||||
USERNAME,
|
||||
str(COURSE_ID),
|
||||
mode=COURSE_MODE,
|
||||
is_active=True,
|
||||
enrollment_attributes=None,
|
||||
enterprise_uuid=ENTERPRISE_UUID,
|
||||
)
|
||||
assert response is None
|
||||
mock_get_enrollment_api.assert_called_once()
|
||||
|
||||
assert response == expected_response
|
||||
mock_add_enrollment_api.assert_called_once_with(
|
||||
USERNAME,
|
||||
str(COURSE_ID),
|
||||
mode=COURSE_MODE,
|
||||
is_active=True,
|
||||
enrollment_attributes=None,
|
||||
enterprise_uuid=ENTERPRISE_UUID,
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.update_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
|
||||
def test_upgrade_user_enrollment_mode(
|
||||
self,
|
||||
mock_tx,
|
||||
mock_user_model,
|
||||
mock_add_enrollment_api,
|
||||
mock_get_enrollment_api,
|
||||
mock_update_enrollment_api,
|
||||
):
|
||||
enrollment_response = {'mode': COURSE_MODE, 'is_active': True}
|
||||
mock_get_enrollment_api.return_value = {
|
||||
'mode': 'audit',
|
||||
'is_active': True,
|
||||
}
|
||||
|
||||
mock_update_enrollment_api.return_value = {
|
||||
'mode': 'verified',
|
||||
'is_active': True,
|
||||
}
|
||||
mock_tx.return_value.atomic.side_effect = None
|
||||
mock_user_model.return_value = self.a_user
|
||||
|
||||
upgraded_enrollment = lms_update_or_create_enrollment(
|
||||
USERNAME, COURSE_ID, desired_mode=COURSE_MODE, is_active=True
|
||||
)
|
||||
|
||||
assert upgraded_enrollment == enrollment_response
|
||||
mock_update_enrollment_api.assert_called_once_with(
|
||||
USERNAME,
|
||||
str(COURSE_ID),
|
||||
mode='verified',
|
||||
is_active=True,
|
||||
enrollment_attributes=None,
|
||||
)
|
||||
|
||||
mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
|
||||
mock_add_enrollment_api.assert_not_called()
|
||||
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.update_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
|
||||
@mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
|
||||
def test_upgrade_user_enrollment_mode_already_verified(
|
||||
self,
|
||||
mock_tx,
|
||||
mock_user_model,
|
||||
mock_add_enrollment_api,
|
||||
mock_get_enrollment_api,
|
||||
mock_update_enrollment_api,
|
||||
):
|
||||
existing_enrollment = {
|
||||
'mode': 'verified',
|
||||
'is_active': True,
|
||||
}
|
||||
mock_get_enrollment_api.return_value = existing_enrollment
|
||||
|
||||
mock_tx.return_value.atomic.side_effect = None
|
||||
mock_user_model.return_value = self.a_user
|
||||
|
||||
upgraded_enrollment = lms_update_or_create_enrollment(
|
||||
USERNAME, COURSE_ID, desired_mode='verified', is_active=True
|
||||
)
|
||||
|
||||
assert upgraded_enrollment == existing_enrollment
|
||||
mock_update_enrollment_api.assert_not_called()
|
||||
mock_get_enrollment_api.assert_called_once()
|
||||
mock_add_enrollment_api.assert_not_called()
|
||||
|
||||
@@ -8,7 +8,11 @@ from django.db import transaction
|
||||
|
||||
from common.djangoapps.student.models import User
|
||||
from openedx.core.djangoapps.enrollments import api as enrollment_api
|
||||
from openedx.core.djangoapps.enrollments.errors import CourseEnrollmentError, CourseEnrollmentExistsError
|
||||
from openedx.core.djangoapps.enrollments.errors import (
|
||||
CourseEnrollmentError,
|
||||
CourseEnrollmentExistsError,
|
||||
CourseEnrollmentNotUpdatableError,
|
||||
)
|
||||
from openedx.core.lib.log_utils import audit_log
|
||||
from openedx.features.enterprise_support.enrollments.exceptions import (
|
||||
CourseIdMissingException,
|
||||
@@ -26,25 +30,7 @@ def lms_enroll_user_in_course(
|
||||
is_active=True,
|
||||
):
|
||||
"""
|
||||
Enrollment function meant to be called by edx-enterprise to replace the
|
||||
current uses of the EnrollmentApiClient
|
||||
The REST enrollment endpoint may also eventually also want to reuse this function
|
||||
since it's a subset of what the endpoint handles
|
||||
|
||||
Unlike the REST endpoint, this function does not check for enterprise enabled, or user api key
|
||||
permissions etc. Those concerns are still going to be used by REST endpoint but this function
|
||||
is meant for use from within edx-enterprise hence already presume such privileges.
|
||||
|
||||
Arguments:
|
||||
- username (str): User name
|
||||
- course_id (obj) : Course key obtained using CourseKey.from_string(course_id_input)
|
||||
- mode (CourseMode): course mode
|
||||
- enterprise_uuid (str): id to identify the enterprise to enroll under
|
||||
- is_active (bool): Optional. A Boolean value that indicates whether the
|
||||
enrollment is to be set to inactive (if False). Usually we want a True if enrolling anew.
|
||||
|
||||
Returns: A serializable dictionary of the new course enrollment. If it hits
|
||||
`CourseEnrollmentExistsError` then it logs the error and returns None.
|
||||
Temporarily keeping the original enrollment function to help with deployment
|
||||
"""
|
||||
user = _validate_enrollment_inputs(username, course_id)
|
||||
|
||||
@@ -81,6 +67,134 @@ def lms_enroll_user_in_course(
|
||||
)
|
||||
|
||||
|
||||
def lms_update_or_create_enrollment(
|
||||
username,
|
||||
course_id,
|
||||
desired_mode,
|
||||
is_active,
|
||||
enterprise_uuid=None,
|
||||
):
|
||||
"""
|
||||
Update or create the user's course enrollment based on the existing enrollment mode.
|
||||
If an enrollment exists and its mode is not equal to the desired mode,
|
||||
then it updates the enrollment.
|
||||
Otherwise, it creates a new enrollment.
|
||||
Enrollment function meant to be called by edx-enterprise to replace the
|
||||
current uses of the EnrollmentApiClient
|
||||
The REST enrollment endpoint may also eventually also want to reuse this function
|
||||
since it's a subset of what the endpoint handles
|
||||
|
||||
Unlike the REST endpoint, this function does not check for enterprise enabled, or user api key
|
||||
permissions etc. Those concerns are still going to be used by REST endpoint but this function
|
||||
is meant for use from within edx-enterprise hence already presume such privileges.
|
||||
|
||||
Arguments:
|
||||
- username (str): User name
|
||||
- course_id (obj) : Course key obtained using CourseKey.from_string(course_id_input)
|
||||
- desired_mode (CourseMode): desired course mode
|
||||
- is_active (bool): A Boolean value that indicates whether the
|
||||
enrollment is to be set to inactive (if False). Usually we want a True if enrolling anew.
|
||||
- enterprise_uuid (str): Optional. id to identify the enterprise to enroll under
|
||||
|
||||
Returns: A serializable dictionary of the new or updated course enrollment. If it hits
|
||||
CourseEnrollmentError or CourseEnrollmentNotUpdatableError, it raises those exceptions.
|
||||
In case of the add_enrollment call, it returns None if the enrollment already exists and
|
||||
the desired_mode or is_active match the existing enrollment.
|
||||
"""
|
||||
user = _validate_enrollment_inputs(username, course_id)
|
||||
current_enrollment = enrollment_api.get_enrollment(username, str(course_id))
|
||||
response = None
|
||||
if (
|
||||
current_enrollment
|
||||
and current_enrollment['mode'] == desired_mode
|
||||
and current_enrollment['is_active'] == is_active
|
||||
):
|
||||
log.info(
|
||||
"Existing enrollment [%s] for user [%s] matches desired enrollment. No action taken.",
|
||||
current_enrollment,
|
||||
username,
|
||||
)
|
||||
return current_enrollment
|
||||
with transaction.atomic():
|
||||
try:
|
||||
if current_enrollment:
|
||||
response = enrollment_api.update_enrollment(
|
||||
username,
|
||||
str(course_id),
|
||||
mode=desired_mode,
|
||||
is_active=is_active,
|
||||
enrollment_attributes=None,
|
||||
)
|
||||
if not response or (
|
||||
response['mode'] != desired_mode or
|
||||
response['is_active'] != is_active
|
||||
):
|
||||
log.exception(
|
||||
"An error occurred while updating the course enrollment for user "
|
||||
"[%s]: course run = [%s], enterprise_uuid = [%s], is_active = [%s], ",
|
||||
username,
|
||||
course_id,
|
||||
str(enterprise_uuid),
|
||||
is_active,
|
||||
)
|
||||
raise CourseEnrollmentNotUpdatableError(
|
||||
f"Unable to upgrade enrollment for user {username} "
|
||||
"in course {course_id} to {desired_mode} mode."
|
||||
"Response from update_enrollment: {response}"
|
||||
)
|
||||
else:
|
||||
response = enrollment_api.add_enrollment(
|
||||
username,
|
||||
str(course_id),
|
||||
mode=desired_mode,
|
||||
is_active=is_active,
|
||||
enrollment_attributes=None,
|
||||
enterprise_uuid=enterprise_uuid,
|
||||
)
|
||||
if not response:
|
||||
log.exception(
|
||||
"An error occurred while creating the new course enrollment for user "
|
||||
"[%s] in course run [%s]",
|
||||
username,
|
||||
course_id,
|
||||
)
|
||||
raise CourseEnrollmentError(
|
||||
f"Unable to create enrollment for user {username} in course {course_id}."
|
||||
)
|
||||
except CourseEnrollmentExistsError as error:
|
||||
# This will rarely be raised when we hit a race condition in adding a net-new enrollment
|
||||
log.warning(
|
||||
"An enrollment [%s] already exists for user [%s] in course run [%s].",
|
||||
error.enrollment,
|
||||
username,
|
||||
course_id,
|
||||
)
|
||||
return None
|
||||
except (CourseEnrollmentError, CourseEnrollmentNotUpdatableError) as error:
|
||||
log.exception(
|
||||
"Raising error [%s] for user "
|
||||
"[%s]: course run = [%s], enterprise_uuid = [%s], is_active = [%s], ",
|
||||
error,
|
||||
username,
|
||||
course_id,
|
||||
str(enterprise_uuid),
|
||||
is_active,
|
||||
)
|
||||
raise error
|
||||
finally:
|
||||
final_enrollment = response or current_enrollment
|
||||
audit_log(
|
||||
'enrollment_change_requested',
|
||||
course_id=str(course_id),
|
||||
requested_mode=desired_mode,
|
||||
actual_mode=final_enrollment['mode'] if final_enrollment else None,
|
||||
requested_activation=is_active,
|
||||
actual_activation=final_enrollment['is_active'] if final_enrollment else None,
|
||||
user_id=user.id
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def _validate_enrollment_inputs(username, course_id):
|
||||
"""
|
||||
Validates username and course_id.
|
||||
|
||||
@@ -27,7 +27,7 @@ django-storages==1.9.1
|
||||
# The team that owns this package will manually bump this package rather than having it pulled in automatically.
|
||||
# This is to allow them to better control its deployment and to do it in a process that works better
|
||||
# for them.
|
||||
edx-enterprise==4.0.0
|
||||
edx-enterprise==4.0.1
|
||||
|
||||
# oauthlib>3.0.1 causes test failures ( also remove the django-oauth-toolkit constraint when this is fixed )
|
||||
oauthlib==3.0.1
|
||||
|
||||
@@ -484,7 +484,7 @@ edx-drf-extensions==8.8.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# learner-pathway-progress
|
||||
edx-enterprise==4.0.0
|
||||
edx-enterprise==4.0.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
|
||||
@@ -741,7 +741,7 @@ edx-drf-extensions==8.8.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# learner-pathway-progress
|
||||
edx-enterprise==4.0.0
|
||||
edx-enterprise==4.0.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
|
||||
@@ -590,7 +590,7 @@ edx-drf-extensions==8.8.0
|
||||
# edx-when
|
||||
# edxval
|
||||
# learner-pathway-progress
|
||||
edx-enterprise==4.0.0
|
||||
edx-enterprise==4.0.1
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
|
||||
Reference in New Issue
Block a user