Symbolic response no longer runs its checker in the Python sandbox.
This commit is contained in:
@@ -927,19 +927,17 @@ class CustomResponse(LoncapaResponse):
|
||||
# actual function that will re-execute the original script,
|
||||
# and invoke the function with the data needed.
|
||||
def make_check_function(script_code, cfn):
|
||||
def check_function(expect, ans, **kwargs):
|
||||
code = [script_code, "kwargs = {}"]
|
||||
for name, val in kwargs.iteritems():
|
||||
if isinstance(val, (str, list, tuple, int, long, float, dict)):
|
||||
code.append("kwargs[%r] = %r" % (name, val))
|
||||
code.append("cfn_return = %s(expect, ans, **kwargs)" % cfn)
|
||||
code.append("") # a final newline
|
||||
def check_function(expect, ans):
|
||||
code = (
|
||||
script_code + "\n" +
|
||||
"cfn_return = %s(expect, ans)\n" % cfn
|
||||
)
|
||||
globals_dict = {
|
||||
'expect': expect,
|
||||
'ans': ans,
|
||||
}
|
||||
locals_dict = {}
|
||||
safe_exec.safe_exec("\n".join(code), globals_dict, locals_dict)
|
||||
safe_exec.safe_exec(code, globals_dict, locals_dict)
|
||||
return locals_dict['cfn_return']
|
||||
return check_function
|
||||
|
||||
@@ -958,8 +956,6 @@ class CustomResponse(LoncapaResponse):
|
||||
else:
|
||||
self.code = answer.text
|
||||
|
||||
self.cfn_kwargs_keys = []
|
||||
|
||||
def get_score(self, student_answers):
|
||||
'''
|
||||
student_answers is a dict with everything from request.POST, but with the first part
|
||||
@@ -1038,16 +1034,30 @@ class CustomResponse(LoncapaResponse):
|
||||
# pass self.system.debug to cfn
|
||||
self.context['debug'] = self.system.DEBUG
|
||||
|
||||
# Run the check function
|
||||
self.execute_check_function(idset, submission)
|
||||
|
||||
# build map giving "correct"ness of the answer(s)
|
||||
correct = self.context['correct']
|
||||
messages = self.context['messages']
|
||||
correct_map = CorrectMap()
|
||||
|
||||
overall_message = self.clean_message_html(self.context['overall_message']))
|
||||
correct_map.set_overall_message(overall_message)
|
||||
|
||||
for k in range(len(idset)):
|
||||
npoints = self.maxpoints[idset[k]] if correct[k] == 'correct' else 0
|
||||
correct_map.set(idset[k], correct[k], msg=messages[k],
|
||||
npoints=npoints)
|
||||
return correct_map
|
||||
|
||||
def execute_check_function(self, idset, submission):
|
||||
# exec the check function
|
||||
if isinstance(self.code, basestring):
|
||||
try:
|
||||
locals_dict = {}
|
||||
safe_exec.safe_exec(self.code, self.context, locals_dict)
|
||||
self.context.update(locals_dict)
|
||||
correct = self.context['correct']
|
||||
messages = self.context['messages']
|
||||
overall_message = self.context['overall_message']
|
||||
|
||||
except Exception as err:
|
||||
self._handle_exec_exception(err)
|
||||
|
||||
@@ -1056,33 +1066,27 @@ class CustomResponse(LoncapaResponse):
|
||||
|
||||
# this is an interface to the Tutor2 check functions
|
||||
fn = self.code
|
||||
ret = None
|
||||
log.debug(" submission = %s" % submission)
|
||||
try:
|
||||
answer_given = submission[0] if (len(idset) == 1) else submission
|
||||
kwargs = {n:self.context.get(n) for n in self.cfn_kwargs_keys}
|
||||
ret = fn(self.expect, answer_given, **kwargs)
|
||||
ret = fn(self.expect, answer_given)
|
||||
except Exception as err:
|
||||
self._handle_exec_exception(err)
|
||||
|
||||
if type(ret) == dict:
|
||||
|
||||
log.error("oops in customresponse (cfn) error %s" % err)
|
||||
# print "context = ",self.context
|
||||
log.error(traceback.format_exc())
|
||||
raise Exception("oops in customresponse (cfn) error %s" % err)
|
||||
log.debug(
|
||||
"[courseware.capa.responsetypes.customresponse.get_score] ret = %s",
|
||||
ret
|
||||
)
|
||||
if isinstance(ret, dict):
|
||||
# One kind of dictionary the check function can return has the
|
||||
# form {'ok': BOOLEAN, 'msg': STRING}
|
||||
# If there are multiple inputs, they all get marked
|
||||
# to the same correct/incorrect value
|
||||
if 'ok' in ret:
|
||||
correct = ['correct'] * len(idset) if ret[
|
||||
'ok'] else ['incorrect'] * len(idset)
|
||||
msg = ret.get('msg', None)
|
||||
msg = self.clean_message_html(msg)
|
||||
|
||||
# If there is only one input, apply the message to that input
|
||||
# Otherwise, apply the message to the whole problem
|
||||
if len(idset) > 1:
|
||||
overall_message = msg
|
||||
else:
|
||||
messages[0] = msg
|
||||
correct = ['correct' if ret['ok'] else 'incorrect'] * len(idset)
|
||||
self.context['messages'][0] = self.clean_message_html(ret['msg'])
|
||||
|
||||
# Another kind of dictionary the check function can return has
|
||||
# the form:
|
||||
@@ -1104,6 +1108,7 @@ class CustomResponse(LoncapaResponse):
|
||||
msg = (self.clean_message_html(input_dict['msg'])
|
||||
if 'msg' in input_dict else None)
|
||||
messages.append(msg)
|
||||
self.context['messages'] = messages
|
||||
|
||||
# Otherwise, we do not recognize the dictionary
|
||||
# Raise an exception
|
||||
@@ -1112,25 +1117,10 @@ class CustomResponse(LoncapaResponse):
|
||||
raise ResponseError(
|
||||
"CustomResponse: check function returned an invalid dict")
|
||||
|
||||
# The check function can return a boolean value,
|
||||
# indicating whether all inputs should be marked
|
||||
# correct or incorrect
|
||||
else:
|
||||
n = len(idset)
|
||||
correct = ['correct'] * n if ret else ['incorrect'] * n
|
||||
correct = ['correct' if ret else 'incorrect'] * len(idset)
|
||||
|
||||
# build map giving "correct"ness of the answer(s)
|
||||
correct_map = CorrectMap()
|
||||
|
||||
overall_message = self.clean_message_html(overall_message)
|
||||
correct_map.set_overall_message(overall_message)
|
||||
|
||||
for k in range(len(idset)):
|
||||
npoints = (self.maxpoints[idset[k]]
|
||||
if correct[k] == 'correct' else 0)
|
||||
correct_map.set(idset[k], correct[k], msg=messages[k],
|
||||
npoints=npoints)
|
||||
return correct_map
|
||||
self.context['correct'] = correct
|
||||
|
||||
def clean_message_html(self, msg):
|
||||
|
||||
@@ -1205,12 +1195,23 @@ class SymbolicResponse(CustomResponse):
|
||||
|
||||
response_tag = 'symbolicresponse'
|
||||
|
||||
def setup_response(self):
|
||||
# No, this is not pretty.
|
||||
self.context['script_code'] += "from symmath import symmath_check\n"
|
||||
self.xml.set('cfn', 'symmath_check')
|
||||
CustomResponse.setup_response(self)
|
||||
self.cfn_kwargs_keys.extend(['dynamath', 'options', 'debug'])
|
||||
def execute_check_function(self, idset, submission):
|
||||
from symmath import symmath_check
|
||||
fn = self.code
|
||||
try:
|
||||
answer_given = submission[0] if (len(idset) == 1) else submission
|
||||
ret = symmath_check(
|
||||
self.expect, answer_given,
|
||||
dynamath=self.context.get('dynamath'),
|
||||
options=self.context.get('options'),
|
||||
debug=self.context.get('debug'),
|
||||
)
|
||||
except Exception as err:
|
||||
log.error("oops in symbolicresponse (cfn) error %s" % err)
|
||||
log.error(traceback.format_exc())
|
||||
raise Exception("oops in symbolicresponse (cfn) error %s" % err)
|
||||
self.context['messages'][0] = self.clean_message_html(ret['msg'])
|
||||
self.context['correct'] = ['correct' if ret['ok'] else 'incorrect'] * len(idset)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user