Commit 7bab863c by polesye

Merge pull request #1698 from edx/anton/multiple-answers-string-response

Allow multiple answers for string response.
parents 99cf0ec5 77c208a1
......@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Blades: Allow multiple strings as the correct answer to a string response question. BLD-474.
Blades: a11y - Videos will alert screenreaders when the video is over.
LMS: Trap focus on the loading element when a user loads more threads
......
......@@ -75,7 +75,9 @@
<img src="${static.url("img/string-example.png")}" />
</div>
<div class="col">
<pre><code>= dog</code></pre>
<pre><code>= dog
or= cat
or= mouse</code></pre>
</div>
</div>
<div class="row">
......
......@@ -946,17 +946,34 @@ class NumericalResponse(LoncapaResponse):
class StringResponse(LoncapaResponse):
'''
This response type allows one or more answers. Use `_or_` separator to set
more than 1 answer.
Example:
# One answer
<stringresponse answer="Michigan">
<textline size="20" />
</stringresponse >
# Multiple answers
<stringresponse answer="Martin Luther King_or_Dr. Martin Luther King Jr.">
<textline size="20" />
</stringresponse >
'''
response_tag = 'stringresponse'
hint_tag = 'stringhint'
allowed_inputfields = ['textline']
required_attributes = ['answer']
max_inputfields = 1
correct_answer = None
correct_answer = []
SEPARATOR = '_or_'
def setup_response(self):
self.correct_answer = contextualize_text(
self.xml.get('answer'), self.context).strip()
self.correct_answer = [contextualize_text(answer, self.context).strip()
for answer in self.xml.get('answer').split(self.SEPARATOR)]
def get_score(self, student_answers):
'''Grade a string response '''
......@@ -966,23 +983,25 @@ class StringResponse(LoncapaResponse):
def check_string(self, expected, given):
if self.xml.get('type') == 'ci':
return given.lower() == expected.lower()
return given == expected
return given.lower() in [i.lower() for i in expected]
return given in expected
def check_hint_condition(self, hxml_set, student_answers):
given = student_answers[self.answer_id].strip()
hints_to_show = []
for hxml in hxml_set:
name = hxml.get('name')
correct_answer = contextualize_text(
hxml.get('answer'), self.context).strip()
correct_answer = [contextualize_text(answer, self.context).strip()
for answer in hxml.get('answer').split(self.SEPARATOR)]
if self.check_string(correct_answer, given):
hints_to_show.append(name)
log.debug('hints_to_show = %s', hints_to_show)
return hints_to_show
def get_answers(self):
return {self.answer_id: self.correct_answer}
return {self.answer_id: ' <b>or</b> '.join(self.correct_answer)}
#-----------------------------------------------------------------------------
......
......@@ -500,6 +500,7 @@ class StringResponseTest(ResponseTest):
xml_factory_class = StringResponseXMLFactory
def test_case_sensitive(self):
# Test single answer
problem = self.build_problem(answer="Second", case_sensitive=True)
# Exact string should be correct
......@@ -509,7 +510,20 @@ class StringResponseTest(ResponseTest):
self.assert_grade(problem, "Other String", "incorrect")
self.assert_grade(problem, "second", "incorrect")
# Test multiple answers
answers = ["Second", "Third", "Fourth"]
problem = self.build_problem(answer="_or_".join(answers), case_sensitive=True)
for answer in answers:
# Exact string should be correct
self.assert_grade(problem, answer, "correct")
# Other strings and the lowercase version of the string are incorrect
self.assert_grade(problem, "Other String", "incorrect")
self.assert_grade(problem, "second", "incorrect")
def test_case_insensitive(self):
# Test single answer
problem = self.build_problem(answer="Second", case_sensitive=False)
# Both versions of the string should be allowed, regardless
......@@ -520,9 +534,28 @@ class StringResponseTest(ResponseTest):
# Other strings are not allowed
self.assert_grade(problem, "Other String", "incorrect")
# Test multiple answers
answers = ["Second", "Third", "Fourth"]
problem = self.build_problem(answer="_or_".join(answers), case_sensitive=False)
for answer in answers:
# Exact string should be correct
self.assert_grade(problem, answer, "correct")
self.assert_grade(problem, answer.lower(), "correct")
# Other strings and the lowercase version of the string are incorrect
self.assert_grade(problem, "Other String", "incorrect")
def test_hints(self):
multiple_answers = [
"Martin Luther King Junior",
"Doctor Martin Luther King Junior",
"Dr. Martin Luther King Jr.",
"Martin Luther King"
]
hints = [("wisconsin", "wisc", "The state capital of Wisconsin is Madison"),
("minnesota", "minn", "The state capital of Minnesota is St. Paul")]
("minnesota", "minn", "The state capital of Minnesota is St. Paul"),
("_or_".join(multiple_answers), "mlk", "He lead the civil right movement in the United States of America.")]
problem = self.build_problem(answer="Michigan",
case_sensitive=False,
......@@ -550,6 +583,14 @@ class StringResponseTest(ResponseTest):
correct_map = problem.grade_answers(input_dict)
self.assertEquals(correct_map.get_hint('1_2_1'), "")
# We should get the same hint for each answer
for answer in multiple_answers:
input_dict = {'1_2_1': answer}
correct_map = problem.grade_answers(input_dict)
self.assertEquals(correct_map.get_hint('1_2_1'),
"He lead the civil right movement in the United States of America.")
def test_computed_hints(self):
problem = self.build_problem(
answer="Michigan",
......
......@@ -228,11 +228,13 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
});
// replace string and numerical
xml = xml.replace(/^\=\s*(.*?$)/gm, function(match, p) {
var string;
var floatValue = parseFloat(p);
xml = xml.replace(/(^\=\s*(.*?$)(\n*or\=\s*(.*?$))*)+/gm, function(match, p) {
var string,
answersList = p.replace(/^(or)?=\s*/gm, '').split('\n'),
floatValue = parseFloat(answersList[0]);
if(!isNaN(floatValue)) {
var params = /(.*?)\+\-\s*(.*?$)/.exec(p);
var params = /(.*?)\+\-\s*(.*?$)/.exec(answersList[0]);
if(params) {
string = '<numericalresponse answer="' + floatValue + '">\n';
string += ' <responseparam type="tolerance" default="' + params[2] + '" />\n';
......@@ -242,10 +244,16 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
string += ' <formulaequationinput />\n';
string += '</numericalresponse>\n\n';
} else {
string = '<stringresponse answer="' + p + '" type="ci">\n <textline size="20"/>\n</stringresponse>\n\n';
var answers = [];
for(var i = 0; i < answersList.length; i++) {
answers.push(answersList[i])
}
string = '<stringresponse answer="' + answers.join('_or_') + '" type="ci">\n <textline size="20"/>\n</stringresponse>\n\n';
}
return string;
});
});
// replace selects
xml = xml.replace(/\[\[(.+?)\]\]/g, function(match, p) {
......@@ -262,13 +270,13 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
selectString += '</optionresponse>\n\n';
return selectString;
});
// replace explanations
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
var selectString = '<solution>\n<div class="detailed-solution">\nExplanation\n\n' + p1 + '\n</div>\n</solution>';
return selectString;
});
// replace code blocks
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) {
var selectString = '<pre><code>\n' + p1 + '</code></pre>';
......@@ -293,7 +301,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
// rid white space
xml = xml.replace(/\n\n\n/g, '\n');
// surround w/ problem tag
xml = '<problem>\n' + xml + '\n</problem>';
......
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