From 19b1c13f575e9829b249b1d496802852cd5d321b Mon Sep 17 00:00:00 2001 From: Ronak Pansuriya Date: Fri, 3 Jul 2026 13:13:14 +0200 Subject: [PATCH] fix: preserve ORA date_config_type through OLX export/import --- openassessment/xblock/openassessmentblock.py | 1 + openassessment/xblock/test/test_xml.py | 115 +++++++++++++++++++ openassessment/xblock/utils/xml.py | 15 +++ 3 files changed, 131 insertions(+) diff --git a/openassessment/xblock/openassessmentblock.py b/openassessment/xblock/openassessmentblock.py index 2b920a2a48..aece74b153 100644 --- a/openassessment/xblock/openassessmentblock.py +++ b/openassessment/xblock/openassessmentblock.py @@ -935,6 +935,7 @@ def parse_xml(cls, node, runtime, keys): block.allow_latex = config['allow_latex'] block.allow_learner_resubmissions = config['allow_learner_resubmissions'] block.allow_multiple_files = config['allow_multiple_files'] + block.date_config_type = config['date_config_type'] or DATE_CONFIG_MANUAL block.display_name = config['title'] block.file_upload_response = config['file_upload_response'] block.file_upload_type = config['file_upload_type'] diff --git a/openassessment/xblock/test/test_xml.py b/openassessment/xblock/test/test_xml.py index db4d35a807..c5f96b4323 100644 --- a/openassessment/xblock/test/test_xml.py +++ b/openassessment/xblock/test/test_xml.py @@ -151,6 +151,7 @@ def _configure_xblock(self, data): self.oa_block.teams_enabled = data.get('teams_enabled', None) self.oa_block.selected_teamset_id = data.get('selected_teamset_id', None) self.oa_block.show_rubric_during_response = data.get('show_rubric_during_response') + self.oa_block.date_config_type = data.get('date_config_type', None) @ddt.file_data('data/serialize.json') def test_serialize(self, data): @@ -559,3 +560,117 @@ def test_parse_from_xml(self, data): def test_parse_from_xml_error(self, data): with self.assertRaises(UpdateFromXmlError): parse_from_xml_str("".join(data['xml'])) + + +class TestDateConfigTypeXml(TestCase): + """ + Regression tests for round-tripping `date_config_type` through XML + (course export/import, re-run, and other OLX round-trip flows). + """ + MINIMAL_XML = ( + '' + 'Foo' + '' + '' + '' + '' + '' + '' + 'Test prompt' + '' + 'Test criterion' + '' + 'Test criterion prompt' + '' + '' + '' + '' + '' + ) + + def _xml(self, date_config_type=None): + attrs = f' date_config_type="{date_config_type}"' if date_config_type is not None else '' + return self.MINIMAL_XML.format(attrs=attrs) + + def test_parse_from_xml_reads_date_config_type(self): + config = parse_from_xml_str(self._xml('subsection')) + self.assertEqual(config['date_config_type'], 'subsection') + + def test_parse_from_xml_missing_attribute_defaults_to_none(self): + # Backwards compatibility: XML exported before this fix (or that never + # set the field) has no "date_config_type" attribute at all. This must + # not error, and must not silently claim a value was set. + config = parse_from_xml_str(self._xml()) + self.assertIsNone(config['date_config_type']) + + def test_parse_from_xml_invalid_value_raises(self): + with self.assertRaises(UpdateFromXmlError): + parse_from_xml_str(self._xml('not_a_real_option')) + + def test_serialize_writes_date_config_type(self): + oa_block = mock.MagicMock(OpenAssessmentBlock) + oa_block.title = 'Foo' + oa_block.display_name = 'Foo' + oa_block.prompts = create_prompts_list('Test prompt') + oa_block.prompts_type = 'text' + oa_block.rubric_criteria = TestSerializeContent.BASIC_CRITERIA + oa_block.rubric_assessments = TestSerializeContent.BASIC_ASSESSMENTS + oa_block.rubric_feedback_prompt = None + oa_block.rubric_feedback_default_text = None + oa_block.submission_start = None + oa_block.submission_due = None + oa_block.leaderboard_show = 0 + oa_block.text_response = '' + oa_block.text_response_editor = 'text' + oa_block.file_upload_response = None + oa_block.file_upload_type = None + oa_block.white_listed_file_types = None + oa_block.allow_multiple_files = None + oa_block.allow_latex = None + oa_block.allow_learner_resubmissions = None + oa_block.resubmissions_grace_period = None + oa_block.group_access = {} + oa_block.teams_enabled = None + oa_block.selected_teamset_id = None + oa_block.show_rubric_during_response = None + oa_block.date_config_type = 'subsection' + + xml = serialize_content(oa_block) + parsed = etree.fromstring(xml) + self.assertEqual(parsed.get('date_config_type'), 'subsection') + + def test_round_trip_preserves_date_config_type(self): + config = parse_from_xml_str(self._xml('course_end')) + self.assertEqual(config['date_config_type'], 'course_end') + + oa_block = mock.MagicMock(OpenAssessmentBlock) + oa_block.title = config['title'] + oa_block.display_name = config['title'] + oa_block.prompts = config['prompts'] + oa_block.prompts_type = config['prompts_type'] + oa_block.rubric_criteria = config['rubric_criteria'] + oa_block.rubric_assessments = config['rubric_assessments'] + oa_block.rubric_feedback_prompt = config['rubric_feedback_prompt'] + oa_block.rubric_feedback_default_text = config['rubric_feedback_default_text'] + oa_block.submission_start = config['submission_start'] + oa_block.submission_due = config['submission_due'] + oa_block.leaderboard_show = config['leaderboard_show'] + oa_block.text_response = config['text_response'] + oa_block.text_response_editor = config['text_response_editor'] + oa_block.file_upload_response = config['file_upload_response'] + oa_block.file_upload_type = config['file_upload_type'] + oa_block.white_listed_file_types = config['white_listed_file_types'] + oa_block.allow_multiple_files = config['allow_multiple_files'] + oa_block.allow_latex = config['allow_latex'] + oa_block.allow_learner_resubmissions = config['allow_learner_resubmissions'] + oa_block.resubmissions_grace_period = config['resubmissions_grace_period'] + oa_block.group_access = config['group_access'] + oa_block.teams_enabled = config['teams_enabled'] + oa_block.selected_teamset_id = config['selected_teamset_id'] + oa_block.show_rubric_during_response = config['show_rubric_during_response'] + oa_block.date_config_type = config['date_config_type'] + + reserialized_xml = serialize_content(oa_block) + reparsed_config = parse_from_xml_str(reserialized_xml) + self.assertEqual(reparsed_config['date_config_type'], 'course_end') diff --git a/openassessment/xblock/utils/xml.py b/openassessment/xblock/utils/xml.py index b6331e9149..8262036ca1 100644 --- a/openassessment/xblock/utils/xml.py +++ b/openassessment/xblock/utils/xml.py @@ -13,6 +13,7 @@ from lxml import etree from openassessment.xblock.utils.data_conversion import update_assessments_format +from openassessment.xblock.utils.defaults import DATE_CONFIG_MANUAL, DATE_CONFIG_SUBSECTION, DATE_CONFIG_COURSE_END from openassessment.xblock.lms_mixin import GroupAccessDict log = logging.getLogger(__name__) @@ -725,6 +726,10 @@ def serialize_content_to_xml(oa_block, root): if oa_block.submission_due is not None: root.set('submission_due', str(oa_block.submission_due)) + # Set date configuration type + if oa_block.date_config_type is not None: + root.set('date_config_type', str(oa_block.date_config_type)) + # Set leaderboard show if oa_block.leaderboard_show: root.set('leaderboard_show', str(oa_block.leaderboard_show)) @@ -947,6 +952,15 @@ def parse_from_xml(root, block=None): if 'show_rubric_during_response' in root.attrib: show_rubric_during_response = _parse_boolean(str(root.attrib['show_rubric_during_response'])) + date_config_type = None + if 'date_config_type' in root.attrib: + date_config_type = str(root.attrib['date_config_type']) + valid_date_config_types = [DATE_CONFIG_MANUAL, DATE_CONFIG_SUBSECTION, DATE_CONFIG_COURSE_END] + if date_config_type not in valid_date_config_types: + raise UpdateFromXmlError( + 'The "date_config_type" value must be one of: {}.'.format(", ".join(valid_date_config_types)) + ) + # Retrieve the title title_el = root.find('title') title = block and block.display_name @@ -1023,6 +1037,7 @@ def parse_from_xml(root, block=None): 'show_rubric_during_response': show_rubric_during_response, 'allow_learner_resubmissions': allow_learner_resubmissions, 'resubmissions_grace_period': resubmissions_grace_period, + 'date_config_type': date_config_type, }