As part of dissolving our sub-projects in edx-platform, we are moving this package under the xmodule directory. We have fixed all the occurences of import of this package and also fixed all documents related references. This might break your platform if you have any reference of `import capa` or `from capa import` in your codebase or in any Xblock. Ref: https://openedx.atlassian.net/browse/BOM-2582
197 lines
7.2 KiB
Python
197 lines
7.2 KiB
Python
# lint-amnesty, pylint: disable=missing-module-docstring
|
|
# -----------------------------------------------------------------------------
|
|
# class used to store graded responses to CAPA questions
|
|
#
|
|
# Used by responsetypes and capa_problem
|
|
|
|
|
|
class CorrectMap(object):
|
|
"""
|
|
Stores map between answer_id and response evaluation result for each question
|
|
in a capa problem. The response evaluation result for each answer_id includes
|
|
(correctness, npoints, msg, hint, hintmode).
|
|
|
|
- correctness : 'correct', 'incorrect', 'partially-correct', or 'incomplete'
|
|
- npoints : None, or integer specifying number of points awarded for this answer_id
|
|
- msg : string (may have HTML) giving extra message response
|
|
(displayed below textline or textbox)
|
|
- hint : string (may have HTML) giving optional hint
|
|
(displayed below textline or textbox, above msg)
|
|
- hintmode : one of (None,'on_request','always') criteria for displaying hint
|
|
- queuestate : Dict {key:'', time:''} where key is a secret string, and time is a string dump
|
|
of a DateTime object in the format '%Y%m%d%H%M%S'. Is None when not queued
|
|
|
|
Behaves as a dict.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
# start with empty dict
|
|
self.cmap = {}
|
|
self.items = self.cmap.items
|
|
self.keys = self.cmap.keys
|
|
self.overall_message = ""
|
|
self.set(*args, **kwargs)
|
|
|
|
def __getitem__(self, *args, **kwargs):
|
|
return self.cmap.__getitem__(*args, **kwargs)
|
|
|
|
def __iter__(self):
|
|
return self.cmap.__iter__()
|
|
|
|
# See the documentation for 'set_dict' for the use of kwargs
|
|
def set( # lint-amnesty, pylint: disable=missing-function-docstring
|
|
self, # lint-amnesty, pylint: disable=unused-argument
|
|
answer_id=None,
|
|
correctness=None,
|
|
npoints=None,
|
|
msg='',
|
|
hint='',
|
|
hintmode=None,
|
|
queuestate=None,
|
|
answervariable=None,
|
|
**kwargs
|
|
):
|
|
|
|
if answer_id is not None:
|
|
self.cmap[answer_id] = {
|
|
'correctness': correctness,
|
|
'npoints': npoints,
|
|
'msg': msg,
|
|
'hint': hint,
|
|
'hintmode': hintmode,
|
|
'queuestate': queuestate,
|
|
'answervariable': answervariable,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return repr(self.cmap)
|
|
|
|
def get_dict(self):
|
|
"""
|
|
return dict version of self
|
|
"""
|
|
return self.cmap
|
|
|
|
def set_dict(self, correct_map):
|
|
"""
|
|
Set internal dict of CorrectMap to provided correct_map dict
|
|
|
|
correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This
|
|
means that when the definition of CorrectMap (e.g. its properties) are altered,
|
|
an existing correct_map dict will not coincide with the newest CorrectMap format as
|
|
defined by self.set.
|
|
|
|
For graceful migration, feed the contents of each correct map to self.set, rather than
|
|
making a direct copy of the given correct_map dict. This way, the common keys between
|
|
the incoming correct_map dict and the new CorrectMap instance will be written, while
|
|
mismatched keys will be gracefully ignored.
|
|
|
|
Special migration case:
|
|
If correct_map is a one-level dict, then convert it to the new dict of dicts format.
|
|
|
|
"""
|
|
# empty current dict
|
|
self.__init__()
|
|
|
|
if not correct_map:
|
|
return
|
|
|
|
# create new dict entries
|
|
if not isinstance(list(correct_map.values())[0], dict):
|
|
# special migration
|
|
for k in correct_map:
|
|
self.set(k, correctness=correct_map[k])
|
|
else:
|
|
for k in correct_map:
|
|
self.set(k, **correct_map[k])
|
|
|
|
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:
|
|
return self.cmap[answer_id]['correctness'] in ['correct', 'partially-correct']
|
|
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):
|
|
return answer_id in self.cmap and self.cmap[answer_id]['queuestate'] is not None
|
|
|
|
def is_right_queuekey(self, answer_id, test_key):
|
|
return self.is_queued(answer_id) and self.cmap[answer_id]['queuestate']['key'] == test_key
|
|
|
|
def get_queuetime_str(self, answer_id):
|
|
if self.cmap[answer_id]['queuestate']:
|
|
return self.cmap[answer_id]['queuestate']['time']
|
|
else:
|
|
return None
|
|
|
|
def get_npoints(self, answer_id):
|
|
"""Return the number of points for an answer, used for partial credit."""
|
|
npoints = self.get_property(answer_id, 'npoints')
|
|
if npoints is not None:
|
|
return npoints
|
|
elif self.is_correct(answer_id):
|
|
return 1
|
|
# if not correct and no points have been assigned, return 0
|
|
return 0
|
|
|
|
def set_property(self, answer_id, property, value): # lint-amnesty, pylint: disable=redefined-builtin
|
|
if answer_id in self.cmap:
|
|
self.cmap[answer_id][property] = value
|
|
else:
|
|
self.cmap[answer_id] = {property: value}
|
|
|
|
def get_property(self, answer_id, property, default=None): # lint-amnesty, pylint: disable=redefined-builtin
|
|
if answer_id in self.cmap:
|
|
return self.cmap[answer_id].get(property, default)
|
|
return default
|
|
|
|
def get_correctness(self, answer_id):
|
|
return self.get_property(answer_id, 'correctness')
|
|
|
|
def get_msg(self, answer_id):
|
|
return self.get_property(answer_id, 'msg', '')
|
|
|
|
def get_hint(self, answer_id):
|
|
return self.get_property(answer_id, 'hint', '')
|
|
|
|
def get_hintmode(self, answer_id):
|
|
return self.get_property(answer_id, 'hintmode', None)
|
|
|
|
def set_hint_and_mode(self, answer_id, hint, hintmode):
|
|
"""
|
|
- hint : (string) HTML text for hint
|
|
- hintmode : (string) mode for hint display ('always' or 'on_request')
|
|
"""
|
|
self.set_property(answer_id, 'hint', hint)
|
|
self.set_property(answer_id, 'hintmode', hintmode)
|
|
|
|
def update(self, other_cmap):
|
|
"""
|
|
Update this CorrectMap with the contents of another CorrectMap
|
|
"""
|
|
if not isinstance(other_cmap, CorrectMap):
|
|
raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap)
|
|
self.cmap.update(other_cmap.get_dict())
|
|
self.set_overall_message(other_cmap.get_overall_message())
|
|
|
|
def set_overall_message(self, message_str):
|
|
""" Set a message that applies to the question as a whole,
|
|
rather than to individual inputs. """
|
|
self.overall_message = str(message_str) if message_str else ""
|
|
|
|
def get_overall_message(self):
|
|
""" Retrieve a message that applies to the question as a whole.
|
|
If no message is available, returns the empty string """
|
|
return self.overall_message
|