From d2ede8339adb71b4fdf43b203b7bf84c853263ca Mon Sep 17 00:00:00 2001
From: Usman Khalid <2200617@gmail.com>
Date: Wed, 15 Jan 2014 17:42:48 +0500
Subject: [PATCH] For multistep openended problems after moving to a
openendedmodule step keep trying to resend submission to grader until it
succeeds. ORA-197
---
.../open_ended_module.py | 11 +-
.../openendedchild.py | 2 +-
.../xmodule/tests/test_combined_open_ended.py | 109 ++++++++++++++++--
3 files changed, 107 insertions(+), 15 deletions(-)
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
index 04c72540c2..d62767ede8 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
@@ -71,10 +71,15 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
self._parse(oeparam, self.child_prompt, self.child_rubric, system)
+ # If there are multiple tasks (like self-assessment followed by ai), once
+ # the the status of the first task is set to DONE, setup_next_task() will
+ # create the OpenEndedChild with parameter child_created=True so that the
+ # submission can be sent to the grader. Keep trying each time this module
+ # is loaded until it succeeds.
if self.child_created is True and self.child_state == self.ASSESSING:
- self.child_created = False
- self.send_to_grader(self.latest_answer(), system)
- self.child_created = False
+ success, message = self.send_to_grader(self.latest_answer(), system)
+ if success:
+ self.child_created = False
def _parse(self, oeparam, prompt, rubric, system):
'''
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
index 3c17f52d4a..aa4b8b5d99 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
@@ -283,7 +283,7 @@ class OpenEndedChild(object):
'child_state': self.child_state,
'max_score': self._max_score,
'child_attempts': self.child_attempts,
- 'child_created': False,
+ 'child_created': self.child_created,
'stored_answer': self.stored_answer,
}
return json.dumps(state)
diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
index e0453963b5..ae43692509 100644
--- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
+++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
@@ -871,7 +871,6 @@ class CombinedOpenEndedModuleConsistencyTest(unittest.TestCase):
'''
-
static_data = {
'max_attempts': 20,
'prompt': prompt,
@@ -990,11 +989,16 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
hint = "blah"
def get_module_system(self, descriptor):
+
+ def construct_callback(dispatch="score_update"):
+ return dispatch
+
test_system = get_test_system()
test_system.open_ended_grading_interface = None
test_system.xqueue['interface'] = Mock(
send_to_queue=Mock(return_value=(0, "Queued"))
)
+ test_system.xqueue['construct_callback'] = construct_callback
return test_system
@@ -1065,6 +1069,97 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
self._handle_ajax("reset", {})
self.assertEqual(self._module().current_task_number, 0)
+ def test_open_ended_flow_with_xqueue_failure(self):
+ """
+ Test a two step problem where the student first goes through the self assessment step, and then the
+ open ended step with the xqueue failing in the first step.
+ @return:
+ """
+ assessment = [1, 1]
+
+ # Simulate a student saving an answer
+ self._handle_ajax("save_answer", {"student_answer": self.answer})
+ status = self._handle_ajax("get_status", {})
+ self.assertIsInstance(status, basestring)
+
+ # Mock a student submitting an assessment
+ assessment_dict = MultiDict({'assessment': sum(assessment)})
+ assessment_dict.extend(('score_list[]', val) for val in assessment)
+
+ mock_xqueue_interface = Mock(
+ send_to_queue=Mock(return_value=(1, "Not Queued"))
+ )
+
+ # Call handle_ajax on the module with xqueue down
+ module = self._module()
+ with patch.dict(module.xmodule_runtime.xqueue, {'interface': mock_xqueue_interface}):
+ module.handle_ajax("save_assessment", assessment_dict)
+ self.assertEqual(module.current_task_number, 1)
+ self.assertTrue((module.child_module.get_task_number(1).child_created))
+ module.save()
+
+ # Check that next time the OpenEndedModule is loaded it calls send_to_grader
+ with patch.object(OpenEndedModule, 'send_to_grader') as mock_send_to_grader:
+ mock_send_to_grader.return_value = (False, "Not Queued")
+ module = self._module().child_module.get_score()
+ self.assertTrue(mock_send_to_grader.called)
+ self.assertTrue((self._module().child_module.get_task_number(1).child_created))
+
+ # Loading it this time should send submission to xqueue correctly
+ self.assertFalse((self._module().child_module.get_task_number(1).child_created))
+ self.assertEqual(self._module().current_task_number, 1)
+ self.assertEqual(self._module().state, OpenEndedChild.ASSESSING)
+
+ task_one_json = json.loads(self._module().task_states[0])
+ self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
+
+ # Move to the next step in the problem
+ self._handle_ajax("next_problem", {})
+ self.assertEqual(self._module().current_task_number, 1)
+ self._module().render('student_view')
+
+ # Try to get the rubric from the module
+ self._handle_ajax("get_combined_rubric", {})
+
+ self.assertEqual(self._module().state, OpenEndedChild.ASSESSING)
+
+ # Make a fake reply from the queue
+ queue_reply = {
+ 'queuekey': "",
+ 'xqueue_body': json.dumps({
+ 'score': 0,
+ 'feedback': json.dumps({"spelling": "Spelling: Ok.", "grammar": "Grammar: Ok.",
+ "markup-text": " all of us can think of a book that we hope none of our children or any other children have taken off the shelf . but if i have the right to remove that book from the shelf that work i abhor then you also have exactly the same right and so does everyone else . and then we have no books left on the shelf for any of us . katherinepaterson , author write a persuasive essay to a newspaper reflecting your vies on censorship in libraries . do you believe that certain materials , such as books , music , movies , magazines , etc . , should be removed from the shelves if they are found offensive ? support your position with convincing arguments from your own experience , observations , and or reading . "}),
+ 'grader_type': "ML",
+ 'success': True,
+ 'grader_id': 1,
+ 'submission_id': 1,
+ 'rubric_xml': "Writing Applications0 Language Conventions 0",
+ 'rubric_scores_complete': True,
+ })
+ }
+
+ self._handle_ajax("check_for_score", {})
+
+ # Update the module with the fake queue reply
+ self._handle_ajax("score_update", queue_reply)
+
+ module = self._module()
+ self.assertFalse(module.ready_to_reset)
+ self.assertEqual(module.current_task_number, 1)
+
+ # Get html and other data client will request
+ module.render('student_view')
+
+ self._handle_ajax("skip_post_assessment", {})
+
+ # Get all results
+ self._handle_ajax("get_combined_rubric", {})
+
+ # reset the problem
+ self._handle_ajax("reset", {})
+ self.assertEqual(self._module().state, "initial")
+
def test_open_ended_flow_correct(self):
"""
Test a two step problem where the student first goes through the self assessment step, and then the
@@ -1088,17 +1183,9 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
# Move to the next step in the problem
- try:
- self._handle_ajax("next_problem", {})
- except GradingServiceError:
- # This error is okay. We don't have a grading service to connect to!
- pass
+ self._handle_ajax("next_problem", {})
self.assertEqual(self._module().current_task_number, 1)
- try:
- self._module().render('student_view')
- except GradingServiceError:
- # This error is okay. We don't have a grading service to connect to!
- pass
+ self._module().render('student_view')
# Try to get the rubric from the module
self._handle_ajax("get_combined_rubric", {})