Add check for problems that (do not) support regrading.
This commit is contained in:
@@ -230,7 +230,6 @@ class LoncapaProblem(object):
|
||||
if hasattr(the_input, 'ungraded_response'):
|
||||
the_input.ungraded_response(xqueue_msg, queuekey)
|
||||
|
||||
|
||||
def is_queued(self):
|
||||
'''
|
||||
Returns True if any part of the problem has been submitted to an external queue
|
||||
@@ -238,7 +237,6 @@ class LoncapaProblem(object):
|
||||
'''
|
||||
return any(self.correct_map.is_queued(answer_id) for answer_id in self.correct_map)
|
||||
|
||||
|
||||
def get_recentmost_queuetime(self):
|
||||
'''
|
||||
Returns a DateTime object that represents the timestamp of the most recent
|
||||
@@ -256,7 +254,6 @@ class LoncapaProblem(object):
|
||||
|
||||
return max(queuetimes)
|
||||
|
||||
|
||||
def grade_answers(self, answers):
|
||||
'''
|
||||
Grade student responses. Called by capa_module.check_problem.
|
||||
@@ -272,6 +269,31 @@ class LoncapaProblem(object):
|
||||
self.student_answers = convert_files_to_filenames(answers)
|
||||
return self._grade_answers(answers)
|
||||
|
||||
def supports_regrading(self):
|
||||
"""
|
||||
Checks that the current problem definition permits regrading.
|
||||
|
||||
More precisely, it checks that there are no response types in
|
||||
the current problem that are not fully supported (yet) for regrading.
|
||||
|
||||
This includes responsetypes for which the student's answer
|
||||
is not properly stored in state, i.e. file submissions. At present,
|
||||
we have no way to know if an existing response was actually a real
|
||||
answer or merely the filename of a file submitted as an answer.
|
||||
|
||||
It turns out that because regrading is a background task, limiting
|
||||
it to responsetypes that don't support file submissions also means
|
||||
that the responsetypes are synchronous. This is convenient as it
|
||||
permits regrading to be complete when the regrading call returns.
|
||||
"""
|
||||
# We check for synchronous grading and no file submissions by
|
||||
# screening out all problems with a CodeResponse type.
|
||||
for responder in self.responders.values():
|
||||
if 'filesubmission' in responder.allowed_inputfields:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def regrade_existing_answers(self):
|
||||
'''
|
||||
Regrade student responses. Called by capa_module.regrade_problem.
|
||||
@@ -298,14 +320,21 @@ class LoncapaProblem(object):
|
||||
# log.debug('Responders: %s' % self.responders)
|
||||
# Call each responsetype instance to do actual grading
|
||||
for responder in self.responders.values():
|
||||
# File objects are passed only if responsetype explicitly allows for file
|
||||
# submissions
|
||||
# File objects are passed only if responsetype explicitly allows
|
||||
# for file submissions. But we have no way of knowing if
|
||||
# student_answers contains a proper answer or the filename of
|
||||
# an earlier submission, so for now skip these entirely.
|
||||
# TODO: figure out where to get file submissions when regrading.
|
||||
if 'filesubmission' in responder.allowed_inputfields and answers is not None:
|
||||
if 'filesubmission' in responder.allowed_inputfields and answers is None:
|
||||
raise Exception("Cannot regrade problems with possible file submissions")
|
||||
|
||||
# use 'answers' if it is provided, otherwise use the saved student_answers.
|
||||
if answers is not None:
|
||||
results = responder.evaluate_answers(answers, oldcmap)
|
||||
else:
|
||||
results = responder.evaluate_answers(self.student_answers, oldcmap)
|
||||
newcmap.update(results)
|
||||
|
||||
self.correct_map = newcmap
|
||||
# log.debug('%s: in grade_answers, answers=%s, cmap=%s' % (self,answers,newcmap))
|
||||
return newcmap
|
||||
|
||||
@@ -822,14 +822,21 @@ class CapaModule(CapaFields, XModule):
|
||||
Returns a dict with one key:
|
||||
{'success' : 'correct' | 'incorrect' | AJAX alert msg string }
|
||||
|
||||
Raises NotFoundError if called on a problem that has not yet been answered
|
||||
(since this is avoidable). Returns the error messages for exceptions
|
||||
occurring while performing the regrading, rather than throwing them.
|
||||
Raises NotFoundError if called on a problem that has not yet been
|
||||
answered, or if it's a problem that cannot be regraded.
|
||||
|
||||
Returns the error messages for exceptions occurring while performing
|
||||
the regrading, rather than throwing them.
|
||||
"""
|
||||
event_info = dict()
|
||||
event_info['state'] = self.lcp.get_state()
|
||||
event_info['problem_id'] = self.location.url()
|
||||
|
||||
if not self.lcp.supports_regrading():
|
||||
event_info['failure'] = 'unsupported'
|
||||
self.system.track_function('problem_regrade_fail', event_info)
|
||||
raise NotFoundError('Problem does not support regrading')
|
||||
|
||||
if not self.done:
|
||||
event_info['failure'] = 'unanswered'
|
||||
self.system.track_function('problem_regrade_fail', event_info)
|
||||
|
||||
@@ -641,6 +641,16 @@ class CapaModuleTest(unittest.TestCase):
|
||||
with self.assertRaises(xmodule.exceptions.NotFoundError):
|
||||
module.regrade_problem()
|
||||
|
||||
def test_regrade_problem_not_supported(self):
|
||||
# Simulate that the problem is NOT done
|
||||
module = CapaFactory.create(done=True)
|
||||
|
||||
# Try to regrade the problem, and get exception
|
||||
with patch('capa.capa_problem.LoncapaProblem.supports_regrading') as mock_supports_regrading:
|
||||
mock_supports_regrading.return_value = False
|
||||
with self.assertRaises(xmodule.exceptions.NotFoundError):
|
||||
module.regrade_problem()
|
||||
|
||||
def test_regrade_problem_error(self):
|
||||
|
||||
# Try each exception that capa_module should handle
|
||||
|
||||
Reference in New Issue
Block a user