Commit 1aa05fd4 by ichuang

externalresponse fixes; clean up semantics of interface, add command

for getting expected answer from external server, add error handling,
extend to use json for multiple input fields, added rows & cols to
textbox inputtype for code input.
parent b1acec7c
......@@ -253,16 +253,19 @@ def textbox(element, value, status, msg=''):
The textbox is used for code input. The message is the return HTML string from
evaluating the code, eg error messages, and output from the code tests.
TODO: make this use rows and cols attribs, not size
'''
eid=element.get('id')
count = int(eid.split('_')[-2])-1 # HACK
size = element.get('size')
rows = element.get('rows') or '30'
cols = element.get('cols') or '80'
mode = element.get('mode') or 'python' # mode for CodeMirror, eg "python" or "xml"
linenumbers = element.get('linenumbers') # for CodeMirror
if not value: value = element.text # if no student input yet, then use the default input given by the problem
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg':msg,
'mode':mode, 'linenumbers':linenumbers }
'mode':mode, 'linenumbers':linenumbers,
'rows':rows, 'cols':cols,
}
html=render_to_string("textbox.html", context)
return etree.XML(html)
......
......@@ -471,13 +471,55 @@ class SymbolicResponse(CustomResponse):
#-----------------------------------------------------------------------------
class ExternalResponse(GenericResponse):
"""
Grade the student's input using an external server.
'''
Grade the students input using an external server.
Typically used by coding problems.
"""
Example:
<externalresponse tests="repeat:10,generate">
<textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[
initial_display = """
def inc(x):
"""
answer = """
def inc(n):
return n+1
"""
preamble = """
import sympy
"""
test_program = """
import random
def testInc(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: inc(%d)'%n
return str(inc(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testInc(0))
elif test == 2: f.write(testInc(1))
else: f.write(testInc())
f.close()
main()
"""
]]>
</answer>
</externalresponse>
'''
def __init__(self, xml, context, system=None):
self.xml = xml
self.url = xml.get('url') or "http://eecs1.mit.edu:8889/pyloncapa" # FIXME - hardcoded URL
self.answer_ids = xml.xpath('//*[@id=$id]//textbox/@id|//*[@id=$id]//textline/@id',
id=xml.get('id'))
self.context = context
......@@ -490,24 +532,29 @@ class ExternalResponse(GenericResponse):
else:
self.code = answer.text
self.tests = xml.get('answer')
self.tests = xml.get('tests')
def get_score(self, student_answers):
submission = [student_answers[k] for k in sorted(self.answer_ids)]
try:
submission = [student_answers[k] for k in sorted(self.answer_ids)]
except Exception,err:
log.error('Error %s: cannot get student answer for %s; student_answers=%s' % (err,self.answer_ids,student_answers))
raise Exception,err
self.context.update({'submission':submission})
xmlstr = etree.tostring(self.xml, pretty_print=True)
payload = {'xml': xmlstr,
### Question: Is this correct/what we want? Shouldn't this be a json.dumps?
'LONCAPA_student_response': ''.join(submission),
'LONCAPA_correct_answer': self.tests,
'edX_cmd' : 'get_score',
'edX_student_response': json.dumps(submission),
'edX_tests': self.tests,
'processor' : self.code,
}
# call external server; TODO: get URL from settings.py
r = requests.post("http://eecs1.mit.edu:8889/pyloncapa",data=payload)
r = requests.post(self.url,data=payload) # call external server
if settings.DEBUG: log.info('response = %s' % r.text)
rxml = etree.fromstring(r.text) # response is XML; prase it
ad = rxml.find('awarddetail').text
admap = {'EXACT_ANS':'correct', # TODO: handle other loncapa responses
......@@ -520,15 +567,32 @@ class ExternalResponse(GenericResponse):
# self.context['correct'] = ['correct','correct']
correct_map = dict(zip(sorted(self.answer_ids), self.context['correct']))
# TODO: separate message for each answer_id?
correct_map['msg'] = rxml.find('message').text.replace('&nbsp;','&#160;') # store message in correct_map
# store message in correct_map
correct_map['msg_%s' % self.answer_ids[0]] = rxml.find('message').text.replace('&nbsp;','&#160;')
return correct_map
def get_answers(self):
# Since this is explicitly specified in the problem, this will
# be handled by capa_problem
return {}
'''
Use external server to get expected answers
'''
xmlstr = etree.tostring(self.xml, pretty_print=True)
payload = {'xml': xmlstr,
'edX_cmd' : 'get_answers',
'edX_tests': self.tests,
'processor' : self.code,
}
r = requests.post(self.url,data=payload) # call external server
if settings.DEBUG: log.info('response = %s' % r.text)
rxml = etree.fromstring(r.text) # response is XML; prase it
exans = json.loads(rxml.find('expected').text)
if not (len(exans)==len(self.answer_ids)):
log.error('Expected %d answers from external server, only got %d!' % (len(self.answer_ids),len(exans)))
raise Exception,'Short response from external server'
return dict(zip(self.answer_ids,exans))
class StudentInputError(Exception):
pass
......
......@@ -351,8 +351,15 @@ class Module(XModule):
self.tracker('save_problem_check', event_info)
try:
html = self.get_problem_html(encapsulate=False)
except Exception,err:
log.error('failed to generate html, error %s' % err)
raise Exception,err
return json.dumps({'success': success,
'contents': self.get_problem_html(encapsulate=False)})
'contents': html,
})
def save_problem(self, get):
event_info = dict()
......
<section class="text-input">
<textarea rows="30" cols="80" name="input_${id}" id="input_${id}">${value|h}</textarea>
<textarea rows="${rows}" cols="${cols}" name="input_${id}" id="input_${id}">${value|h}</textarea>
<span id="answer_${id}"></span>
......
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