def test_create_assessment(self): # Initially, there should be no submission or self assessment self.assertEqual(get_assessment("5"), None) # Create a submission to self-assess submission = create_submission(self.STUDENT_ITEM, "Test answer") # Now there should be a submission, but no self-assessment assessment = get_assessment(submission["uuid"]) self.assertIs(assessment, None) self.assertFalse(is_complete(submission['uuid'])) # Create a self-assessment for the submission assessment = create_assessment( submission['uuid'], u'𝖙𝖊𝖘𝖙 𝖚𝖘𝖊𝖗', self.OPTIONS_SELECTED, self.RUBRIC, scored_at=datetime.datetime(2014, 4, 1).replace(tzinfo=pytz.utc) ) # Self-assessment should be complete self.assertTrue(is_complete(submission['uuid'])) # Retrieve the self-assessment retrieved = get_assessment(submission["uuid"]) # Check that the assessment we created matches the assessment we retrieved # and that both have the correct values self.assertItemsEqual(assessment, retrieved) self.assertEqual(assessment['submission_uuid'], submission['uuid']) self.assertEqual(assessment['points_earned'], 8) self.assertEqual(assessment['points_possible'], 10) self.assertEqual(assessment['feedback'], u'') self.assertEqual(assessment['score_type'], u'SE')
def test_self_assess_handler(self, xblock): student_item = xblock.get_student_item_dict() # Create a submission for the student submission = xblock.create_submission(student_item, self.SUBMISSION) # Submit a self-assessment assessment = copy.deepcopy(self.ASSESSMENT) assessment['submission_uuid'] = submission['uuid'] resp = self.request(xblock, 'self_assess', json.dumps(assessment), response_format='json') self.assertTrue(resp['success']) # Expect that a self-assessment was created assessment = self_api.get_assessment(submission["uuid"]) self.assertEqual(assessment['submission_uuid'], submission['uuid']) self.assertEqual(assessment['points_earned'], 5) self.assertEqual(assessment['points_possible'], 6) self.assertEqual(assessment['scorer_id'], 'Bob') self.assertEqual(assessment['score_type'], 'SE') self.assertEqual(assessment['feedback'], u'') parts = sorted(assessment['parts']) self.assertEqual(len(parts), 2) self.assertEqual(parts[0]['option']['criterion']['name'], u'Form') self.assertEqual(parts[0]['option']['name'], 'Fair') self.assertEqual(parts[1]['option']['criterion']['name'], u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮') self.assertEqual(parts[1]['option']['name'], u'ﻉซƈﻉɭɭﻉกՇ')
def test_self_assess_handler(self, xblock): student_item = xblock.get_student_item_dict() # Create a submission for the student submission = xblock.create_submission(student_item, self.SUBMISSION) # Submit a self-assessment resp = self.request(xblock, 'self_assess', json.dumps(self.ASSESSMENT), response_format='json') self.assertTrue(resp['success']) # Expect that a self-assessment was created assessment = self_api.get_assessment(submission["uuid"]) self.assertEqual(assessment['submission_uuid'], submission['uuid']) self.assertEqual(assessment['points_earned'], 5) self.assertEqual(assessment['points_possible'], 6) self.assertEqual(assessment['scorer_id'], 'Bob') self.assertEqual(assessment['score_type'], 'SE') self.assertEqual(assessment['feedback'], u'') parts = sorted(assessment['parts']) self.assertEqual(len(parts), 2) self.assertEqual(parts[0]['option']['criterion']['name'], u'Form') self.assertEqual(parts[0]['option']['name'], 'Fair') self.assertEqual(parts[1]['option']['criterion']['name'], u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮') self.assertEqual(parts[1]['option']['name'], u'ﻉซƈﻉɭɭﻉกՇ')
def self_path_and_context(self): """ Determine the template path and context to use when rendering the self-assessment step. Returns: tuple of `(path, context)`, where `path` (str) is the path to the template, and `context` (dict) is the template context. Raises: SubmissionError: Error occurred while retrieving the current submission. SelfAssessmentRequestError: Error occurred while checking if we had a self-assessment. """ context = {} path = 'openassessmentblock/self/oa_self_unavailable.html' problem_closed, reason, start_date, due_date = self.is_closed( step="self-assessment") # We display the due date whether the problem is open or closed. # If no date is set, it defaults to the distant future, in which # case we don't display the date. if due_date < DISTANT_FUTURE: context['self_due'] = due_date # If we haven't submitted yet, `workflow` will be an empty dict, # and `workflow_status` will be None. workflow = self.get_workflow_info() workflow_status = workflow.get('status') if workflow_status == 'waiting' or workflow_status == 'done': path = 'openassessmentblock/self/oa_self_complete.html' elif workflow_status == 'self' or problem_closed: assessment = self_api.get_assessment( workflow.get("submission_uuid")) if assessment is not None: path = 'openassessmentblock/self/oa_self_complete.html' elif problem_closed: if reason == 'start': context["self_start"] = start_date path = 'openassessmentblock/self/oa_self_unavailable.html' elif reason == 'due': path = 'openassessmentblock/self/oa_self_closed.html' else: submission = submission_api.get_submission( self.submission_uuid) context["rubric_criteria"] = self.rubric_criteria context[ "estimated_time"] = "20 minutes" # TODO: Need to configure this. context["self_submission"] = submission path = 'openassessmentblock/self/oa_self_assessment.html' else: # No submission yet or in peer assessment path = 'openassessmentblock/self/oa_self_unavailable.html' return path, context
def render_grade_complete(self, workflow): """ Render the grade complete state. Args: workflow (dict): The serialized Workflow model. Returns: tuple of context (dict), template_path (string) """ feedback = peer_api.get_assessment_feedback(self.submission_uuid) feedback_text = feedback.get('feedback', '') if feedback else '' student_submission = sub_api.get_submission( workflow['submission_uuid']) peer_assessments = peer_api.get_assessments(student_submission['uuid']) self_assessment = self_api.get_assessment(student_submission['uuid']) has_submitted_feedback = peer_api.get_assessment_feedback( workflow['submission_uuid']) is not None # We retrieve the score from the workflow, which in turn retrieves # the score for our current submission UUID. # We look up the score by submission UUID instead of student item # to ensure that the score always matches the rubric. score = workflow['score'] context = { 'score': score, 'feedback_text': feedback_text, 'student_submission': student_submission, 'peer_assessments': peer_assessments, 'self_assessment': self_assessment, 'rubric_criteria': self._rubric_criteria_with_feedback(peer_assessments), 'has_submitted_feedback': has_submitted_feedback, } # Update the scores we will display to the user # Note that we are updating a *copy* of the rubric criteria stored in the XBlock field max_scores = peer_api.get_rubric_max_scores(self.submission_uuid) median_scores = peer_api.get_assessment_median_scores( student_submission["uuid"]) if median_scores is not None and max_scores is not None: for criterion in context["rubric_criteria"]: criterion["median_score"] = median_scores[criterion["name"]] criterion["total_value"] = max_scores[criterion["name"]] return ('openassessmentblock/grade/oa_grade_complete.html', context)
def self_path_and_context(self): """ Determine the template path and context to use when rendering the self-assessment step. Returns: tuple of `(path, context)`, where `path` (str) is the path to the template, and `context` (dict) is the template context. Raises: SubmissionError: Error occurred while retrieving the current submission. SelfAssessmentRequestError: Error occurred while checking if we had a self-assessment. """ context = {} path = 'openassessmentblock/self/oa_self_unavailable.html' problem_closed, reason, start_date, due_date = self.is_closed(step="self-assessment") # We display the due date whether the problem is open or closed. # If no date is set, it defaults to the distant future, in which # case we don't display the date. if due_date < DISTANT_FUTURE: context['self_due'] = due_date # If we haven't submitted yet, `workflow` will be an empty dict, # and `workflow_status` will be None. workflow = self.get_workflow_info() workflow_status = workflow.get('status') if workflow_status == 'waiting' or workflow_status == 'done': path = 'openassessmentblock/self/oa_self_complete.html' elif workflow_status == 'self' or problem_closed: assessment = self_api.get_assessment(workflow.get("submission_uuid")) if assessment is not None: path = 'openassessmentblock/self/oa_self_complete.html' elif problem_closed: if reason == 'start': context["self_start"] = start_date path = 'openassessmentblock/self/oa_self_unavailable.html' elif reason == 'due': path = 'openassessmentblock/self/oa_self_closed.html' else: submission = submission_api.get_submission(self.submission_uuid) context["rubric_criteria"] = self.rubric_criteria context["estimated_time"] = "20 minutes" # TODO: Need to configure this. context["self_submission"] = submission path = 'openassessmentblock/self/oa_self_assessment.html' else: # No submission yet or in peer assessment path = 'openassessmentblock/self/oa_self_unavailable.html' return path, context
def render_grade_complete(self, workflow): """ Render the grade complete state. Args: workflow (dict): The serialized Workflow model. Returns: tuple of context (dict), template_path (string) """ feedback = peer_api.get_assessment_feedback(self.submission_uuid) feedback_text = feedback.get('feedback', '') if feedback else '' student_submission = sub_api.get_submission(workflow['submission_uuid']) peer_assessments = peer_api.get_assessments(student_submission['uuid']) self_assessment = self_api.get_assessment(student_submission['uuid']) has_submitted_feedback = peer_api.get_assessment_feedback(workflow['submission_uuid']) is not None # We retrieve the score from the workflow, which in turn retrieves # the score for our current submission UUID. # We look up the score by submission UUID instead of student item # to ensure that the score always matches the rubric. score = workflow['score'] context = { 'score': score, 'feedback_text': feedback_text, 'student_submission': student_submission, 'peer_assessments': peer_assessments, 'self_assessment': self_assessment, 'rubric_criteria': self._rubric_criteria_with_feedback(peer_assessments), 'has_submitted_feedback': has_submitted_feedback, } # Update the scores we will display to the user # Note that we are updating a *copy* of the rubric criteria stored in the XBlock field max_scores = peer_api.get_rubric_max_scores(self.submission_uuid) median_scores = peer_api.get_assessment_median_scores(student_submission["uuid"]) if median_scores is not None and max_scores is not None: for criterion in context["rubric_criteria"]: criterion["median_score"] = median_scores[criterion["name"]] criterion["total_value"] = max_scores[criterion["name"]] return ('openassessmentblock/grade/oa_grade_complete.html', context)
def render_self_assessment(self, data, suffix=''): context = {} assessment_module = self.get_assessment_module('self-assessment') path = 'openassessmentblock/self/oa_self_unavailable.html' problem_closed, reason, date = self.is_closed(step="self-assessment") if problem_closed: if date == 'start': context["self_start"] = self.format_datetime_string(date) elif date == 'due': context["self_due"] = self.format_datetime_string(date) workflow = self.get_workflow_info() if not workflow: return self.render_assessment(path, context) try: submission = submission_api.get_submission(self.submission_uuid) assessment = self_api.get_assessment( workflow["submission_uuid"] ) except (submission_api.SubmissionError, self_api.SelfAssessmentRequestError): logger.exception( u"Could not retrieve self assessment for submission {}" .format(workflow["submission_uuid"]) ) return self.render_error(_(u"An unexpected error occurred.")) if workflow["status"] == "self": path = 'openassessmentblock/self/oa_self_assessment.html' context = { "rubric_criteria": self.rubric_criteria, "estimated_time": "20 minutes", # TODO: Need to configure this. "self_submission": submission, } elif assessment is not None: path = 'openassessmentblock/self/oa_self_complete.html' elif date == "due" and problem_closed: path = 'openassessmentblock/self/oa_self_closed.html' return self.render_assessment(path, context)
def test_create_multiple_self_assessments(self): # Create a submission to self-assess submission = create_submission(self.STUDENT_ITEM, "Test answer") # Self assess once assessment = create_assessment( submission['uuid'], u'𝖙𝖊𝖘𝖙 𝖚𝖘𝖊𝖗', self.OPTIONS_SELECTED, self.RUBRIC, ) # Attempt to self-assess again, which should raise an exception with self.assertRaises(SelfAssessmentRequestError): create_assessment( submission['uuid'], u'𝖙𝖊𝖘𝖙 𝖚𝖘𝖊𝖗', self.OPTIONS_SELECTED, self.RUBRIC, ) # Expect that we still have the original assessment retrieved = get_assessment(submission["uuid"]) self.assertItemsEqual(assessment, retrieved)
def test_create_assessment_timestamp(self): # Create a submission to self-assess submission = create_submission(self.STUDENT_ITEM, "Test answer") # Record the current system clock time before = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) # Create a self-assessment for the submission # Do not override the scored_at timestamp, so it should be set to the current time assessment = create_assessment( submission['uuid'], u'𝖙𝖊𝖘𝖙 𝖚𝖘𝖊𝖗', self.OPTIONS_SELECTED, self.RUBRIC, ) # Retrieve the self-assessment retrieved = get_assessment(submission["uuid"]) # Expect that both the created and retrieved assessments have the same # timestamp, and it's >= our recorded time. self.assertEqual(assessment['scored_at'], retrieved['scored_at']) self.assertGreaterEqual(assessment['scored_at'], before)
def render_grade_complete(self, workflow): """ Render the grade complete state. Args: workflow (dict): The serialized Workflow model. Returns: tuple of context (dict), template_path (string) """ feedback = peer_api.get_assessment_feedback(self.submission_uuid) feedback_text = feedback.get('feedback', '') if feedback else '' student_submission = sub_api.get_submission(workflow['submission_uuid']) peer_assessments = peer_api.get_assessments(student_submission['uuid']) self_assessment = self_api.get_assessment(student_submission['uuid']) has_submitted_feedback = peer_api.get_assessment_feedback(workflow['submission_uuid']) is not None context = { 'score': workflow['score'], 'feedback_text': feedback_text, 'student_submission': student_submission, 'peer_assessments': peer_assessments, 'self_assessment': self_assessment, 'rubric_criteria': copy.deepcopy(self.rubric_criteria), 'has_submitted_feedback': has_submitted_feedback, } # Update the scores we will display to the user # Note that we are updating a *copy* of the rubric criteria stored in the XBlock field max_scores = peer_api.get_rubric_max_scores(self.submission_uuid) median_scores = peer_api.get_assessment_median_scores(student_submission["uuid"]) if median_scores is not None and max_scores is not None: for criterion in context["rubric_criteria"]: criterion["median_score"] = median_scores[criterion["name"]] criterion["total_value"] = max_scores[criterion["name"]] return ('openassessmentblock/grade/oa_grade_complete.html', context)
def test_create_submissions(self): # Create some submissions cmd = create_oa_submissions.Command() cmd.handle("test_course", "test_item", "5") self.assertEqual(len(cmd.student_items), 5) for student_item in cmd.student_items: # Check that the student item was created for the right course / item self.assertEqual(student_item['course_id'], 'test_course') self.assertEqual(student_item['item_id'], 'test_item') # Check that a submission was created submissions = sub_api.get_submissions(student_item) self.assertEqual(len(submissions), 1) answer_dict = submissions[0]['answer'] self.assertIsInstance(answer_dict['text'], basestring) self.assertGreater(len(answer_dict['text']), 0) # Check that peer and self assessments were created assessments = peer_api.get_assessments(submissions[0]['uuid'], scored_only=False) # Verify that the assessments exist and have content self.assertEqual(len(assessments), cmd.NUM_PEER_ASSESSMENTS) for assessment in assessments: self.assertGreater(assessment['points_possible'], 0) # Check that a self-assessment was created assessment = self_api.get_assessment(submissions[0]['uuid']) # Verify that the assessment exists and has content self.assertIsNot(assessment, None) self.assertGreater(assessment['points_possible'], 0)