def test_delete_submission_scores_attempts_children(self): parent_state = json.loads(self.get_state(self.parent.location)) self.assertEqual(parent_state['attempts'], 32) self.assertEqual(parent_state['otherstuff'], 'alsorobots') child_state = json.loads(self.get_state(self.child.location)) self.assertEqual(child_state['attempts'], 10) self.assertEqual(child_state['whatever'], 'things') unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state['attempts'], 12) self.assertEqual(unrelated_state['brains'], 'zombie') reset_student_attempts(self.course_key, self.user, self.parent.location, delete_module=True) self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.parent.location) self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.child.location) unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state['attempts'], 12) self.assertEqual(unrelated_state['brains'], 'zombie')
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 - student_email is an email - 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 = request.GET.get('problem_to_reset') student_email = request.GET.get('student_email') 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_email: return HttpResponseBadRequest( "all_students and student_email 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_email: try: student = User.objects.get(email=student_email) enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module) except StudentModule.DoesNotExist: return HttpResponseBadRequest("Module does not exist.") elif all_students: instructor_task.api.submit_reset_problem_attempts_for_all_students(request, course_id, module_state_key) response_payload['task'] = 'created' else: return HttpResponseBadRequest() return JsonResponse(response_payload)
def test_delete_submission_scores(self): user = UserFactory() course_id = 'ora2/1/1' item_id = 'i4x://ora2/1/openassessment/b3dce2586c9c4876b73e7f390e42ef8f' # Create a student module for the user StudentModule.objects.create( student=user, course_id=course_id, module_state_key=item_id, state=json.dumps({}) ) # Create a submission and score for the student using the submissions API student_item = { 'student_id': anonymous_id_for_user(user, course_id), 'course_id': course_id, 'item_id': item_id, 'item_type': 'openassessment' } submission = sub_api.create_submission(student_item, 'test answer') sub_api.set_score(submission['uuid'], 1, 2) # Delete student state using the instructor dash reset_student_attempts(course_id, user, item_id, delete_module=True) # Verify that the student's scores have been reset in the submissions API score = sub_api.get_score(student_item) self.assertIs(score, None)
def test_reset_student_attempts_children(self): parent_state = json.loads(self.get_state(self.parent.location)) self.assertEqual(parent_state["attempts"], 32) self.assertEqual(parent_state["otherstuff"], "alsorobots") child_state = json.loads(self.get_state(self.child.location)) self.assertEqual(child_state["attempts"], 10) self.assertEqual(child_state["whatever"], "things") unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state["attempts"], 12) self.assertEqual(unrelated_state["brains"], "zombie") reset_student_attempts(self.course_key, self.user, self.parent.location, requesting_user=self.user) parent_state = json.loads(self.get_state(self.parent.location)) self.assertEqual(json.loads(self.get_state(self.parent.location))["attempts"], 0) self.assertEqual(parent_state["otherstuff"], "alsorobots") child_state = json.loads(self.get_state(self.child.location)) self.assertEqual(child_state["attempts"], 0) self.assertEqual(child_state["whatever"], "things") unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state["attempts"], 12) self.assertEqual(unrelated_state["brains"], "zombie")
def test_delete_submission_scores(self): user = UserFactory() problem_location = self.course_key.make_usage_key('dummy', 'module') # Create a student module for the user StudentModule.objects.create( student=user, course_id=self.course_key, module_state_key=problem_location, state=json.dumps({}) ) # Create a submission and score for the student using the submissions API student_item = { 'student_id': anonymous_id_for_user(user, self.course_key), 'course_id': self.course_key.to_deprecated_string(), 'item_id': problem_location.to_deprecated_string(), 'item_type': 'openassessment' } submission = sub_api.create_submission(student_item, 'test answer') sub_api.set_score(submission['uuid'], 1, 2) # Delete student state using the instructor dash reset_student_attempts( self.course_key, user, problem_location, delete_module=True ) # Verify that the student's scores have been reset in the submissions API score = sub_api.get_score(student_item) self.assertIs(score, None)
def test_delete_submission_scores(self, _lti_mock): user = UserFactory() problem_location = self.course_key.make_usage_key("dummy", "module") # Create a student module for the user StudentModule.objects.create( student=user, course_id=self.course_key, module_state_key=problem_location, state=json.dumps({}) ) # Create a submission and score for the student using the submissions API student_item = { "student_id": anonymous_id_for_user(user, self.course_key), "course_id": self.course_key.to_deprecated_string(), "item_id": problem_location.to_deprecated_string(), "item_type": "openassessment", } submission = sub_api.create_submission(student_item, "test answer") sub_api.set_score(submission["uuid"], 1, 2) # Delete student state using the instructor dash reset_student_attempts(self.course_key, user, problem_location, requesting_user=user, delete_module=True) # Verify that the student's scores have been reset in the submissions API score = sub_api.get_score(student_item) self.assertIs(score, None)
def test_delete_submission_scores_attempts_children(self): parent_state = json.loads(self.get_state(self.parent.location)) self.assertEqual(parent_state['attempts'], 32) self.assertEqual(parent_state['otherstuff'], 'alsorobots') child_state = json.loads(self.get_state(self.child.location)) self.assertEqual(child_state['attempts'], 10) self.assertEqual(child_state['whatever'], 'things') unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state['attempts'], 12) self.assertEqual(unrelated_state['brains'], 'zombie') reset_student_attempts( self.course_key, self.user, self.parent.location, requesting_user=self.user, delete_module=True, ) self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.parent.location) self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.child.location) unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state['attempts'], 12) self.assertEqual(unrelated_state['brains'], 'zombie')
def test_delete_submission_scores(self): user = UserFactory() problem_location = self.course_key.make_usage_key('dummy', 'module') # Create a student module for the user StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=problem_location, state=json.dumps({})) # Create a submission and score for the student using the submissions API student_item = { 'student_id': anonymous_id_for_user(user, self.course_key), 'course_id': self.course_key.to_deprecated_string(), 'item_id': problem_location.to_deprecated_string(), 'item_type': 'openassessment' } submission = sub_api.create_submission(student_item, 'test answer') sub_api.set_score(submission['uuid'], 1, 2) # Delete student state using the instructor dash reset_student_attempts(self.course_key, user, problem_location, delete_module=True) # Verify that the student's scores have been reset in the submissions API score = sub_api.get_score(student_item) self.assertIs(score, None)
def test_reset_student_attempts_children(self): parent_state = json.loads(self.get_state(self.parent.location)) self.assertEqual(parent_state['attempts'], 32) self.assertEqual(parent_state['otherstuff'], 'alsorobots') child_state = json.loads(self.get_state(self.child.location)) self.assertEqual(child_state['attempts'], 10) self.assertEqual(child_state['whatever'], 'things') unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state['attempts'], 12) self.assertEqual(unrelated_state['brains'], 'zombie') reset_student_attempts(self.course_key, self.user, self.parent.location) parent_state = json.loads(self.get_state(self.parent.location)) self.assertEqual(json.loads(self.get_state(self.parent.location))['attempts'], 0) self.assertEqual(parent_state['otherstuff'], 'alsorobots') child_state = json.loads(self.get_state(self.child.location)) self.assertEqual(child_state['attempts'], 0) self.assertEqual(child_state['whatever'], 'things') unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state['attempts'], 12) self.assertEqual(unrelated_state['brains'], 'zombie')
def test_delete_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key('dummy', 'module') original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state) self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 1) reset_student_attempts(self.course_key, user, msk, delete_module=True) self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 0)
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 test_reset_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key('dummy', 'module') original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state) # lambda to reload the module state from the database module = lambda: StudentModule.objects.get(student=user, course_id=self.course_key, module_state_key=msk) self.assertEqual(json.loads(module().state)['attempts'], 32) reset_student_attempts(self.course_key, user, msk) self.assertEqual(json.loads(module().state)['attempts'], 0)
def test_reset_student_attempts(self): msk = self.course_key.make_usage_key("dummy", "module") original_state = json.dumps({"attempts": 32, "otherstuff": "alsorobots"}) StudentModule.objects.create( student=self.user, course_id=self.course_key, module_state_key=msk, state=original_state ) # lambda to reload the module state from the database module = lambda: StudentModule.objects.get(student=self.user, course_id=self.course_key, module_state_key=msk) self.assertEqual(json.loads(module().state)["attempts"], 32) reset_student_attempts(self.course_key, self.user, msk, requesting_user=self.user) self.assertEqual(json.loads(module().state)["attempts"], 0)
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 test_delete_student_attempts(self): msk = self.course_key.make_usage_key("dummy", "module") original_state = json.dumps({"attempts": 32, "otherstuff": "alsorobots"}) StudentModule.objects.create( student=self.user, course_id=self.course_key, module_state_key=msk, state=original_state ) self.assertEqual( StudentModule.objects.filter(student=self.user, course_id=self.course_key, module_state_key=msk).count(), 1 ) reset_student_attempts(self.course_key, self.user, msk, requesting_user=self.user, delete_module=True) self.assertEqual( StudentModule.objects.filter(student=self.user, course_id=self.course_key, module_state_key=msk).count(), 0 )
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 test_delete_student_state(self, _crum_mock): problem_location = self.problem.location self._get_subsection_grade_and_verify(0, 1, 0, 1) answer_problem(course=self.course, request=self.request, problem=self.problem, score=1, max_value=1) self._get_subsection_grade_and_verify(1, 1, 1, 1) # Delete student state using the instructor dash reset_student_attempts( self.course.id, self.user, problem_location, requesting_user=self.user, delete_module=True, ) # Verify that the student's grades are reset self._get_subsection_grade_and_verify(0, 1, 0, 1)
def test_delete_submission_scores_attempts_children(self): parent_state = json.loads(self.get_state(self.parent.location)) self.assertEqual(parent_state["attempts"], 32) self.assertEqual(parent_state["otherstuff"], "alsorobots") child_state = json.loads(self.get_state(self.child.location)) self.assertEqual(child_state["attempts"], 10) self.assertEqual(child_state["whatever"], "things") unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state["attempts"], 12) self.assertEqual(unrelated_state["brains"], "zombie") reset_student_attempts( self.course_key, self.user, self.parent.location, requesting_user=self.user, delete_module=True ) self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.parent.location) self.assertRaises(StudentModule.DoesNotExist, self.get_state, self.child.location) unrelated_state = json.loads(self.get_state(self.unrelated.location)) self.assertEqual(unrelated_state["attempts"], 12) self.assertEqual(unrelated_state["brains"], "zombie")
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_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access( request.user, 'staff', course_id, 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, 'instructor', course): return HttpResponseForbidden("Requires instructor access.") try: module_state_key = course_id.make_usage_key_from_deprecated_string(problem_to_reset) except InvalidKeyError: return HttpResponseBadRequest() 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.")) except sub_api.SubmissionError: # Trust the submissions API to log the error error_msg = _("An error occurred while deleting the score.") return HttpResponse(error_msg, status=500) response_payload['student'] = student_identifier elif all_students: instructor_task.api.submit_reset_problem_attempts_for_all_students(request, 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.")) except sub_api.SubmissionError: # Trust the submissions API to log the error error_msg = _("An error occurred while deleting the score.") return HttpResponse(error_msg, status=500) 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)