feat: Upgrading api to use drf 5th api. register_and_enroll_students api (#35084)
* feat: upgrading simple api to drf compatible.
This commit is contained in:
@@ -642,6 +642,25 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(SharedModuleStoreTestCas
|
||||
last_name='Student'
|
||||
)
|
||||
|
||||
def test_api_without_login(self):
|
||||
"""
|
||||
verify in case of no authentication it returns 401.
|
||||
"""
|
||||
self.client.logout()
|
||||
uploaded_file = SimpleUploadedFile("temp.jpg", io.BytesIO(b"some initial binary data: \x00\x01").read())
|
||||
response = self.client.post(self.url, {'students_list': uploaded_file})
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_api_without_permission(self):
|
||||
"""
|
||||
verify in case of no authentication it returns 403.
|
||||
"""
|
||||
# removed role from course for instructor
|
||||
CourseInstructorRole(self.course.id).remove_users(self.instructor)
|
||||
uploaded_file = SimpleUploadedFile("temp.jpg", io.BytesIO(b"some initial binary data: \x00\x01").read())
|
||||
response = self.client.post(self.url, {'students_list': uploaded_file})
|
||||
assert response.status_code == 403
|
||||
|
||||
@patch('lms.djangoapps.instructor.views.api.log.info')
|
||||
@ddt.data(
|
||||
b"test_student@example.com,test_student_1,tester1,USA", # Typical use case.
|
||||
|
||||
@@ -283,299 +283,305 @@ def require_finance_admin(func):
|
||||
return wrapped
|
||||
|
||||
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.CAN_ENROLL)
|
||||
def register_and_enroll_students(request, course_id): # pylint: disable=too-many-statements
|
||||
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
|
||||
class RegisterAndEnrollStudents(APIView):
|
||||
"""
|
||||
Create new account and Enroll students in this course.
|
||||
Passing a csv file that contains a list of students.
|
||||
Order in csv should be the following email = 0; username = 1; name = 2; country = 3.
|
||||
If there are more than 4 columns in the csv: cohort = 4, course mode = 5.
|
||||
Requires staff access.
|
||||
|
||||
-If the email address and username already exists and the user is enrolled in the course,
|
||||
do nothing (including no email gets sent out)
|
||||
|
||||
-If the email address already exists, but the username is different,
|
||||
match on the email address only and continue to enroll the user in the course using the email address
|
||||
as the matching criteria. Note the change of username as a warning message (but not a failure).
|
||||
Send a standard enrollment email which is the same as the existing manual enrollment
|
||||
|
||||
-If the username already exists (but not the email), assume it is a different user and fail
|
||||
to create the new account.
|
||||
The failure will be messaged in a response in the browser.
|
||||
"""
|
||||
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
|
||||
permission_name = permissions.CAN_ENROLL
|
||||
|
||||
if not configuration_helpers.get_value(
|
||||
'ALLOW_AUTOMATED_SIGNUPS',
|
||||
settings.FEATURES.get('ALLOW_AUTOMATED_SIGNUPS', False),
|
||||
):
|
||||
return HttpResponseForbidden()
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
def post(self, request, course_id): # pylint: disable=too-many-statements
|
||||
"""
|
||||
Create new account and Enroll students in this course.
|
||||
Passing a csv file that contains a list of students.
|
||||
Order in csv should be the following email = 0; username = 1; name = 2; country = 3.
|
||||
If there are more than 4 columns in the csv: cohort = 4, course mode = 5.
|
||||
Requires staff access.
|
||||
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
warnings = []
|
||||
row_errors = []
|
||||
general_errors = []
|
||||
-If the email address and username already exists and the user is enrolled in the course,
|
||||
do nothing (including no email gets sent out)
|
||||
|
||||
# email-students is a checkbox input type; will be present in POST if checked, absent otherwise
|
||||
notify_by_email = 'email-students' in request.POST
|
||||
-If the email address already exists, but the username is different,
|
||||
match on the email address only and continue to enroll the user in the course using the email address
|
||||
as the matching criteria. Note the change of username as a warning message (but not a failure).
|
||||
Send a standard enrollment email which is the same as the existing manual enrollment
|
||||
|
||||
# for white labels we use 'shopping cart' which uses CourseMode.HONOR as
|
||||
# course mode for creating course enrollments.
|
||||
if CourseMode.is_white_label(course_id):
|
||||
default_course_mode = CourseMode.HONOR
|
||||
else:
|
||||
default_course_mode = None
|
||||
-If the username already exists (but not the email), assume it is a different user and fail
|
||||
to create the new account.
|
||||
The failure will be messaged in a response in the browser.
|
||||
"""
|
||||
if not configuration_helpers.get_value(
|
||||
'ALLOW_AUTOMATED_SIGNUPS',
|
||||
settings.FEATURES.get('ALLOW_AUTOMATED_SIGNUPS', False),
|
||||
):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
# Allow bulk enrollments in all non-expired course modes including "credit" (which is non-selectable)
|
||||
valid_course_modes = set(map(lambda x: x.slug, CourseMode.modes_for_course(
|
||||
course_id=course_id,
|
||||
only_selectable=False,
|
||||
include_expired=False,
|
||||
)))
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
warnings = []
|
||||
row_errors = []
|
||||
general_errors = []
|
||||
|
||||
if 'students_list' in request.FILES: # lint-amnesty, pylint: disable=too-many-nested-blocks
|
||||
students = []
|
||||
# email-students is a checkbox input type; will be present in POST if checked, absent otherwise
|
||||
notify_by_email = 'email-students' in request.POST
|
||||
|
||||
try:
|
||||
upload_file = request.FILES.get('students_list')
|
||||
if upload_file.name.endswith('.csv'):
|
||||
students = list(csv.reader(upload_file.read().decode('utf-8-sig').splitlines()))
|
||||
course = get_course_by_id(course_id)
|
||||
else:
|
||||
general_errors.append({
|
||||
'username': '', 'email': '',
|
||||
'response': _(
|
||||
'Make sure that the file you upload is in CSV format with no extraneous characters or rows.')
|
||||
})
|
||||
# for white labels we use 'shopping cart' which uses CourseMode.HONOR as
|
||||
# course mode for creating course enrollments.
|
||||
if CourseMode.is_white_label(course_id):
|
||||
default_course_mode = CourseMode.HONOR
|
||||
else:
|
||||
default_course_mode = None
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
general_errors.append({
|
||||
'username': '', 'email': '', 'response': _('Could not read uploaded file.')
|
||||
})
|
||||
finally:
|
||||
upload_file.close()
|
||||
# Allow bulk enrollments in all non-expired course modes including "credit" (which is non-selectable)
|
||||
valid_course_modes = set(map(lambda x: x.slug, CourseMode.modes_for_course(
|
||||
course_id=course_id,
|
||||
only_selectable=False,
|
||||
include_expired=False,
|
||||
)))
|
||||
|
||||
generated_passwords = []
|
||||
# To skip fetching cohorts from the DB while iterating on students,
|
||||
# {<cohort name>: CourseUserGroup}
|
||||
cohorts_cache = {}
|
||||
already_warned_not_cohorted = False
|
||||
extra_fields_is_enabled = configuration_helpers.get_value(
|
||||
'ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS',
|
||||
settings.FEATURES.get('ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS', False),
|
||||
)
|
||||
if 'students_list' in request.FILES: # lint-amnesty, pylint: disable=too-many-nested-blocks
|
||||
students = []
|
||||
|
||||
# Iterate each student in the uploaded csv file.
|
||||
for row_num, student in enumerate(students, 1):
|
||||
|
||||
# Verify that we have the expected number of columns in every row
|
||||
# but allow for blank lines.
|
||||
if not student:
|
||||
continue
|
||||
|
||||
if extra_fields_is_enabled:
|
||||
is_valid_csv = 4 <= len(student) <= 6
|
||||
error = _('Data in row #{row_num} must have between four and six columns: '
|
||||
'email, username, full name, country, cohort, and course mode. '
|
||||
'The last two columns are optional.').format(row_num=row_num)
|
||||
else:
|
||||
is_valid_csv = len(student) == 4
|
||||
error = _('Data in row #{row_num} must have exactly four columns: '
|
||||
'email, username, full name, and country.').format(row_num=row_num)
|
||||
|
||||
if not is_valid_csv:
|
||||
general_errors.append({
|
||||
'username': '',
|
||||
'email': '',
|
||||
'response': error
|
||||
})
|
||||
continue
|
||||
|
||||
# Extract each column, handle optional columns if they exist.
|
||||
email, username, name, country, *optional_cols = student
|
||||
if optional_cols:
|
||||
optional_cols.append(default_course_mode)
|
||||
cohort_name, course_mode, *_tail = optional_cols
|
||||
else:
|
||||
cohort_name = None
|
||||
course_mode = None
|
||||
|
||||
# Validate cohort name, and get the cohort object. Skip if course
|
||||
# is not cohorted.
|
||||
cohort = None
|
||||
|
||||
if cohort_name and not already_warned_not_cohorted:
|
||||
if not is_course_cohorted(course_id):
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Course is not cohorted but cohort provided. '
|
||||
'Ignoring cohort assignment for all users.')
|
||||
})
|
||||
already_warned_not_cohorted = True
|
||||
elif cohort_name in cohorts_cache:
|
||||
cohort = cohorts_cache[cohort_name]
|
||||
try:
|
||||
upload_file = request.FILES.get('students_list')
|
||||
if upload_file.name.endswith('.csv'):
|
||||
students = list(csv.reader(upload_file.read().decode('utf-8-sig').splitlines()))
|
||||
course = get_course_by_id(course_id)
|
||||
else:
|
||||
# Don't attempt to create cohort or assign student if cohort
|
||||
# does not exist.
|
||||
try:
|
||||
cohort = get_cohort_by_name(course_id, cohort_name)
|
||||
except CourseUserGroup.DoesNotExist:
|
||||
general_errors.append({
|
||||
'username': '', 'email': '',
|
||||
'response': _(
|
||||
'Make sure that the file you upload is in CSV format with no '
|
||||
'extraneous characters or rows.')
|
||||
})
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
general_errors.append({
|
||||
'username': '', 'email': '', 'response': _('Could not read uploaded file.')
|
||||
})
|
||||
finally:
|
||||
upload_file.close()
|
||||
|
||||
generated_passwords = []
|
||||
# To skip fetching cohorts from the DB while iterating on students,
|
||||
# {<cohort name>: CourseUserGroup}
|
||||
cohorts_cache = {}
|
||||
already_warned_not_cohorted = False
|
||||
extra_fields_is_enabled = configuration_helpers.get_value(
|
||||
'ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS',
|
||||
settings.FEATURES.get('ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS', False),
|
||||
)
|
||||
|
||||
# Iterate each student in the uploaded csv file.
|
||||
for row_num, student in enumerate(students, 1):
|
||||
|
||||
# Verify that we have the expected number of columns in every row
|
||||
# but allow for blank lines.
|
||||
if not student:
|
||||
continue
|
||||
|
||||
if extra_fields_is_enabled:
|
||||
is_valid_csv = 4 <= len(student) <= 6
|
||||
error = _('Data in row #{row_num} must have between four and six columns: '
|
||||
'email, username, full name, country, cohort, and course mode. '
|
||||
'The last two columns are optional.').format(row_num=row_num)
|
||||
else:
|
||||
is_valid_csv = len(student) == 4
|
||||
error = _('Data in row #{row_num} must have exactly four columns: '
|
||||
'email, username, full name, and country.').format(row_num=row_num)
|
||||
|
||||
if not is_valid_csv:
|
||||
general_errors.append({
|
||||
'username': '',
|
||||
'email': '',
|
||||
'response': error
|
||||
})
|
||||
continue
|
||||
|
||||
# Extract each column, handle optional columns if they exist.
|
||||
email, username, name, country, *optional_cols = student
|
||||
if optional_cols:
|
||||
optional_cols.append(default_course_mode)
|
||||
cohort_name, course_mode, *_tail = optional_cols
|
||||
else:
|
||||
cohort_name = None
|
||||
course_mode = None
|
||||
|
||||
# Validate cohort name, and get the cohort object. Skip if course
|
||||
# is not cohorted.
|
||||
cohort = None
|
||||
|
||||
if cohort_name and not already_warned_not_cohorted:
|
||||
if not is_course_cohorted(course_id):
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Cohort name not found: {cohort}. '
|
||||
'Ignoring cohort assignment for '
|
||||
'all users.').format(cohort=cohort_name)
|
||||
'response': _('Course is not cohorted but cohort provided. '
|
||||
'Ignoring cohort assignment for all users.')
|
||||
})
|
||||
cohorts_cache[cohort_name] = cohort
|
||||
|
||||
# Validate course mode.
|
||||
if not course_mode:
|
||||
course_mode = default_course_mode
|
||||
|
||||
if (course_mode is not None
|
||||
and course_mode not in valid_course_modes):
|
||||
# If `default is None` and the user is already enrolled,
|
||||
# `CourseEnrollment.change_mode()` will not update the mode,
|
||||
# hence two error messages.
|
||||
if default_course_mode is None:
|
||||
err_msg = _(
|
||||
'Invalid course mode: {mode}. Falling back to the '
|
||||
'default mode, or keeping the current mode in case the '
|
||||
'user is already enrolled.'
|
||||
).format(mode=course_mode)
|
||||
else:
|
||||
err_msg = _(
|
||||
'Invalid course mode: {mode}. Failling back to '
|
||||
'{default_mode}, or resetting to {default_mode} in case '
|
||||
'the user is already enrolled.'
|
||||
).format(mode=course_mode, default_mode=default_course_mode)
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': err_msg,
|
||||
})
|
||||
course_mode = default_course_mode
|
||||
|
||||
email_params = get_email_params(course, True, secure=request.is_secure())
|
||||
try:
|
||||
validate_email(email) # Raises ValidationError if invalid
|
||||
except ValidationError:
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Invalid email {email_address}.').format(email_address=email)
|
||||
})
|
||||
else:
|
||||
if User.objects.filter(email=email).exists():
|
||||
# Email address already exists. assume it is the correct user
|
||||
# and just register the user in the course and send an enrollment email.
|
||||
user = User.objects.get(email=email)
|
||||
|
||||
# see if it is an exact match with email and username
|
||||
# if it's not an exact match then just display a warning message, but continue onwards
|
||||
if not User.objects.filter(email=email, username=username).exists():
|
||||
warning_message = _(
|
||||
'An account with email {email} exists but the provided username {username} '
|
||||
'is different. Enrolling anyway with {email}.'
|
||||
).format(email=email, username=username)
|
||||
|
||||
warnings.append({
|
||||
'username': username, 'email': email, 'response': warning_message
|
||||
})
|
||||
log.warning('email %s already exist', email)
|
||||
already_warned_not_cohorted = True
|
||||
elif cohort_name in cohorts_cache:
|
||||
cohort = cohorts_cache[cohort_name]
|
||||
else:
|
||||
log.info(
|
||||
"user already exists with username '%s' and email '%s'",
|
||||
username,
|
||||
email
|
||||
)
|
||||
|
||||
# enroll a user if it is not already enrolled.
|
||||
if not is_user_enrolled_in_course(user, course_id):
|
||||
# Enroll user to the course and add manual enrollment audit trail
|
||||
create_manual_course_enrollment(
|
||||
user=user,
|
||||
course_id=course_id,
|
||||
mode=course_mode,
|
||||
enrolled_by=request.user,
|
||||
reason='Enrolling via csv upload',
|
||||
state_transition=UNENROLLED_TO_ENROLLED,
|
||||
)
|
||||
enroll_email(course_id=course_id,
|
||||
student_email=email,
|
||||
auto_enroll=True,
|
||||
email_students=notify_by_email,
|
||||
email_params=email_params)
|
||||
else:
|
||||
# update the course mode if already enrolled
|
||||
existing_enrollment = CourseEnrollment.get_enrollment(user, course_id)
|
||||
if existing_enrollment.mode != course_mode:
|
||||
existing_enrollment.change_mode(mode=course_mode)
|
||||
if cohort:
|
||||
# Don't attempt to create cohort or assign student if cohort
|
||||
# does not exist.
|
||||
try:
|
||||
add_user_to_cohort(cohort, user)
|
||||
except ValueError:
|
||||
# user already in this cohort; ignore
|
||||
pass
|
||||
elif is_email_retired(email):
|
||||
# We are either attempting to enroll a retired user or create a new user with an email which is
|
||||
# already associated with a retired account. Simply block these attempts.
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Invalid email {email_address}.').format(email_address=email),
|
||||
})
|
||||
log.warning('Email address %s is associated with a retired user, so course enrollment was ' + # lint-amnesty, pylint: disable=logging-not-lazy
|
||||
'blocked.', email)
|
||||
else:
|
||||
# This email does not yet exist, so we need to create a new account
|
||||
# If username already exists in the database, then create_and_enroll_user
|
||||
# will raise an IntegrityError exception.
|
||||
password = generate_unique_password(generated_passwords)
|
||||
errors = create_and_enroll_user(
|
||||
email,
|
||||
username,
|
||||
name,
|
||||
country,
|
||||
password,
|
||||
course_id,
|
||||
course_mode,
|
||||
request.user,
|
||||
email_params,
|
||||
email_user=notify_by_email,
|
||||
)
|
||||
row_errors.extend(errors)
|
||||
if cohort:
|
||||
try:
|
||||
add_user_to_cohort(cohort, email)
|
||||
except ValueError:
|
||||
# user already in this cohort; ignore
|
||||
# NOTE: Checking this here may be unnecessary if we can prove that a new user will never be
|
||||
# automatically assigned to a cohort from the above.
|
||||
pass
|
||||
except ValidationError:
|
||||
cohort = get_cohort_by_name(course_id, cohort_name)
|
||||
except CourseUserGroup.DoesNotExist:
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Invalid email {email_address}.').format(email_address=email),
|
||||
'response': _('Cohort name not found: {cohort}. '
|
||||
'Ignoring cohort assignment for '
|
||||
'all users.').format(cohort=cohort_name)
|
||||
})
|
||||
cohorts_cache[cohort_name] = cohort
|
||||
|
||||
else:
|
||||
general_errors.append({
|
||||
'username': '', 'email': '', 'response': _('File is not attached.')
|
||||
})
|
||||
# Validate course mode.
|
||||
if not course_mode:
|
||||
course_mode = default_course_mode
|
||||
|
||||
results = {
|
||||
'row_errors': row_errors,
|
||||
'general_errors': general_errors,
|
||||
'warnings': warnings
|
||||
}
|
||||
return JsonResponse(results)
|
||||
if (course_mode is not None
|
||||
and course_mode not in valid_course_modes):
|
||||
# If `default is None` and the user is already enrolled,
|
||||
# `CourseEnrollment.change_mode()` will not update the mode,
|
||||
# hence two error messages.
|
||||
if default_course_mode is None:
|
||||
err_msg = _(
|
||||
'Invalid course mode: {mode}. Falling back to the '
|
||||
'default mode, or keeping the current mode in case the '
|
||||
'user is already enrolled.'
|
||||
).format(mode=course_mode)
|
||||
else:
|
||||
err_msg = _(
|
||||
'Invalid course mode: {mode}. Failling back to '
|
||||
'{default_mode}, or resetting to {default_mode} in case '
|
||||
'the user is already enrolled.'
|
||||
).format(mode=course_mode, default_mode=default_course_mode)
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': err_msg,
|
||||
})
|
||||
course_mode = default_course_mode
|
||||
|
||||
email_params = get_email_params(course, True, secure=request.is_secure())
|
||||
try:
|
||||
validate_email(email) # Raises ValidationError if invalid
|
||||
except ValidationError:
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Invalid email {email_address}.').format(email_address=email)
|
||||
})
|
||||
else:
|
||||
if User.objects.filter(email=email).exists():
|
||||
# Email address already exists. assume it is the correct user
|
||||
# and just register the user in the course and send an enrollment email.
|
||||
user = User.objects.get(email=email)
|
||||
|
||||
# see if it is an exact match with email and username
|
||||
# if it's not an exact match then just display a warning message, but continue onwards
|
||||
if not User.objects.filter(email=email, username=username).exists():
|
||||
warning_message = _(
|
||||
'An account with email {email} exists but the provided username {username} '
|
||||
'is different. Enrolling anyway with {email}.'
|
||||
).format(email=email, username=username)
|
||||
|
||||
warnings.append({
|
||||
'username': username, 'email': email, 'response': warning_message
|
||||
})
|
||||
log.warning('email %s already exist', email)
|
||||
else:
|
||||
log.info(
|
||||
"user already exists with username '%s' and email '%s'",
|
||||
username,
|
||||
email
|
||||
)
|
||||
|
||||
# enroll a user if it is not already enrolled.
|
||||
if not is_user_enrolled_in_course(user, course_id):
|
||||
# Enroll user to the course and add manual enrollment audit trail
|
||||
create_manual_course_enrollment(
|
||||
user=user,
|
||||
course_id=course_id,
|
||||
mode=course_mode,
|
||||
enrolled_by=request.user,
|
||||
reason='Enrolling via csv upload',
|
||||
state_transition=UNENROLLED_TO_ENROLLED,
|
||||
)
|
||||
enroll_email(course_id=course_id,
|
||||
student_email=email,
|
||||
auto_enroll=True,
|
||||
email_students=notify_by_email,
|
||||
email_params=email_params)
|
||||
else:
|
||||
# update the course mode if already enrolled
|
||||
existing_enrollment = CourseEnrollment.get_enrollment(user, course_id)
|
||||
if existing_enrollment.mode != course_mode:
|
||||
existing_enrollment.change_mode(mode=course_mode)
|
||||
if cohort:
|
||||
try:
|
||||
add_user_to_cohort(cohort, user)
|
||||
except ValueError:
|
||||
# user already in this cohort; ignore
|
||||
pass
|
||||
elif is_email_retired(email):
|
||||
# We are either attempting to enroll a retired user or create a new user with an email which is
|
||||
# already associated with a retired account. Simply block these attempts.
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Invalid email {email_address}.').format(email_address=email),
|
||||
})
|
||||
log.warning('Email address %s is associated with a retired user, so course enrollment was ' + # lint-amnesty, pylint: disable=logging-not-lazy
|
||||
'blocked.', email)
|
||||
else:
|
||||
# This email does not yet exist, so we need to create a new account
|
||||
# If username already exists in the database, then create_and_enroll_user
|
||||
# will raise an IntegrityError exception.
|
||||
password = generate_unique_password(generated_passwords)
|
||||
errors = create_and_enroll_user(
|
||||
email,
|
||||
username,
|
||||
name,
|
||||
country,
|
||||
password,
|
||||
course_id,
|
||||
course_mode,
|
||||
request.user,
|
||||
email_params,
|
||||
email_user=notify_by_email,
|
||||
)
|
||||
row_errors.extend(errors)
|
||||
if cohort:
|
||||
try:
|
||||
add_user_to_cohort(cohort, email)
|
||||
except ValueError:
|
||||
# user already in this cohort; ignore
|
||||
# NOTE: Checking this here may be unnecessary if we can prove that a
|
||||
# new user will never be
|
||||
# automatically assigned to a cohort from the above.
|
||||
pass
|
||||
except ValidationError:
|
||||
row_errors.append({
|
||||
'username': username,
|
||||
'email': email,
|
||||
'response': _('Invalid email {email_address}.').format(email_address=email),
|
||||
})
|
||||
|
||||
else:
|
||||
general_errors.append({
|
||||
'username': '', 'email': '', 'response': _('File is not attached.')
|
||||
})
|
||||
|
||||
results = {
|
||||
'row_errors': row_errors,
|
||||
'general_errors': general_errors,
|
||||
'warnings': warnings
|
||||
}
|
||||
return JsonResponse(results)
|
||||
|
||||
|
||||
def generate_random_string(length):
|
||||
|
||||
@@ -22,7 +22,7 @@ v1_api_urls = [
|
||||
|
||||
urlpatterns = [
|
||||
path('students_update_enrollment', api.students_update_enrollment, name='students_update_enrollment'),
|
||||
path('register_and_enroll_students', api.register_and_enroll_students, name='register_and_enroll_students'),
|
||||
path('register_and_enroll_students', api.RegisterAndEnrollStudents.as_view(), name='register_and_enroll_students'),
|
||||
path('list_course_role_members', api.ListCourseRoleMembersView.as_view(), name='list_course_role_members'),
|
||||
path('modify_access', api.modify_access, name='modify_access'),
|
||||
path('bulk_beta_modify_access', api.bulk_beta_modify_access, name='bulk_beta_modify_access'),
|
||||
|
||||
Reference in New Issue
Block a user