From 2fab97c1660a65cc741b296f4a028067e28f71e5 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 26 Oct 2012 19:06:34 -0400 Subject: [PATCH] Refactor imageinput and crystallography. - also add a test for chemicalequationinput --- common/lib/capa/capa/inputtypes.py | 203 +++++++++--------- .../capa/capa/templates/crystallography.html | 18 +- .../lib/capa/capa/templates/vsepr_input.html | 24 +-- common/lib/capa/capa/tests/test_inputtypes.py | 121 ++++++++++- 4 files changed, 231 insertions(+), 135 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 5d271f257e..145eabd953 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -647,9 +647,8 @@ _reg(solution) #----------------------------------------------------------------------------- - -def imageinput(element, value, status, render_template, msg=''): - ''' +class ImageInput(InputTypeBase): + """ Clickable image as an input field. Element should specify the image source, height, and width, e.g. @@ -657,130 +656,120 @@ def imageinput(element, value, status, render_template, msg=''): TODO: showanswer for imageimput does not work yet - need javascript to put rectangle over acceptable area of image. - ''' - eid = element.get('id') - src = element.get('src') - height = element.get('height') - width = element.get('width') + """ - # if value is of the form [x,y] then parse it and send along coordinates of previous answer - m = re.match('\[([0-9]+),([0-9]+)]', value.strip().replace(' ', '')) - if m: - (gx, gy) = [int(x) - 15 for x in m.groups()] - else: - (gx, gy) = (0, 0) + template = "imageinput.html" + tags = ['imageinput'] - context = { - 'id': eid, - 'value': value, - 'height': height, - 'width': width, - 'src': src, - 'gx': gx, - 'gy': gy, - 'state': status, # to change - 'msg': msg, # to change - } - html = render_template("imageinput.html", context) - return etree.XML(html) + def __init__(self, system, xml, state): + super(ImageInput, self).__init__(system, xml, state) + self.src = xml.get('src') + self.height = xml.get('height') + self.width = xml.get('width') -_reg(imageinput) + # if value is of the form [x,y] then parse it and send along coordinates of previous answer + m = re.match('\[([0-9]+),([0-9]+)]', self.value.strip().replace(' ', '')) + if m: + # TODO (vshnayder): why is there a "-15" here?? + (self.gx, self.gy) = [int(x) - 15 for x in m.groups()] + else: + (self.gx, self.gy) = (0, 0) -def crystallography(element, value, status, render_template, msg=''): - eid = element.get('id') - if eid is None: - msg = 'cryst has no id: it probably appears outside of a known response type' - msg += "\nSee problem XML source line %s" % getattr(element, 'sourceline', '') - raise Exception(msg) - height = element.get('height') - width = element.get('width') - display_file = element.get('display_file') + def _get_render_context(self): - count = int(eid.split('_')[-2]) - 1 # HACK - size = element.get('size') - # if specified, then textline is hidden and id is stored in div of name given by hidden - hidden = element.get('hidden', '') - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - value = saxutils.escape(value, escapedict) - - context = {'id': eid, - 'value': value, - 'state': status, - 'count': count, - 'size': size, - 'msg': msg, - 'hidden': hidden, - 'inline': element.get('inline', ''), - 'width': width, - 'height': height, - 'display_file': display_file, + context = {'id': self.id, + 'value': self.value, + 'height': self.height, + 'width': self.width, + 'src': self.src, + 'gx': self.gx, + 'gy': self.gy, + 'state': self.status, # to change (VS: to what??) + 'msg': self.msg, # to change } + return context - html = render_template("crystallography.html", context) +register_input_class(ImageInput) - try: - xhtml = etree.XML(html) - except Exception as err: - # TODO: needs to be self.system.DEBUG - but can't access system - if True: - log.debug('[inputtypes.crystallography] failed to parse XML for:\n%s' % html) - raise - return xhtml +#----------------------------------------------------------------------------- -_reg(crystallography) +class Crystallography(InputTypeBase): + """ + An input for crystallography -- user selects 3 points on the axes, and we get a plane. + TODO: what's the actual value format? + """ -def vsepr_input(element, value, status, render_template, msg=''): - eid = element.get('id') - if eid is None: - msg = 'cryst has no id: it probably appears outside of a known response type' - msg += "\nSee problem XML source line %s" % getattr(element, 'sourceline', '') - raise Exception(msg) - height = element.get('height') - width = element.get('width') - display_file = element.get('display_file') + template = "crystallography.html" + tags = ['crystallography'] - count = int(eid.split('_')[-2]) - 1 # HACK - size = element.get('size') - # if specified, then textline is hidden and id is stored in div of name given by hidden - hidden = element.get('hidden', '') - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - value = saxutils.escape(value, escapedict) + def __init__(self, system, xml, state): + super(Crystallography, self).__init__(system, xml, state) - molecules = element.get('molecules') - geometries = element.get('geometries') + self.height = xml.get('height') + self.width = xml.get('width') + self.size = xml.get('size') - context = {'id': eid, - 'value': value, - 'state': status, - 'count': count, - 'size': size, - 'msg': msg, - 'hidden': hidden, - 'inline': element.get('inline', ''), - 'width': width, - 'height': height, - 'display_file': display_file, - 'molecules': molecules, - 'geometries': geometries, + # if specified, then textline is hidden and id is stored in div of name given by hidden + self.hidden = xml.get('hidden', '') + + # Escape answers with quotes, so they don't crash the system! + escapedict = {'"': '"'} + self.value = saxutils.escape(self.value, escapedict) + + def _get_render_context(self): + context = {'id': self.id, + 'value': self.value, + 'state': self.status, + 'size': self.size, + 'msg': self.msg, + 'hidden': self.hidden, + 'width': self.width, + 'height': self.height, } + return context - html = render_template("vsepr_input.html", context) +register_input_class(Crystallography) - try: - xhtml = etree.XML(html) - except Exception as err: - # TODO: needs to be self.system.DEBUG - but can't access system - if True: - log.debug('[inputtypes.vsepr_input] failed to parse XML for:\n%s' % html) - raise - return xhtml +# ------------------------------------------------------------------------- -_reg(vsepr_input) +class VseprInput(InputTypeBase): + """ + Input for molecular geometry--show possible structures, let student + pick structure and label positions with atoms or electron pairs. + """ + template = 'vsepr_input.html' + tags = ['vsepr_input'] + + def __init__(self, system, xml, state): + super(ImageInput, self).__init__(system, xml, state) + + self.height = xml.get('height') + self.width = xml.get('width') + + # Escape answers with quotes, so they don't crash the system! + escapedict = {'"': '"'} + self.value = saxutils.escape(self.value, escapedict) + + self.molecules = xml.get('molecules') + self.geometries = xml.get('geometries') + + def _get_render_context(self): + + context = {'id': self.id, + 'value': self.value, + 'state': self.status, + 'msg': self.msg, + 'width': self.width, + 'height': self.height, + 'molecules': self.molecules, + 'geometries': self.geometries, + } + return context + +register_input_class(VseprInput) #-------------------------------------------------------------------------------- diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 1fc638b356..71578f1fa0 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -1,19 +1,19 @@ -<% doinline = "inline" if inline else "" %> - -
+
-
+
+
+
% if state == 'unsubmitted': -
+
% elif state == 'correct': -
+
% elif state == 'incorrect': -
+
% elif state == 'incomplete': -
+
% endif % if hidden:
@@ -45,7 +45,7 @@ % if msg: ${msg|n} % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden: +% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif
diff --git a/common/lib/capa/capa/templates/vsepr_input.html b/common/lib/capa/capa/templates/vsepr_input.html index 588e53c914..5194551d50 100644 --- a/common/lib/capa/capa/templates/vsepr_input.html +++ b/common/lib/capa/capa/templates/vsepr_input.html @@ -1,6 +1,4 @@ -<% doinline = "inline" if inline else "" %> - -
+
@@ -14,25 +12,17 @@
% if state == 'unsubmitted': -
+
% elif state == 'correct': -
+
% elif state == 'incorrect': -
+
% elif state == 'incomplete': -
- % endif - % if hidden: -
+
% endif

