def get_valid_student_email(identifier): """ Helper function to get an user email from an identifier and validate it. In the UI a Coach can enroll users using both an email and an username. This function takes care of: - in case the identifier is an username, extracting the user object from the DB and then the associated email - validating the email Arguments: identifier (str): Username or email of the user to enroll Returns: str: A validated email for the user to enroll Raises: CCXUserValidationException: if the username is not found or the email is not valid. """ user = email = None try: user = get_student_from_identifier(identifier) except User.DoesNotExist: email = identifier else: email = user.email try: validate_email(email) except ValidationError: raise CCXUserValidationException('Could not find a user with name or email "{0}" '.format(identifier)) return email
def ccx_student_management(request, course): """Manage the enrollment of individual students in a CCX """ ccx = get_ccx_for_coach(course, request.user) 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 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(ccx, email, auto_enroll=True, email_students=False) elif action == "revoke": unenroll_email(ccx, 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.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 modify_special_forum_contributors(request, course_id): unique_student_identifier = request.GET.get('unique_student_identifier') rolename = request.GET.get('rolename') action = request.GET.get('action') try: course_id = _check_rights(course_id, request.user, rolename) except UnauthorizedAccessError as e: return HttpResponseBadRequest(e.message) course = get_course_by_id(course_id) _check_custom_roles(course_id) user = get_student_from_identifier(unique_student_identifier) target_is_instructor = has_access(user, 'instructor', course) # cannot revoke instructor if target_is_instructor and action == 'revoke' and rolename == FORUM_ROLE_ADMINISTRATOR: return HttpResponseBadRequest("Cannot revoke instructor forum admin privileges.") try: update_forum_role(course_id, user, rolename, action) except Role.DoesNotExist: return HttpResponseBadRequest("Role does not exist.") response_payload = { 'course_id': course_id.to_deprecated_string(), 'action': action, } return JsonResponse(response_payload)
def ccx_invite(request, course): """ Invite users to new ccx """ ccx = get_ccx_for_coach(course, request.user) 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) if action == 'Enroll': enroll_email(ccx, email, auto_enroll=auto_enroll, email_students=email_students) if action == "Unenroll": unenroll_email(ccx, email, email_students=email_students) except ValidationError: log.info( 'Invalid user name or email when trying to invite students: %s', email) url = reverse('ccx_coach_dashboard', kwargs={'course_id': course.id}) return redirect(url)
def ccx_invite(request, course): """ Invite users to new ccx """ ccx = get_ccx_for_coach(course, request.user) 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) if action == 'Enroll': enroll_email( ccx, email, auto_enroll=auto_enroll, email_students=email_students ) if action == "Unenroll": unenroll_email(ccx, email, email_students=email_students) except ValidationError: log.info('Invalid user name or email when trying to invite students: %s', email) url = reverse('ccx_coach_dashboard', kwargs={'course_id': course.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 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_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 get_valid_student_email(identifier): """ Helper function to get an user email from an identifier and validate it. In the UI a Coach can enroll users using both an email and an username. This function takes care of: - in case the identifier is an username, extracting the user object from the DB and then the associated email - validating the email Arguments: identifier (str): Username or email of the user to enroll Returns: str: A validated email for the user to enroll Raises: CCXUserValidationException: if the username is not found or the email is not valid. """ user = email = None try: user = get_student_from_identifier(identifier) except User.DoesNotExist: email = identifier else: email = user.email try: validate_email(email) except ValidationError: raise CCXUserValidationException( 'Could not find a user with name or email "{0}" '.format( identifier)) return email
def delete_student_attempt(self, student_identifier, course_id, content_id, requesting_user): """ Deletes student state for a problem. requesting_user may be kept as an audit trail. Takes some of the following query parameters - student_identifier is an email or username - content_id is a url-name of a problem - course_id is the id for the course """ course_id = CourseKey.from_string(course_id) try: student = get_student_from_identifier(student_identifier) except ObjectDoesNotExist: err_msg = ( 'Error occurred while attempting to reset student attempts for user ' '{student_identifier} for content_id {content_id}. ' 'User does not exist!'.format( student_identifier=student_identifier, content_id=content_id ) ) log.error(err_msg) return try: module_state_key = UsageKey.from_string(content_id) except InvalidKeyError: err_msg = ( 'Invalid content_id {content_id}!'.format(content_id=content_id) ) log.error(err_msg) return if student: try: enrollment.reset_student_attempts( course_id, student, module_state_key, requesting_user=requesting_user, delete_module=True, ) except (StudentModule.DoesNotExist, enrollment.sub_api.SubmissionError): err_msg = ( 'Error occurred while attempting to reset student attempts for user ' '{student_identifier} for content_id {content_id}.'.format( student_identifier=student_identifier, content_id=content_id ) ) log.error(err_msg)
def delete_student_attempt(self, student_identifier, course_id, content_id, requesting_user): """ Deletes student state for a problem. requesting_user may be kept as an audit trail. Takes some of the following query parameters - student_identifier is an email or username - content_id is a url-name of a problem - course_id is the id for the course """ course_id = CourseKey.from_string(course_id) try: student = get_student_from_identifier(student_identifier) except ObjectDoesNotExist: err_msg = ( 'Error occurred while attempting to reset student attempts for user ' '{student_identifier} for content_id {content_id}. ' 'User does not exist!'.format( student_identifier=student_identifier, content_id=content_id)) log.error(err_msg) return try: module_state_key = UsageKey.from_string(content_id) except InvalidKeyError: err_msg = ('Invalid content_id {content_id}!'.format( content_id=content_id)) log.error(err_msg) return if student: try: enrollment.reset_student_attempts( course_id, student, module_state_key, requesting_user=requesting_user, delete_module=True, ) except (StudentModule.DoesNotExist, enrollment.sub_api.SubmissionError): err_msg = ( 'Error occurred while attempting to reset student attempts for user ' '{student_identifier} for content_id {content_id}.'.format( student_identifier=student_identifier, content_id=content_id)) log.error(err_msg)
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 rescore_problem(request, course_id): """ Starts a background process a students attempts counter. Optionally deletes student state for a problem. Limited to instructor access. Takes either of the following query paremeters - problem_to_reset is a urlname of a problem - unique_student_identifier is an email or username - all_students is a boolean all_students and unique_student_identifier cannot both be present. """ problem_to_reset = strip_if_string(request.GET.get('problem_to_reset')) student_identifier = request.GET.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) all_students = request.GET.get('all_students') in ['true', 'True', True] if not (problem_to_reset and (all_students or student)): return HttpResponseBadRequest("Missing query parameters.") if all_students and student: return HttpResponseBadRequest( "Cannot rescore with all_students and unique_student_identifier.") module_state_key = _msk_from_problem_urlname(course_id, problem_to_reset) response_payload = {} response_payload['problem_to_reset'] = problem_to_reset if student: response_payload['student'] = student_identifier instructor_task.api.submit_rescore_problem_for_student( request, course_id, module_state_key, student) response_payload['task'] = 'created' elif all_students: instructor_task.api.submit_rescore_problem_for_all_students( request, course_id, module_state_key) response_payload['task'] = 'created' else: return HttpResponseBadRequest() return JsonResponse(response_payload)
def rescore_problem(request, course_id): """ Starts a background process a students attempts counter. Optionally deletes student state for a problem. Limited to instructor access. Takes either of the following query paremeters - problem_to_reset is a urlname of a problem - unique_student_identifier is an email or username - all_students is a boolean all_students and unique_student_identifier cannot both be present. """ problem_to_reset = strip_if_string(request.GET.get('problem_to_reset')) student_identifier = request.GET.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) all_students = request.GET.get('all_students') in ['true', 'True', True] if not (problem_to_reset and (all_students or student)): return HttpResponseBadRequest("Missing query parameters.") if all_students and student: return HttpResponseBadRequest( "Cannot rescore with all_students and unique_student_identifier." ) module_state_key = _msk_from_problem_urlname(course_id, problem_to_reset) response_payload = {} response_payload['problem_to_reset'] = problem_to_reset if student: response_payload['student'] = student_identifier instructor_task.api.submit_rescore_problem_for_student(request, course_id, module_state_key, student) response_payload['task'] = 'created' elif all_students: instructor_task.api.submit_rescore_problem_for_all_students(request, course_id, module_state_key) response_payload['task'] = 'created' else: return HttpResponseBadRequest() return JsonResponse(response_payload)
def get_student_progress_url(request, course_id): """ Get the progress url of a student. Limited to staff access. Takes query paremeter unique_student_identifier and if the student exists returns e.g. { 'progress_url': '/../...' } """ user = get_student_from_identifier(request.GET.get('unique_student_identifier')) progress_url = reverse('student_progress', kwargs={'course_id': course_id, 'student_id': user.id}) response_payload = { 'course_id': course_id, 'progress_url': progress_url, } return JsonResponse(response_payload)
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 modify_special_forum_contributors(request, course_id): unique_student_identifier = request.GET.get("unique_student_identifier") rolename = request.GET.get("rolename") action = request.GET.get("action") course_id = _check_rights(course_id, request.user, rolename) course = get_course_by_id(course_id) _check_custom_roles(course_id) user = get_student_from_identifier(unique_student_identifier) target_is_instructor = has_access(user, "instructor", course) # cannot revoke instructor if target_is_instructor and action == "revoke" and rolename == FORUM_ROLE_ADMINISTRATOR: return HttpResponseBadRequest("Cannot revoke instructor forum admin privileges.") try: update_forum_role(course_id, user, rolename, action) except Role.DoesNotExist: return HttpResponseBadRequest("Role does not exist.") response_payload = {"course_id": course_id.to_deprecated_string(), "action": action} return JsonResponse(response_payload)
def list_instructor_tasks(request, course_id): """ List instructor tasks. Limited to instructor access. Takes optional query paremeters. - With no arguments, lists running tasks. - `problem_urlname` lists task history for problem - `problem_urlname` and `unique_student_identifier` lists task history for problem AND student (intersection) """ problem_urlname = strip_if_string(request.GET.get('problem_urlname', False)) student = request.GET.get('unique_student_identifier', None) if student is not None: student = get_student_from_identifier(student) if student and not problem_urlname: return HttpResponseBadRequest( "unique_student_identifier must accompany problem_urlname" ) if problem_urlname: module_state_key = _msk_from_problem_urlname(course_id, problem_urlname) if student: tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key, student) else: tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key) else: tasks = instructor_task.api.get_running_instructor_tasks(course_id) def extract_task_features(task): """ Convert task to dict for json rendering """ features = ['task_type', 'task_input', 'task_id', 'requester', 'created', 'task_state'] return dict((feature, str(getattr(task, feature))) for feature in features) response_payload = { 'tasks': map(extract_task_features, tasks), } return JsonResponse(response_payload)
def list_instructor_tasks(request, course_id): """ List instructor tasks. Takes optional query paremeters. - With no arguments, lists running tasks. - `problem_urlname` lists task history for problem - `problem_urlname` and `unique_student_identifier` lists task history for problem AND student (intersection) """ problem_urlname = strip_if_string(request.GET.get('problem_urlname', False)) student = request.GET.get('unique_student_identifier', None) if student is not None: student = get_student_from_identifier(student) if student and not problem_urlname: return HttpResponseBadRequest( "unique_student_identifier must accompany problem_urlname") if problem_urlname: module_state_key = _msk_from_problem_urlname(course_id, problem_urlname) if student: # Specifying for a single student's history on this problem tasks = instructor_task.api.get_instructor_task_history( course_id, module_state_key, student) else: # Specifying for single problem's history tasks = instructor_task.api.get_instructor_task_history( course_id, module_state_key) else: # If no problem or student, just get currently running tasks tasks = instructor_task.api.get_running_instructor_tasks(course_id) response_payload = { 'tasks': map(extract_task_features, tasks), } return JsonResponse(response_payload)
def list_instructor_tasks(request, course_id): """ List instructor tasks. Takes optional query paremeters. - With no arguments, lists running tasks. - `problem_urlname` lists task history for problem - `problem_urlname` and `unique_student_identifier` lists task history for problem AND student (intersection) """ problem_urlname = strip_if_string(request.GET.get('problem_urlname', False)) student = request.GET.get('unique_student_identifier', None) if student is not None: student = get_student_from_identifier(student) if student and not problem_urlname: return HttpResponseBadRequest( "unique_student_identifier must accompany problem_urlname" ) if problem_urlname: module_state_key = _msk_from_problem_urlname(course_id, problem_urlname) if student: # Specifying for a single student's history on this problem tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key, student) else: # Specifying for single problem's history tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key) else: # If no problem or student, just get currently running tasks tasks = instructor_task.api.get_running_instructor_tasks(course_id) response_payload = { 'tasks': map(extract_task_features, tasks), } return JsonResponse(response_payload)
def list_instructor_tasks(request, course_id): """ List instructor tasks. Limited to instructor access. Takes optional query paremeters. - With no arguments, lists running tasks. - `problem_urlname` lists task history for problem - `problem_urlname` and `unique_student_identifier` lists task history for problem AND student (intersection) """ problem_urlname = strip_if_string(request.GET.get('problem_urlname', False)) student = request.GET.get('unique_student_identifier', None) if student is not None: student = get_student_from_identifier(student) if student and not problem_urlname: return HttpResponseBadRequest( "unique_student_identifier must accompany problem_urlname" ) if problem_urlname: module_state_key = _msk_from_problem_urlname(course_id, problem_urlname) if student: tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key, student) else: tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key) else: tasks = instructor_task.api.get_running_instructor_tasks(course_id) def extract_task_features(task): """ Convert task to dict for json rendering. Expects tasks have the following features: * task_type (str, type of task) * task_input (dict, input(s) to the task) * task_id (str, celery id of the task) * requester (str, username who submitted the task) * task_state (str, state of task eg PROGRESS, COMPLETED) * created (datetime, when the task was completed) * task_output (optional) """ # Pull out information from the task features = ['task_type', 'task_input', 'task_id', 'requester', 'task_state'] task_feature_dict = {feature: str(getattr(task, feature)) for feature in features} # Some information (created, duration, status, task message) require additional formatting task_feature_dict['created'] = task.created.isoformat() # Get duration info, if known duration_sec = 'unknown' if hasattr(task, 'task_output') and task.task_output is not None: try: task_output = json.loads(task.task_output) except ValueError: log.error("Could not parse task output as valid json; task output: %s", task.task_output) else: if 'duration_ms' in task_output: duration_sec = int(task_output['duration_ms'] / 1000.0) task_feature_dict['duration_sec'] = duration_sec # Get progress status message & success information success, task_message = get_task_completion_info(task) status = _("Complete") if success else _("Incomplete") task_feature_dict['status'] = status task_feature_dict['task_message'] = task_message return task_feature_dict response_payload = { 'tasks': map(extract_task_features, tasks), } return JsonResponse(response_payload)
def list_instructor_tasks(request, course_id): """ List instructor tasks. Limited to instructor access. Takes optional query paremeters. - With no arguments, lists running tasks. - `problem_urlname` lists task history for problem - `problem_urlname` and `unique_student_identifier` lists task history for problem AND student (intersection) """ problem_urlname = strip_if_string(request.GET.get('problem_urlname', False)) student = request.GET.get('unique_student_identifier', None) if student is not None: student = get_student_from_identifier(student) if student and not problem_urlname: return HttpResponseBadRequest( "unique_student_identifier must accompany problem_urlname") if problem_urlname: module_state_key = _msk_from_problem_urlname(course_id, problem_urlname) if student: tasks = instructor_task.api.get_instructor_task_history( course_id, module_state_key, student) else: tasks = instructor_task.api.get_instructor_task_history( course_id, module_state_key) else: tasks = instructor_task.api.get_running_instructor_tasks(course_id) def extract_task_features(task): """ Convert task to dict for json rendering. Expects tasks have the following features: * task_type (str, type of task) * task_input (dict, input(s) to the task) * task_id (str, celery id of the task) * requester (str, username who submitted the task) * task_state (str, state of task eg PROGRESS, COMPLETED) * created (datetime, when the task was completed) * task_output (optional) """ # Pull out information from the task features = [ 'task_type', 'task_input', 'task_id', 'requester', 'task_state' ] task_feature_dict = { feature: str(getattr(task, feature)) for feature in features } # Some information (created, duration, status, task message) require additional formatting task_feature_dict['created'] = task.created.isoformat() # Get duration info, if known duration_sec = 'unknown' if hasattr(task, 'task_output') and task.task_output is not None: try: task_output = json.loads(task.task_output) except ValueError: log.error( "Could not parse task output as valid json; task output: %s", task.task_output) else: if 'duration_ms' in task_output: duration_sec = int(task_output['duration_ms'] / 1000.0) task_feature_dict['duration_sec'] = duration_sec # Get progress status message & success information success, task_message = get_task_completion_info(task) status = _("Complete") if success else _("Incomplete") task_feature_dict['status'] = status task_feature_dict['task_message'] = task_message return task_feature_dict response_payload = { 'tasks': map(extract_task_features, tasks), } return JsonResponse(response_payload)
def reset_student_attempts(request, course_id): """ Resets a students attempts counter or starts a task to reset all students attempts counters. Optionally deletes student state for a problem. Limited to staff access. Some sub-methods limited to instructor access. Takes some of the following query paremeters - problem_to_reset is a urlname of a problem - unique_student_identifier is an email or username - all_students is a boolean requires instructor access mutually exclusive with delete_module mutually exclusive with delete_module - delete_module is a boolean requires instructor access mutually exclusive with all_students """ course = get_course_with_access(request.user, course_id, 'staff', depth=None) problem_to_reset = strip_if_string(request.GET.get('problem_to_reset')) student_identifier = request.GET.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) all_students = request.GET.get('all_students', False) in ['true', 'True', True] delete_module = request.GET.get('delete_module', False) in ['true', 'True', True] # parameter combinations if all_students and student: return HttpResponseBadRequest( "all_students and unique_student_identifier are mutually exclusive." ) if all_students and delete_module: return HttpResponseBadRequest( "all_students and delete_module are mutually exclusive.") # instructor authorization if all_students or delete_module: if not has_access(request.user, course, 'instructor'): return HttpResponseForbidden("Requires instructor access.") module_state_key = _msk_from_problem_urlname(course_id, problem_to_reset) response_payload = {} response_payload['problem_to_reset'] = problem_to_reset if student: try: enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module) except StudentModule.DoesNotExist: return HttpResponseBadRequest("Module does not exist.") response_payload['student'] = student_identifier elif all_students: instructor_task.api.submit_reset_problem_attempts_for_all_students( request, course_id, module_state_key) response_payload['task'] = 'created' response_payload['student'] = 'All Students' else: return HttpResponseBadRequest() return JsonResponse(response_payload)
def reset_student_attempts(request, course_id): """ Resets a students attempts counter or starts a task to reset all students attempts counters. Optionally deletes student state for a problem. Limited to staff access. Some sub-methods limited to instructor access. Takes some of the following query paremeters - problem_to_reset is a urlname of a problem - unique_student_identifier is an email or username - all_students is a boolean requires instructor access mutually exclusive with delete_module mutually exclusive with delete_module - delete_module is a boolean requires instructor access mutually exclusive with all_students """ course = get_course_with_access( request.user, course_id, 'staff', depth=None ) problem_to_reset = strip_if_string(request.GET.get('problem_to_reset')) student_identifier = request.GET.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) all_students = request.GET.get('all_students', False) in ['true', 'True', True] delete_module = request.GET.get('delete_module', False) in ['true', 'True', True] # parameter combinations if all_students and student: return HttpResponseBadRequest( "all_students and unique_student_identifier are mutually exclusive." ) if all_students and delete_module: return HttpResponseBadRequest( "all_students and delete_module are mutually exclusive." ) # instructor authorization if all_students or delete_module: if not has_access(request.user, course, 'instructor'): return HttpResponseForbidden("Requires instructor access.") module_state_key = _msk_from_problem_urlname(course_id, problem_to_reset) response_payload = {} response_payload['problem_to_reset'] = problem_to_reset if student: try: enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module) except StudentModule.DoesNotExist: return HttpResponseBadRequest("Module does not exist.") response_payload['student'] = student_identifier elif all_students: instructor_task.api.submit_reset_problem_attempts_for_all_students(request, course_id, module_state_key) response_payload['task'] = 'created' response_payload['student'] = 'All Students' else: return HttpResponseBadRequest() return JsonResponse(response_payload)