diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index c441539aee..9dd1e74409 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -927,15 +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): + def check_function(expect, ans, **kwargs): + extra_args = "".join(", {0}={0}".format(k) for k in kwargs) code = ( script_code + "\n" + - "cfn_return = %s(expect, ans)\n" % cfn + "cfn_return = %s(expect, ans%s)\n" % (cfn, extra_args) ) globals_dict = { 'expect': expect, 'ans': ans, } + globals_dict.update(kwargs) safe_exec.safe_exec(code, globals_dict) return globals_dict['cfn_return'] return check_function @@ -1063,10 +1065,12 @@ class CustomResponse(LoncapaResponse): # this is an interface to the Tutor2 check functions fn = self.code + answer_given = submission[0] if (len(idset) == 1) else submission + kwnames = self.xml.get("cfn_extra_args", "").split() + kwargs = {n:self.context.get(n) for n in kwnames} log.debug(" submission = %s" % submission) try: - answer_given = submission[0] if (len(idset) == 1) else submission - ret = fn(self.expect, answer_given) + ret = fn(self.expect, answer_given, **kwargs) except Exception as err: log.error("oops in customresponse (cfn) error %s" % err) # print "context = ",self.context diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py index 1d04b7897d..ee1fdfafc9 100644 --- a/common/lib/capa/capa/tests/response_xml_factory.py +++ b/common/lib/capa/capa/tests/response_xml_factory.py @@ -221,6 +221,8 @@ class CustomResponseXMLFactory(ResponseXMLFactory): cfn = kwargs.get('cfn', None) expect = kwargs.get('expect', None) answer = kwargs.get('answer', None) + options = kwargs.get('options', None) + cfn_extra_args = kwargs.get('cfn_extra_args', None) # Create the response element response_element = etree.Element("customresponse") @@ -235,6 +237,12 @@ class CustomResponseXMLFactory(ResponseXMLFactory): answer_element = etree.SubElement(response_element, "answer") answer_element.text = str(answer) + if options: + response_element.set('options', str(options)) + + if cfn_extra_args: + response_element.set('cfn_extra_args', str(cfn_extra_args)) + return response_element def create_input_element(self, **kwargs): diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 018d9240d7..c2ee62ed7a 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -2,7 +2,6 @@ Tests of responsetypes """ - from datetime import datetime import json from nose.plugins.skip import SkipTest @@ -806,9 +805,8 @@ class CustomResponseTest(ResponseTest): # # 'answer_given' is the answer the student gave (if there is just one input) # or an ordered list of answers (if there are multiple inputs) - # - # - # The function should return a dict of the form + # + # The function should return a dict of the form # { 'ok': BOOL, 'msg': STRING } # script = textwrap.dedent(""" @@ -917,6 +915,35 @@ class CustomResponseTest(ResponseTest): self.assertEqual(correct_map.get_msg('1_2_2'), 'Feedback 2') self.assertEqual(correct_map.get_msg('1_2_3'), 'Feedback 3') + def test_function_code_with_extra_args(self): + script = textwrap.dedent("""\ + def check_func(expect, answer_given, options, dynamath): + assert options == "xyzzy", "Options was %r" % options + return {'ok': answer_given == expect, 'msg': 'Message text'} + """) + + problem = self.build_problem(script=script, cfn="check_func", expect="42", options="xyzzy", cfn_extra_args="options dynamath") + + # Correct answer + input_dict = {'1_2_1': '42'} + correct_map = problem.grade_answers(input_dict) + + correctness = correct_map.get_correctness('1_2_1') + msg = correct_map.get_msg('1_2_1') + + self.assertEqual(correctness, 'correct') + self.assertEqual(msg, "Message text\n") + + # Incorrect answer + input_dict = {'1_2_1': '0'} + correct_map = problem.grade_answers(input_dict) + + correctness = correct_map.get_correctness('1_2_1') + msg = correct_map.get_msg('1_2_1') + + self.assertEqual(correctness, 'incorrect') + self.assertEqual(msg, "Message text\n") + def test_multiple_inputs_return_one_status(self): # When given multiple inputs, the 'answer_given' argument # to the check_func() is a list of inputs