@@ -52,7 +42,7 @@ % if msg: ${msg|n} % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden: +% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:

% endif diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 573f52a01a..773f2b3519 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -4,6 +4,7 @@ Tests of input types (and actually responsetypes too). TODO: - test unicode in values, parameters, etc. - test various html escapes +- test funny xml chars -- should never get xml parse error if things are escaped properly. """ from datetime import datetime @@ -351,8 +352,7 @@ class SchematicTest(unittest.TestCase): value = 'three resistors and an oscilating pendulum' state = {'value': value, - 'status': 'unsubmitted', - 'feedback' : {'message': '3'}, } + 'status': 'unsubmitted'} the_input = inputtypes.get_class_for_tag('schematic')(system, element, state) @@ -371,3 +371,120 @@ class SchematicTest(unittest.TestCase): self.assertEqual(context, expected) + +class ImageInputTest(unittest.TestCase): + ''' + Check that image inputs work + ''' + + def check(self, value, egx, egy): + height = '78' + width = '427' + src = 'http://www.edx.org/cowclicker.jpg' + + xml_str = """""".format(s=src, h=height, w=width) + + element = etree.fromstring(xml_str) + + state = {'value': value, + 'status': 'unsubmitted'} + + the_input = inputtypes.get_class_for_tag('imageinput')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': value, + 'state': 'unsubmitted', + 'width': width, + 'height': height, + 'src': src, + 'gx': egx, + 'gy': egy, + 'state': 'unsubmitted', + 'msg': ''} + + self.assertEqual(context, expected) + + def test_with_value(self): + self.check('[50,40]', 35, 25) + + def test_without_value(self): + self.check('', 0, 0) + + def test_corrupt_values(self): + self.check('[12', 0, 0) + self.check('[12, a]', 0, 0) + self.check('[12 10]', 0, 0) + self.check('[12]', 0, 0) + self.check('[12 13 14]', 0, 0) + + + +class CrystallographyTest(unittest.TestCase): + ''' + Check that crystallography inputs work + ''' + + def test_rendering(self): + height = '12' + width = '33' + size = '10' + + xml_str = """""".format(h=height, w=width, s=size) + + element = etree.fromstring(xml_str) + + value = 'abc' + state = {'value': value, + 'status': 'unsubmitted'} + + the_input = inputtypes.get_class_for_tag('crystallography')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': value, + 'state': 'unsubmitted', + 'size': size, + 'msg': '', + 'hidden': '', + 'width': width, + 'height': height, + } + + self.assertEqual(context, expected) + + +class ChemicalEquationTest(unittest.TestCase): + ''' + Check that chemical equation inputs work. + ''' + + def test_rendering(self): + size = "42" + xml_str = """""".format(size=size) + + element = etree.fromstring(xml_str) + + state = {'value': 'H2OYeah',} + the_input = inputtypes.get_class_for_tag('chemicalequationinput')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': 'H2OYeah', + 'status': 'unanswered', + 'size': size, + 'previewer': '/static/js/capa/chemical_equation_preview.js', + } + self.assertEqual(context, expected) +