def test_full_assessment_dict_criteria_no_options(self): # Create a rubric with a criterion that has no options (just feedback) rubric_dict = copy.deepcopy(RUBRIC) rubric_dict['criteria'].append({ 'order_num': 2, 'name': 'feedback only', 'prompt': 'feedback only', 'options': [] }) rubric = rubric_from_dict(rubric_dict) # Create an assessment for the rubric assessment = Assessment.create(rubric, "Bob", "submission UUID", "PE") selected = { u"vøȼȺƀᵾłȺɍɏ": u"𝓰𝓸𝓸𝓭", u"ﻭɼค๓๓คɼ": u"єχ¢єℓℓєηт", } feedback = { u"feedback only": u"enjoy the feedback!" } AssessmentPart.create_from_option_names(assessment, selected, feedback=feedback) # Serialize the assessment serialized = full_assessment_dict(assessment) # Verify that the assessment dict correctly serialized the criterion with options. self.assertEqual(serialized['parts'][0]['criterion']['name'], u"vøȼȺƀᵾłȺɍɏ") self.assertEqual(serialized['parts'][0]['option']['name'], u"𝓰𝓸𝓸𝓭") self.assertEqual(serialized['parts'][1]['criterion']['name'], u"ﻭɼค๓๓คɼ") self.assertEqual(serialized['parts'][1]['option']['name'], u"єχ¢єℓℓєηт") # Verify that the assessment dict correctly serialized the criterion with no options. self.assertIs(serialized['parts'][2]['option'], None) self.assertEqual(serialized['parts'][2]['criterion']['name'], u"feedback only")
def test_full_assessment_dict_criteria_no_options(self): # Create a rubric with a criterion that has no options (just feedback) rubric_dict = copy.deepcopy(RUBRIC) rubric_dict['criteria'].append({ 'order_num': 2, 'name': 'feedback only', 'prompt': 'feedback only', 'options': [] }) rubric = rubric_from_dict(rubric_dict) # Create an assessment for the rubric assessment = Assessment.create(rubric, "Bob", "submission-UUID", "PE") selected = { "vøȼȺƀᵾłȺɍɏ": "𝓰𝓸𝓸𝓭", "ﻭɼค๓๓คɼ": "єχ¢єℓℓєηт", } feedback = { "feedback only": "enjoy the feedback!" } AssessmentPart.create_from_option_names(assessment, selected, feedback=feedback) # Serialize the assessment serialized = full_assessment_dict(assessment) # Verify that the assessment dict correctly serialized the criterion with options. self.assertEqual(serialized['parts'][0]['criterion']['name'], "vøȼȺƀᵾłȺɍɏ") self.assertEqual(serialized['parts'][0]['option']['name'], "𝓰𝓸𝓸𝓭") self.assertEqual(serialized['parts'][1]['criterion']['name'], "ﻭɼค๓๓คɼ") self.assertEqual(serialized['parts'][1]['option']['name'], "єχ¢єℓℓєηт") # Verify that the assessment dict correctly serialized the criterion with no options. self.assertIs(serialized['parts'][2]['option'], None) self.assertEqual(serialized['parts'][2]['criterion']['name'], "feedback only")
def create_assessment( team_submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at=None ): """ Creates an assessment for each member of the submitting team. Closely mirrors openassessment.assessment.api.staff.py::create_assessment Can use _complete_assessment from Staff API as is, but has the side-effect of only associating the last graded assessment with the workflow Returns: dict: the Assessment model, serialized as a dict. """ try: try: scorer_workflow = TeamStaffWorkflow.objects.get(team_submission_uuid=team_submission_uuid) except TeamStaffWorkflow.DoesNotExist: scorer_workflow = None # Get the submissions for a team team_submission = team_submissions_api.get_team_submission(team_submission_uuid) assessment_dicts = [] for submission_uuid in team_submission['submission_uuids']: assessment = _complete_assessment( submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at, scorer_workflow ) assessment_dicts.append(full_assessment_dict(assessment)) return assessment_dicts except InvalidRubric as ex: error_message = "The rubric definition is not valid." logger.exception(error_message) raise StaffAssessmentRequestError(error_message) from ex except InvalidRubricSelection as ex: error_message = "Invalid options were selected in the rubric." logger.warning(error_message, exc_info=True) raise StaffAssessmentRequestError(error_message) from ex except DatabaseError as ex: error_message = ( "An error occurred while creating an assessment by the scorer with this ID: {}" ).format(scorer_id) logger.exception(error_message) raise StaffAssessmentInternalError(error_message) from ex
def get_latest_staff_assessment(team_submission_uuid): """ Retrieve the latest staff assessment for a team submission. Args: team_submission_uuid (str): The UUID of the team submission being assessed. Returns: dict: The serialized assessment model or None if no assessments are available Raises: StaffAssessmentInternalError if there are problems connecting to the database. Example usage: >>> get_latest_staff_assessment('10df7db776686822e501b05f452dc1e4b9141fe5') { 'points_earned': 6, 'points_possible': 12, 'scored_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 649284 tzinfo=<UTC>), 'scorer': "staff", 'feedback': '' } """ try: # Get the reference submissions team_submission = team_submissions_api.get_team_submission( team_submission_uuid) # Return the most-recently graded assessment for any team member's submisison assessment = Assessment.objects.filter( submission_uuid__in=team_submission['submission_uuids'], score_type=STAFF_TYPE, ).first() except DatabaseError as ex: msg = ("An error occurred while retrieving staff assessments " "for the submission with UUID {uuid}: {ex}").format( uuid=team_submission_uuid, ex=ex) logger.exception(msg) raise StaffAssessmentInternalError(msg) if assessment: return full_assessment_dict(assessment) return None
def get_latest_staff_assessment(submission_uuid): """ Retrieve the latest staff assessment for a submission. Args: submission_uuid (str): The UUID of the submission being assessed. Returns: dict: The serialized assessment model or None if no assessments are available Raises: StaffAssessmentInternalError if there are problems connecting to the database. Example usage: >>> get_latest_staff_assessment('10df7db776686822e501b05f452dc1e4b9141fe5') { 'points_earned': 6, 'points_possible': 12, 'scored_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 649284 tzinfo=<UTC>), 'scorer': u"staff", 'feedback': u'' } """ try: assessments = Assessment.objects.filter( submission_uuid=submission_uuid, score_type=STAFF_TYPE, )[:1] except DatabaseError as ex: msg = ( u"An error occurred while retrieving staff assessments " u"for the submission with UUID {uuid}: {ex}" ).format(uuid=submission_uuid, ex=ex) logger.exception(msg) raise StaffAssessmentInternalError(msg) if len(assessments) > 0: return full_assessment_dict(assessments[0]) else: return None
def create_assessment( scorer_submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, num_required_grades, scored_at=None ): """Creates an assessment on the given submission. Assessments are created based on feedback associated with a particular rubric. Args: scorer_submission_uuid (str): The submission uuid for the Scorer's workflow. The submission being assessed can be determined via the peer workflow of the grading student. scorer_id (str): The user ID for the user giving this assessment. This is required to create an assessment on a submission. options_selected (dict): Dictionary mapping criterion names to the option names the user selected for that criterion. criterion_feedback (dict): Dictionary mapping criterion names to the free-form text feedback the user gave for the criterion. Since criterion feedback is optional, some criteria may not appear in the dictionary. overall_feedback (unicode): Free-form text feedback on the submission overall. num_required_grades (int): The required number of assessments a submission requires before it is completed. If this number of assessments is reached, the grading_completed_at timestamp is set for the Workflow. Kwargs: scored_at (datetime): Optional argument to override the time in which the assessment took place. If not specified, scored_at is set to now. Returns: dict: the Assessment model, serialized as a dict. Raises: PeerAssessmentRequestError: Raised when the submission_id is invalid, or the assessment_dict does not contain the required values to create an assessment. PeerAssessmentInternalError: Raised when there is an internal error while creating a new assessment. Examples: >>> options_selected = {"clarity": "Very clear", "precision": "Somewhat precise"} >>> criterion_feedback = {"clarity": "I thought this essay was very clear."} >>> feedback = "Your submission was thrilling." >>> create_assessment("1", "Tim", options_selected, criterion_feedback, feedback, rubric_dict) """ try: # Retrieve workflow information scorer_workflow = PeerWorkflow.objects.get(submission_uuid=scorer_submission_uuid) peer_workflow_item = scorer_workflow.get_latest_open_workflow_item() if peer_workflow_item is None: message = ( u"There are no open assessments associated with the scorer's " u"submission UUID {}." ).format(scorer_submission_uuid) logger.warning(message) raise PeerAssessmentWorkflowError(message) peer_submission_uuid = peer_workflow_item.author.submission_uuid # Get or create the rubric rubric = rubric_from_dict(rubric_dict) # Create the peer assessment assessment = Assessment.create( rubric, scorer_id, peer_submission_uuid, PEER_TYPE, scored_at=scored_at, feedback=overall_feedback ) # Create assessment parts for each criterion in the rubric # This will raise an `InvalidRubricSelection` if the selected options do not match the rubric. AssessmentPart.create_from_option_names(assessment, options_selected, feedback=criterion_feedback) # Close the active assessment scorer_workflow.close_active_assessment(peer_submission_uuid, assessment, num_required_grades) _log_assessment(assessment, scorer_workflow) return full_assessment_dict(assessment) except PeerWorkflow.DoesNotExist: message = ( u"There is no Peer Workflow associated with the given " u"submission UUID {}." ).format(scorer_submission_uuid) logger.exception(message) raise PeerAssessmentWorkflowError(message) except InvalidRubric: msg = u"Rubric definition was not valid" logger.exception(msg) raise PeerAssessmentRequestError(msg) except InvalidRubricSelection: msg = u"Invalid options selected in the rubric" logger.warning(msg, exc_info=True) raise PeerAssessmentRequestError(msg) except DatabaseError: error_message = ( u"An error occurred while retrieving the peer workflow item by scorer with ID: {}" ).format(scorer_id) logger.exception(error_message) raise PeerAssessmentInternalError(error_message)
def create_assessment( scorer_submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, num_required_grades, scored_at=None): """Creates an assessment on the given submission. Assessments are created based on feedback associated with a particular rubric. Args: scorer_submission_uuid (str): The submission uuid for the Scorer's workflow. The submission being assessed can be determined via the peer workflow of the grading student. scorer_id (str): The user ID for the user giving this assessment. This is required to create an assessment on a submission. options_selected (dict): Dictionary mapping criterion names to the option names the user selected for that criterion. criterion_feedback (dict): Dictionary mapping criterion names to the free-form text feedback the user gave for the criterion. Since criterion feedback is optional, some criteria may not appear in the dictionary. overall_feedback (unicode): Free-form text feedback on the submission overall. num_required_grades (int): The required number of assessments a submission requires before it is completed. If this number of assessments is reached, the grading_completed_at timestamp is set for the Workflow. Kwargs: scored_at (datetime): Optional argument to override the time in which the assessment took place. If not specified, scored_at is set to now. Returns: dict: the Assessment model, serialized as a dict. Raises: PeerAssessmentRequestError: Raised when the submission_id is invalid, or the assessment_dict does not contain the required values to create an assessment. PeerAssessmentInternalError: Raised when there is an internal error while creating a new assessment. Examples: >>> options_selected = {"clarity": "Very clear", "precision": "Somewhat precise"} >>> criterion_feedback = {"clarity": "I thought this essay was very clear."} >>> feedback = "Your submission was thrilling." >>> create_assessment("1", "Tim", options_selected, criterion_feedback, feedback, rubric_dict) """ # Ensure that this variables is declared so if an error occurs # we don't get an error when trying to log it! assessment_dict = None try: rubric = rubric_from_dict(rubric_dict) # Validate that the selected options matched the rubric # and raise an error if this is not the case try: option_ids = rubric.options_ids(options_selected) except InvalidOptionSelection: msg = "Selected options do not match the rubric" logger.warning(msg, exc_info=True) raise PeerAssessmentRequestError(msg) scorer_workflow = PeerWorkflow.objects.get(submission_uuid=scorer_submission_uuid) peer_workflow_item = scorer_workflow.get_latest_open_workflow_item() if peer_workflow_item is None: message = ( u"There are no open assessments associated with the scorer's " u"submission UUID {}." ).format(scorer_submission_uuid) logger.warning(message) raise PeerAssessmentWorkflowError(message) peer_submission_uuid = peer_workflow_item.author.submission_uuid peer_assessment = { "rubric": rubric.id, "scorer_id": scorer_id, "submission_uuid": peer_submission_uuid, "score_type": PEER_TYPE, "feedback": overall_feedback[0:Assessment.MAXSIZE], } if scored_at is not None: peer_assessment["scored_at"] = scored_at peer_serializer = AssessmentSerializer(data=peer_assessment) if not peer_serializer.is_valid(): msg = ( u"An error occurred while serializing " u"the peer assessment associated with " u"the scorer's submission UUID {}." ).format(scorer_submission_uuid) raise PeerAssessmentRequestError(msg) assessment = peer_serializer.save() # We do this to do a run around django-rest-framework serializer # validation, which would otherwise require two DB queries per # option to do validation. We already validated these options above. AssessmentPart.add_to_assessment(assessment, option_ids, criterion_feedback=criterion_feedback) # Close the active assessment scorer_workflow.close_active_assessment(peer_submission_uuid, assessment, num_required_grades) assessment_dict = full_assessment_dict(assessment) _log_assessment(assessment, scorer_workflow) return assessment_dict except DatabaseError: error_message = ( u"An error occurred while creating assessment {} by: {}" ).format(assessment_dict, scorer_id) logger.exception(error_message) raise PeerAssessmentInternalError(error_message) except PeerWorkflow.DoesNotExist: message = ( u"There is no Peer Workflow associated with the given " u"submission UUID {}." ).format(scorer_submission_uuid) logger.error(message) raise PeerAssessmentWorkflowError(message)
def create_assessment(submission_uuid, user_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at=None): """ Create a self-assessment for a submission. Args: submission_uuid (str): The unique identifier for the submission being assessed. user_id (str): The ID of the user creating the assessment. This must match the ID of the user who made the submission. options_selected (dict): Mapping of rubric criterion names to option values selected. criterion_feedback (dict): Dictionary mapping criterion names to the free-form text feedback the user gave for the criterion. Since criterion feedback is optional, some criteria may not appear in the dictionary. overall_feedback (unicode): Free-form text feedback on the submission overall. rubric_dict (dict): Serialized Rubric model. Keyword Arguments: scored_at (datetime): The timestamp of the assessment; defaults to the current time. Returns: dict: serialized Assessment model Raises: SelfAssessmentRequestError: Could not retrieve a submission that the user is allowed to score. """ # Check that there are not any assessments for this submission if Assessment.objects.filter(submission_uuid=submission_uuid, score_type=SELF_TYPE).exists(): msg = ( u"Cannot submit a self-assessment for the submission {uuid} " "because another self-assessment already exists for that submission." ).format(uuid=submission_uuid) raise SelfAssessmentRequestError(msg) # Check that the student is allowed to assess this submission try: submission = get_submission_and_student(submission_uuid) if submission['student_item']['student_id'] != user_id: msg = ( u"Cannot submit a self-assessment for the submission {uuid} " u"because it was created by another learner " u"(submission learner ID {student_id} does not match your " u"learner id {other_id})").format( uuid=submission_uuid, student_id=submission['student_item']['student_id'], other_id=user_id) raise SelfAssessmentRequestError(msg) except SubmissionNotFoundError: msg = ("Could not submit a self-assessment because no submission " "exists with UUID {uuid}").format(uuid=submission_uuid) raise SelfAssessmentRequestError() try: assessment = _complete_assessment(submission_uuid, user_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at) _log_assessment(assessment, submission) except InvalidRubric as ex: msg = "Invalid rubric definition: " + str(ex) logger.warning(msg, exc_info=True) raise SelfAssessmentRequestError(msg) except InvalidRubricSelection as ex: msg = "Selected options do not match the rubric: " + str(ex) logger.warning(msg, exc_info=True) raise SelfAssessmentRequestError(msg) except DatabaseError: error_message = (u"Error creating self assessment for submission {}" ).format(submission_uuid) logger.exception(error_message) raise SelfAssessmentInternalError(error_message) # Return the serialized assessment return full_assessment_dict(assessment)
def create_assessment(scorer_submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, num_required_grades, scored_at=None): """Creates an assessment on the given submission. Assessments are created based on feedback associated with a particular rubric. Args: scorer_submission_uuid (str): The submission uuid for the Scorer's workflow. The submission being assessed can be determined via the peer workflow of the grading student. scorer_id (str): The user ID for the user giving this assessment. This is required to create an assessment on a submission. options_selected (dict): Dictionary mapping criterion names to the option names the user selected for that criterion. criterion_feedback (dict): Dictionary mapping criterion names to the free-form text feedback the user gave for the criterion. Since criterion feedback is optional, some criteria may not appear in the dictionary. overall_feedback (unicode): Free-form text feedback on the submission overall. num_required_grades (int): The required number of assessments a submission requires before it is completed. If this number of assessments is reached, the grading_completed_at timestamp is set for the Workflow. Keyword Args: scored_at (datetime): Optional argument to override the time in which the assessment took place. If not specified, scored_at is set to now. Returns: dict: the Assessment model, serialized as a dict. Raises: PeerAssessmentRequestError: Raised when the submission_id is invalid, or the assessment_dict does not contain the required values to create an assessment. PeerAssessmentInternalError: Raised when there is an internal error while creating a new assessment. Examples: >>> options_selected = {"clarity": "Very clear", "precision": "Somewhat precise"} >>> criterion_feedback = {"clarity": "I thought this essay was very clear."} >>> feedback = "Your submission was thrilling." >>> create_assessment("1", "Tim", options_selected, criterion_feedback, feedback, rubric_dict) """ try: # Retrieve workflow information scorer_workflow = PeerWorkflow.objects.get( submission_uuid=scorer_submission_uuid) peer_workflow_item = scorer_workflow.find_active_assessments() if peer_workflow_item is None: message = ( u"There are no open assessments associated with the scorer's " u"submission UUID {}.").format(scorer_submission_uuid) logger.warning(message) raise PeerAssessmentWorkflowError(message) peer_submission_uuid = peer_workflow_item.submission_uuid assessment = _complete_assessment(rubric_dict, scorer_id, peer_submission_uuid, options_selected, criterion_feedback, scorer_workflow, overall_feedback, num_required_grades, scored_at) _log_assessment(assessment, scorer_workflow) return full_assessment_dict(assessment) except PeerWorkflow.DoesNotExist: message = (u"There is no Peer Workflow associated with the given " u"submission UUID {}.").format(scorer_submission_uuid) logger.exception(message) raise PeerAssessmentWorkflowError(message) except InvalidRubric: msg = u"The rubric definition is not valid." logger.exception(msg) raise PeerAssessmentRequestError(msg) except InvalidRubricSelection: msg = u"Invalid options were selected in the rubric." logger.warning(msg, exc_info=True) raise PeerAssessmentRequestError(msg) except DatabaseError: error_message = ( u"An error occurred while creating an assessment by the scorer with this ID: {}" ).format(scorer_id) logger.exception(error_message) raise PeerAssessmentInternalError(error_message)
def create_assessment(submission_uuid, user_id, options_selected, rubric_dict, scored_at=None): """ Create a self-assessment for a submission. Args: submission_uuid (str): The unique identifier for the submission being assessed. user_id (str): The ID of the user creating the assessment. This must match the ID of the user who made the submission. options_selected (dict): Mapping of rubric criterion names to option values selected. rubric_dict (dict): Serialized Rubric model. Kwargs: scored_at (datetime): The timestamp of the assessment; defaults to the current time. Returns: dict: serialized Assessment model Raises: SelfAssessmentRequestError: Could not retrieve a submission that the user is allowed to score. """ # Check that there are not any assessments for this submission if Assessment.objects.filter(submission_uuid=submission_uuid, score_type=SELF_TYPE).exists(): raise SelfAssessmentRequestError(_("You've already completed your self assessment for this response.")) # Check that the student is allowed to assess this submission try: submission = get_submission_and_student(submission_uuid) if submission['student_item']['student_id'] != user_id: raise SelfAssessmentRequestError(_("You can only complete a self assessment on your own response.")) except SubmissionNotFoundError: raise SelfAssessmentRequestError(_("Could not retrieve the response.")) # Get or create the rubric try: rubric = rubric_from_dict(rubric_dict) option_ids = rubric.options_ids(options_selected) except InvalidRubric as ex: msg = _("Invalid rubric definition: {errors}").format(errors=ex.errors) raise SelfAssessmentRequestError(msg) except InvalidOptionSelection: msg = _("Selected options do not match the rubric") raise SelfAssessmentRequestError(msg) # Create the assessment # Since we have already retrieved the submission, we can assume that # the user who created the submission exists. self_assessment = { "rubric": rubric.id, "scorer_id": user_id, "submission_uuid": submission_uuid, "score_type": SELF_TYPE, "feedback": u"", } if scored_at is not None: self_assessment['scored_at'] = scored_at # Serialize the assessment serializer = AssessmentSerializer(data=self_assessment) if not serializer.is_valid(): msg = _("Could not create self assessment: {errors}").format(errors=serializer.errors) raise SelfAssessmentRequestError(msg) assessment = serializer.save() # We do this to do a run around django-rest-framework serializer # validation, which would otherwise require two DB queries per # option to do validation. We already validated these options above. AssessmentPart.add_to_assessment(assessment, option_ids) assessment_dict = full_assessment_dict(assessment) _log_assessment(assessment, submission) # Return the serialized assessment return assessment_dict
def create_assessment( submission_uuid, user_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at=None ): """ Create a self-assessment for a submission. Args: submission_uuid (str): The unique identifier for the submission being assessed. user_id (str): The ID of the user creating the assessment. This must match the ID of the user who made the submission. options_selected (dict): Mapping of rubric criterion names to option values selected. criterion_feedback (dict): Dictionary mapping criterion names to the free-form text feedback the user gave for the criterion. Since criterion feedback is optional, some criteria may not appear in the dictionary. overall_feedback (unicode): Free-form text feedback on the submission overall. rubric_dict (dict): Serialized Rubric model. Keyword Arguments: scored_at (datetime): The timestamp of the assessment; defaults to the current time. Returns: dict: serialized Assessment model Raises: SelfAssessmentRequestError: Could not retrieve a submission that the user is allowed to score. """ # Check that there are not any assessments for this submission if Assessment.objects.filter(submission_uuid=submission_uuid, score_type=SELF_TYPE).exists(): msg = ( u"Cannot submit a self-assessment for the submission {uuid} " "because another self-assessment already exists for that submission." ).format(uuid=submission_uuid) raise SelfAssessmentRequestError(msg) # Check that the student is allowed to assess this submission try: submission = get_submission_and_student(submission_uuid) if submission['student_item']['student_id'] != user_id: msg = ( u"Cannot submit a self-assessment for the submission {uuid} " u"because it was created by another learner " u"(submission learner ID {student_id} does not match your " u"learner id {other_id})" ).format( uuid=submission_uuid, student_id=submission['student_item']['student_id'], other_id=user_id ) raise SelfAssessmentRequestError(msg) except SubmissionNotFoundError: msg = ( "Could not submit a self-assessment because no submission " "exists with UUID {uuid}" ).format(uuid=submission_uuid) raise SelfAssessmentRequestError() try: assessment = _complete_assessment( submission_uuid, user_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at ) _log_assessment(assessment, submission) except InvalidRubric as ex: msg = "Invalid rubric definition: " + str(ex) logger.warning(msg, exc_info=True) raise SelfAssessmentRequestError(msg) except InvalidRubricSelection as ex: msg = "Selected options do not match the rubric: " + str(ex) logger.warning(msg, exc_info=True) raise SelfAssessmentRequestError(msg) except DatabaseError: error_message = ( u"Error creating self assessment for submission {}" ).format(submission_uuid) logger.exception(error_message) raise SelfAssessmentInternalError(error_message) # Return the serialized assessment return full_assessment_dict(assessment)
def create_assessment( submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at=None ): """Creates an assessment on the given submission. Assessments are created based on feedback associated with a particular rubric. Assumes that the user creating the assessment has the permissions to do so. Args: submission_uuid (str): The submission uuid for the submission being assessed. scorer_id (str): The user ID for the user giving this assessment. This is required to create an assessment on a submission. options_selected (dict): Dictionary mapping criterion names to the option names the user selected for that criterion. criterion_feedback (dict): Dictionary mapping criterion names to the free-form text feedback the user gave for the criterion. Since criterion feedback is optional, some criteria may not appear in the dictionary. overall_feedback (unicode): Free-form text feedback on the submission overall. rubric_dict (dict): The rubric model associated with this assessment scored_at (datetime): Optional argument to override the time in which the assessment took place. If not specified, scored_at is set to now. Keyword Args: scored_at (datetime): Optional argument to override the time in which the assessment took place. If not specified, scored_at is set to now. Returns: dict: the Assessment model, serialized as a dict. Raises: StaffAssessmentRequestError: Raised when the submission_id is invalid, or the assessment_dict does not contain the required values to create an assessment. StaffAssessmentInternalError: Raised when there is an internal error while creating a new assessment. Examples: >>> options_selected = {"clarity": "Very clear", "precision": "Somewhat precise"} >>> criterion_feedback = {"clarity": "I thought this essay was very clear."} >>> feedback = "Your submission was thrilling." >>> create_assessment("Tim", options_selected, criterion_feedback, feedback, rubric_dict) """ try: try: scorer_workflow = StaffWorkflow.objects.get(submission_uuid=submission_uuid) except StaffWorkflow.DoesNotExist: scorer_workflow = None assessment = _complete_assessment( submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, scored_at, scorer_workflow ) return full_assessment_dict(assessment) except InvalidRubric: error_message = u"The rubric definition is not valid." logger.exception(error_message) raise StaffAssessmentRequestError(error_message) except InvalidRubricSelection: error_message = u"Invalid options were selected in the rubric." logger.warning(error_message, exc_info=True) raise StaffAssessmentRequestError(error_message) except DatabaseError: error_message = ( u"An error occurred while creating an assessment by the scorer with this ID: {}" ).format(scorer_id) logger.exception(error_message) raise StaffAssessmentInternalError(error_message)
def create_assessment( scorer_submission_uuid, scorer_id, options_selected, criterion_feedback, overall_feedback, rubric_dict, num_required_grades, scored_at=None, track_changes_edits=None, ): """Creates an assessment on the given submission. Assessments are created based on feedback associated with a particular rubric. Args: scorer_submission_uuid (str): The submission uuid for the Scorer's workflow. The submission being assessed can be determined via the peer workflow of the grading student. scorer_id (str): The user ID for the user giving this assessment. This is required to create an assessment on a submission. options_selected (dict): Dictionary mapping criterion names to the option names the user selected for that criterion. criterion_feedback (dict): Dictionary mapping criterion names to the free-form text feedback the user gave for the criterion. Since criterion feedback is optional, some criteria may not appear in the dictionary. overall_feedback (unicode): Free-form text feedback on the submission overall. num_required_grades (int): The required number of assessments a submission requires before it is completed. If this number of assessments is reached, the grading_completed_at timestamp is set for the Workflow. Keyword Args: scored_at (datetime): Optional argument to override the time in which the assessment took place. If not specified, scored_at is set to now. track_changes_edits (str): Optional argument to specify that a track_changes entry should be created for this assessment, storing suggested edits to the original submission. Returns: dict: the Assessment model, serialized as a dict. Raises: PeerAssessmentRequestError: Raised when the submission_id is invalid, or the assessment_dict does not contain the required values to create an assessment. PeerAssessmentInternalError: Raised when there is an internal error while creating a new assessment. Examples: >>> options_selected = {"clarity": "Very clear", "precision": "Somewhat precise"} >>> criterion_feedback = {"clarity": "I thought this essay was very clear."} >>> feedback = "Your submission was thrilling." >>> create_assessment("1", "Tim", options_selected, criterion_feedback, feedback, rubric_dict) """ try: # Retrieve workflow information scorer_workflow = PeerWorkflow.objects.get(submission_uuid=scorer_submission_uuid) peer_workflow_item = scorer_workflow.find_active_assessments() if peer_workflow_item is None: message = ( u"There are no open assessments associated with the scorer's " u"submission UUID {}." ).format(scorer_submission_uuid) logger.warning(message) raise PeerAssessmentWorkflowError(message) peer_submission_uuid = peer_workflow_item.submission_uuid assessment = _complete_assessment( rubric_dict, scorer_id, peer_submission_uuid, options_selected, criterion_feedback, scorer_workflow, overall_feedback, num_required_grades, scored_at ) if track_changes_edits: json_edited_content = serialize_edited_content(track_changes_edits) change_tracker = TrackChanges( scorer_id=scorer_id, owner_submission_uuid=peer_submission_uuid, edited_content=track_changes_edits, json_edited_content=json_edited_content, ) change_tracker.save() _log_assessment(assessment, scorer_workflow) return full_assessment_dict(assessment) except PeerWorkflow.DoesNotExist: message = ( u"There is no Peer Workflow associated with the given " u"submission UUID {}." ).format(scorer_submission_uuid) logger.exception(message) raise PeerAssessmentWorkflowError(message) except InvalidRubric: msg = u"The rubric definition is not valid." logger.exception(msg) raise PeerAssessmentRequestError(msg) except InvalidRubricSelection: msg = u"Invalid options were selected in the rubric." logger.warning(msg, exc_info=True) raise PeerAssessmentRequestError(msg) except DatabaseError: error_message = ( u"An error occurred while creating an assessment by the scorer with this ID: {}" ).format(scorer_id) logger.exception(error_message) raise PeerAssessmentInternalError(error_message)
def create_assessment( submission_uuid, scorer_id, assessment_dict, rubric_dict, num_required_grades, scored_at=None): """Creates an assessment on the given submission. Assessments are created based on feedback associated with a particular rubric. Args: submission_uuid (str): The submission uuid this assessment is associated with. The submission uuid is required and must already exist in the Submission model. scorer_id (str): The user ID for the user giving this assessment. This is required to create an assessment on a submission. assessment_dict (dict): All related information for the assessment. An assessment contains points_earned, points_possible, and feedback. num_required_grades (int): The required number of assessments a submission requires before it is completed. If this number of assessments is reached, the grading_completed_at timestamp is set for the Workflow. Kwargs: scored_at (datetime): Optional argument to override the time in which the assessment took place. If not specified, scored_at is set to now. Returns: dict: the Assessment model, serialized as a dict. Raises: PeerAssessmentRequestError: Raised when the submission_id is invalid, or the assessment_dict does not contain the required values to create an assessment. PeerAssessmentInternalError: Raised when there is an internal error while creating a new assessment. Examples: >>> assessment_dict = dict( >>> options_selected={"clarity": "Very clear", "precision": "Somewhat precise"}, >>> feedback="Your submission was thrilling.", >>> ) >>> create_assessment("1", "Tim", assessment_dict, rubric_dict) """ try: submission = sub_api.get_submission_and_student(submission_uuid) rubric = rubric_from_dict(rubric_dict) # Validate that the selected options matched the rubric # and raise an error if this is not the case try: option_ids = rubric.options_ids(assessment_dict["options_selected"]) except InvalidOptionSelection as ex: msg = _("Selected options do not match the rubric: {error}").format(error=ex.message) raise PeerAssessmentRequestError(msg) feedback = assessment_dict.get('feedback', u'') peer_assessment = { "rubric": rubric.id, "scorer_id": scorer_id, "submission_uuid": submission_uuid, "score_type": PEER_TYPE, "feedback": feedback, } if scored_at is not None: peer_assessment["scored_at"] = scored_at peer_serializer = AssessmentSerializer(data=peer_assessment) if not peer_serializer.is_valid(): raise PeerAssessmentRequestError(peer_serializer.errors) assessment = peer_serializer.save() # We do this to do a run around django-rest-framework serializer # validation, which would otherwise require two DB queries per # option to do validation. We already validated these options above. AssessmentPart.add_to_assessment(assessment, option_ids) student_item = submission['student_item'] scorer_item = copy.deepcopy(student_item) scorer_item['student_id'] = scorer_id scorer_workflow = _get_latest_workflow(scorer_item) workflow = _get_latest_workflow(student_item) if not scorer_workflow: raise PeerAssessmentWorkflowError(_( "You must make a submission before assessing another student.")) if not workflow: raise PeerAssessmentWorkflowError(_( "The submission you reviewed is not in the peer workflow. This " "assessment cannot be submitted unless the associated " "submission came from the peer workflow.")) # Close the active assessment _close_active_assessment(scorer_workflow, submission_uuid, assessment, num_required_grades) assessment_dict = full_assessment_dict(assessment) _log_assessment(assessment, student_item, scorer_item) return assessment_dict except DatabaseError: error_message = _( u"An error occurred while creating assessment {} for submission: " u"{} by: {}" .format(assessment_dict, submission_uuid, scorer_id) ) logger.exception(error_message) raise PeerAssessmentInternalError(error_message)