Files
edx-platform/lms/djangoapps/ora_staff_grader/serializers.py
Nathan Sprenkle e1c8fb51be feat: add text response type to ora config (#30721)
For staff grader setup, we want to know the response type to do better
rendering of the response. This derives from ORA's text_response_editor
setting and is either "text" (default) or "tinymce".

Co-authored-by: nsprenkle <nsprenkle@2u.com>
2022-07-12 15:44:24 -04:00

322 lines
10 KiB
Python

"""
Serializers for Enhanced Staff Grader (ESG)
"""
# pylint: disable=abstract-method
# pylint: disable=missing-function-docstring
from rest_framework import serializers
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
class GradeStatusField(serializers.ChoiceField):
"""Field that can have the values ['graded' 'ungraded']"""
def __init__(self, *args, **kwargs):
kwargs["choices"] = ["graded", "ungraded"]
super().__init__(*args, **kwargs)
class LockStatusField(serializers.ChoiceField):
"""Field that can have the values ['unlocked', 'locked', 'in-progress']"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs, choices=["unlocked", "locked", "in-progress"])
class CourseMetadataSerializer(serializers.Serializer):
"""
Serialize top-level info about a course, used for creating header in ESG
"""
title = serializers.CharField(source="display_name")
org = serializers.CharField(source="display_org_with_default")
number = serializers.CharField(source="display_number_with_default")
courseId = serializers.CharField(source="id")
class Meta:
model = CourseOverview
fields = [
"title",
"org",
"number",
"courseId",
]
read_only_fields = fields
class RubricCriterionOptionsSerializer(serializers.Serializer):
"""Serializer for selectable options in a rubric criterion"""
label = serializers.CharField()
points = serializers.IntegerField()
explanation = serializers.CharField()
name = serializers.CharField()
orderNum = serializers.IntegerField(source="order_num")
class RubricCriterionSerializer(serializers.Serializer):
"""Serializer for individual criteria in a rubric"""
label = serializers.CharField()
prompt = serializers.CharField()
feedback = serializers.ChoiceField(
required=False, choices=["optional", "disabled", "required"], default="disabled"
)
name = serializers.CharField()
orderNum = serializers.IntegerField(source="order_num")
options = serializers.ListField(child=RubricCriterionOptionsSerializer())
class RubricConfigSerializer(serializers.Serializer):
"""Serializer for rubric config"""
feedbackPrompt = serializers.CharField(source="rubric_feedback_prompt")
criteria = serializers.ListField(
source="rubric_criteria", child=RubricCriterionSerializer()
)
class OpenResponseMetadataSerializer(serializers.Serializer):
"""
Serialize ORA metadata, used for setting up views in ESG
"""
name = serializers.CharField(source="display_name")
prompts = serializers.ListField()
type = serializers.SerializerMethodField()
textResponseConfig = serializers.SerializerMethodField()
textResponseEditor = serializers.CharField(source='text_response_editor')
fileUploadResponseConfig = serializers.SerializerMethodField()
rubricConfig = RubricConfigSerializer(source="*")
def get_textResponseConfig(self, instance):
return instance.text_response or "none"
def get_fileUploadResponseConfig(self, instance):
return instance.file_upload_response or "none"
def get_type(self, instance):
return "team" if instance.teams_enabled else "individual"
class Meta:
fields = [
"name",
"prompts",
"type",
"textResponseConfig",
"textResponseEditor",
"fileUploadResponseConfig",
"rubricConfig",
]
read_only_fields = fields
class ScoreField(serializers.Field):
"""Returns None if score is not given for a submission"""
def to_representation(self, value):
if ("pointsEarned" not in value) and ("pointsPossible" not in value):
return None
return ScoreSerializer(value).data
class ScoreSerializer(serializers.Serializer):
"""
Score (points earned/possible) for use in SubmissionMetadataSerializer
"""
pointsEarned = serializers.IntegerField(required=False)
pointsPossible = serializers.IntegerField(required=False)
class SubmissionMetadataSerializer(serializers.Serializer):
"""
Submission metadata for displaying submissions table in ESG
"""
submissionUUID = serializers.CharField(source="submissionUuid")
username = serializers.CharField(allow_null=True)
teamName = serializers.CharField(allow_null=True)
dateSubmitted = serializers.DateTimeField()
dateGraded = serializers.DateTimeField(allow_null=True)
gradedBy = serializers.CharField(allow_null=True)
gradeStatus = GradeStatusField(source="gradingStatus")
lockStatus = LockStatusField()
score = ScoreField()
class Meta:
fields = [
"submissionUUID",
"username",
"teamName",
"dateSubmitted",
"dateGraded",
"gradedBy",
"gradeStatus",
"lockStatus",
"score",
]
read_only_fields = fields
class InitializeSerializer(serializers.Serializer):
"""
Serialize info for the initialize call. Packages ORA, course, submission, and rubric data.
"""
courseMetadata = CourseMetadataSerializer()
oraMetadata = OpenResponseMetadataSerializer()
submissions = serializers.DictField(child=SubmissionMetadataSerializer())
isEnabled = serializers.SerializerMethodField()
class Meta:
fields = [
"courseMetadata",
"oraMetadata",
"submissions",
"isEnabled"
]
read_only_fields = fields
def get_isEnabled(self, obj):
"""
Only enable ESG if the flag is enabled and also this is not a Team ORA
Revert back to BooleanField in AU-617 when ESG officially supports team ORAs
"""
return obj['isEnabled'] and not obj['oraMetadata'].teams_enabled
class UploadedFileSerializer(serializers.Serializer):
"""Serializer for a file uploaded as a part of a response"""
downloadUrl = serializers.URLField(source="download_url")
description = serializers.CharField()
name = serializers.CharField()
size = serializers.IntegerField()
class ResponseSerializer(serializers.Serializer):
"""Serializer for the responseData api construct, which represents the contents of a submitted learner response"""
files = serializers.ListField(child=UploadedFileSerializer(), allow_empty=True)
text = serializers.ListField(child=serializers.CharField(), allow_empty=True)
class FileListSerializer(serializers.Serializer):
"""Serializer for a list of files in a submission"""
files = serializers.ListField(child=UploadedFileSerializer(), allow_empty=True)
class AssessmentCriteriaSerializer(serializers.Serializer):
"""Serializer for information about a criterion, in the context of a completed assessment"""
name = serializers.CharField()
feedback = serializers.CharField()
points = serializers.IntegerField()
selectedOption = serializers.CharField(source="option")
class GradeDataSerializer(serializers.Serializer):
"""Serializer for the `gradeData` api construct, which represents a completed staff assessment"""
score = ScoreField(required=False)
overallFeedback = serializers.CharField(source="feedback", required=False)
criteria = serializers.ListField(
child=AssessmentCriteriaSerializer(), allow_empty=True, required=False
)
class SubmissionStatusFetchSerializer(serializers.Serializer):
"""Serializer for the response from the submission status fetch endpoint"""
gradeData = GradeDataSerializer(source="assessment_info")
gradeStatus = serializers.SerializerMethodField()
lockStatus = LockStatusField(source="lock_info.lock_status")
def get_gradeStatus(self, obj):
if not obj.get("assessment_info", {}) == {}:
return "graded"
else:
return "ungraded"
class SubmissionFetchSerializer(SubmissionStatusFetchSerializer):
"""
Serializer for the response from the submission fetch endpoint
Same as the SubmissionStatusFetchSerializer with an added submission_info field
"""
response = ResponseSerializer(source="submission_info")
class LockStatusSerializer(serializers.Serializer):
"""
Info about the status of a submission lock, with extra metadata stripped out.
"""
lockStatus = LockStatusField(source="lock_status")
class Meta:
fields = ["lockStatus"]
read_only_fields = fields
class StaffAssessSerializer(serializers.Serializer):
"""
Converts grade data to the format used for doing staff assessments
From: {
"overallFeedback": "was pretty good",
"criteria": [
{
"name": "<criterion_name_1>",
"feedback": (string),
"selectedOption": <selected_option_name>
}
]
}
To: {
'options_selected': {
'<criterion_name_1>': <selected_option_name>,
'<criterion_name_2>': <selected_option_name>,
},
'criterion_feedback': {
'<criterion_label_1>': (string)
},
'overall_feedback': (string)
'submission_uuid': (string)
'assess_type': (string) one of ['regrade', full-grade']
}
"""
# Context should include 'submission_uuid' for serialization
requires_context = True
options_selected = serializers.SerializerMethodField()
criterion_feedback = serializers.SerializerMethodField()
overall_feedback = serializers.CharField(source="overallFeedback", allow_null=True)
submission_uuid = serializers.SerializerMethodField()
assess_type = serializers.CharField(default="full-grade")
def get_options_selected(self, instance):
options_selected = {}
for criterion in instance.get("criteria"):
options_selected[criterion["name"]] = criterion["selectedOption"]
return options_selected
def get_criterion_feedback(self, instance):
criterion_feedback = {}
for criterion in instance.get("criteria"):
if criterion.get("feedback"):
criterion_feedback[criterion["name"]] = criterion["feedback"]
return criterion_feedback
def get_submission_uuid(self, instance): # pylint: disable=unused-argument
return self.context.get("submission_uuid")