diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 7d7ca2c912..5bacc3c080 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -655,7 +655,7 @@ class CapaModule(CapaFields, XModule): @staticmethod def make_dict_of_responses(get): '''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 to key 'y' in the returned dict. For example, @@ -739,13 +739,13 @@ class CapaModule(CapaFields, XModule): # Too late. Cannot submit if self.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') # Problem submitted. Student should reset before checking again if self.done and self.rerandomize == "always": 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') # 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['success'] = success 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 self.system.psychometrics_handler(self.get_state_for_lcp()) @@ -813,21 +813,33 @@ class CapaModule(CapaFields, XModule): } 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, - 'contents' : html} - ''' + This is called when the correct answer to a problem has been changed, + 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['state'] = self.lcp.get_state() event_info['problem_id'] = self.location.url() if not self.done: 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') + # 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: correct_map = self.lcp.regrade_existing_answers() # regrading should have no effect on attempts, so don't @@ -835,8 +847,12 @@ class CapaModule(CapaFields, XModule): self.set_state_from_lcp() except StudentInputError as inst: 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} except Exception, err: + event_info['failure'] = 'unexpected' + self.system.track_function('problem_regrade_fail', event_info) if self.system.DEBUG: msg = "Error checking problem: " + str(err) msg += '\nTraceback:\n' + traceback.format_exc() @@ -845,6 +861,10 @@ class CapaModule(CapaFields, XModule): 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' for answer_id in correct_map: @@ -856,25 +876,20 @@ class CapaModule(CapaFields, XModule): event_info['correct_map'] = correct_map.get_dict() event_info['success'] = success 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 if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback self.system.psychometrics_handler(self.get_instance_state()) - # render problem into HTML - html = self.get_problem_html(encapsulate=False) - - return {'success': success, - 'contents': html, - } + return {'success': success} def save_problem(self, get): - ''' + """ Save the passed in answers. - Returns a dict { 'success' : bool, ['error' : error-msg]}, - with the error key only present if success is False. - ''' + Returns a dict { 'success' : bool, 'msg' : message } + The message is informative on success, and an error message on failure. + """ event_info = dict() event_info['state'] = self.lcp.get_state() event_info['problem_id'] = self.location.url() diff --git a/common/lib/xmodule/xmodule/tests/test_conditional.py b/common/lib/xmodule/xmodule/tests/test_conditional.py index e88bf0c588..fed40b690f 100644 --- a/common/lib/xmodule/xmodule/tests/test_conditional.py +++ b/common/lib/xmodule/xmodule/tests/test_conditional.py @@ -20,7 +20,7 @@ from . import test_system 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): 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): - raise Exception("Shouldn't be called") + raise Exception("Shouldn't be called") + class ConditionalFactory(object): """ @@ -93,7 +94,7 @@ class ConditionalFactory(object): # return dict: return {'cond_module': cond_module, 'source_module': source_module, - 'child_module': child_module } + 'child_module': child_module} class ConditionalModuleBasicTest(unittest.TestCase): @@ -109,12 +110,11 @@ class ConditionalModuleBasicTest(unittest.TestCase): '''verify that get_icon_class works independent of condition satisfaction''' modules = ConditionalFactory.create(self.test_system) 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['child_module'].get_icon_class = lambda: icon_class self.assertEqual(modules['cond_module'].get_icon_class(), icon_class) - def test_get_html(self): modules = ConditionalFactory.create(self.test_system) # 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 html = ajax['html'] self.assertTrue(any(['This is a secret' in item for item in html])) - diff --git a/lms/envs/test.py b/lms/envs/test.py index 3a93f6d820..8e8097759c 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -193,3 +193,5 @@ PASSWORD_HASHERS = ( # By default don't use a worker, execute tasks as if they were local functions CELERY_ALWAYS_EAGER = True +CELERY_RESULT_BACKEND = 'cache' +BROKER_TRANSPORT = 'memory'