diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index ba99ee681e..92823667e7 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -203,8 +203,9 @@ class LoncapaProblem(object): cmap.update(self.correct_map) for responder in self.responders.values(): if hasattr(responder, 'update_score'): - # Each LoncapaResponse will update the specific entries of 'cmap' that it's responsible for - cmap = responder.update_score(score_msg, cmap, queuekey) + # Each LoncapaResponse will update its specific entries in cmap + # cmap is passed by reference + responder.update_score(score_msg, cmap, queuekey) self.correct_map.set_dict(cmap.get_dict()) return cmap diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 4a4e827752..7cb44686d0 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -834,6 +834,9 @@ class CodeResponse(LoncapaResponse): self.tests = xml.get('tests') + # TODO: A common XML format for interacting with external graders + # New format will not require exec + # # Extract 'answer' and 'initial_display' from XML. Note that the code to be exec'ed here is: # (1) Internal edX code, i.e. NOT student submissions, and # (2) The code should only define the strings 'initial_display', 'answer', 'preamble', 'test_program' @@ -903,27 +906,18 @@ class CodeResponse(LoncapaResponse): return cmap def update_score(self, score_msg, oldcmap, queuekey): - # Parse 'score_msg' as XML - try: - rxml = etree.fromstring(score_msg) - except Exception as err: - msg = 'Error in CodeResponse %s: cannot parse response from xworker r.text=%s' % (err, score_msg) - raise Exception(err) - # The following process is lifted directly from ExternalResponse - ad = rxml.find('awarddetail').text - admap = {'EXACT_ANS': 'correct', # TODO: handle other loncapa responses - 'WRONG_FORMAT': 'incorrect', - } - self.context['correct'] = ['correct'] - if ad in admap: - self.context['correct'][0] = admap[ad] + (valid_score_msg, correctness, score, msg) = self._parse_score_msg(score_msg) + if not valid_score_msg: + oldcmap.set(self.answer_id, msg='Error: Invalid grader reply.') + return oldcmap + + self.context['correct'] = correctness # TODO: Find out how this is used elsewhere, if any # Replace 'oldcmap' with new grading results if queuekey matches. # If queuekey does not match, we keep waiting for the score_msg whose key actually matches if oldcmap.is_right_queuekey(self.answer_id, queuekey): - msg = rxml.find('message').text.replace(' ', ' ') - oldcmap.set(self.answer_id, correctness=self.context['correct'][0], msg=msg, queuekey=None) # Queuekey is consumed + oldcmap.set(self.answer_id, correctness=correctness, msg=msg.replace(' ', ' '), queuekey=None) # Queuekey is consumed else: log.debug('CodeResponse: queuekey %s does not match for answer_id=%s.' % (queuekey, self.answer_id)) @@ -936,6 +930,31 @@ class CodeResponse(LoncapaResponse): def get_initial_display(self): return {self.answer_id: self.initial_display} + def _parse_score_msg(self, score_msg): + ''' + Grader reply is a JSON-dump of the following dict + { 'correct': True/False, + 'score': # TODO -- Partial grading + 'msg': grader_msg } + + Returns (valid_score_msg, correct, score, msg): + valid_score_msg: Flag indicating valid score_msg format (Boolean) + correct: Correctness of submission (Boolean) + score: # TODO: Implement partial grading + msg: Message from grader to display to student (string) + ''' + fail = (False, False, -1, '') + try: + score_result = json.loads(score_msg) + except (TypeError, ValueError): + return fail + if not isinstance(score_result, dict): + return fail + for tag in ['correct', 'score', 'msg']: + if not score_result.has_key(tag): + return fail + return (True, score_result['correct'], score_result['score'], score_result['msg']) + #----------------------------------------------------------------------------- diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 18e05b1247..5da8cdd869 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -257,13 +257,16 @@ def xqueue_callback(request, userid, id, dispatch): ''' Entry point for graded results from the queueing system. ''' - # Parse xqueue response + # Test xqueue package, which we expect to be: + # xpackage = {'xqueue_header': json.dumps({'lms_key':'secretkey',...}), + # 'xqueue_body' : 'Message from grader} get = request.POST.copy() - try: - header = json.loads(get['xqueue_header']) - except Exception as err: - msg = "Error in xqueue_callback %s: Invalid return format" % err - raise Exception(msg) + for key in ['xqueue_header', 'xqueue_body']: + if not get.has_key(key): + return Http404 + header = json.loads(get['xqueue_header']) + if not isinstance(header, dict) or not header.has_key('lms_key'): + return Http404 # Retrieve target StudentModule user = User.objects.get(id=userid) @@ -273,8 +276,7 @@ def xqueue_callback(request, userid, id, dispatch): instance_module = get_instance_module(user, instance, student_module_cache) if instance_module is None: - log.debug("Couldn't find module '%s' for user '%s'", - id, user) + log.debug("Couldn't find module '%s' for user '%s'", id, user) raise Http404 oldgrade = instance_module.grade