From a949efc6b5313657a51d23ef64eb903b920fbc3d Mon Sep 17 00:00:00 2001 From: cahrens Date: Wed, 30 Nov 2016 16:22:08 -0500 Subject: [PATCH] Delete javascriptinput and javascriptresponse. TNL-6034 --- common/lib/capa/capa/inputtypes.py | 37 ---- common/lib/capa/capa/responsetypes.py | 205 ------------------ .../capa/capa/templates/javascriptinput.html | 11 - .../capa/capa/tests/response_xml_factory.py | 54 ----- common/lib/capa/capa/tests/test_inputtypes.py | 45 ---- .../lib/capa/capa/tests/test_responsetypes.py | 41 ---- .../xmodule/xmodule/js/src/capa/display.js | 40 ---- 7 files changed, 433 deletions(-) delete mode 100644 common/lib/capa/capa/templates/javascriptinput.html diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 9be71ade8f..7edfd4f1a1 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -9,7 +9,6 @@ Module containing the problem elements which render into input objects - textbox (aka codeinput) - schematic - choicegroup (aka radiogroup, checkboxgroup) -- javascriptinput - imageinput (for clickable image) - optioninput (for option list) - filesubmission (upload a file) @@ -544,42 +543,6 @@ class ChoiceGroup(InputTypeBase): return [self._choices_map[i] for i in internal_answer] - -#----------------------------------------------------------------------------- - - -@registry.register -class JavascriptInput(InputTypeBase): - """ - Hidden field for javascript to communicate via; also loads the required - scripts for rendering the problem and passes data to the problem. - - TODO (arjun?): document this in detail. Initial notes: - - display_class is a subclass of XProblemClassDisplay (see - xmodule/xmodule/js/src/capa/display.js), - - display_file is the js script to be in /static/js/ where display_class is defined. - """ - - template = "javascriptinput.html" - tags = ['javascriptinput'] - - @classmethod - def get_attributes(cls): - """ - Register the attributes. - """ - return [Attribute('params', None), - Attribute('problem_state', None), - Attribute('display_class', None), - Attribute('display_file', None), ] - - def setup(self): - # Need to provide a value that JSON can parse if there is no - # student-supplied value yet. - if self.value == "": - self.value = 'null' - - #----------------------------------------------------------------------------- diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 6d064845a5..e8cd5ddc56 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -610,210 +610,6 @@ class LoncapaResponse(object): """True if the response has an answer-pool transformation.""" return hasattr(self, '_has_answerpool') -#----------------------------------------------------------------------------- - - -@registry.register -class JavascriptResponse(LoncapaResponse): - """ - This response type is used when the student's answer is graded via - Javascript using Node.js. - """ - - human_name = _('JavaScript Input') - tags = ['javascriptresponse'] - max_inputfields = 1 - allowed_inputfields = ['javascriptinput'] - - def setup_response(self): - # Sets up generator, grader, display, and their dependencies. - self.parse_xml() - - self.compile_display_javascript() - - self.params = self.extract_params() - - if self.generator: - self.problem_state = self.generate_problem_state() - else: - self.problem_state = None - - self.solution = None - - self.prepare_inputfield() - - def compile_display_javascript(self): - - # TODO FIXME - # arjun: removing this behavior for now (and likely forever). Keeping - # until we decide on exactly how to solve this issue. For now, files are - # manually being compiled to DATA_DIR/js/compiled. - - # latestTimestamp = 0 - # basepath = self.capa_system.filestore.root_path + '/js/' - # for filename in (self.display_dependencies + [self.display]): - # filepath = basepath + filename - # timestamp = os.stat(filepath).st_mtime - # if timestamp > latestTimestamp: - # latestTimestamp = timestamp - # - # h = hashlib.md5() - # h.update(self.answer_id + str(self.display_dependencies)) - # compiled_filename = 'compiled/' + h.hexdigest() + '.js' - # compiled_filepath = basepath + compiled_filename - - # if not os.path.exists(compiled_filepath) or os.stat(compiled_filepath).st_mtime < latestTimestamp: - # outfile = open(compiled_filepath, 'w') - # for filename in (self.display_dependencies + [self.display]): - # filepath = basepath + filename - # infile = open(filepath, 'r') - # outfile.write(infile.read()) - # outfile.write(';\n') - # infile.close() - # outfile.close() - - # TODO this should also be fixed when the above is fixed. - filename = self.capa_system.ajax_url.split('/')[-1] + '.js' - self.display_filename = 'compiled/' + filename - - def parse_xml(self): - self.generator_xml = self.xml.xpath('//*[@id=$id]//generator', - id=self.xml.get('id'))[0] - - self.grader_xml = self.xml.xpath('//*[@id=$id]//grader', - id=self.xml.get('id'))[0] - - self.display_xml = self.xml.xpath('//*[@id=$id]//display', - id=self.xml.get('id'))[0] - - self.xml.remove(self.generator_xml) - self.xml.remove(self.grader_xml) - self.xml.remove(self.display_xml) - - self.generator = self.generator_xml.get("src") - self.grader = self.grader_xml.get("src") - self.display = self.display_xml.get("src") - - if self.generator_xml.get("dependencies"): - self.generator_dependencies = self.generator_xml.get( - "dependencies").split() - else: - self.generator_dependencies = [] - - if self.grader_xml.get("dependencies"): - self.grader_dependencies = self.grader_xml.get( - "dependencies").split() - else: - self.grader_dependencies = [] - - if self.display_xml.get("dependencies"): - self.display_dependencies = self.display_xml.get( - "dependencies").split() - else: - self.display_dependencies = [] - - self.display_class = self.display_xml.get("class") - - def get_node_env(self): - - js_dir = os.path.join(self.capa_system.filestore.root_path, 'js') - tmp_env = os.environ.copy() - node_path = self.capa_system.node_path + ":" + os.path.normpath(js_dir) - tmp_env["NODE_PATH"] = node_path - return tmp_env - - def call_node(self, args): - # Node.js code is un-sandboxed. If the LoncapaSystem says we aren't - # allowed to run unsafe code, then stop now. - if not self.capa_system.can_execute_unsafe_code(): - _ = self.capa_system.i18n.ugettext - msg = _("Execution of unsafe Javascript code is not allowed.") - raise LoncapaProblemError(msg) - - subprocess_args = ["node"] - subprocess_args.extend(args) - - return subprocess.check_output(subprocess_args, env=self.get_node_env()) - - def generate_problem_state(self): - - generator_file = os.path.dirname(os.path.normpath( - __file__)) + '/javascript_problem_generator.js' - output = self.call_node([generator_file, - self.generator, - json.dumps(self.generator_dependencies), - json.dumps(str(self.context['seed'])), - json.dumps(self.params)]).strip() - - return json.loads(output) - - def extract_params(self): - - params = {} - - for param in self.xml.xpath('//*[@id=$id]//responseparam', - id=self.xml.get('id')): - - raw_param = param.get("value") - params[param.get("name")] = json.loads( - contextualize_text(raw_param, self.context)) - - return params - - def prepare_inputfield(self): - - for inputfield in self.xml.xpath('//*[@id=$id]//javascriptinput', - id=self.xml.get('id')): - - escapedict = {'"': '"'} - - encoded_params = json.dumps(self.params) - encoded_params = saxutils.escape(encoded_params, escapedict) - inputfield.set("params", encoded_params) - - encoded_problem_state = json.dumps(self.problem_state) - encoded_problem_state = saxutils.escape(encoded_problem_state, - escapedict) - inputfield.set("problem_state", encoded_problem_state) - - inputfield.set("display_file", self.display_filename) - inputfield.set("display_class", self.display_class) - - def get_score(self, student_answers): - json_submission = student_answers[self.answer_id] - (all_correct, evaluation, solution) = self.run_grader(json_submission) - self.solution = solution - correctness = 'correct' if all_correct else 'incorrect' - if all_correct: - points = self.get_max_score() - else: - points = 0 - return CorrectMap(self.answer_id, correctness, npoints=points, msg=evaluation) - - def run_grader(self, submission): - if submission is None or submission == '': - submission = json.dumps(None) - - grader_file = os.path.dirname(os.path.normpath( - __file__)) + '/javascript_problem_grader.js' - outputs = self.call_node([grader_file, - self.grader, - json.dumps(self.grader_dependencies), - submission, - json.dumps(self.problem_state), - json.dumps(self.params)]).split('\n') - - all_correct = json.loads(outputs[0].strip()) - evaluation = outputs[1].strip() - solution = outputs[2].strip() - return (all_correct, evaluation, solution) - - def get_answers(self): - if self.solution is None: - (_, _, self.solution) = self.run_grader(None) - - return {self.answer_id: self.solution} - #----------------------------------------------------------------------------- @registry.register @@ -4119,7 +3915,6 @@ __all__ = [ ChoiceResponse, MultipleChoiceResponse, TrueFalseResponse, - JavascriptResponse, AnnotationResponse, ChoiceTextResponse, ] diff --git a/common/lib/capa/capa/templates/javascriptinput.html b/common/lib/capa/capa/templates/javascriptinput.html deleted file mode 100644 index 7d3810d2e8..0000000000 --- a/common/lib/capa/capa/templates/javascriptinput.html +++ /dev/null @@ -1,11 +0,0 @@ -<%page expression_filter="h"/> -
- -
-
-
-
-
- diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py index 076cba3315..12dc8184e9 100644 --- a/common/lib/capa/capa/tests/response_xml_factory.py +++ b/common/lib/capa/capa/tests/response_xml_factory.py @@ -608,60 +608,6 @@ class ImageResponseXMLFactory(ResponseXMLFactory): return input_element -class JavascriptResponseXMLFactory(ResponseXMLFactory): - """ Factory for producing XML """ - - def create_response_element(self, **kwargs): - """ Create the element. - - Uses **kwargs: - - *generator_src*: Name of the JS file to generate the problem. - *grader_src*: Name of the JS file to grade the problem. - *display_class*: Name of the class used to display the problem - *display_src*: Name of the JS file used to display the problem - *param_dict*: Dictionary of parameters to pass to the JS - """ - # Get **kwargs - generator_src = kwargs.get("generator_src", None) - grader_src = kwargs.get("grader_src", None) - display_class = kwargs.get("display_class", None) - display_src = kwargs.get("display_src", None) - param_dict = kwargs.get("param_dict", {}) - - # Both display_src and display_class given, - # or neither given - assert((display_src and display_class) or - (not display_src and not display_class)) - - # Create the element - response_element = etree.Element("javascriptresponse") - - if generator_src: - generator_element = etree.SubElement(response_element, "generator") - generator_element.set("src", str(generator_src)) - - if grader_src: - grader_element = etree.SubElement(response_element, "grader") - grader_element.set("src", str(grader_src)) - - if display_class and display_src: - display_element = etree.SubElement(response_element, "display") - display_element.set("class", str(display_class)) - display_element.set("src", str(display_src)) - - for (param_name, param_val) in param_dict.items(): - responseparam_element = etree.SubElement(response_element, "responseparam") - responseparam_element.set("name", str(param_name)) - responseparam_element.set("value", str(param_val)) - - return response_element - - def create_input_element(self, **kwargs): - """ Create the element """ - return etree.Element("javascriptinput") - - class MultipleChoiceResponseXMLFactory(ResponseXMLFactory): """ Factory for producing XML """ diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 469367a98d..959cadef91 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -162,51 +162,6 @@ class ChoiceGroupTest(unittest.TestCase): self.check_group('checkboxgroup', 'checkbox', '[]') -class JavascriptInputTest(unittest.TestCase): - ''' - The javascript input is a pretty straightforward pass-thru, but test it anyway - ''' - - def test_rendering(self): - params = "(1,2,3)" - - problem_state = "abc12',12&hi" - display_class = "a_class" - display_file = "my_files/hi.js" - - xml_str = """""".format( - params=params, - ps=quote_attr(problem_state), - dc=display_class, df=display_file) - - element = etree.fromstring(xml_str) - - state = { - 'value': '3', - 'response_data': RESPONSE_DATA - } - the_input = lookup_tag('javascriptinput')(test_capa_system(), element, state) - - context = the_input._get_render_context() # pylint: disable=protected-access - prob_id = 'prob_1_2' - expected = { - 'STATIC_URL': '/dummy-static/', - 'id': prob_id, - 'status': inputtypes.Status('unanswered'), - 'msg': '', - 'value': '3', - 'params': params, - 'display_file': display_file, - 'display_class': display_class, - 'problem_state': problem_state, - 'response_data': RESPONSE_DATA, - 'describedby_html': DESCRIBEDBY.format(status_id=prob_id) - } - - self.assertEqual(context, expected) - - class TextLineTest(unittest.TestCase): ''' Check that textline inputs work, with and without math. diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 1f92b6f915..e70ee0ae10 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -31,7 +31,6 @@ from capa.tests.response_xml_factory import ( CustomResponseXMLFactory, FormulaResponseXMLFactory, ImageResponseXMLFactory, - JavascriptResponseXMLFactory, MultipleChoiceResponseXMLFactory, NumericalResponseXMLFactory, OptionResponseXMLFactory, @@ -1342,46 +1341,6 @@ class ChoiceResponseTest(ResponseTest): # pylint: disable=missing-docstring self.assert_grade(problem, ['choice_1', 'choice_3'], 'incorrect') -class JavascriptResponseTest(ResponseTest): # pylint: disable=missing-docstring - xml_factory_class = JavascriptResponseXMLFactory - - def test_grade(self): - # Compile coffee files into javascript used by the response - coffee_file_path = os.path.dirname(__file__) + "/test_files/js/*.coffee" - os.system("node_modules/.bin/coffee -c %s" % (coffee_file_path)) - - capa_system = test_capa_system() - capa_system.can_execute_unsafe_code = lambda: True - problem = self.build_problem( - capa_system=capa_system, - generator_src="test_problem_generator.js", - grader_src="test_problem_grader.js", - display_class="TestProblemDisplay", - display_src="test_problem_display.js", - param_dict={'value': '4'}, - ) - - # Test that we get graded correctly - self.assert_grade(problem, json.dumps({0: 4}), "correct") - self.assert_grade(problem, json.dumps({0: 5}), "incorrect") - - def test_cant_execute_javascript(self): - # If the system says to disallow unsafe code execution, then making - # this problem will raise an exception. - capa_system = test_capa_system() - capa_system.can_execute_unsafe_code = lambda: False - - with self.assertRaises(LoncapaProblemError): - self.build_problem( - capa_system=capa_system, - generator_src="test_problem_generator.js", - grader_src="test_problem_grader.js", - display_class="TestProblemDisplay", - display_src="test_problem_display.js", - param_dict={'value': '4'}, - ) - - class NumericalResponseTest(ResponseTest): # pylint: disable=missing-docstring xml_factory_class = NumericalResponseXMLFactory diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.js b/common/lib/xmodule/xmodule/js/src/capa/display.js index aeb9c0998c..ab51436107 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.js +++ b/common/lib/xmodule/xmodule/js/src/capa/display.js @@ -997,24 +997,6 @@ return preprocessor.fn; } }, - javascriptinput: function(element) { - var container, data, display, displayClass, evaluation, params, problemState, submission, - submissionField; - data = $(element).find('.javascriptinput_data'); - params = data.data('params'); - submission = data.data('submission'); - evaluation = data.data('evaluation'); - problemState = data.data('problem_state'); - displayClass = window[data.data('display_class')]; - if (evaluation === '') { - evaluation = null; - } - container = $(element).find('.javascriptinput_container'); - submissionField = $(element).find('.javascriptinput_input'); - display = new displayClass(problemState, submission, evaluation, container, submissionField, params); - display.render(); - return display; - }, cminput: function(container) { var CodeMirrorEditor, CodeMirrorTextArea, element, id, linenumbers, mode, spaces, tabsize; element = $(container).find('textarea'); @@ -1064,14 +1046,6 @@ } return results; }, - javascriptinput: function(element, display, answers) { - var answer, answerId; - answerId = $(element).attr('id').split('_') - .slice(1) - .join('_'); - answer = JSON.parse(answers[answerId]); - return display.showAnswer(answer); - }, choicetextgroup: function(element, display, answers) { var answer, choice, inputId, i, len, results, $element; $element = $(element); @@ -1172,20 +1146,6 @@ } }; - Problem.prototype.inputtypeHideAnswerMethods = { - choicegroup: function(element) { - var $element = $(element); - return $element.find('label').removeClass('choicegroup_correct'); - }, - javascriptinput: function(element, display) { - return display.hideAnswer(); - }, - choicetextgroup: function(element) { - var $element = $(element); - return $element.find('section[id^="forinput"]').removeClass('choicetextgroup_show_correct'); - } - }; - /** * Used to keep the buttons disabled while operationCallback is running. *