def delete(self, request, course_id): """ Unenroll a user from a course; requires staff access **Example Request** DELETE /api/enrollment/v1/roster/course-v1:foo+bar+foobar { 'email': '*****@*****.**', 'email_students': false, 'auto_enroll': true } """ try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': u'Invalid or missing course_id', }, ) if not user_has_role(request.user, CourseStaffRole(course_key)): return Response( status=status.HTTP_403_FORBIDDEN, data={ 'message': u'User does not have permission to update enrollment for [{course_id}].' .format(course_id=course_id, ), }, ) email = request.data.get('email') try: validate_email(email) except ValidationError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': u'Invalid email address', }, ) email_students = request.data.get('email_students', False) in ['true', 'True', True] auto_enroll = request.data.get('auto_enroll', False) in ['true', 'True', True] email_params = {} language = None if email_students: course = get_course_by_id(course_key) email_params = get_email_params(course, auto_enroll) if User.objects.filter(email=email).exists(): user = User.objects.get(email=email) language = get_user_email_language(user) unenroll_email(course_key, email, email_students, email_params, language=language) return Response(status=status.HTTP_204_NO_CONTENT)
def ccx_students_enrolling_center(action, identifiers, email_students, course_key, email_params, coach): """ Function to enroll/add or unenroll/revoke students. This function exists for backwards compatibility: in CCX there are two different views to manage students that used to implement a different logic. Now the logic has been reconciled at the point that this function can be used by both. The two different views can be merged after some UI refactoring. Arguments: action (str): type of action to perform (add, Enroll, revoke, Unenroll) identifiers (list): list of students username/email email_students (bool): Flag to send an email to students course_key (CCXLocator): a CCX course key email_params (dict): dictionary of settings for the email to be sent coach (User): ccx coach Returns: list: list of error """ errors = [] if action == 'Enroll' or action == 'add': ccx_course_overview = CourseOverview.get_from_id(course_key) course_locator = course_key.to_course_locator() staff = CourseStaffRole(course_locator).users_with_role() admins = CourseInstructorRole(course_locator).users_with_role() for identifier in identifiers: must_enroll = False try: email, student = get_valid_student_with_email(identifier) if student: must_enroll = student in staff or student in admins or student == coach except CCXUserValidationException as exp: log.info("%s", exp) errors.append("{0}".format(exp)) continue if CourseEnrollment.objects.is_course_full(ccx_course_overview) and not must_enroll: error = _('The course is full: the limit is {max_student_enrollments_allowed}').format( max_student_enrollments_allowed=ccx_course_overview.max_student_enrollments_allowed) log.info("%s", error) errors.append(error) break enroll_email(course_key, email, auto_enroll=True, email_students=email_students, email_params=email_params) elif action == 'Unenroll' or action == 'revoke': for identifier in identifiers: try: email, __ = get_valid_student_with_email(identifier) except CCXUserValidationException as exp: log.info("%s", exp) errors.append("{0}".format(exp)) continue unenroll_email(course_key, email, email_students=email_students, email_params=email_params) return errors
def ccx_students_enrolling_center(action, identifiers, email_students, course_key, email_params, coach): """ Function to enroll or unenroll/revoke students. Arguments: action (str): type of action to perform (Enroll, Unenroll/revoke) identifiers (list): list of students username/email email_students (bool): Flag to send an email to students course_key (CCXLocator): a CCX course key email_params (dict): dictionary of settings for the email to be sent coach (User): ccx coach Returns: list: list of error """ errors = [] if action == 'Enroll': ccx_course_overview = CourseOverview.get_from_id(course_key) course_locator = course_key.to_course_locator() staff = CourseStaffRole(course_locator).users_with_role() admins = CourseInstructorRole(course_locator).users_with_role() for identifier in identifiers: must_enroll = False try: email, student = get_valid_student_with_email(identifier) if student: must_enroll = student in staff or student in admins or student == coach except CCXUserValidationException as exp: log.info("%s", exp) errors.append("{0}".format(exp)) continue if CourseEnrollment.objects.is_course_full(ccx_course_overview) and not must_enroll: error = _('The course is full: the limit is {max_student_enrollments_allowed}').format( max_student_enrollments_allowed=ccx_course_overview.max_student_enrollments_allowed) log.info("%s", error) errors.append(error) break enroll_email(course_key, email, auto_enroll=True, email_students=email_students, email_params=email_params) elif action == 'Unenroll' or action == 'revoke': for identifier in identifiers: try: email, __ = get_valid_student_with_email(identifier) except CCXUserValidationException as exp: log.info("%s", exp) errors.append("{0}".format(exp)) continue unenroll_email(course_key, email, email_students=email_students, email_params=email_params) return errors
def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, send_email=True): """ Remove staff and instructor roles on ccx to all the staff and instructors members of master course. Arguments: master_course (CourseBlockWithMixins): Master course instance. ccx_key (CCXLocator): CCX course key. display_name (str): ccx display name for email. send_email (bool): flag to switch on or off email to the users on revoke access. """ list_staff = list_with_level(master_course.id, 'staff') list_instructor = list_with_level(master_course.id, 'instructor') with ccx_course(ccx_key) as course_ccx: list_staff_ccx = list_with_level(course_ccx.id, 'staff') list_instructor_ccx = list_with_level(course_ccx.id, 'instructor') email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) for staff in list_staff: if staff in list_staff_ccx: # revoke 'staff' access on ccx. revoke_access(course_ccx, staff, 'staff') # Unenroll the staff on ccx. unenroll_email( course_id=ccx_key, student_email=staff.email, email_students=send_email, email_params=email_params, ) for instructor in list_instructor: if instructor in list_instructor_ccx: # revoke 'instructor' access on ccx. revoke_access(course_ccx, instructor, 'instructor') # Unenroll the instructor on ccx. unenroll_email( course_id=ccx_key, student_email=instructor.email, email_students=send_email, email_params=email_params, )
def delete(self, request, course_id): """ Unenroll a user from a course; requires staff access **Example Request** DELETE /api/enrollment/v1/roster/course-v1:foo+bar+foobar { 'email': '*****@*****.**', 'email_students': false, 'auto_enroll': true } """ try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': u'Invalid or missing course_id', }, ) if not user_has_role(request.user, CourseStaffRole(course_key)): return Response( status=status.HTTP_403_FORBIDDEN, data={ 'message': u'User does not have permission to update enrollment for [{course_id}].'.format( course_id=course_id, ), }, ) email = request.data.get('email') try: validate_email(email) except ValidationError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': u'Invalid email address', }, ) email_students, auto_enroll, email_params, language = self.api_params_helper(request, course_key, email) unenroll_email( course_key, email, email_students, email_params, language=language ) return Response(status=status.HTTP_204_NO_CONTENT)
def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, send_email=True): """ Remove staff and instructor roles on ccx to all the staff and instructors members of master course. Arguments: master_course (CourseDescriptorWithMixins): Master course instance. ccx_key (CCXLocator): CCX course key. display_name (str): ccx display name for email. send_email (bool): flag to switch on or off email to the users on revoke access. """ list_staff = list_with_level(master_course, 'staff') list_instructor = list_with_level(master_course, 'instructor') with ccx_course(ccx_key) as course_ccx: list_staff_ccx = list_with_level(course_ccx, 'staff') list_instructor_ccx = list_with_level(course_ccx, 'instructor') email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) for staff in list_staff: if staff in list_staff_ccx: # revoke 'staff' access on ccx. revoke_access(course_ccx, staff, 'staff') # Unenroll the staff on ccx. unenroll_email( course_id=ccx_key, student_email=staff.email, email_students=send_email, email_params=email_params, ) for instructor in list_instructor: if instructor in list_instructor_ccx: # revoke 'instructor' access on ccx. revoke_access(course_ccx, instructor, 'instructor') # Unenroll the instructor on ccx. unenroll_email( course_id=ccx_key, student_email=instructor.email, email_students=send_email, email_params=email_params, )
def test_unenroll_norecord(self): before_ideal = SettableEnrollmentState(user=False, enrollment=False, allowed=False, auto_enroll=False) after_ideal = SettableEnrollmentState(user=False, enrollment=False, allowed=False, auto_enroll=False) action = lambda email: unenroll_email(self.course_key, email) return self._run_state_change_test(before_ideal, after_ideal, action)
def test_unenroll_norecord(self): before_ideal = SettableEnrollmentState( user=False, enrollment=False, allowed=False, auto_enroll=False ) after_ideal = SettableEnrollmentState( user=False, enrollment=False, allowed=False, auto_enroll=False ) action = lambda email: unenroll_email(self.course_key, email) return self._run_state_change_test(before_ideal, after_ideal, action)
def post(self, request, *args, **kwargs): course_id = request.POST.get('course_id', False) try: course_id = SlashSeparatedCourseKey.from_deprecated_string( course_id) except Exception: course_id = None if not course_id: self.msg = u"课程ID错误" context = {'msg': self.msg, 'datatable': self.make_datatable()} return render_to_response(self.template_name, context) elif not request.POST.get('identifiers'): self.msg = u"邮箱用户名错误" context = {'msg': self.msg, 'datatable': self.make_datatable()} return render_to_response(self.template_name, context) action = request.POST.get('action') identifiers_raw = request.POST.get('identifiers') identifiers = _split_input_list(identifiers_raw) auto_enroll = _get_boolean_param(request, 'auto_enroll') email_students = _get_boolean_param(request, 'email_students') is_white_label = CourseMode.is_white_label(course_id) reason = request.POST.get('reason') if is_white_label: if not reason: self.msg = "400" context = {'msg': self.msg, 'datatable': self.make_datatable()} return render_to_response(self.template_name, context) enrollment_obj = None state_transition = DEFAULT_TRANSITION_STATE email_params = {} if email_students: course = get_course_by_id(course_id) email_params = get_email_params(course, auto_enroll, secure=request.is_secure()) results = [] for identifier in identifiers: # First try to get a user object from the identifer user = None email = None language = None try: user = get_student_from_identifier(identifier) except User.DoesNotExist: email = identifier else: email = user.email language = get_user_email_language(user) try: # Use django.core.validators.validate_email to check email address # validity (obviously, cannot check if email actually /exists/, # simply that it is plausibly valid) validate_email(email) # Raises ValidationError if invalid if action == 'enroll': before, after, enrollment_obj = enroll_email( course_id, email, auto_enroll, email_students, email_params, language=language) before_enrollment = before.to_dict()['enrollment'] before_user_registered = before.to_dict()['user'] before_allowed = before.to_dict()['allowed'] after_enrollment = after.to_dict()['enrollment'] after_allowed = after.to_dict()['allowed'] if before_user_registered: if after_enrollment: if before_enrollment: state_transition = ENROLLED_TO_ENROLLED else: if before_allowed: state_transition = ALLOWEDTOENROLL_TO_ENROLLED else: state_transition = UNENROLLED_TO_ENROLLED else: if after_allowed: state_transition = UNENROLLED_TO_ALLOWEDTOENROLL elif action == 'unenroll': before, after = unenroll_email(course_id, email, email_students, email_params, language=language) before_enrollment = before.to_dict()['enrollment'] before_allowed = before.to_dict()['allowed'] enrollment_obj = CourseEnrollment.get_enrollment( user, course_id) if before_enrollment: state_transition = ENROLLED_TO_UNENROLLED else: if before_allowed: state_transition = ALLOWEDTOENROLL_TO_UNENROLLED else: state_transition = UNENROLLED_TO_UNENROLLED else: return HttpResponseBadRequest( strip_tags("Unrecognized action '{}'".format(action))) except ValidationError: # Flag this email as an error if invalid, but continue checking # the remaining in the list results.append({ 'identifier': identifier, 'invalidIdentifier': True, }) except Exception as exc: # pylint: disable=broad-except # catch and log any exceptions # so that one error doesn't cause a 500. log.exception(u"Error while #{}ing student") log.exception(exc) results.append({ 'identifier': identifier, 'error': True, }) else: ManualEnrollmentAudit.create_manual_enrollment_audit( request.user, email, state_transition, reason, enrollment_obj) results.append({ 'identifier': identifier, 'before': before.to_dict(), 'after': after.to_dict(), }) invalid_id = [] valid_id = [] for result in results: if ('error' in result) or ('invalidIdentifier' in result): invalid_id.append(result['identifier']) else: valid_id.append(result['identifier']) invalid_message = [ "{} 无效 <br>".format(i.encode('utf-8')) for i in invalid_id ] valid_message = [] action = "选课" if action == "enroll" else "弃选" for i in valid_id: if action == "弃选": valid_message.append("{0} {1} 成功 <br>".format(i, action)) continue if email_students: valid_message.append("{0} {1} 成功,并向他发送电子邮件 <br>".format( i, action)) else: valid_message.append("{0} {1} 成功<br>".format(i, action)) invalid_message.extend(valid_message) self.msg = "".join(invalid_message) context = {'msg': self.msg, 'datatable': self.make_datatable()} return render_to_response(self.template_name, context)
def ccx_students_enrolling_center(action, identifiers, email_students, course_key, email_params, coach): """ Function to enroll/add or unenroll/revoke students. This function exists for backwards compatibility: in CCX there are two different views to manage students that used to implement a different logic. Now the logic has been reconciled at the point that this function can be used by both. The two different views can be merged after some UI refactoring. Arguments: action (str): type of action to perform (add, Enroll, revoke, Unenroll) identifiers (list): list of students username/email email_students (bool): Flag to send an email to students course_key (CCXLocator): a CCX course key email_params (dict): dictionary of settings for the email to be sent coach (User): ccx coach Returns: list: list of error """ errors = [] if action == 'Enroll' or action == 'add': ccx_course_overview = CourseOverview.get_from_id(course_key) course_locator = course_key.to_course_locator() staff = CourseStaffRole(course_locator).users_with_role() admins = CourseInstructorRole(course_locator).users_with_role() for identifier in identifiers: must_enroll = False try: email, student = get_valid_student_with_email(identifier) if student: must_enroll = student in staff or student in admins or student == coach except CCXUserValidationException as exp: log.info("%s", exp) errors.append("{0}".format(exp)) continue if CourseEnrollment.objects.is_course_full( ccx_course_overview) and not must_enroll: error = _( 'The course is full: the limit is {max_student_enrollments_allowed}' ).format(max_student_enrollments_allowed=ccx_course_overview. max_student_enrollments_allowed) log.info("%s", error) errors.append(error) break enroll_email(course_key, email, auto_enroll=True, email_students=email_students, email_params=email_params) elif action == 'Unenroll' or action == 'revoke': for identifier in identifiers: try: email, __ = get_valid_student_with_email(identifier) except CCXUserValidationException as exp: log.info("%s", exp) errors.append("{0}".format(exp)) continue unenroll_email(course_key, email, email_students=email_students, email_params=email_params) return errors