Commit ee5ffedf by Brian Wilson

Clean up task progress javascript. Add before/after scores to tracking of regrading requests.

parent 0d38789a
...@@ -655,7 +655,7 @@ class CapaModule(CapaFields, XModule): ...@@ -655,7 +655,7 @@ class CapaModule(CapaFields, XModule):
@staticmethod @staticmethod
def make_dict_of_responses(get): def make_dict_of_responses(get):
'''Make dictionary of student responses (aka "answers") '''Make dictionary of student responses (aka "answers")
get is POST dictionary (Djano QueryDict). get is POST dictionary (Django QueryDict).
The *get* dict has keys of the form 'x_y', which are mapped The *get* dict has keys of the form 'x_y', which are mapped
to key 'y' in the returned dict. For example, to key 'y' in the returned dict. For example,
...@@ -739,13 +739,13 @@ class CapaModule(CapaFields, XModule): ...@@ -739,13 +739,13 @@ class CapaModule(CapaFields, XModule):
# Too late. Cannot submit # Too late. Cannot submit
if self.closed(): if self.closed():
event_info['failure'] = 'closed' event_info['failure'] = 'closed'
self.system.track_function('save_problem_check_fail', event_info) self.system.track_function('problem_check_fail', event_info)
raise NotFoundError('Problem is closed') raise NotFoundError('Problem is closed')
# Problem submitted. Student should reset before checking again # Problem submitted. Student should reset before checking again
if self.done and self.rerandomize == "always": if self.done and self.rerandomize == "always":
event_info['failure'] = 'unreset' event_info['failure'] = 'unreset'
self.system.track_function('save_problem_check_fail', event_info) self.system.track_function('problem_check_fail', event_info)
raise NotFoundError('Problem must be reset before it can be checked again') raise NotFoundError('Problem must be reset before it can be checked again')
# Problem queued. Students must wait a specified waittime before they are allowed to submit # Problem queued. Students must wait a specified waittime before they are allowed to submit
...@@ -800,7 +800,7 @@ class CapaModule(CapaFields, XModule): ...@@ -800,7 +800,7 @@ class CapaModule(CapaFields, XModule):
event_info['correct_map'] = correct_map.get_dict() event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success event_info['success'] = success
event_info['attempts'] = self.attempts event_info['attempts'] = self.attempts
self.system.track_function('save_problem_check', event_info) self.system.track_function('problem_check', event_info)
if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback
self.system.psychometrics_handler(self.get_state_for_lcp()) self.system.psychometrics_handler(self.get_state_for_lcp())
...@@ -813,21 +813,33 @@ class CapaModule(CapaFields, XModule): ...@@ -813,21 +813,33 @@ class CapaModule(CapaFields, XModule):
} }
def regrade_problem(self): def regrade_problem(self):
''' Checks whether answers to a problem are correct, and """
returns a map of correct/incorrect answers: Checks whether the existing answers to a problem are correct.
{'success' : 'correct' | 'incorrect' | AJAX alert msg string, This is called when the correct answer to a problem has been changed,
'contents' : html} and the grade should be re-evaluated.
'''
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.
"""
event_info = dict() event_info = dict()
event_info['state'] = self.lcp.get_state() event_info['state'] = self.lcp.get_state()
event_info['problem_id'] = self.location.url() event_info['problem_id'] = self.location.url()
if not self.done: if not self.done:
event_info['failure'] = 'unanswered' event_info['failure'] = 'unanswered'
self.system.track_function('save_problem_regrade_fail', event_info) self.system.track_function('problem_regrade_fail', event_info)
raise NotFoundError('Problem must be answered before it can be graded again') raise NotFoundError('Problem must be answered before it can be graded again')
# get old score, for comparison:
orig_score = self.lcp.get_score()
event_info['orig_score'] = orig_score['score']
event_info['orig_max_score'] = orig_score['total']
try: try:
correct_map = self.lcp.regrade_existing_answers() correct_map = self.lcp.regrade_existing_answers()
# regrading should have no effect on attempts, so don't # regrading should have no effect on attempts, so don't
...@@ -835,8 +847,12 @@ class CapaModule(CapaFields, XModule): ...@@ -835,8 +847,12 @@ class CapaModule(CapaFields, XModule):
self.set_state_from_lcp() self.set_state_from_lcp()
except StudentInputError as inst: except StudentInputError as inst:
log.exception("StudentInputError in capa_module:problem_regrade") log.exception("StudentInputError in capa_module:problem_regrade")
event_info['failure'] = 'student_input_error'
self.system.track_function('problem_regrade_fail', event_info)
return {'success': inst.message} return {'success': inst.message}
except Exception, err: except Exception, err:
event_info['failure'] = 'unexpected'
self.system.track_function('problem_regrade_fail', event_info)
if self.system.DEBUG: if self.system.DEBUG:
msg = "Error checking problem: " + str(err) msg = "Error checking problem: " + str(err)
msg += '\nTraceback:\n' + traceback.format_exc() msg += '\nTraceback:\n' + traceback.format_exc()
...@@ -845,6 +861,10 @@ class CapaModule(CapaFields, XModule): ...@@ -845,6 +861,10 @@ class CapaModule(CapaFields, XModule):
self.publish_grade() self.publish_grade()
new_score = self.lcp.get_score()
event_info['new_score'] = new_score['score']
event_info['new_max_score'] = new_score['total']
# success = correct if ALL questions in this problem are correct # success = correct if ALL questions in this problem are correct
success = 'correct' success = 'correct'
for answer_id in correct_map: for answer_id in correct_map:
...@@ -856,25 +876,20 @@ class CapaModule(CapaFields, XModule): ...@@ -856,25 +876,20 @@ class CapaModule(CapaFields, XModule):
event_info['correct_map'] = correct_map.get_dict() event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success event_info['success'] = success
event_info['attempts'] = self.attempts event_info['attempts'] = self.attempts
self.system.track_function('save_problem_regrade', event_info) self.system.track_function('problem_regrade', event_info)
# TODO: figure out if psychometrics should be called on regrading requests # TODO: figure out if psychometrics should be called on regrading requests
if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback
self.system.psychometrics_handler(self.get_instance_state()) self.system.psychometrics_handler(self.get_instance_state())
# render problem into HTML return {'success': success}
html = self.get_problem_html(encapsulate=False)
return {'success': success,
'contents': html,
}
def save_problem(self, get): def save_problem(self, get):
''' """
Save the passed in answers. Save the passed in answers.
Returns a dict { 'success' : bool, ['error' : error-msg]}, Returns a dict { 'success' : bool, 'msg' : message }
with the error key only present if success is False. The message is informative on success, and an error message on failure.
''' """
event_info = dict() event_info = dict()
event_info['state'] = self.lcp.get_state() event_info['state'] = self.lcp.get_state()
event_info['problem_id'] = self.location.url() event_info['problem_id'] = self.location.url()
......
...@@ -20,7 +20,7 @@ from . import test_system ...@@ -20,7 +20,7 @@ from . import test_system
class DummySystem(ImportSystem): class DummySystem(ImportSystem):
@patch('xmodule.modulestore.xml.OSFS', lambda dir: MemoryFS()) @patch('xmodule.modulestore.xml.OSFS', lambda directory: MemoryFS())
def __init__(self, load_error_modules): def __init__(self, load_error_modules):
xmlstore = XMLModuleStore("data_dir", course_dirs=[], load_error_modules=load_error_modules) xmlstore = XMLModuleStore("data_dir", course_dirs=[], load_error_modules=load_error_modules)
...@@ -41,7 +41,8 @@ class DummySystem(ImportSystem): ...@@ -41,7 +41,8 @@ class DummySystem(ImportSystem):
) )
def render_template(self, template, context): def render_template(self, template, context):
raise Exception("Shouldn't be called") raise Exception("Shouldn't be called")
class ConditionalFactory(object): class ConditionalFactory(object):
""" """
...@@ -93,7 +94,7 @@ class ConditionalFactory(object): ...@@ -93,7 +94,7 @@ class ConditionalFactory(object):
# return dict: # return dict:
return {'cond_module': cond_module, return {'cond_module': cond_module,
'source_module': source_module, 'source_module': source_module,
'child_module': child_module } 'child_module': child_module}
class ConditionalModuleBasicTest(unittest.TestCase): class ConditionalModuleBasicTest(unittest.TestCase):
...@@ -109,12 +110,11 @@ class ConditionalModuleBasicTest(unittest.TestCase): ...@@ -109,12 +110,11 @@ class ConditionalModuleBasicTest(unittest.TestCase):
'''verify that get_icon_class works independent of condition satisfaction''' '''verify that get_icon_class works independent of condition satisfaction'''
modules = ConditionalFactory.create(self.test_system) modules = ConditionalFactory.create(self.test_system)
for attempted in ["false", "true"]: for attempted in ["false", "true"]:
for icon_class in [ 'other', 'problem', 'video']: for icon_class in ['other', 'problem', 'video']:
modules['source_module'].is_attempted = attempted modules['source_module'].is_attempted = attempted
modules['child_module'].get_icon_class = lambda: icon_class modules['child_module'].get_icon_class = lambda: icon_class
self.assertEqual(modules['cond_module'].get_icon_class(), icon_class) self.assertEqual(modules['cond_module'].get_icon_class(), icon_class)
def test_get_html(self): def test_get_html(self):
modules = ConditionalFactory.create(self.test_system) modules = ConditionalFactory.create(self.test_system)
# because test_system returns the repr of the context dict passed to render_template, # because test_system returns the repr of the context dict passed to render_template,
...@@ -224,4 +224,3 @@ class ConditionalModuleXmlTest(unittest.TestCase): ...@@ -224,4 +224,3 @@ class ConditionalModuleXmlTest(unittest.TestCase):
print "post-attempt ajax: ", ajax print "post-attempt ajax: ", ajax
html = ajax['html'] html = ajax['html']
self.assertTrue(any(['This is a secret' in item for item in html])) self.assertTrue(any(['This is a secret' in item for item in html]))
...@@ -193,3 +193,5 @@ PASSWORD_HASHERS = ( ...@@ -193,3 +193,5 @@ PASSWORD_HASHERS = (
# By default don't use a worker, execute tasks as if they were local functions # By default don't use a worker, execute tasks as if they were local functions
CELERY_ALWAYS_EAGER = True CELERY_ALWAYS_EAGER = True
CELERY_RESULT_BACKEND = 'cache'
BROKER_TRANSPORT = 'memory'
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