Commit d8d49f75 by Sarina Canelake

Merge pull request #9606 from Colin-Fredericks/ColinF-PC-not-dropdown

TNL-3197 Partial Credit (not Dropdown)
parents 358f8056 350387c0
...@@ -10,7 +10,7 @@ class CorrectMap(object): ...@@ -10,7 +10,7 @@ class CorrectMap(object):
in a capa problem. The response evaluation result for each answer_id includes in a capa problem. The response evaluation result for each answer_id includes
(correctness, npoints, msg, hint, hintmode). (correctness, npoints, msg, hint, hintmode).
- correctness : either 'correct' or 'incorrect' - correctness : 'correct', 'incorrect', or 'partially-correct'
- npoints : None, or integer specifying number of points awarded for this answer_id - npoints : None, or integer specifying number of points awarded for this answer_id
- msg : string (may have HTML) giving extra message response - msg : string (may have HTML) giving extra message response
(displayed below textline or textbox) (displayed below textline or textbox)
...@@ -101,10 +101,23 @@ class CorrectMap(object): ...@@ -101,10 +101,23 @@ class CorrectMap(object):
self.set(k, **correct_map[k]) self.set(k, **correct_map[k])
def is_correct(self, answer_id): def is_correct(self, answer_id):
"""
Takes an answer_id
Returns true if the problem is correct OR partially correct.
"""
if answer_id in self.cmap: if answer_id in self.cmap:
return self.cmap[answer_id]['correctness'] in ['correct', 'partially-correct'] return self.cmap[answer_id]['correctness'] in ['correct', 'partially-correct']
return None return None
def is_partially_correct(self, answer_id):
"""
Takes an answer_id
Returns true if the problem is partially correct.
"""
if answer_id in self.cmap:
return self.cmap[answer_id]['correctness'] == 'partially-correct'
return None
def is_queued(self, answer_id): def is_queued(self, answer_id):
return answer_id in self.cmap and self.cmap[answer_id]['queuestate'] is not None return answer_id in self.cmap and self.cmap[answer_id]['queuestate'] is not None
......
...@@ -85,6 +85,7 @@ class Status(object): ...@@ -85,6 +85,7 @@ class Status(object):
names = { names = {
'correct': _('correct'), 'correct': _('correct'),
'incorrect': _('incorrect'), 'incorrect': _('incorrect'),
'partially-correct': _('partially correct'),
'incomplete': _('incomplete'), 'incomplete': _('incomplete'),
'unanswered': _('unanswered'), 'unanswered': _('unanswered'),
'unsubmitted': _('unanswered'), 'unsubmitted': _('unanswered'),
...@@ -94,6 +95,7 @@ class Status(object): ...@@ -94,6 +95,7 @@ class Status(object):
# Translators: these are tooltips that indicate the state of an assessment question # Translators: these are tooltips that indicate the state of an assessment question
'correct': _('This is correct.'), 'correct': _('This is correct.'),
'incorrect': _('This is incorrect.'), 'incorrect': _('This is incorrect.'),
'partially-correct': _('This is partially correct.'),
'unanswered': _('This is unanswered.'), 'unanswered': _('This is unanswered.'),
'unsubmitted': _('This is unanswered.'), 'unsubmitted': _('This is unanswered.'),
'queued': _('This is being processed.'), 'queued': _('This is being processed.'),
...@@ -896,7 +898,7 @@ class MatlabInput(CodeInput): ...@@ -896,7 +898,7 @@ class MatlabInput(CodeInput):
Right now, we only want this button to show up when a problem has not been Right now, we only want this button to show up when a problem has not been
checked. checked.
""" """
if self.status in ['correct', 'incorrect']: if self.status in ['correct', 'incorrect', 'partially-correct']:
return False return False
else: else:
return True return True
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<div id="input_${id}_preview" class="equation"></div> <div id="input_${id}_preview" class="equation"></div>
<p id="answer_${id}" class="answer"></p> <p id="answer_${id}" class="answer"></p>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
</div> </div>
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
<% <%
if status == 'correct': if status == 'correct':
correctness = 'correct' correctness = 'correct'
elif status == 'partially-correct':
correctness = 'partially-correct'
elif status == 'incorrect': elif status == 'incorrect':
correctness = 'incorrect' correctness = 'incorrect'
else: else:
...@@ -31,7 +33,7 @@ ...@@ -31,7 +33,7 @@
/> ${choice_description} /> ${choice_description}
% if input_type == 'radio' and ( (isinstance(value, basestring) and (choice_id == value)) or (not isinstance(value, basestring) and choice_id in value) ): % if input_type == 'radio' and ( (isinstance(value, basestring) and (choice_id == value)) or (not isinstance(value, basestring) and choice_id in value) ):
% if status in ('correct', 'incorrect') and not show_correctness=='never': % if status in ('correct', 'partially-correct', 'incorrect') and not show_correctness=='never':
<span class="sr status">${choice_description|h} - ${status.display_name}</span> <span class="sr status">${choice_description|h} - ${status.display_name}</span>
% endif % endif
% endif % endif
...@@ -60,4 +62,4 @@ ...@@ -60,4 +62,4 @@
% if msg: % if msg:
<span class="message">${msg|n}</span> <span class="message">${msg|n}</span>
% endif % endif
</form> </form>
\ No newline at end of file
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
correctness = 'correct' correctness = 'correct'
elif status == 'incorrect': elif status == 'incorrect':
correctness = 'incorrect' correctness = 'incorrect'
elif status == 'partially-correct':
correctness = 'partially-correct'
else: else:
correctness = None correctness = None
%> %>
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<div class="script_placeholder" data-src="/static/js/sylvester.js"></div> <div class="script_placeholder" data-src="/static/js/sylvester.js"></div>
<div class="script_placeholder" data-src="/static/js/crystallography.js"></div> <div class="script_placeholder" data-src="/static/js/crystallography.js"></div>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
<div class="status ${status.classname}" id="status_${id}"> <div class="status ${status.classname}" id="status_${id}">
% endif % endif
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<span class="message">${msg|n}</span> <span class="message">${msg|n}</span>
% endif % endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
</section> </section>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="script_placeholder" data-src="/static/js/capa/protex/protex.nocache.js?raw"/> <div class="script_placeholder" data-src="/static/js/capa/protex/protex.nocache.js?raw"/>
<div class="script_placeholder" data-src="${applet_loader}"/> <div class="script_placeholder" data-src="${applet_loader}"/>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
<div class="${status.classname}" id="status_${id}"> <div class="${status.classname}" id="status_${id}">
% endif % endif
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
</p> </p>
<p id="answer_${id}" class="answer"></p> <p id="answer_${id}" class="answer"></p>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
</section> </section>
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<div class="script_placeholder" data-src="${STATIC_URL}js/capa/drag_and_drop.js"></div> <div class="script_placeholder" data-src="${STATIC_URL}js/capa/drag_and_drop.js"></div>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
<div class="${status.classname}" id="status_${id}"> <div class="${status.classname}" id="status_${id}">
% endif % endif
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<span class="message">${msg|n}</span> <span class="message">${msg|n}</span>
% endif % endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
</div> </div>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="script_placeholder" data-src="/static/js/capa/genex/genex.nocache.js?raw"/> <div class="script_placeholder" data-src="/static/js/capa/genex/genex.nocache.js?raw"/>
<div class="script_placeholder" data-src="${applet_loader}"/> <div class="script_placeholder" data-src="${applet_loader}"/>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
<div class="${status.classname}" id="status_${id}"> <div class="${status.classname}" id="status_${id}">
% endif % endif
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
</p> </p>
<p id="answer_${id}" class="answer"></p> <p id="answer_${id}" class="answer"></p>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
</section> </section>
......
<section id="editamoleculeinput_${id}" class="editamoleculeinput"> <section id="editamoleculeinput_${id}" class="editamoleculeinput">
<div class="script_placeholder" data-src="${applet_loader}"/> <div class="script_placeholder" data-src="${applet_loader}"/>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
<div class="${status.classname}" id="status_${id}"> <div class="${status.classname}" id="status_${id}">
% endif % endif
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
<div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div> <div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
</section> </section>
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<div class="script_placeholder" data-src="${jschannel_loader}"/> <div class="script_placeholder" data-src="${jschannel_loader}"/>
<div class="script_placeholder" data-src="${jsinput_loader}"/> <div class="script_placeholder" data-src="${jsinput_loader}"/>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
<div class="${status.classname}" id="status_${id}"> <div class="${status.classname}" id="status_${id}">
% endif % endif
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
<div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div> <div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<div class="script_placeholder" data-src="${preprocessor['script_src']}"/> <div class="script_placeholder" data-src="${preprocessor['script_src']}"/>
% endif % endif
% if status in ('unsubmitted', 'correct', 'incorrect', 'incomplete'): % if status in ('unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete'):
<div class="${status.classname} ${doinline}" id="status_${id}"> <div class="${status.classname} ${doinline}" id="status_${id}">
% endif % endif
% if hidden: % if hidden:
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
% endif % endif
% if status in ('unsubmitted', 'correct', 'incorrect', 'incomplete'): % if status in ('unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete'):
</div> </div>
% endif % endif
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<div class="script_placeholder" data-src="/static/js/vsepr/vsepr.js"></div> <div class="script_placeholder" data-src="/static/js/vsepr/vsepr.js"></div>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
<div class="${status.classname}" id="status_${id}"> <div class="${status.classname}" id="status_${id}">
% endif % endif
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
% if msg: % if msg:
<span class="message">${msg|n}</span> <span class="message">${msg|n}</span>
% endif % endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']:
</div> </div>
% endif % endif
</section> </section>
...@@ -49,6 +49,9 @@ class ResponseXMLFactory(object): ...@@ -49,6 +49,9 @@ class ResponseXMLFactory(object):
*num_inputs*: The number of input elements *num_inputs*: The number of input elements
to create [DEFAULT: 1] to create [DEFAULT: 1]
*credit_type*: String of comma-separated words specifying the
partial credit grading scheme.
Returns a string representation of the XML tree. Returns a string representation of the XML tree.
""" """
...@@ -58,6 +61,7 @@ class ResponseXMLFactory(object): ...@@ -58,6 +61,7 @@ class ResponseXMLFactory(object):
script = kwargs.get('script', None) script = kwargs.get('script', None)
num_responses = kwargs.get('num_responses', 1) num_responses = kwargs.get('num_responses', 1)
num_inputs = kwargs.get('num_inputs', 1) num_inputs = kwargs.get('num_inputs', 1)
credit_type = kwargs.get('credit_type', None)
# The root is <problem> # The root is <problem>
root = etree.Element("problem") root = etree.Element("problem")
...@@ -75,6 +79,11 @@ class ResponseXMLFactory(object): ...@@ -75,6 +79,11 @@ class ResponseXMLFactory(object):
# Add the response(s) # Add the response(s)
for __ in range(int(num_responses)): for __ in range(int(num_responses)):
response_element = self.create_response_element(**kwargs) response_element = self.create_response_element(**kwargs)
# Set partial credit
if credit_type is not None:
response_element.set('partial_credit', str(credit_type))
root.append(response_element) root.append(response_element)
# Add input elements # Add input elements
...@@ -132,6 +141,10 @@ class ResponseXMLFactory(object): ...@@ -132,6 +141,10 @@ class ResponseXMLFactory(object):
*choice_names": List of strings identifying the choices. *choice_names": List of strings identifying the choices.
If specified, you must ensure that If specified, you must ensure that
len(choice_names) == len(choices) len(choice_names) == len(choices)
*points*: List of strings giving partial credit values (0-1)
for each choice. Interpreted as floats in problem.
If specified, ensure len(points) == len(choices)
""" """
# Names of group elements # Names of group elements
group_element_names = { group_element_names = {
...@@ -144,15 +157,23 @@ class ResponseXMLFactory(object): ...@@ -144,15 +157,23 @@ class ResponseXMLFactory(object):
choices = kwargs.get('choices', [True]) choices = kwargs.get('choices', [True])
choice_type = kwargs.get('choice_type', 'multiple') choice_type = kwargs.get('choice_type', 'multiple')
choice_names = kwargs.get('choice_names', [None] * len(choices)) choice_names = kwargs.get('choice_names', [None] * len(choices))
points = kwargs.get('points', [None] * len(choices))
# Create the <choicegroup>, <checkboxgroup>, or <radiogroup> element # Create the <choicegroup>, <checkboxgroup>, or <radiogroup> element
assert choice_type in group_element_names assert choice_type in group_element_names
group_element = etree.Element(group_element_names[choice_type]) group_element = etree.Element(group_element_names[choice_type])
# Create the <choice> elements # Create the <choice> elements
for (correct_val, name) in zip(choices, choice_names): for (correct_val, name, pointval) in zip(choices, choice_names, points):
choice_element = etree.SubElement(group_element, "choice") choice_element = etree.SubElement(group_element, "choice")
choice_element.set("correct", "true" if correct_val else "false") if correct_val is True:
correctness = 'true'
elif correct_val is False:
correctness = 'false'
elif 'partial' in correct_val:
correctness = 'partial'
choice_element.set('correct', correctness)
# Add a name identifying the choice, if one exists # Add a name identifying the choice, if one exists
# For simplicity, we use the same string as both the # For simplicity, we use the same string as both the
...@@ -161,6 +182,10 @@ class ResponseXMLFactory(object): ...@@ -161,6 +182,10 @@ class ResponseXMLFactory(object):
choice_element.text = str(name) choice_element.text = str(name)
choice_element.set("name", str(name)) choice_element.set("name", str(name))
# Add point values for partially-correct choices.
if pointval:
choice_element.set("point_value", str(pointval))
return group_element return group_element
...@@ -176,10 +201,22 @@ class NumericalResponseXMLFactory(ResponseXMLFactory): ...@@ -176,10 +201,22 @@ class NumericalResponseXMLFactory(ResponseXMLFactory):
*tolerance*: The tolerance within which a response *tolerance*: The tolerance within which a response
is considered correct. Can be a decimal (e.g. "0.01") is considered correct. Can be a decimal (e.g. "0.01")
or percentage (e.g. "2%") or percentage (e.g. "2%")
*credit_type*: String of comma-separated words specifying the
partial credit grading scheme.
*partial_range*: The multiplier for the tolerance that will
still provide partial credit in the "close" grading style
*partial_answers*: A string of comma-separated alternate
answers that will receive partial credit in the "list" style
""" """
answer = kwargs.get('answer', None) answer = kwargs.get('answer', None)
tolerance = kwargs.get('tolerance', None) tolerance = kwargs.get('tolerance', None)
credit_type = kwargs.get('credit_type', None)
partial_range = kwargs.get('partial_range', None)
partial_answers = kwargs.get('partial_answers', None)
response_element = etree.Element('numericalresponse') response_element = etree.Element('numericalresponse')
...@@ -193,6 +230,13 @@ class NumericalResponseXMLFactory(ResponseXMLFactory): ...@@ -193,6 +230,13 @@ class NumericalResponseXMLFactory(ResponseXMLFactory):
responseparam_element = etree.SubElement(response_element, 'responseparam') responseparam_element = etree.SubElement(response_element, 'responseparam')
responseparam_element.set('type', 'tolerance') responseparam_element.set('type', 'tolerance')
responseparam_element.set('default', str(tolerance)) responseparam_element.set('default', str(tolerance))
if partial_range is not None and 'close' in credit_type:
responseparam_element.set('partial_range', str(partial_range))
if partial_answers is not None and 'list' in credit_type:
# The line below throws a false positive pylint violation, so it's excepted.
responseparam_element = etree.SubElement(response_element, 'responseparam') # pylint: disable=E1101
responseparam_element.set('partial_answers', partial_answers)
return response_element return response_element
......
...@@ -17,7 +17,7 @@ class CorrectMapTest(unittest.TestCase): ...@@ -17,7 +17,7 @@ class CorrectMapTest(unittest.TestCase):
self.cmap = CorrectMap() self.cmap = CorrectMap()
def test_set_input_properties(self): def test_set_input_properties(self):
# Set the correctmap properties for two inputs # Set the correctmap properties for three inputs
self.cmap.set( self.cmap.set(
answer_id='1_2_1', answer_id='1_2_1',
correctness='correct', correctness='correct',
...@@ -41,15 +41,34 @@ class CorrectMapTest(unittest.TestCase): ...@@ -41,15 +41,34 @@ class CorrectMapTest(unittest.TestCase):
queuestate=None queuestate=None
) )
self.cmap.set(
answer_id='3_2_1',
correctness='partially-correct',
npoints=3,
msg=None,
hint=None,
hintmode=None,
queuestate=None
)
# Assert that each input has the expected properties # Assert that each input has the expected properties
self.assertTrue(self.cmap.is_correct('1_2_1')) self.assertTrue(self.cmap.is_correct('1_2_1'))
self.assertFalse(self.cmap.is_correct('2_2_1')) self.assertFalse(self.cmap.is_correct('2_2_1'))
self.assertTrue(self.cmap.is_correct('3_2_1'))
self.assertTrue(self.cmap.is_partially_correct('3_2_1'))
self.assertFalse(self.cmap.is_partially_correct('2_2_1'))
# Intentionally testing an item that's not in cmap.
self.assertFalse(self.cmap.is_partially_correct('9_2_1'))
self.assertEqual(self.cmap.get_correctness('1_2_1'), 'correct') self.assertEqual(self.cmap.get_correctness('1_2_1'), 'correct')
self.assertEqual(self.cmap.get_correctness('2_2_1'), 'incorrect') self.assertEqual(self.cmap.get_correctness('2_2_1'), 'incorrect')
self.assertEqual(self.cmap.get_correctness('3_2_1'), 'partially-correct')
self.assertEqual(self.cmap.get_npoints('1_2_1'), 5) self.assertEqual(self.cmap.get_npoints('1_2_1'), 5)
self.assertEqual(self.cmap.get_npoints('2_2_1'), 0) self.assertEqual(self.cmap.get_npoints('2_2_1'), 0)
self.assertEqual(self.cmap.get_npoints('3_2_1'), 3)
self.assertEqual(self.cmap.get_msg('1_2_1'), 'Test message') self.assertEqual(self.cmap.get_msg('1_2_1'), 'Test message')
self.assertEqual(self.cmap.get_msg('2_2_1'), None) self.assertEqual(self.cmap.get_msg('2_2_1'), None)
...@@ -83,6 +102,8 @@ class CorrectMapTest(unittest.TestCase): ...@@ -83,6 +102,8 @@ class CorrectMapTest(unittest.TestCase):
# 3) incorrect, 5 points # 3) incorrect, 5 points
# 4) incorrect, None points # 4) incorrect, None points
# 5) correct, 0 points # 5) correct, 0 points
# 4) partially correct, 2.5 points
# 5) partially correct, None points
self.cmap.set( self.cmap.set(
answer_id='1_2_1', answer_id='1_2_1',
correctness='correct', correctness='correct',
...@@ -113,15 +134,30 @@ class CorrectMapTest(unittest.TestCase): ...@@ -113,15 +134,30 @@ class CorrectMapTest(unittest.TestCase):
npoints=0 npoints=0
) )
self.cmap.set(
answer_id='6_2_1',
correctness='partially-correct',
npoints=2.5
)
self.cmap.set(
answer_id='7_2_1',
correctness='partially-correct',
npoints=None
)
# Assert that we get the expected points # Assert that we get the expected points
# If points assigned --> npoints # If points assigned --> npoints
# If no points assigned and correct --> 1 point # If no points assigned and correct --> 1 point
# If no points assigned and partially correct --> 1 point
# If no points assigned and incorrect --> 0 points # If no points assigned and incorrect --> 0 points
self.assertEqual(self.cmap.get_npoints('1_2_1'), 5.3) self.assertEqual(self.cmap.get_npoints('1_2_1'), 5.3)
self.assertEqual(self.cmap.get_npoints('2_2_1'), 1) self.assertEqual(self.cmap.get_npoints('2_2_1'), 1)
self.assertEqual(self.cmap.get_npoints('3_2_1'), 5) self.assertEqual(self.cmap.get_npoints('3_2_1'), 5)
self.assertEqual(self.cmap.get_npoints('4_2_1'), 0) self.assertEqual(self.cmap.get_npoints('4_2_1'), 0)
self.assertEqual(self.cmap.get_npoints('5_2_1'), 0) self.assertEqual(self.cmap.get_npoints('5_2_1'), 0)
self.assertEqual(self.cmap.get_npoints('6_2_1'), 2.5)
self.assertEqual(self.cmap.get_npoints('7_2_1'), 1)
def test_set_overall_message(self): def test_set_overall_message(self):
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
$annotation-yellow: rgba(255,255,10,0.3); $annotation-yellow: rgba(255,255,10,0.3);
$color-copy-tip: rgb(100,100,100); $color-copy-tip: rgb(100,100,100);
$correct: $green-d1; $correct: $green-d1;
$partiallycorrect: $green-d1;
$incorrect: $red; $incorrect: $red;
// +Extends - Capa // +Extends - Capa
...@@ -75,6 +76,11 @@ h2 { ...@@ -75,6 +76,11 @@ h2 {
color: $correct; color: $correct;
} }
.feedback-hint-partially-correct {
margin-top: ($baseline/2);
color: $partiallycorrect;
}
.feedback-hint-incorrect { .feedback-hint-incorrect {
margin-top: ($baseline/2); margin-top: ($baseline/2);
color: $incorrect; color: $incorrect;
...@@ -174,6 +180,16 @@ div.problem { ...@@ -174,6 +180,16 @@ div.problem {
} }
} }
&.choicegroup_partially-correct {
@include status-icon($partiallycorrect, "\f069");
border: 2px solid $partiallycorrect;
// keep green for correct answers on hover.
&:hover {
border-color: $partiallycorrect;
}
}
&.choicegroup_incorrect { &.choicegroup_incorrect {
@include status-icon($incorrect, "\f00d"); @include status-icon($incorrect, "\f00d");
border: 2px solid $incorrect; border: 2px solid $incorrect;
...@@ -227,6 +243,11 @@ div.problem { ...@@ -227,6 +243,11 @@ div.problem {
@include status-icon($correct, "\f00c"); @include status-icon($correct, "\f00c");
} }
// CASE: partially correct answer
&.partially-correct {
@include status-icon($partiallycorrect, "\f069");
}
// CASE: incorrect answer // CASE: incorrect answer
&.incorrect { &.incorrect {
@include status-icon($incorrect, "\f00d"); @include status-icon($incorrect, "\f00d");
...@@ -338,6 +359,19 @@ div.problem { ...@@ -338,6 +359,19 @@ div.problem {
} }
} }
&.partially-correct, &.ui-icon-check {
p.status {
display: inline-block;
width: 25px;
height: 20px;
background: url('../images/partially-correct-icon.png') center center no-repeat;
}
input {
border-color: $partiallycorrect;
}
}
&.processing { &.processing {
p.status { p.status {
display: inline-block; display: inline-block;
...@@ -735,6 +769,18 @@ div.problem { ...@@ -735,6 +769,18 @@ div.problem {
} }
} }
// CASE: partially correct answer
> .partially-correct {
input {
border: 2px solid $partiallycorrect;
}
.status {
@include status-icon($partiallycorrect, "\f069");
}
}
// CASE: correct answer // CASE: correct answer
> .correct { > .correct {
...@@ -776,7 +822,7 @@ div.problem { ...@@ -776,7 +822,7 @@ div.problem {
.indicator-container { .indicator-container {
display: inline-block; display: inline-block;
.status.correct:after, .status.incorrect:after, .status.unanswered:after { .status.correct:after, .status.partially-correct:after, .status.incorrect:after, .status.unanswered:after {
@include margin-left(0); @include margin-left(0);
} }
} }
...@@ -942,6 +988,20 @@ div.problem { ...@@ -942,6 +988,20 @@ div.problem {
} }
} }
.detailed-targeted-feedback-partially-correct {
> p:first-child {
@extend %t-strong;
color: $partiallycorrect;
text-transform: uppercase;
font-style: normal;
font-size: 0.9em;
}
p:last-child {
margin-bottom: 0;
}
}
.detailed-targeted-feedback-correct { .detailed-targeted-feedback-correct {
> p:first-child { > p:first-child {
@extend %t-strong; @extend %t-strong;
...@@ -1136,6 +1196,14 @@ div.problem { ...@@ -1136,6 +1196,14 @@ div.problem {
} }
} }
.result-partially-correct {
background: url('../images/partially-correct-icon.png') left 20px no-repeat;
.result-actual-output {
color: #090;
}
}
.result-incorrect { .result-incorrect {
background: url('#{$static-path}/images/incorrect-icon.png') left 20px no-repeat; background: url('#{$static-path}/images/incorrect-icon.png') left 20px no-repeat;
...@@ -1341,6 +1409,14 @@ div.problem { ...@@ -1341,6 +1409,14 @@ div.problem {
} }
} }
label.choicetextgroup_partially-correct, section.choicetextgroup_partially-correct {
@extend label.choicegroup_partially-correct;
input[type="text"] {
border-color: $partiallycorrect;
}
}
label.choicetextgroup_incorrect, section.choicetextgroup_incorrect { label.choicetextgroup_incorrect, section.choicetextgroup_incorrect {
@extend label.choicegroup_incorrect; @extend label.choicegroup_incorrect;
} }
......
...@@ -64,6 +64,12 @@ class ProblemPage(PageObject): ...@@ -64,6 +64,12 @@ class ProblemPage(PageObject):
""" """
self.q(css='div.problem div.capa_inputtype.textline input').fill(text) self.q(css='div.problem div.capa_inputtype.textline input').fill(text)
def fill_answer_numerical(self, text):
"""
Fill in the answer to a numerical problem.
"""
self.q(css='div.problem section.inputtype input').fill(text)
def click_check(self): def click_check(self):
""" """
Click the Check button! Click the Check button!
...@@ -84,6 +90,24 @@ class ProblemPage(PageObject): ...@@ -84,6 +90,24 @@ class ProblemPage(PageObject):
""" """
return self.q(css="div.problem div.capa_inputtype.textline div.correct span.status").is_present() return self.q(css="div.problem div.capa_inputtype.textline div.correct span.status").is_present()
def simpleprob_is_correct(self):
"""
Is there a "correct" status showing? Works with simple problem types.
"""
return self.q(css="div.problem section.inputtype div.correct span.status").is_present()
def simpleprob_is_partially_correct(self):
"""
Is there a "partially correct" status showing? Works with simple problem types.
"""
return self.q(css="div.problem section.inputtype div.partially-correct span.status").is_present()
def simpleprob_is_incorrect(self):
"""
Is there an "incorrect" status showing? Works with simple problem types.
"""
return self.q(css="div.problem section.inputtype div.incorrect span.status").is_present()
def click_clarification(self, index=0): def click_clarification(self, index=0):
""" """
Click on an inline icon that can be included in problem text using an HTML <clarification> element: Click on an inline icon that can be included in problem text using an HTML <clarification> element:
......
...@@ -288,3 +288,35 @@ class ProblemWithMathjax(ProblemsTest): ...@@ -288,3 +288,35 @@ class ProblemWithMathjax(ProblemsTest):
self.assertIn("Hint (2 of 2): mathjax should work2", problem_page.hint_text) self.assertIn("Hint (2 of 2): mathjax should work2", problem_page.hint_text)
self.assertTrue(problem_page.mathjax_rendered_in_hint, "MathJax did not rendered in problem hint") self.assertTrue(problem_page.mathjax_rendered_in_hint, "MathJax did not rendered in problem hint")
class ProblemPartialCredit(ProblemsTest):
"""
Makes sure that the partial credit is appearing properly.
"""
def get_problem(self):
"""
Create a problem with partial credit.
"""
xml = dedent("""
<problem>
<p>The answer is 1. Partial credit for -1.</p>
<numericalresponse answer="1" partial_credit="list">
<formulaequationinput label="How many miles away from Earth is the sun? Use scientific notation to answer." />
<responseparam type="tolerance" default="0.01" />
<responseparam partial_answers="-1" />
</numericalresponse>
</problem>
""")
return XBlockFixtureDesc('problem', 'PARTIAL CREDIT TEST PROBLEM', data=xml)
def test_partial_credit(self):
"""
Test that we can see the partial credit value and feedback.
"""
self.courseware_page.visit()
problem_page = ProblemPage(self.browser)
self.assertEqual(problem_page.problem_name, 'PARTIAL CREDIT TEST PROBLEM')
problem_page.fill_answer_numerical('-1')
problem_page.click_check()
self.assertTrue(problem_page.simpleprob_is_partially_correct())
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