def _cancel_workflow(self, submission_uuid, comments, requesting_user_id=None): """ Internal helper method to cancel a workflow using the workflow API. If requesting_user is not provided, we will use the user to which this xblock is currently bound. """ # Import is placed here to avoid model import at project startup. from openassessment.workflow import api as workflow_api try: assessment_requirements = self.workflow_requirements() if requesting_user_id is None: # The student_id is actually the bound user, which is the staff user in this context. requesting_user_id = self.get_student_item_dict()["student_id"] # Cancel the related workflow. workflow_api.cancel_workflow( submission_uuid=submission_uuid, comments=comments, cancelled_by_id=requesting_user_id, assessment_requirements=assessment_requirements ) return { "success": True, 'msg': self._( u"The learner submission has been removed from peer assessment. " u"The learner receives a grade of zero unless you delete " u"the learner's state for the problem to allow them to " u"resubmit a response." ) } except ( AssessmentWorkflowError, AssessmentWorkflowInternalError ) as ex: msg = str(ex) logger.exception(msg) return {"success": False, 'msg': msg}
def _cancel_workflow(self, submission_uuid, comments): """ Internal helper method to cancel a workflow using the workflow API. """ try: assessment_requirements = self.workflow_requirements() student_item_dict = self.get_student_item_dict() # Cancel the related workflow. workflow_api.cancel_workflow( submission_uuid=submission_uuid, comments=comments, cancelled_by_id=student_item_dict['student_id'], assessment_requirements=assessment_requirements ) return { "success": True, 'msg': self._( u"The learner submission has been removed from peer assessment. " u"The learner receives a grade of zero unless you delete " u"the learner's state for the problem to allow them to " u"resubmit a response." ) } except ( AssessmentWorkflowError, AssessmentWorkflowInternalError ) as ex: msg = ex.message logger.exception(msg) return {"success": False, 'msg': msg}
def test_cancel_the_assessment_workflow_does_not_exist(self): # Create the submission and assessment workflow. submission = sub_api.create_submission(ITEM_1, ANSWER_1) workflow = workflow_api.create_workflow(submission["uuid"], ["peer"]) requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 } } # Check if workflow is cancelled. self.assertFalse(workflow_api.is_workflow_cancelled(submission["uuid"])) self.assertNotEqual(workflow.get('status'), 'cancelled') # Cancel the workflow raises DoesNotExist. with self.assertRaises(workflow_api.AssessmentWorkflowError): workflow_api.cancel_workflow( submission_uuid="1234567098789", comments="Inappropriate language", cancelled_by_id=ITEM_2['student_id'], assessment_requirements=requirements ) # Status for workflow should not be cancelled. workflow = AssessmentWorkflow.get_by_submission_uuid(submission["uuid"]) self.assertNotEqual(workflow.status, 'cancelled')
def test_cancel_the_assessment_workflow(self): # Create the submission and assessment workflow. submission = sub_api.create_submission(ITEM_1, ANSWER_1) workflow = workflow_api.create_workflow(submission["uuid"], ["peer"]) requirements = {"peer": {"must_grade": 1, "must_be_graded_by": 1}} # Check the workflow is not cancelled. self.assertFalse(workflow_api.is_workflow_cancelled( submission["uuid"])) # Check the status is not cancelled. self.assertNotEqual(workflow.get('status'), 'cancelled') # Check the points_earned are not 0 self.assertNotEqual(workflow['score'], 0) # Cancel the workflow for submission. workflow_api.cancel_workflow(submission_uuid=submission["uuid"], comments="Inappropriate language", cancelled_by_id=ITEM_2['student_id'], assessment_requirements=requirements) # Check workflow is cancelled. self.assertTrue(workflow_api.is_workflow_cancelled(submission["uuid"])) # Status for workflow should be cancelled. workflow = AssessmentWorkflow.get_by_submission_uuid( submission["uuid"]) self.assertEqual(workflow.status, 'cancelled') # Score points_earned should be 0. # In case of 0 earned points the score would be None. self.assertEqual(workflow.score, None)
def test_peer_assess_for_already_cancelled_submission(self, xblock): # Create a submission for this problem from another user student_item = xblock.get_student_item_dict() submission = xblock.create_submission(student_item, self.SUBMISSION) # Create a submission for the scorer (required before assessing another student) another_student = copy.deepcopy(student_item) another_submission = xblock.create_submission(another_student, self.SUBMISSION) assessment = self.ASSESSMENT assessment['submission_uuid'] = assessment.get('submission_uuid', submission.get('uuid', None)) # Pull the submission to assess peer_api.get_submission_to_assess(another_submission['uuid'], 3) requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 }, } workflow_api.cancel_workflow( submission_uuid=submission['uuid'], comments="Inappropriate language", cancelled_by_id=another_student['student_id'], assessment_requirements=requirements ) # Submit an assessment and expect a failure resp = self.request(xblock, 'peer_assess', json.dumps(assessment), response_format='json') self.assertEqual(resp['success'], False) self.assertGreater(len(resp['msg']), 0)
def test_staff_debug_student_info_with_cancelled_submission(self, xblock): requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 }, } # Simulate that we are course staff xblock.xmodule_runtime = self._create_mock_runtime( xblock.scope_ids.usage_id, True, False, "Bob" ) xblock.runtime._services['user'] = NullUserService() bob_item = STUDENT_ITEM.copy() bob_item["item_id"] = xblock.scope_ids.usage_id # Create a submission for Bob, and corresponding workflow. submission = sub_api.create_submission( bob_item, prepare_submission_for_serialization(("Bob Answer 1", "Bob Answer 2")) ) peer_api.on_start(submission["uuid"]) workflow_api.create_workflow(submission["uuid"], ['peer']) workflow_api.cancel_workflow( submission_uuid=submission["uuid"], comments="Inappropriate language", cancelled_by_id=bob_item['student_id'], assessment_requirements=requirements ) path, context = xblock.get_student_info_path_and_context("Bob") self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text']) self.assertIsNotNone(context['workflow_cancellation']) self.assertEquals("openassessmentblock/staff_debug/student_info.html", path)
def test_cancelled_submission_peer_assessment_render_path(self, xblock): # Test that peer assessment path should be oa_peer_cancelled.html for a cancelled submission. # Simulate that we are course staff xblock.xmodule_runtime = self._create_mock_runtime( xblock.scope_ids.usage_id, True, False, "Bob" ) bob_item = STUDENT_ITEM.copy() bob_item["item_id"] = xblock.scope_ids.usage_id # Create a submission for Bob, and corresponding workflow. submission = sub_api.create_submission(bob_item, {'text': "Bob Answer"}) peer_api.on_start(submission["uuid"]) workflow_api.create_workflow(submission["uuid"], ['peer']) requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 }, } workflow_api.cancel_workflow( submission_uuid=submission['uuid'], comments="Inappropriate language", cancelled_by_id=bob_item['student_id'], assessment_requirements=requirements ) xblock.submission_uuid = submission["uuid"] path, context = xblock.peer_path_and_context(False) self.assertEquals("openassessmentblock/peer/oa_peer_cancelled.html", path)
def _cancel_workflow(self, submission_uuid, comments, requesting_user_id=None): """ Internal helper method to cancel a workflow using the workflow API. If requesting_user is not provided, we will use the user to which this xblock is currently bound. """ try: assessment_requirements = self.workflow_requirements() if requesting_user_id is None: "The student_id is actually the bound user, which is the staff user in this context." requesting_user_id = self.get_student_item_dict()["student_id"] # Cancel the related workflow. workflow_api.cancel_workflow( submission_uuid=submission_uuid, comments=comments, cancelled_by_id=requesting_user_id, assessment_requirements=assessment_requirements ) return { "success": True, 'msg': self._( u"The learner submission has been removed from peer assessment. " u"The learner receives a grade of zero unless you delete " u"the learner's state for the problem to allow them to " u"resubmit a response." ) } except ( AssessmentWorkflowError, AssessmentWorkflowInternalError ) as ex: msg = ex.message logger.exception(msg) return {"success": False, 'msg': msg}
def test_get_the_cancelled_workflow(self): # Create the submission and assessment workflow. submission = sub_api.create_submission(ITEM_1, ANSWER_1) workflow = workflow_api.create_workflow(submission["uuid"], ["peer"]) requirements = {"peer": {"must_grade": 1, "must_be_graded_by": 1}} # Check the workflow is not cancelled. self.assertFalse(workflow_api.is_workflow_cancelled( submission["uuid"])) # Check the status is not cancelled. self.assertNotEqual(workflow.get('status'), 'cancelled') # Check the points_earned are not 0 self.assertNotEqual(workflow['score'], 0) cancelled_workflow = workflow_api.get_assessment_workflow_cancellation( submission["uuid"]) self.assertIsNone(cancelled_workflow) # Cancel the workflow for submission. workflow_api.cancel_workflow(submission_uuid=submission["uuid"], comments="Inappropriate language", cancelled_by_id=ITEM_2['student_id'], assessment_requirements=requirements) # Check workflow is cancelled. self.assertTrue(workflow_api.is_workflow_cancelled(submission["uuid"])) workflow = workflow_api.get_assessment_workflow_cancellation( submission["uuid"]) self.assertIsNotNone(workflow)
def test_cancelled_submission(self, xblock): student_item = xblock.get_student_item_dict() mock_staff = Mock(name='Bob') xblock.get_username = Mock(return_value=mock_staff) submission = xblock.create_submission( student_item, ('A man must have a code', 'A man must have an umbrella too.') ) workflow_api.cancel_workflow( submission_uuid=submission['uuid'], comments='Inappropriate language', cancelled_by_id='Bob', assessment_requirements=xblock.workflow_requirements() ) self._assert_path_and_context( xblock, 'openassessmentblock/response/oa_response_cancelled.html', { 'text_response': 'required', 'file_upload_response': None, 'file_upload_type': None, 'allow_latex': False, 'submission_due': dt.datetime(2999, 5, 6).replace(tzinfo=pytz.utc), 'student_submission': submission, 'workflow_cancellation': { 'comments': 'Inappropriate language', 'cancelled_at': xblock.get_workflow_cancellation_info(submission['uuid']).get('cancelled_at'), 'cancelled_by_id': 'Bob', 'cancelled_by': mock_staff }, 'user_timezone': None, 'user_language': None, 'prompts_type': 'text' } )
def test_staff_area_student_info_with_cancelled_submission(self, xblock): requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 }, } # Simulate that we are course staff xblock.xmodule_runtime = self._create_mock_runtime( xblock.scope_ids.usage_id, True, False, "Bob") xblock.runtime._services['user'] = NullUserService() bob_item = STUDENT_ITEM.copy() bob_item["item_id"] = xblock.scope_ids.usage_id # Create a submission for Bob, and corresponding workflow. submission = self._create_submission( bob_item, prepare_submission_for_serialization( ("Bob Answer 1", "Bob Answer 2")), ['peer']) workflow_api.cancel_workflow(submission_uuid=submission["uuid"], comments="Inappropriate language", cancelled_by_id=bob_item['student_id'], assessment_requirements=requirements) path, context = xblock.get_student_info_path_and_context("Bob") self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text']) self.assertIsNotNone(context['workflow_cancellation']) self.assertEquals( "openassessmentblock/staff_area/oa_student_info.html", path)
def test_peer_assess_for_already_cancelled_submission(self, xblock): # Create a submission for this problem from another user student_item = xblock.get_student_item_dict() submission = xblock.create_submission(student_item, self.SUBMISSION) # Create a submission for the scorer (required before assessing another student) another_student = copy.deepcopy(student_item) another_submission = xblock.create_submission(another_student, self.SUBMISSION) assessment = self.ASSESSMENT assessment["submission_uuid"] = assessment.get("submission_uuid", submission.get("uuid", None)) # Pull the submission to assess peer_api.get_submission_to_assess(another_submission["uuid"], 3) requirements = {"peer": {"must_grade": 1, "must_be_graded_by": 1}} workflow_api.cancel_workflow( submission_uuid=submission["uuid"], comments="Inappropriate language", cancelled_by_id=another_student["student_id"], assessment_requirements=requirements, ) # Submit an assessment and expect a failure resp = self.request(xblock, "peer_assess", json.dumps(assessment), response_format="json") self.assertEqual(resp["success"], False) self.assertGreater(len(resp["msg"]), 0)
def test_cancelled_submission_peer_assessment_render_path(self, xblock): # Test that peer assessment path should be oa_peer_cancelled.html for a cancelled submission. # Simulate that we are course staff xblock.xmodule_runtime = self._create_mock_runtime( xblock.scope_ids.usage_id, True, False, "Bob") bob_item = STUDENT_ITEM.copy() bob_item["item_id"] = xblock.scope_ids.usage_id # Create a submission for Bob, and corresponding workflow. submission = self._create_submission(bob_item, {'text': "Bob Answer"}, ['peer']) requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 }, } workflow_api.cancel_workflow(submission_uuid=submission['uuid'], comments="Inappropriate language", cancelled_by_id=bob_item['student_id'], assessment_requirements=requirements) xblock.submission_uuid = submission["uuid"] path, context = xblock.peer_path_and_context(False) self.assertEquals("openassessmentblock/peer/oa_peer_cancelled.html", path)
def cancel_submission(self, data, suffix=''): """ This will cancel the assessment + peer workflow for the particular submission. Args: data (dict): Data contain two attributes: submission_uuid and comments. submission_uuid is id of submission which is to be removed from the grading pool. Comments is the reason given by the user. suffix (not used) Return: Json serializable dict with the following elements: 'success': (bool) Indicates whether or not the workflow cancelled successfully. 'msg': The response (could be error message or success message). """ submission_uuid = data.get('submission_uuid') comments = data.get('comments') if not comments: return { "success": False, "msg": self._(u'Please enter valid reason to remove the submission.') } student_item_dict = self.get_student_item_dict() try: assessment_requirements = self.workflow_requirements() # Cancel the related workflow. workflow_api.cancel_workflow( submission_uuid=submission_uuid, comments=comments, cancelled_by_id=student_item_dict['student_id'], assessment_requirements=assessment_requirements) return { "success": True, 'msg': self. _(u"The learner submission has been removed from peer assessment. " u"The learner receives a grade of zero unless you delete " u"the learner's state for the problem to allow them to " u"resubmit a response.") } except (AssessmentWorkflowError, AssessmentWorkflowInternalError) as ex: msg = ex.message logger.exception(msg) return {"success": False, 'msg': msg}
def test_grading_statistics(self): bob_sub, bob = self._create_student_and_submission( "bob", "bob's answer") course_id = bob['course_id'] item_id = bob['item_id'] tim_sub, tim = self._create_student_and_submission( "Tim", "Tim's answer") sue_sub, sue = self._create_student_and_submission( "Sue", "Sue's answer") stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 0, 'ungraded': 3, 'in-progress': 0}) # Fetch a grade so that there's one 'in-progress' tim_to_grade = staff_api.get_submission_to_assess( course_id, item_id, tim['student_id']) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 0, 'ungraded': 2, 'in-progress': 1}) bob_to_grade = staff_api.get_submission_to_assess( tim['course_id'], tim['item_id'], bob['student_id']) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 0, 'ungraded': 1, 'in-progress': 2}) # Grade one of the submissions staff_assessment = staff_api.create_assessment( tim_to_grade["uuid"], tim['student_id'], OPTIONS_SELECTED_DICT["all"]["options"], dict(), "", RUBRIC, ) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 1, 'ungraded': 1, 'in-progress': 1}) # When one of the 'locks' times out, verify that it is no longer # considered ungraded. workflow = StaffWorkflow.objects.get(scorer_id=bob['student_id']) timestamp = (now() - (workflow.TIME_LIMIT + timedelta(hours=1)) ).strftime("%Y-%m-%d %H:%M:%S") workflow.grading_started_at = timestamp workflow.save() stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 1, 'ungraded': 2, 'in-progress': 0}) workflow_api.cancel_workflow(bob_to_grade['uuid'], "Test Cancel", bob['student_id'], {}) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 1, 'ungraded': 1, 'in-progress': 0})
def cancel_submission(self, data, suffix=''): """ This will cancel the assessment + peer workflow for the particular submission. Args: data (dict): Data contain two attributes: submission_uuid and comments. submission_uuid is id of submission which is to be removed from the grading pool. Comments is the reason given by the user. suffix (not used) Return: Json serializable dict with the following elements: 'success': (bool) Indicates whether or not the workflow cancelled successfully. 'msg': The response (could be error message or success message). """ submission_uuid = data.get('submission_uuid') comments = data.get('comments') if not comments: return {"success": False, "msg": self._(u'Please enter valid reason to remove the submission.')} student_item_dict = self.get_student_item_dict() try: assessment_requirements = self.workflow_requirements() # Cancel the related workflow. workflow_api.cancel_workflow( submission_uuid=submission_uuid, comments=comments, cancelled_by_id=student_item_dict['student_id'], assessment_requirements=assessment_requirements ) return { "success": True, 'msg': self._( u"The learner submission has been removed from peer assessment. " u"The learner receives a grade of zero unless you delete " u"the learner's state for the problem to allow them to " u"resubmit a response." ) } except ( AssessmentWorkflowError, AssessmentWorkflowInternalError ) as ex: msg = ex.message logger.exception(msg) return {"success": False, 'msg': msg}
def test_grading_statistics(self): _, bob = self._create_student_and_submission("bob", "bob's answer") course_id = bob['course_id'] item_id = bob['item_id'] _, tim = self._create_student_and_submission("Tim", "Tim's answer") self._create_student_and_submission("Sue", "Sue's answer") stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 0, 'ungraded': 3, 'in-progress': 0}) # Fetch a grade so that there's one 'in-progress' tim_to_grade = staff_api.get_submission_to_assess(course_id, item_id, tim['student_id']) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 0, 'ungraded': 2, 'in-progress': 1}) bob_to_grade = staff_api.get_submission_to_assess(tim['course_id'], tim['item_id'], bob['student_id']) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 0, 'ungraded': 1, 'in-progress': 2}) # Grade one of the submissions staff_api.create_assessment( tim_to_grade["uuid"], tim['student_id'], OPTIONS_SELECTED_DICT["all"]["options"], dict(), "", RUBRIC, ) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 1, 'ungraded': 1, 'in-progress': 1}) # When one of the 'locks' times out, verify that it is no longer # considered ungraded. workflow = StaffWorkflow.objects.get(scorer_id=bob['student_id']) timestamp = (now() - (workflow.TIME_LIMIT + timedelta(hours=1))).strftime("%Y-%m-%d %H:%M:%S") workflow.grading_started_at = timestamp workflow.save() stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 1, 'ungraded': 2, 'in-progress': 0}) workflow_api.cancel_workflow(bob_to_grade['uuid'], "Test Cancel", bob['student_id'], {}) stats = staff_api.get_staff_grading_statistics(course_id, item_id) self.assertEqual(stats, {'graded': 1, 'ungraded': 1, 'in-progress': 0})
def test_cancel_the_assessment_workflow(self): # Create the submission and assessment workflow. submission = sub_api.create_submission(ITEM_1, ANSWER_1) workflow = workflow_api.create_workflow(submission["uuid"], ["peer"]) requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 } } # Check the workflow is not cancelled. self.assertFalse(workflow_api.is_workflow_cancelled(submission["uuid"])) # Check the status is not cancelled. self.assertNotEqual(workflow.get('status'), 'cancelled') # Check the points_earned are not 0 self.assertNotEqual(workflow['score'], 0) # Cancel the workflow for submission. workflow_api.cancel_workflow( submission_uuid=submission["uuid"], comments="Inappropriate language", cancelled_by_id=ITEM_2['student_id'], assessment_requirements=requirements ) # Check workflow is cancelled. self.assertTrue(workflow_api.is_workflow_cancelled(submission["uuid"])) # Status for workflow should be cancelled. workflow = AssessmentWorkflow.get_by_submission_uuid(submission["uuid"]) self.assertEqual(workflow.status, 'cancelled') # Score points_earned should be 0. # In case of 0 earned points the score would be None. self.assertEqual(workflow.score, None)
def test_get_the_cancelled_workflow(self): # Create the submission and assessment workflow. submission = sub_api.create_submission(ITEM_1, ANSWER_1) workflow = workflow_api.create_workflow(submission["uuid"], ["peer"]) requirements = { "peer": { "must_grade": 1, "must_be_graded_by": 1 } } # Check the workflow is not cancelled. self.assertFalse(workflow_api.is_workflow_cancelled(submission["uuid"])) # Check the status is not cancelled. self.assertNotEqual(workflow.get('status'), 'cancelled') # Check the points_earned are not 0 self.assertNotEqual(workflow['score'], 0) cancelled_workflow = workflow_api.get_assessment_workflow_cancellation(submission["uuid"]) self.assertIsNone(cancelled_workflow) # Cancel the workflow for submission. workflow_api.cancel_workflow( submission_uuid=submission["uuid"], comments="Inappropriate language", cancelled_by_id=ITEM_2['student_id'], assessment_requirements=requirements ) # Check workflow is cancelled. self.assertTrue(workflow_api.is_workflow_cancelled(submission["uuid"])) workflow = workflow_api.get_assessment_workflow_cancellation(submission["uuid"]) self.assertIsNotNone(workflow)
def test_cancel_staff_workflow(self): tim_sub, _ = self._create_student_and_submission("Tim", "Tim's answer") workflow_api.cancel_workflow(tim_sub['uuid'], "Test Cancel", "Bob", {}) workflow = StaffWorkflow.objects.get(submission_uuid=tim_sub['uuid']) self.assertTrue(workflow.is_cancelled)