Commit 81ff20c0 by Christina Roberts Committed by GitHub

Merge pull request #14080 from edx/christina/delete-javascriptinput

Delete javascriptinput and javascriptresponse.
parents 6ed02f3b a949efc6
......@@ -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'
#-----------------------------------------------------------------------------
......
......@@ -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 = {'"': '&quot;'}
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,
]
......
<%page expression_filter="h"/>
<div class="javascriptinput capa_inputtype" id="inputtype_${id}">
<input type="hidden" name="input_${id}" id="input_${id}" class="javascriptinput_input"/>
<div class="javascriptinput_data" data-display_class="${display_class}"
data-problem_state="${problem_state}" data-params="${params}"
data-submission="${value}" data-evaluation="${msg}">
</div>
<div class="script_placeholder" data-src="/static/js/${display_file}"></div>
<div class="javascriptinput_container"></div>
</div>
......@@ -608,60 +608,6 @@ class ImageResponseXMLFactory(ResponseXMLFactory):
return input_element
class JavascriptResponseXMLFactory(ResponseXMLFactory):
""" Factory for producing <javascriptresponse> XML """
def create_response_element(self, **kwargs):
""" Create the <javascriptresponse> 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 <javascriptresponse> 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 <javascriptinput> element """
return etree.Element("javascriptinput")
class MultipleChoiceResponseXMLFactory(ResponseXMLFactory):
""" Factory for producing <multiplechoiceresponse> XML """
......
......@@ -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<there>"
display_class = "a_class"
display_file = "my_files/hi.js"
xml_str = """<javascriptinput id="prob_1_2" params="{params}" problem_state="{ps}"
display_class="{dc}" display_file="{df}"/>""".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.
......
......@@ -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
......
......@@ -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.
*
......
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