Commit 8124aacf by Jillian Vogel

Allows ChoiceResponse CAPA problems to use script-computed variables to

determine choice correctness, as MultipleChoiceResponse problems already do.

Adds a unit test for this change, and for the existing MultipleChoiceResponse
computed correctness capability.
parent d145457b
...@@ -851,31 +851,30 @@ class ChoiceResponse(LoncapaResponse): ...@@ -851,31 +851,30 @@ class ChoiceResponse(LoncapaResponse):
def setup_response(self): def setup_response(self):
self.assign_choice_names() self.assign_choice_names()
correct_xml = self.xml.xpath( self.correct_choices = set()
'//*[@id=$id]//choice[@correct="true"]', self.incorrect_choices = set()
id=self.xml.get('id') for choice in self.get_choices():
)
self.correct_choices = set([ # contextualize the name and correct attributes
choice.get('name') for choice in correct_xml name = contextualize_text(choice.get('name'), self.context)
]) correct = contextualize_text(choice.get('correct'), self.context).upper()
incorrect_xml = self.xml.xpath( # divide choices into correct and incorrect
'//*[@id=$id]//choice[@correct="false"]', if correct == 'TRUE':
id=self.xml.get('id') self.correct_choices.add(name)
) elif correct == 'FALSE':
self.incorrect_choices.add(name)
self.incorrect_choices = set([ def get_choices(self):
choice.get('name') for choice in incorrect_xml """Returns this response's XML choice elements."""
]) return self.xml.xpath('//*[@id=$id]//choice', id=self.xml.get('id'))
def assign_choice_names(self): def assign_choice_names(self):
""" """
Initialize name attributes in <choice> tags for this response. Initialize name attributes in <choice> tags for this response.
""" """
for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice', for index, choice in enumerate(self.get_choices()):
id=self.xml.get('id'))):
choice.set("name", "choice_" + str(index)) choice.set("name", "choice_" + str(index))
# If a choice does not have an id, assign 'A' 'B', .. used by CompoundHint # If a choice does not have an id, assign 'A' 'B', .. used by CompoundHint
if not choice.get('id'): if not choice.get('id'):
......
...@@ -172,6 +172,8 @@ class ResponseXMLFactory(object): ...@@ -172,6 +172,8 @@ class ResponseXMLFactory(object):
correctness = 'false' correctness = 'false'
elif 'partial' in correct_val: elif 'partial' in correct_val:
correctness = 'partial' correctness = 'partial'
else:
correctness = correct_val
choice_element.set('correct', correctness) choice_element.set('correct', correctness)
......
...@@ -169,6 +169,35 @@ class MultiChoiceResponseTest(ResponseTest): # pylint: disable=missing-docstrin ...@@ -169,6 +169,35 @@ class MultiChoiceResponseTest(ResponseTest): # pylint: disable=missing-docstrin
correct_map = problem.grade_answers({'1_2_1': 'choice_2'}) correct_map = problem.grade_answers({'1_2_1': 'choice_2'})
self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0) self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0)
def test_contextualized_choices(self):
script = textwrap.dedent("""
a = 2
b = 9
c = a + b
ok0 = c % 2 == 0 # check remainder modulo 2
text0 = "$a + $b is even"
ok1 = c % 2 == 1 # check remainder modulo 2
text1 = "$a + $b is odd"
ok2 = "partial"
text2 = "infinity may be both"
""")
choices = ["$ok0", "$ok1", "$ok2"]
choice_names = ["$text0 ... (should be $ok0)",
"$text1 ... (should be $ok1)",
"$text2 ... (should be $ok2)"]
problem = self.build_problem(script=script,
choices=choices,
choice_names=choice_names,
credit_type='points')
# Ensure the expected correctness and choice names
self.assert_grade(problem, 'choice_2 + 9 is even ... (should be False)', 'incorrect')
self.assert_grade(problem, 'choice_2 + 9 is odd ... (should be True)', 'correct')
self.assert_grade(problem, 'choice_infinity may be both ... (should be partial)', 'partially-correct')
class TrueFalseResponseTest(ResponseTest): # pylint: disable=missing-docstring class TrueFalseResponseTest(ResponseTest): # pylint: disable=missing-docstring
xml_factory_class = TrueFalseResponseXMLFactory xml_factory_class = TrueFalseResponseXMLFactory
...@@ -1292,6 +1321,26 @@ class ChoiceResponseTest(ResponseTest): # pylint: disable=missing-docstring ...@@ -1292,6 +1321,26 @@ class ChoiceResponseTest(ResponseTest): # pylint: disable=missing-docstring
correct_map = problem.grade_answers({}) correct_map = problem.grade_answers({})
self.assertEqual(correct_map.get_correctness('1_2_1'), 'incorrect') self.assertEqual(correct_map.get_correctness('1_2_1'), 'incorrect')
def test_contextualized_choices(self):
script = textwrap.dedent("""
a = 6
b = 4
c = a + b
ok0 = c % 2 == 0 # check remainder modulo 2
ok1 = c % 3 == 0 # check remainder modulo 3
ok2 = c % 5 == 0 # check remainder modulo 5
ok3 = not any([ok0, ok1, ok2])
""")
choices = ["$ok0", "$ok1", "$ok2", "$ok3"]
problem = self.build_problem(script=script,
choice_type='checkbox',
choices=choices)
# Ensure the expected correctness
self.assert_grade(problem, ['choice_0', 'choice_2'], 'correct')
self.assert_grade(problem, ['choice_1', 'choice_3'], 'incorrect')
class JavascriptResponseTest(ResponseTest): # pylint: disable=missing-docstring class JavascriptResponseTest(ResponseTest): # pylint: disable=missing-docstring
xml_factory_class = JavascriptResponseXMLFactory xml_factory_class = JavascriptResponseXMLFactory
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment