diff --git a/openassessment/xblock/ui_mixins/mfe/ora_config_serializer.py b/openassessment/xblock/ui_mixins/mfe/ora_config_serializer.py index 285b7eeda3..ace6d2a34f 100644 --- a/openassessment/xblock/ui_mixins/mfe/ora_config_serializer.py +++ b/openassessment/xblock/ui_mixins/mfe/ora_config_serializer.py @@ -4,7 +4,6 @@ These are the response shapes that power the MFE implementation of the ORA UI. """ # pylint: disable=abstract-method - from rest_framework.serializers import ( BooleanField, DateTimeField, @@ -13,6 +12,8 @@ CharField, SerializerMethodField, ) + +from openassessment.xblock.utils.resolve_dates import DISTANT_FUTURE, DISTANT_PAST from openassessment.xblock.apis.workflow_api import WorkflowStep from openassessment.xblock.ui_mixins.mfe.serializer_utils import ( @@ -61,14 +62,31 @@ def get_teamsetName(self, block): class SubmissionConfigSerializer(Serializer): - startDatetime = DateTimeField(source="submission_start") - endDatetime = DateTimeField(source="submission_due") + startDatetime = SerializerMethodField() + endDatetime = SerializerMethodField() + _date_range = None textResponseConfig = TextResponseConfigSerializer(source="*") fileResponseConfig = FileResponseConfigSerializer(source="*") teamsConfig = TeamsConfigSerializer(source="*") + def _get_start_end_date(self, xblock): + """ Cached calculation of step due dates """ + if self._date_range is None: + _, _, start, end = xblock.is_closed(step='submission') + self._date_range = ( + start.isoformat() if start > DISTANT_PAST else None, + end.isoformat() if end < DISTANT_FUTURE else None, + ) + return self._date_range + + def get_startDatetime(self, xblock): + return self._get_start_end_date(xblock)[0] + + def get_endDatetime(self, xblock): + return self._get_start_end_date(xblock)[1] + class RubricFeedbackConfigSerializer(Serializer): description = CharField(source="rubric_feedback_prompt") # is this this field? @@ -111,36 +129,6 @@ class RubricConfigSerializer(Serializer): ) -class SelfSettingsSerializer(Serializer): - required = BooleanField(default=True) - - startDatetime = DateTimeField(source="start") - endDatetime = DateTimeField(source="due") - - -class StudentTrainingSettingsSerializer(Serializer): - required = BooleanField(default=True) - - numberOfExamples = SerializerMethodField(source="*", default=0) - - def get_numberOfExamples(self, assessment): - return len(assessment["examples"]) - - -class PeerSettingsSerializer(Serializer): - required = BooleanField(default=True) - - startDatetime = DateTimeField(source="start") - endDatetime = DateTimeField(source="due") - - minNumberToGrade = IntegerField(source="must_grade") - minNumberToBeGradedBy = IntegerField(source="must_be_graded_by") - - enableFlexibleGrading = BooleanField( - source="enable_flexible_grading", required=False - ) - - class AssessmentStepSettingsSerializer(Serializer): """ Generic Assessments step, where we just need to know if the step is @@ -148,6 +136,7 @@ class AssessmentStepSettingsSerializer(Serializer): """ required = BooleanField(default=True) + STEP_NAME = None # Overridden by child classes def _get_step(self, rubric_assessments, step_name): """Get the assessment step config for a given step_name""" @@ -156,42 +145,60 @@ def _get_step(self, rubric_assessments, step_name): return step return None - def __init__(self, *args, **kwargs): - self.step_name = kwargs.pop("step_name") - super().__init__(*args, **kwargs) - - def to_representation(self, instance): - assessment_step = self._get_step(instance, self.step_name) - - # Special handling for the peer step which includes extra fields - if assessment_step and self.step_name == "peer-assessment": - return PeerSettingsSerializer(assessment_step).data - elif assessment_step and self.step_name == "self-assessment": - return SelfSettingsSerializer(assessment_step).data - elif assessment_step and self.step_name == "student-training": - return StudentTrainingSettingsSerializer(assessment_step).data - + # pylint: disable=arguments-renamed + def to_representation(self, xblock): + assessment_step = self._get_step(xblock.rubric_assessments, self.STEP_NAME) # If we didn't find a step, it is not required if assessment_step is None: - assessment_step = {"required": False} + return {"required": False} + + assessment_step = dict(assessment_step) + # Add overridden start and due dates for peer assessment and self assessment + if self.STEP_NAME in ('peer-assessment', 'self-assessment'): + _, _, start, due = xblock.is_closed(step=self.STEP_NAME) + assessment_step['start'] = start.isoformat() + assessment_step['due'] = due.isoformat() return super().to_representation(assessment_step) -class AssessmentStepsSettingsSerializer(Serializer): - studentTraining = AssessmentStepSettingsSerializer( - step_name="student-training", source="rubric_assessments" - ) - peer = AssessmentStepSettingsSerializer( - step_name="peer-assessment", source="rubric_assessments" +class SelfSettingsSerializer(AssessmentStepSettingsSerializer): + STEP_NAME = 'self-assessment' + startDatetime = DateTimeField(source='start') + endDatetime = DateTimeField(source='due') + + +class StudentTrainingSettingsSerializer(AssessmentStepSettingsSerializer): + STEP_NAME = 'student-training' + numberOfExamples = SerializerMethodField(source="*", default=0) + + def get_numberOfExamples(self, assessment): + return len(assessment["examples"]) + + +class PeerSettingsSerializer(AssessmentStepSettingsSerializer): + STEP_NAME = 'peer-assessment' + minNumberToGrade = IntegerField(source="must_grade") + minNumberToBeGradedBy = IntegerField(source="must_be_graded_by") + + startDatetime = DateTimeField(source='start') + endDatetime = DateTimeField(source='due') + + enableFlexibleGrading = BooleanField( + source="enable_flexible_grading", required=False ) + + +class StaffSettingsSerializer(AssessmentStepSettingsSerializer): + STEP_NAME = 'staff-assessment' + + +class AssessmentStepsSettingsSerializer(Serializer): + studentTraining = StudentTrainingSettingsSerializer(source="*") + peer = PeerSettingsSerializer(source="*") # Workaround to allow reserved keyword in serializer key - vars()["self"] = AssessmentStepSettingsSerializer( - step_name="self-assessment", source="rubric_assessments" - ) - staff = AssessmentStepSettingsSerializer( - step_name="staff-assessment", source="rubric_assessments" - ) + vars()["self"] = SelfSettingsSerializer(source='*') + staff = StaffSettingsSerializer(source='*') class AssessmentStepsSerializer(Serializer): diff --git a/openassessment/xblock/ui_mixins/mfe/test_serializers.py b/openassessment/xblock/ui_mixins/mfe/test_serializers.py index 2cebc9e63a..28c35e7ce8 100644 --- a/openassessment/xblock/ui_mixins/mfe/test_serializers.py +++ b/openassessment/xblock/ui_mixins/mfe/test_serializers.py @@ -1,9 +1,10 @@ """ Tests for data layer of ORA XBlock """ - +from datetime import datetime from unittest.mock import MagicMock import ddt +import pytz from openassessment.xblock.ui_mixins.mfe.ora_config_serializer import ( @@ -36,8 +37,9 @@ def test_dates(self, xblock): submission_config = SubmissionConfigSerializer(xblock).data # Then I get the expected values - expected_start = xblock.submission_start - expected_due = xblock.submission_due + expected_start = pytz.utc.localize(datetime.fromisoformat(xblock.submission_start)).isoformat() + expected_due = pytz.utc.localize(datetime.fromisoformat(xblock.submission_due)).isoformat() + self.assertEqual(submission_config["startDatetime"], expected_start) self.assertEqual(submission_config["endDatetime"], expected_due) @@ -291,8 +293,8 @@ def test_peer_settings(self, xblock): @scenario("data/dates_scenario.xml") def test_peer_dates(self, xblock): # Given a basic setup - expected_start = "2015-01-02T00:00:00" - expected_due = "2015-04-01T00:00:00" + expected_start = "2015-01-02T00:00:00+00:00" + expected_due = "2015-04-01T00:00:00+00:00" # When I ask for peer step config peer_config = AssessmentStepsSerializer(xblock).data["settings"][