def ccx_invite(request, course, ccx=None): """ Invite users to new ccx """ if not ccx: raise Http404 action = request.POST.get("enrollment-button") identifiers_raw = request.POST.get("student-ids") identifiers = _split_input_list(identifiers_raw) auto_enroll = True if "auto-enroll" in request.POST else False email_students = True if "email-students" in request.POST else False for identifier in identifiers: user = None email = None try: user = get_student_from_identifier(identifier) except User.DoesNotExist: email = identifier else: email = user.email try: validate_email(email) course_key = CCXLocator.from_course_locator(course.id, ccx.id) email_params = get_email_params(course, auto_enroll, course_key=course_key, display_name=ccx.display_name) if action == "Enroll": enroll_email( course_key, email, auto_enroll=auto_enroll, email_students=email_students, email_params=email_params ) if action == "Unenroll": unenroll_email(course_key, email, email_students=email_students, email_params=email_params) except ValidationError: log.info("Invalid user name or email when trying to invite students: %s", email) url = reverse("ccx_coach_dashboard", kwargs={"course_id": CCXLocator.from_course_locator(course.id, ccx.id)}) return redirect(url)
def ccx_student_management(request, course, ccx=None): """Manage the enrollment of individual students in a CCX """ if not ccx: raise Http404 action = request.POST.get('student-action', None) student_id = request.POST.get('student-id', '') user = email = None try: user = get_student_from_identifier(student_id) except User.DoesNotExist: email = student_id else: email = user.email course_key = CCXLocator.from_course_locator(course.id, ccx.id) try: validate_email(email) if action == 'add': # by decree, no emails sent to students added this way # by decree, any students added this way are auto_enrolled enroll_email(course_key, email, auto_enroll=True, email_students=False) elif action == 'revoke': unenroll_email(course_key, email, email_students=False) except ValidationError: log.info('Invalid user name or email when trying to enroll student: %s', email) url = reverse( 'ccx_coach_dashboard', kwargs={'course_id': course_key} ) return redirect(url)
def ccx_student_management(request, course, ccx=None): """Manage the enrollment of individual students in a CCX """ if not ccx: raise Http404 action = request.POST.get("student-action", None) student_id = request.POST.get("student-id", "") user = email = None error_message = "" course_key = CCXLocator.from_course_locator(course.id, ccx.id) try: user = get_student_from_identifier(student_id) except User.DoesNotExist: email = student_id error_message = validate_student_email(email) if email and not error_message: error_message = _('Could not find a user with name or email "{email}" ').format(email=email) else: email = user.email error_message = validate_student_email(email) if error_message is None: if action == "add": # by decree, no emails sent to students added this way # by decree, any students added this way are auto_enrolled enroll_email(course_key, email, auto_enroll=True, email_students=False) elif action == "revoke": unenroll_email(course_key, email, email_students=False) else: messages.error(request, error_message) url = reverse("ccx_coach_dashboard", kwargs={"course_id": course_key}) return redirect(url)
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_enrrolling_center(action, identifiers, email_students, course_key, email_params): """ 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 Returns: list: list of error """ errors = [] if action == "Enroll" or action == "add": ccx_course_overview = CourseOverview.get_from_id(course_key) for identifier in identifiers: if CourseEnrollment.objects.is_course_full(ccx_course_overview): error = "The course is full: the limit is {0}".format( ccx_course_overview.max_student_enrollments_allowed ) log.info("%s", error) errors.append(error) break try: email = get_valid_student_email(identifier) except CCXUserValidationException as exp: log.info("%s", exp) errors.append("{0}".format(exp)) continue 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_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 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 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_notenrolled(self): before_ideal = SettableEnrollmentState(user=True, enrollment=False, allowed=False, auto_enroll=False) after_ideal = SettableEnrollmentState(user=True, 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 reverse_add_master_course_staff_to_ccx(master_course, ccx_key, display_name): """ Remove staff of ccx. Arguments: master_course (CourseDescriptorWithMixins): Master course instance ccx_key (CCXLocator): CCX course key display_name (str): ccx display name for email """ list_staff = list_with_level(master_course, 'staff') list_instructor = list_with_level(master_course, 'instructor') with ccx_course(ccx_key) as course_ccx: email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name) for staff in list_staff: # allow 'staff' access on ccx to staff of master course revoke_access(course_ccx, staff, 'staff') # Enroll the staff in the ccx unenroll_email( course_id=ccx_key, student_email=staff.email, email_students=True, email_params=email_params, ) for instructor in list_instructor: # allow 'instructor' access on ccx to instructor of master course revoke_access(course_ccx, instructor, 'instructor') # Enroll the instructor in the ccx unenroll_email( course_id=ccx_key, student_email=instructor.email, email_students=True, email_params=email_params, )
def ccx_student_management(request, course, ccx=None): """Manage the enrollment of individual students in a CCX """ if not ccx: raise Http404 action = request.POST.get('student-action', None) student_id = request.POST.get('student-id', '') user = email = None error_message = "" course_key = CCXLocator.from_course_locator(course.id, ccx.id) try: user = get_student_from_identifier(student_id) except User.DoesNotExist: email = student_id error_message = validate_student_email(email) if email and not error_message: error_message = _( 'Could not find a user with name or email "{email}" ' ).format(email=email) else: email = user.email error_message = validate_student_email(email) if error_message is None: if action == 'add': # by decree, no emails sent to students added this way # by decree, any students added this way are auto_enrolled enroll_email(course_key, email, auto_enroll=True, email_students=False) elif action == 'revoke': unenroll_email(course_key, email, email_students=False) else: messages.error(request, error_message) url = reverse('ccx_coach_dashboard', kwargs={'course_id': course_key}) return redirect(url)
def students_update_enrollment(request, course_id): """ Enroll or unenroll students by email. Requires staff access. Query Parameters: - action in ['enroll', 'unenroll'] - identifiers is string containing a list of emails and/or usernames separated by anything split_input_list can handle. - auto_enroll is a boolean (defaults to false) If auto_enroll is false, students will be allowed to enroll. If auto_enroll is true, students will be enrolled as soon as they register. - email_students is a boolean (defaults to false) If email_students is true, students will be sent email notification If email_students is false, students will not be sent email notification Returns an analog to this JSON structure: { "action": "enroll", "auto_enroll": false, "results": [ { "email": "*****@*****.**", "before": { "enrollment": false, "auto_enroll": false, "user": true, "allowed": false }, "after": { "enrollment": true, "auto_enroll": false, "user": true, "allowed": false } } ] } """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) action = request.GET.get('action') identifiers_raw = request.GET.get('identifiers') identifiers = _split_input_list(identifiers_raw) auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True] email_students = request.GET.get('email_students') in ['true', 'True', True] email_params = {} if email_students: course = get_course_by_id(course_id) email_params = get_email_params(course, auto_enroll) results = [] for identifier in identifiers: # First try to get a user object from the identifer user = None email = None try: user = get_student_from_identifier(identifier) except User.DoesNotExist: email = identifier else: email = user.email 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 = enroll_email(course_id, email, auto_enroll, email_students, email_params) elif action == 'unenroll': before, after = unenroll_email(course_id, email, email_students, email_params) 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=W0703 # catch and log any exceptions # so that one error doesn't cause a 500. log.exception("Error while #{}ing student") log.exception(exc) results.append({ 'identifier': identifier, 'error': True, }) else: results.append({ 'identifier': identifier, 'before': before.to_dict(), 'after': after.to_dict(), }) response_payload = { 'action': action, 'results': results, 'auto_enroll': auto_enroll, } return JsonResponse(response_payload)
def students_update_enrollment(request, course_id): """ Enroll or unenroll students by email. Requires staff access. Query Parameters: - action in ['enroll', 'unenroll'] - emails is string containing a list of emails separated by anything split_input_list can handle. - auto_enroll is a boolean (defaults to false) If auto_enroll is false, students will be allowed to enroll. If auto_enroll is true, students will be enrolled as soon as they register. - email_students is a boolean (defaults to false) If email_students is true, students will be sent email notification If email_students is false, students will not be sent email notification Returns an analog to this JSON structure: { "action": "enroll", "auto_enroll": false, "results": [ { "email": "*****@*****.**", "before": { "enrollment": false, "auto_enroll": false, "user": true, "allowed": false }, "after": { "enrollment": true, "auto_enroll": false, "user": true, "allowed": false } } ] } """ action = request.GET.get('action') emails_raw = request.GET.get('emails') emails = _split_input_list(emails_raw) auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True] email_students = request.GET.get('email_students') in ['true', 'True', True] email_params = {} if email_students: course = get_course_by_id(course_id) email_params = get_email_params(course, auto_enroll) results = [] for email in emails: try: if action == 'enroll': before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params) elif action == 'unenroll': before, after = unenroll_email(course_id, email, email_students, email_params) else: return HttpResponseBadRequest("Unrecognized action '{}'".format(action)) results.append({ 'email': email, 'before': before.to_dict(), 'after': after.to_dict(), }) # catch and log any exceptions # so that one error doesn't cause a 500. except Exception as exc: # pylint: disable=W0703 log.exception("Error while #{}ing student") log.exception(exc) results.append({ 'email': email, 'error': True, }) response_payload = { 'action': action, 'results': results, 'auto_enroll': auto_enroll, } return JsonResponse(response_payload)
def students_update_enrollment(request, course_id): """ Enroll or unenroll students by email. Requires staff access. Query Parameters: - action in ['enroll', 'unenroll'] - emails is string containing a list of emails separated by anything split_input_list can handle. - auto_enroll is a boolean (defaults to false) If auto_enroll is false, students will be allowed to enroll. If auto_enroll is true, students will be enroled as soon as they register. Returns an analog to this JSON structure: { "action": "enroll", "auto_enroll": false, "results": [ { "email": "*****@*****.**", "before": { "enrollment": false, "auto_enroll": false, "user": true, "allowed": false }, "after": { "enrollment": true, "auto_enroll": false, "user": true, "allowed": false } } ] } """ action = request.GET.get('action') emails_raw = request.GET.get('emails') emails = _split_input_list(emails_raw) auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True] results = [] for email in emails: try: if action == 'enroll': before, after = enroll_email(course_id, email, auto_enroll) elif action == 'unenroll': before, after = unenroll_email(course_id, email) else: return HttpResponseBadRequest( "Unrecognized action '{}'".format(action)) results.append({ 'email': email, 'before': before.to_dict(), 'after': after.to_dict(), }) # catch and log any exceptions # so that one error doesn't cause a 500. except Exception as exc: # pylint: disable=W0703 log.exception("Error while #{}ing student") log.exception(exc) results.append({ 'email': email, 'error': True, }) response_payload = { 'action': action, 'results': results, 'auto_enroll': auto_enroll, } return JsonResponse(response_payload)
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 students_update_enrollment(request, course_id): """ Enroll or unenroll students by email. Requires staff access. Query Parameters: - action in ['enroll', 'unenroll'] - identifiers is string containing a list of emails and/or usernames separated by anything split_input_list can handle. - auto_enroll is a boolean (defaults to false) If auto_enroll is false, students will be allowed to enroll. If auto_enroll is true, students will be enrolled as soon as they register. - email_students is a boolean (defaults to false) If email_students is true, students will be sent email notification If email_students is false, students will not be sent email notification Returns an analog to this JSON structure: { "action": "enroll", "auto_enroll": false, "results": [ { "email": "*****@*****.**", "before": { "enrollment": false, "auto_enroll": false, "user": true, "allowed": false }, "after": { "enrollment": true, "auto_enroll": false, "user": true, "allowed": false } } ] } """ action = request.GET.get('action') identifiers_raw = request.GET.get('identifiers') identifiers = _split_input_list(identifiers_raw) auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True] email_students = request.GET.get('email_students') in [ 'true', 'True', True ] email_params = {} if email_students: course = get_course_by_id(course_id) email_params = get_email_params(course, auto_enroll) results = [] for identifier in identifiers: # First try to get a user object from the identifer user = None email = None try: user = get_student_from_identifier(identifier) except User.DoesNotExist: email = identifier else: email = user.email 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 = enroll_email(course_id, email, auto_enroll, email_students, email_params) elif action == 'unenroll': before, after = unenroll_email(course_id, email, email_students, email_params) 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=W0703 # catch and log any exceptions # so that one error doesn't cause a 500. log.exception("Error while #{}ing student") log.exception(exc) results.append({ 'identifier': identifier, 'error': True, }) else: results.append({ 'identifier': identifier, 'before': before.to_dict(), 'after': after.to_dict(), }) response_payload = { 'action': action, 'results': results, 'auto_enroll': auto_enroll, } return JsonResponse(response_payload)
def students_update_enrollment(request, course_id): """ Enroll or unenroll students by email. Requires staff access. Query Parameters: - action in ['enroll', 'unenroll'] - emails is string containing a list of emails separated by anything split_input_list can handle. - auto_enroll is a boolean (defaults to false) If auto_enroll is false, students will be allowed to enroll. If auto_enroll is true, students will be enrolled as soon as they register. - email_students is a boolean (defaults to false) If email_students is true, students will be sent email notification If email_students is false, students will not be sent email notification Returns an analog to this JSON structure: { "action": "enroll", "auto_enroll": false, "results": [ { "email": "*****@*****.**", "before": { "enrollment": false, "auto_enroll": false, "user": true, "allowed": false }, "after": { "enrollment": true, "auto_enroll": false, "user": true, "allowed": false } } ] } """ action = request.GET.get("action") emails_raw = request.GET.get("emails") emails = _split_input_list(emails_raw) auto_enroll = request.GET.get("auto_enroll") in ["true", "True", True] email_students = request.GET.get("email_students") in ["true", "True", True] email_params = {} if email_students: course = get_course_by_id(course_id) email_params = get_email_params(course, auto_enroll) results = [] for email in emails: 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) except ValidationError: # Flag this email as an error if invalid, but continue checking # the remaining in the list results.append({"email": email, "error": True, "invalidEmail": True}) continue try: if action == "enroll": before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params) elif action == "unenroll": before, after = unenroll_email(course_id, email, email_students, email_params) else: return HttpResponseBadRequest(strip_tags("Unrecognized action '{}'".format(action))) results.append({"email": email, "before": before.to_dict(), "after": after.to_dict()}) # catch and log any exceptions # so that one error doesn't cause a 500. except Exception as exc: # pylint: disable=W0703 log.exception("Error while #{}ing student") log.exception(exc) results.append({"email": email, "error": True, "invalidEmail": False}) response_payload = {"action": action, "results": results, "auto_enroll": auto_enroll} return JsonResponse(response_payload)