def set_assessment_feedback(feedback_dict): """ Set a feedback object for an assessment to have some new values. Sets or updates the assessment feedback with the given values in the dict. Args: feedback_dict (dict): A dictionary of all the values to update or create a new assessment feedback. Returns: None Raises: PeerAssessmentRequestError PeerAssessmentInternalError """ submission_uuid = feedback_dict.get('submission_uuid') feedback_text = feedback_dict.get('feedback_text') selected_options = feedback_dict.get('options', list()) if feedback_text and len(feedback_text) > AssessmentFeedback.MAXSIZE: error_message = u"Assessment feedback too large." raise PeerAssessmentRequestError(error_message) try: # Get or create the assessment model for this submission # If we receive an integrity error, assume that someone else is trying to create # another feedback model for this submission, and raise an exception. if submission_uuid: feedback, created = AssessmentFeedback.objects.get_or_create( submission_uuid=submission_uuid) else: error_message = u"An error occurred creating assessment feedback: bad or missing submission_uuid." logger.error(error_message) raise PeerAssessmentRequestError(error_message) # Update the feedback text if feedback_text is not None: feedback.feedback_text = feedback_text # Save the feedback model. We need to do this before setting m2m relations. if created or feedback_text is not None: feedback.save() # Associate the feedback with selected options feedback.add_options(selected_options) # Associate the feedback with scored assessments assessments = PeerWorkflowItem.get_scored_assessments(submission_uuid) feedback.assessments.add(*assessments) except DatabaseError: msg = u"Error occurred while creating or updating feedback on assessment: {}".format( feedback_dict) logger.exception(msg) raise PeerAssessmentInternalError(msg)
def submitter_is_finished(submission_uuid, peer_requirements): """ Check whether the submitter has made the required number of assessments. If the requirements dict is None (because we're being updated asynchronously or when the workflow is first created), then automatically return False. Args: submission_uuid (str): The UUID of the submission being tracked. peer_requirements (dict): Dictionary with the key "must_grade" indicating the required number of submissions the student must grade. Returns: bool """ if peer_requirements is None: return False try: workflow = PeerWorkflow.objects.get(submission_uuid=submission_uuid) if workflow.completed_at is not None: return True elif workflow.num_peers_graded() >= peer_requirements["must_grade"]: workflow.completed_at = timezone.now() workflow.save() return True return False except PeerWorkflow.DoesNotExist: return False except KeyError: raise PeerAssessmentRequestError( u'Requirements dict must contain "must_grade" key')
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(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)