Clean up task progress javascript. Add before/after scores to tracking of regrading requests.
This commit is contained in:
@@ -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):
|
|||||||
# 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):
|
|||||||
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):
|
|||||||
}
|
}
|
||||||
|
|
||||||
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):
|
|||||||
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):
|
|||||||
|
|
||||||
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):
|
|||||||
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
|
|||||||
|
|
||||||
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):
|
|||||||
)
|
)
|
||||||
|
|
||||||
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):
|
|||||||
# 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):
|
|||||||
'''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):
|
|||||||
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 = (
|
|||||||
|
|
||||||
# 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'
|
||||||
|
|||||||
Reference in New Issue
Block a user