feat: update initialize endpoint and create assessments/feedback endpoint in ORA Staff Grader (#33632)
This commit is contained in:
committed by
GitHub
parent
5007418803
commit
20570ff417
@@ -12,6 +12,10 @@ Other handlers return status OK even for an error, but contain error info in the
|
||||
These are checked (usually by checking for a {"success":false} response) and raise errors, possibly with extra context.
|
||||
"""
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
|
||||
from rest_framework.request import Request
|
||||
|
||||
from lms.djangoapps.ora_staff_grader.errors import (
|
||||
LockContestedError,
|
||||
XBlockInternalError,
|
||||
@@ -33,6 +37,42 @@ def get_submissions(request, usage_id):
|
||||
return json.loads(response.content)
|
||||
|
||||
|
||||
def get_assessments(request: Request, usage_id: str, handler_name: str, submission_uuid: str):
|
||||
"""
|
||||
Get a list of assessments according to the handler name from ORA XBlock.json_handler
|
||||
|
||||
* `list_assessments_to` handler
|
||||
Lists all assessments given by a user (according to their submissionUUID)
|
||||
in an ORA assignment. Assessments can be Self, Peer and Staff.
|
||||
|
||||
* `list_assessments_from` handler
|
||||
Lists all assessments received by a user (according to their submissionUUID)
|
||||
in an ORA assignment. Assessments can be Self, Peer and Staff.
|
||||
|
||||
Args:
|
||||
request (Request): The request object.
|
||||
usage_id (str): Usage ID of the XBlock for running the handler
|
||||
handler_name (str): The name of the handler to call
|
||||
submission_uuid (str): The ORA submission UUID
|
||||
"""
|
||||
data = {
|
||||
"item_id": usage_id,
|
||||
"submission_uuid": submission_uuid,
|
||||
}
|
||||
|
||||
response = call_xblock_json_handler(request, usage_id, handler_name, data)
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
raise XBlockInternalError(context={"handler": handler_name})
|
||||
|
||||
try:
|
||||
return json.loads(response.content)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise XBlockInternalError(
|
||||
context={"handler": handler_name, "details": response.content}
|
||||
) from exc
|
||||
|
||||
|
||||
def get_submission_info(request, usage_id, submission_uuid):
|
||||
"""
|
||||
Get submission content from ORA 'get_submission_info' XBlock.json_handler
|
||||
|
||||
@@ -135,11 +135,13 @@ class ScoreSerializer(serializers.Serializer):
|
||||
|
||||
class SubmissionMetadataSerializer(serializers.Serializer):
|
||||
"""
|
||||
Submission metadata for displaying submissions table in ESG
|
||||
Submission metadata for displaying submissions table in Enhanced Staff Grader (ESG)
|
||||
"""
|
||||
|
||||
submissionUUID = serializers.CharField(source="submissionUuid")
|
||||
username = serializers.CharField(allow_null=True)
|
||||
email = serializers.CharField(allow_null=True)
|
||||
fullname = serializers.CharField(allow_null=True)
|
||||
teamName = serializers.CharField(allow_null=True)
|
||||
dateSubmitted = serializers.DateTimeField()
|
||||
dateGraded = serializers.DateTimeField(allow_null=True)
|
||||
@@ -159,6 +161,57 @@ class SubmissionMetadataSerializer(serializers.Serializer):
|
||||
"gradeStatus",
|
||||
"lockStatus",
|
||||
"score",
|
||||
"email",
|
||||
"fullname",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class AssessmentScoresSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for score information associated with a specific assessment.
|
||||
|
||||
This serializer is included in the `AssessmentSerializer` as a `ListField`
|
||||
"""
|
||||
criterion_name = serializers.CharField()
|
||||
score_earned = serializers.IntegerField()
|
||||
score_type = serializers.CharField()
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
"criterion_name",
|
||||
"score_earned",
|
||||
"score_type",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class AssessmentSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for the each assessment metadata in the response from the assessments
|
||||
feedback endpoints (from/to) in Enhanced Staff Grader (ESG)
|
||||
|
||||
This serializer is included in the `AssessmentFeedbackSerializer` as a `ListField`
|
||||
"""
|
||||
assessment_id = serializers.CharField()
|
||||
scorer_name = serializers.CharField(allow_null=True)
|
||||
scorer_username = serializers.CharField(allow_null=True)
|
||||
scorer_email = serializers.CharField(allow_null=True)
|
||||
assessment_date = serializers.DateTimeField()
|
||||
assessment_scores = serializers.ListField(child=AssessmentScoresSerializer())
|
||||
problem_step = serializers.CharField(allow_null=True)
|
||||
feedback = serializers.CharField(allow_null=True)
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
"assessment_id",
|
||||
"scorer_name",
|
||||
"scorer_username",
|
||||
"scorer_email",
|
||||
"assessment_date",
|
||||
"assessment_scores",
|
||||
"problem_step",
|
||||
"feedback",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
@@ -190,6 +243,19 @@ class InitializeSerializer(serializers.Serializer):
|
||||
return obj['isEnabled'] and not obj['oraMetadata'].teams_enabled
|
||||
|
||||
|
||||
class AssessmentFeedbackSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for a list of assessments for the response from the assessments
|
||||
feedback endpoints (from/to) in Enhanced Staff Grader (ESG)
|
||||
"""
|
||||
|
||||
assessments = serializers.ListField(child=AssessmentSerializer())
|
||||
|
||||
class Meta:
|
||||
fields = ["assessments"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class UploadedFileSerializer(serializers.Serializer):
|
||||
"""Serializer for a file uploaded as a part of a response"""
|
||||
|
||||
|
||||
@@ -241,6 +241,8 @@ class TestSubmissionMetadataSerializer(TestCase):
|
||||
"submissionUuid": "a",
|
||||
"username": "foo",
|
||||
"teamName": "",
|
||||
'email': "jondoes@example.com",
|
||||
'fullname': "",
|
||||
"dateSubmitted": "1969-07-16 13:32:00",
|
||||
"dateGraded": "None",
|
||||
"gradedBy": "",
|
||||
@@ -251,6 +253,8 @@ class TestSubmissionMetadataSerializer(TestCase):
|
||||
"b": {
|
||||
"submissionUuid": "b",
|
||||
"username": "",
|
||||
'email': "jondoes@example.com",
|
||||
'fullname': "Jhon Does",
|
||||
"teamName": "bar",
|
||||
"dateSubmitted": "1969-07-20 20:17:40",
|
||||
"dateGraded": "None",
|
||||
@@ -262,6 +266,8 @@ class TestSubmissionMetadataSerializer(TestCase):
|
||||
"c": {
|
||||
"submissionUuid": "c",
|
||||
"username": "baz",
|
||||
'email': "jondoes@example.com",
|
||||
'fullname': "Jhon Does",
|
||||
"teamName": "",
|
||||
"dateSubmitted": "1969-07-21 21:35:00",
|
||||
"dateGraded": "1969-07-24 16:44:00",
|
||||
@@ -293,6 +299,8 @@ class TestSubmissionMetadataSerializer(TestCase):
|
||||
submission = {
|
||||
"submissionUuid": "empty-score",
|
||||
"username": "WOPR",
|
||||
'email': "jhondoes@example.com",
|
||||
'fullname': "",
|
||||
"dateSubmitted": "1983-06-03 00:00:00",
|
||||
"dateGraded": None,
|
||||
"gradedBy": None,
|
||||
@@ -304,6 +312,8 @@ class TestSubmissionMetadataSerializer(TestCase):
|
||||
expected_output = {
|
||||
"submissionUUID": "empty-score",
|
||||
"username": "WOPR",
|
||||
'email': "jhondoes@example.com",
|
||||
'fullname': "",
|
||||
"teamName": None,
|
||||
"dateSubmitted": "1983-06-03 00:00:00",
|
||||
"dateGraded": None,
|
||||
|
||||
@@ -3,7 +3,7 @@ URLs for Enhanced Staff Grader (ESG) backend-for-frontend (BFF)
|
||||
"""
|
||||
from django.urls import include
|
||||
from django.urls import path
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from lms.djangoapps.ora_staff_grader.views import (
|
||||
InitializeView,
|
||||
@@ -13,12 +13,15 @@ from lms.djangoapps.ora_staff_grader.views import (
|
||||
SubmissionLockView,
|
||||
SubmissionStatusFetchView,
|
||||
UpdateGradeView,
|
||||
AssessmentFeedbackView,
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = []
|
||||
app_name = "ora-staff-grader"
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register("assessments/feedback", AssessmentFeedbackView, basename="assessment-feedback")
|
||||
|
||||
urlpatterns += [
|
||||
path("mock/", include("lms.djangoapps.ora_staff_grader.mock.urls")),
|
||||
path("initialize", InitializeView.as_view(), name="initialize"),
|
||||
@@ -33,3 +36,5 @@ urlpatterns += [
|
||||
path("submission/grade", UpdateGradeView.as_view(), name="update-grade"),
|
||||
path("submission", SubmissionFetchView.as_view(), name="fetch-submission"),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
@@ -15,9 +15,12 @@ from edx_rest_framework_extensions.auth.session.authentication import (
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from openassessment.xblock.config_mixin import WAFFLE_NAMESPACE, ENHANCED_STAFF_GRADER
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ViewSet
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
@@ -43,11 +46,13 @@ from lms.djangoapps.ora_staff_grader.ora_api import (
|
||||
get_assessment_info,
|
||||
get_submission_info,
|
||||
get_submissions,
|
||||
get_assessments,
|
||||
submit_grade,
|
||||
)
|
||||
from lms.djangoapps.ora_staff_grader.serializers import (
|
||||
FileListSerializer,
|
||||
InitializeSerializer,
|
||||
AssessmentFeedbackSerializer,
|
||||
LockStatusSerializer,
|
||||
StaffAssessSerializer,
|
||||
SubmissionFetchSerializer,
|
||||
@@ -147,6 +152,102 @@ class InitializeView(StaffGraderBaseView):
|
||||
return UnknownErrorResponse()
|
||||
|
||||
|
||||
class AssessmentFeedbackView(StaffGraderBaseView, ViewSet):
|
||||
"""
|
||||
View for fetching assessment feedback for a submission.
|
||||
|
||||
**Methods**
|
||||
|
||||
* (GET) `api/ora_staff_grader/assessments/feedback/from`
|
||||
List all assessments received by a user (according to
|
||||
their submissionUUID) in an ORA assignment.
|
||||
|
||||
* (GET) `api/ora_staff_grader/assessments/feedback/to`
|
||||
List all assessments given by a user (according to
|
||||
their submissionUUID) in an ORA assignment.
|
||||
|
||||
**Query Params**:
|
||||
|
||||
* `oraLocation` (str): ORA location for XBlock handling
|
||||
* `submissionUUID` (str): The ORA submission UUID
|
||||
|
||||
**Response**:
|
||||
|
||||
{
|
||||
assessments (List[dict]): [
|
||||
{
|
||||
"assessment_id: (str) Assessment id
|
||||
"scorer_name: (str) Scorer name
|
||||
"scorer_username: (str) Scorer username
|
||||
"scorer_email: (str) Scorer email
|
||||
"assessment_date: (str) Assessment date
|
||||
"assessment_scores (List[dict]) [
|
||||
{
|
||||
"criterion_name: (str) Criterion name
|
||||
"score_earned: (int) Score earned
|
||||
"score_type: (str) Score type
|
||||
}
|
||||
]
|
||||
"problem_step (str) Problem step (Self, Peer, or Staff)
|
||||
"feedback: (str) Feedback of the assessment
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
**Errors**:
|
||||
|
||||
* `MissingParamResponse` (HTTP 400) for missing params
|
||||
* `BadOraLocationResponse` (HTTP 400) for bad ORA location
|
||||
* `XBlockInternalError` (HTTP 500) for an issue with ORA
|
||||
* `UnknownError` (HTTP 500) for other errors
|
||||
"""
|
||||
@action(methods=["get"], detail=False, url_path="from")
|
||||
@require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID])
|
||||
def get_from(self, request: Request, ora_location: str, submission_uuid: str, *args, **kwargs):
|
||||
return self._get_assessments(request, ora_location, "list_assessments_from", submission_uuid)
|
||||
|
||||
@action(methods=["get"], detail=False, url_path="to")
|
||||
@require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID])
|
||||
def get_to(self, request: Request, ora_location: str, submission_uuid: str, *args, **kwargs):
|
||||
return self._get_assessments(request, ora_location, "list_assessments_to", submission_uuid)
|
||||
|
||||
def _get_assessments(
|
||||
self, request: Request, ora_location: str, handler_name: str, submission_uuid: str
|
||||
):
|
||||
"""
|
||||
Fetches assessment data using the given handler name.
|
||||
|
||||
Args:
|
||||
request (Request): The Django request object.
|
||||
ora_location (str): The ORA location for XBlock handling.
|
||||
handler_name (str): The name of the XBlock handler to use.
|
||||
submission_uuid (str): The ORA submission UUID.
|
||||
|
||||
Returns:
|
||||
A Django response object containing serialized assessment data or an error response.
|
||||
"""
|
||||
try:
|
||||
assessments_data = {
|
||||
"assessments": get_assessments(
|
||||
request, ora_location, handler_name, submission_uuid
|
||||
)
|
||||
}
|
||||
response_data = AssessmentFeedbackSerializer(assessments_data).data
|
||||
return Response(response_data)
|
||||
|
||||
except (InvalidKeyError, ItemNotFoundError):
|
||||
log.error(f"Bad ORA location provided: {ora_location}")
|
||||
return BadOraLocationResponse()
|
||||
|
||||
except XBlockInternalError as ex:
|
||||
log.error(ex)
|
||||
return InternalErrorResponse(context=ex.context)
|
||||
|
||||
except Exception as ex:
|
||||
log.exception(ex)
|
||||
return UnknownErrorResponse()
|
||||
|
||||
|
||||
class SubmissionFetchView(StaffGraderBaseView):
|
||||
"""
|
||||
GET submission contents and assessment info, if any
|
||||
|
||||
Reference in New Issue
Block a user