Commit 58a1248d by kimth

New external grader format: JSON-serialized dict

parent f318cbea
...@@ -203,8 +203,9 @@ class LoncapaProblem(object): ...@@ -203,8 +203,9 @@ class LoncapaProblem(object):
cmap.update(self.correct_map) cmap.update(self.correct_map)
for responder in self.responders.values(): for responder in self.responders.values():
if hasattr(responder, 'update_score'): if hasattr(responder, 'update_score'):
# Each LoncapaResponse will update the specific entries of 'cmap' that it's responsible for # Each LoncapaResponse will update its specific entries in cmap
cmap = responder.update_score(score_msg, cmap, queuekey) # cmap is passed by reference
responder.update_score(score_msg, cmap, queuekey)
self.correct_map.set_dict(cmap.get_dict()) self.correct_map.set_dict(cmap.get_dict())
return cmap return cmap
......
...@@ -834,6 +834,9 @@ class CodeResponse(LoncapaResponse): ...@@ -834,6 +834,9 @@ class CodeResponse(LoncapaResponse):
self.tests = xml.get('tests') 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: # 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 # (1) Internal edX code, i.e. NOT student submissions, and
# (2) The code should only define the strings 'initial_display', 'answer', 'preamble', 'test_program' # (2) The code should only define the strings 'initial_display', 'answer', 'preamble', 'test_program'
...@@ -903,27 +906,18 @@ class CodeResponse(LoncapaResponse): ...@@ -903,27 +906,18 @@ class CodeResponse(LoncapaResponse):
return cmap return cmap
def update_score(self, score_msg, oldcmap, queuekey): 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 (valid_score_msg, correctness, score, msg) = self._parse_score_msg(score_msg)
ad = rxml.find('awarddetail').text if not valid_score_msg:
admap = {'EXACT_ANS': 'correct', # TODO: handle other loncapa responses oldcmap.set(self.answer_id, msg='Error: Invalid grader reply.')
'WRONG_FORMAT': 'incorrect', return oldcmap
}
self.context['correct'] = ['correct'] self.context['correct'] = correctness # TODO: Find out how this is used elsewhere, if any
if ad in admap:
self.context['correct'][0] = admap[ad]
# Replace 'oldcmap' with new grading results if queuekey matches. # 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 queuekey does not match, we keep waiting for the score_msg whose key actually matches
if oldcmap.is_right_queuekey(self.answer_id, queuekey): if oldcmap.is_right_queuekey(self.answer_id, queuekey):
msg = rxml.find('message').text.replace(' ', ' ') oldcmap.set(self.answer_id, correctness=correctness, msg=msg.replace(' ', ' '), queuekey=None) # Queuekey is consumed
oldcmap.set(self.answer_id, correctness=self.context['correct'][0], msg=msg, queuekey=None) # Queuekey is consumed
else: else:
log.debug('CodeResponse: queuekey %s does not match for answer_id=%s.' % (queuekey, self.answer_id)) log.debug('CodeResponse: queuekey %s does not match for answer_id=%s.' % (queuekey, self.answer_id))
...@@ -936,6 +930,31 @@ class CodeResponse(LoncapaResponse): ...@@ -936,6 +930,31 @@ class CodeResponse(LoncapaResponse):
def get_initial_display(self): def get_initial_display(self):
return {self.answer_id: self.initial_display} 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'])
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
......
...@@ -257,13 +257,16 @@ def xqueue_callback(request, userid, id, dispatch): ...@@ -257,13 +257,16 @@ def xqueue_callback(request, userid, id, dispatch):
''' '''
Entry point for graded results from the queueing system. 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() get = request.POST.copy()
try: for key in ['xqueue_header', 'xqueue_body']:
header = json.loads(get['xqueue_header']) if not get.has_key(key):
except Exception as err: return Http404
msg = "Error in xqueue_callback %s: Invalid return format" % err header = json.loads(get['xqueue_header'])
raise Exception(msg) if not isinstance(header, dict) or not header.has_key('lms_key'):
return Http404
# Retrieve target StudentModule # Retrieve target StudentModule
user = User.objects.get(id=userid) user = User.objects.get(id=userid)
...@@ -273,8 +276,7 @@ def xqueue_callback(request, userid, id, dispatch): ...@@ -273,8 +276,7 @@ def xqueue_callback(request, userid, id, dispatch):
instance_module = get_instance_module(user, instance, student_module_cache) instance_module = get_instance_module(user, instance, student_module_cache)
if instance_module is None: if instance_module is None:
log.debug("Couldn't find module '%s' for user '%s'", log.debug("Couldn't find module '%s' for user '%s'", id, user)
id, user)
raise Http404 raise Http404
oldgrade = instance_module.grade oldgrade = instance_module.grade
......
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