diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index ae6361ecd3..8f1ca25fa8 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -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) #-----------------------------------------------------------------------------