Commit 350387c0 by Colin-Fredericks

TNL-3197 Partial Credit (not Dropdown)

Updated templates

Updating responsetype tests.

Updating other tests for PC

Putting OptionInput back the way it was.

Acceptance tests for partial credit

Testing tools

pep8

scss name fix

Missing }

icon fix

icon fix
parent fac7b248
...@@ -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