From 001d2458309b2ab3f38dadcc58583f44b4fca375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 16:14:24 -0700 Subject: [PATCH 01/10] Save when setvalue actions to survey --- pyxform/builder.py | 33 +++++- pyxform/survey.py | 9 ++ pyxform/tests_v1/test_when.py | 191 ++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 pyxform/tests_v1/test_when.py diff --git a/pyxform/builder.py b/pyxform/builder.py index bf4a39e27..6041b4f8a 100644 --- a/pyxform/builder.py +++ b/pyxform/builder.py @@ -4,6 +4,7 @@ """ import copy import os +import re from pyxform import file_utils, utils from pyxform.errors import PyXFormError @@ -74,6 +75,9 @@ def __init__(self, **kwargs): self._add_none_option = False self.set_sections(kwargs.get("sections", {})) + # dictionary of setvalue target and value tuple indexed by triggering element + self.setvalues_by_triggering_ref = {} + def set_sections(self, sections): """ sections is a dict of python objects, a key in this dict is @@ -92,7 +96,12 @@ def create_survey_element_from_dict(self, d): if "add_none_option" in d: self._add_none_option = d["add_none_option"] if d["type"] in self.SECTION_CLASSES: - return self._create_section_from_dict(d) + section = self._create_section_from_dict(d) + + if d["type"] == "survey": + section["values_by_triggering_ref"] = self.setvalues_by_triggering_ref + + return section elif d["type"] == "loop": return self._create_loop_from_dict(d) elif d["type"] == "include": @@ -109,10 +118,31 @@ def create_survey_element_from_dict(self, d): elif d["type"] == "xml-external": return ExternalInstance(**d) else: + self._save_when_as_setvalue(d) + return self._create_question_from_dict( d, copy_json_dict(QUESTION_TYPE_DICT), self._add_none_option ) + def _save_when_as_setvalue(self, d): + if "when" in d: + if "bind" in d and "calculate" in d["bind"]: + triggering_ref = re.sub(r"\s+", "", d["when"]) + + if triggering_ref in self.setvalues_by_triggering_ref: + self.setvalues_by_triggering_ref[triggering_ref].append( + (d["name"], d["bind"]["calculate"]) + ) + else: + self.setvalues_by_triggering_ref[triggering_ref] = [ + (d["name"], d["bind"]["calculate"]) + ] + else: + raise PyXFormError( + "There should be a calculation for the ${%s} field because it specifies a triggering event in the 'when' column." + % d["name"] + ) + @staticmethod def _create_question_from_dict(d, question_type_dictionary, add_none_option=False): question_type_str = d["type"] @@ -353,7 +383,6 @@ def create_survey( if title is not None: survey.title = title survey.def_lang = default_language - return survey diff --git a/pyxform/survey.py b/pyxform/survey.py index a31d48a03..5ac33f957 100644 --- a/pyxform/survey.py +++ b/pyxform/survey.py @@ -115,6 +115,7 @@ class Survey(Section): { "_xpath": dict, "_created": datetime.now, # This can't be dumped to json + "setvalues_by_triggering_ref": dict, "title": unicode, "id_string": unicode, "sms_keyword": unicode, @@ -188,6 +189,11 @@ def xml(self): """ self.validate() self._setup_xpath_dictionary() + + for triggering_reference in self.setvalues_by_triggering_ref.keys(): + # trigger exception if reference can't be resolved + self.insert_xpaths(triggering_reference, self) + body_kwargs = {} if hasattr(self, constants.STYLE) and getattr(self, constants.STYLE): body_kwargs["class"] = getattr(self, constants.STYLE) @@ -200,6 +206,9 @@ def xml(self): **nsmap ) + def get_setvalue_actions_for_question_reference(self, question): + return self.setvalues_by_triggering_ref.get("${%s}" % question.name) + @staticmethod def _generate_static_instances(list_name, choice_list): """ diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_when.py new file mode 100644 index 000000000..c591e0656 --- /dev/null +++ b/pyxform/tests_v1/test_when.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +""" +Test handling setvalue of 'when' column in forms +""" + +from pyxform.tests_v1.pyxform_test_case import PyxformTestCase + + +class WhenSetvalueTests(PyxformTestCase): + """ + Handling 'when' column setvalue(s) tests + """ + + def test_when_without_calculation_gives_error(self): + self.assertPyxformXform( + name="when-missing-ref", + md=""" + | survey | | | | | + | | type | name | label | when | + | | integer | a | A | | + | | dateTime | b | B | ${a} | + """, + errored=True, + error__contains=[ + "There should be a calculation for the ${b} field because it specifies a triggering event in the 'when' column." + ], + ) + + def test_when_reference_to_nonexistent_node_gives_error(self): + self.assertPyxformXform( + name="when-missing-ref", + md=""" + | survey | | | | | | + | | type | name | label | calculation | when | + | | dateTime | b | B | now() | ${a} | + """, + errored=True, + error__contains=[ + "There has been a problem trying to replace ${a} with the XPath to the survey element named 'a'. There is no survey element with this name." + ], + ) + + # def test_handling_when_column_no_label_and_no_hint(self): + # md = """ + # | survey | | | | | | + # | | type | name | label | calculation | when | + # | | text | a | Enter text | | | + # | | dateTime | b | | now() | ${a} | + # """ + # self.assertPyxformXform( + # md=md, + # name="when-column", + # id_string="id", + # model__contains=["", "",], + # xml__contains=[ + # '', + # '', + # '', + # ], + # xml__excludes=[ + # '' + # '', + # ], + # ) + + # def test_handling_when_column_with_label_and_hint(self): + # md = """ + # | survey | | | | | | + # | | type | name | label | calculation | when | + # | | text | a | Enter text | | | + # | | dateTime | c | Date of diagnostic | now() | ${a} | + # """ + + # self.assertPyxformXform( + # md=md, + # name="when-column", + # id_string="id", + # model__contains=["", "",], + # xml__contains=[ + # '', + # '', + # '', + # '', + # ], + # xml__excludes=[ + # '' + # ], + # ) + + # def test_handling_multiple_when_column(self): + # md = """ + # | survey | | | | | | | + # | | type | name | label | calculation | when | hint | + # | | text | a | Enter text | | | | + # | | integer | b | | 1+1 | ${a} | | + # | | dateTime | c | | now() | ${a} | A hint | + # """ + + # self.assertPyxformXform( + # md=md, + # name="when-column", + # id_string="id", + # model__contains=["", "", "",], + # xml__contains=[ + # '', + # '', + # '', + # '', + # '', + # '', + # ], + # xml__excludes=[ + # '', + # '', + # ], + # ) + + # def test_handling_when_column_with_no_calculation(self): + # md = """ + # | survey | | | | | | + # | | type | name | label | calculation | when | + # | | text | a | Enter text | | | + # | | dateTime | d | Date of something | | ${a} | + # """ + + # self.assertPyxformXform( + # md=md, + # name="when-column", + # id_string="id", + # model__contains=["", "",], + # xml__contains=[ + # '', + # '', + # '', + # '', + # ], + # xml__excludes=[ + # '' + # ], + # ) + + # def test_handling_when_column_with_no_calculation_no_label_no_hint(self): + # md = """ + # | survey | | | | | | + # | | type | name | label | calculation | when | + # | | text | a | Enter text | | | + # | | decimal | e | | | ${a} | + # """ + + # self.assertPyxformXform( + # md=md, + # name="when-column", + # id_string="id", + # model__contains=["", "",], + # xml__contains=[ + # '', + # '', + # '', + # ], + # xml__excludes=[ + # '', + # '', + # ], + # ) + + # def test_handling_when_column_in_group(self): + # md = """ + # | survey | | | | | | + # | | type | name | label | calculation | when | + # | | text | a | Enter text | | | + # | | begin_group | grp | | | ${a} | + # | | dateTime | c | Date of diagnostic | now() | ${a} | + # | | end_group | | | | | + # """ + + # self.assertPyxformXform( + # md=md, + # name="when-column", + # id_string="id", + # model__contains=["", "", "",], + # xml__contains=[ + # '', + # '', + # '', + # '', + # '', + # ], + # xml__excludes=[ + # '', + # ], + # ) From e78039747a397c4150942e327f1e6684849a0e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 16:26:27 -0700 Subject: [PATCH 02/10] Add support for typed calculates --- pyxform/question.py | 3 ++ pyxform/tests_v1/test_typed_calculates.py | 59 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 pyxform/tests_v1/test_typed_calculates.py diff --git a/pyxform/question.py b/pyxform/question.py index 7a852f0c5..460327c7a 100644 --- a/pyxform/question.py +++ b/pyxform/question.py @@ -41,6 +41,9 @@ class InputQuestion(Question): """ def xml_control(self): + if "calculate" in self.bind and not (self.label or self.hint): + return None + control_dict = self.control label_and_hint = self.xml_label_and_hint() survey = self.get_root() diff --git a/pyxform/tests_v1/test_typed_calculates.py b/pyxform/tests_v1/test_typed_calculates.py new file mode 100644 index 000000000..42e9f0c79 --- /dev/null +++ b/pyxform/tests_v1/test_typed_calculates.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +""" +Test that any row with a calculation becomes a calculate of the row's type or of type string if the type is "calculate". A hint or label +error should only be thrown for a row without a calculation. +""" + +from pyxform.tests_v1.pyxform_test_case import PyxformTestCase + + +class TypedCalculatesTest(PyxformTestCase): + def test_xls_type_calculate_has_type_string(self): + self.assertPyxformXform( + name="calculate-type", + md=""" + | survey | | | | | + | | type | name | label | calculation | + | | calculate| a | | 2 * 2 | + """, + xml__contains=[ + '' + ], + ) + + def test_other_xls_type_with_calculation_is_passed_through(self): + self.assertPyxformXform( + name="non-calculate-type", + md=""" + | survey | | | | | + | | type | name | label | calculation | + | | integer | a | | 2 * 2 | + """, + xml__contains=[ + '' + ], + ) + + def test_other_xls_type_with_calculation_and_no_label_has_no_control(self): + self.assertPyxformXform( + name="no-label", + md=""" + | survey | | | | | + | | type | name | label | calculation | + | | integer | a | | 2 * 2 | + """, + instance__contains=[""], + xml__excludes=["input"], + ) + + def test_row_without_label_or_calculation_throws_error(self): + self.assertPyxformXform( + name="no-label", + md=""" + | survey | | | | + | | type | name | label | + | | integer | a | | + """, + errored=True, + error__contains="The survey element named 'a' has no label or hint.", + ) From bc6c915387eaaca5ba668c0c98a303e29eb6e03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 16:43:52 -0700 Subject: [PATCH 03/10] Fail if when value is not a reference --- pyxform/builder.py | 2 +- pyxform/survey.py | 5 ++++- pyxform/tests_v1/test_when.py | 25 ++++++++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pyxform/builder.py b/pyxform/builder.py index 6041b4f8a..1785583ae 100644 --- a/pyxform/builder.py +++ b/pyxform/builder.py @@ -99,7 +99,7 @@ def create_survey_element_from_dict(self, d): section = self._create_section_from_dict(d) if d["type"] == "survey": - section["values_by_triggering_ref"] = self.setvalues_by_triggering_ref + section.setvalues_by_triggering_ref = self.setvalues_by_triggering_ref return section elif d["type"] == "loop": diff --git a/pyxform/survey.py b/pyxform/survey.py index 5ac33f957..2600f7ef0 100644 --- a/pyxform/survey.py +++ b/pyxform/survey.py @@ -191,7 +191,10 @@ def xml(self): self._setup_xpath_dictionary() for triggering_reference in self.setvalues_by_triggering_ref.keys(): - # trigger exception if reference can't be resolved + if not(re.match(BRACKETED_TAG_REGEX, triggering_reference)): + raise PyXFormError("Only references to other fields are allowed in the 'when' column.") + + # try to resolve reference and fail if can't self.insert_xpaths(triggering_reference, self) body_kwargs = {} diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_when.py index c591e0656..8bda3fc29 100644 --- a/pyxform/tests_v1/test_when.py +++ b/pyxform/tests_v1/test_when.py @@ -7,10 +7,6 @@ class WhenSetvalueTests(PyxformTestCase): - """ - Handling 'when' column setvalue(s) tests - """ - def test_when_without_calculation_gives_error(self): self.assertPyxformXform( name="when-missing-ref", @@ -18,7 +14,7 @@ def test_when_without_calculation_gives_error(self): | survey | | | | | | | type | name | label | when | | | integer | a | A | | - | | dateTime | b | B | ${a} | + | | dateTime | b | | ${a} | """, errored=True, error__contains=[ @@ -32,7 +28,7 @@ def test_when_reference_to_nonexistent_node_gives_error(self): md=""" | survey | | | | | | | | type | name | label | calculation | when | - | | dateTime | b | B | now() | ${a} | + | | dateTime | b | | now() | ${a} | """, errored=True, error__contains=[ @@ -40,6 +36,21 @@ def test_when_reference_to_nonexistent_node_gives_error(self): ], ) + def test_when_with_something_other_than_node_ref_gives_error(self): + self.assertPyxformXform( + name="when-invalid-ref", + md=""" + | survey | | | | | | + | | type | name | label | calculation | when | + | | dateTime | b | | now() | 6 | + """, + errored=True, + error__contains=[ + "Only references to other fields are allowed in the 'when' column." + ] + ) + + # # def test_handling_when_column_no_label_and_no_hint(self): # md = """ # | survey | | | | | | @@ -62,7 +73,7 @@ def test_when_reference_to_nonexistent_node_gives_error(self): # '', # ], # ) - + # # def test_handling_when_column_with_label_and_hint(self): # md = """ # | survey | | | | | | From 741f853cb2d3a47b63e4aa5c3459b517f10d05fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 21:31:21 -0700 Subject: [PATCH 04/10] Generate nested setvalue XML --- pyxform/builder.py | 27 +++--- pyxform/question.py | 39 +++++--- pyxform/survey.py | 6 +- pyxform/survey_element.py | 4 + pyxform/tests_v1/test_when.py | 170 +++++++++++++++------------------- 5 files changed, 123 insertions(+), 123 deletions(-) diff --git a/pyxform/builder.py b/pyxform/builder.py index 1785583ae..0c998b49b 100644 --- a/pyxform/builder.py +++ b/pyxform/builder.py @@ -118,30 +118,25 @@ def create_survey_element_from_dict(self, d): elif d["type"] == "xml-external": return ExternalInstance(**d) else: - self._save_when_as_setvalue(d) + self._save_when_as_setvalue_and_remove_calculate(d) return self._create_question_from_dict( d, copy_json_dict(QUESTION_TYPE_DICT), self._add_none_option ) - def _save_when_as_setvalue(self, d): + def _save_when_as_setvalue_and_remove_calculate(self, d): if "when" in d: - if "bind" in d and "calculate" in d["bind"]: - triggering_ref = re.sub(r"\s+", "", d["when"]) + triggering_ref = re.sub(r"\s+", "", d["when"]) + value = d["bind"]["calculate"] if "bind" in d and "calculate" in d["bind"] else '' - if triggering_ref in self.setvalues_by_triggering_ref: - self.setvalues_by_triggering_ref[triggering_ref].append( - (d["name"], d["bind"]["calculate"]) - ) - else: - self.setvalues_by_triggering_ref[triggering_ref] = [ - (d["name"], d["bind"]["calculate"]) - ] - else: - raise PyXFormError( - "There should be a calculation for the ${%s} field because it specifies a triggering event in the 'when' column." - % d["name"] + if triggering_ref in self.setvalues_by_triggering_ref: + self.setvalues_by_triggering_ref[triggering_ref].append( + (d["name"], value) ) + else: + self.setvalues_by_triggering_ref[triggering_ref] = [ + (d["name"], value) + ] @staticmethod def _create_question_from_dict(d, question_type_dictionary, add_none_option=False): diff --git a/pyxform/question.py b/pyxform/question.py index 460327c7a..cd7f94aa3 100644 --- a/pyxform/question.py +++ b/pyxform/question.py @@ -31,6 +31,28 @@ def xml_instance(self, **kwargs): return node(self.name, **attributes) def xml_control(self): + xml_node = self.build_xml() + + if xml_node: + self.nest_setvalues(xml_node) + + return xml_node + + def nest_setvalues(self, xml_node): + nested_setvalues = self.get_root().get_setvalues_for_question_name(self.name) + + if nested_setvalues: + for setvalue in nested_setvalues: + setvalue_node = node( + "setvalue", + ref=self.get_root().insert_xpaths('${%s}' % setvalue[0], self), + value=setvalue[1], + event='xforms-value-changed', + ) + + xml_node.appendChild(setvalue_node) + + def build_xml(self): return None @@ -40,7 +62,7 @@ class InputQuestion(Question): dates, geopoints, barcodes ... """ - def xml_control(self): + def build_xml(self): if "calculate" in self.bind and not (self.label or self.hint): return None @@ -69,7 +91,7 @@ def xml_control(self): class TriggerQuestion(Question): - def xml_control(self): + def build_xml(self): control_dict = self.control survey = self.get_root() # Resolve field references in attributes @@ -83,7 +105,7 @@ class UploadQuestion(Question): def _get_media_type(self): return self.control["mediatype"] - def xml_control(self): + def build_xml(self): control_dict = self.control survey = self.get_root() # Resolve field references in attributes @@ -136,7 +158,7 @@ def validate(self): for choice in descendants: choice.validate() - def xml_control(self): + def build_xml(self): assert self.bind["type"] in ["string", "odk:rank"] survey = self.get_root() control_dict = self.control.copy() @@ -253,7 +275,7 @@ def add_tag(self, **kwargs): tag = Tag(**kwargs) self.add_child(tag) - def xml_control(self): + def build_xml(self): control_dict = self.control control_dict["ref"] = self.get_xpath() control_dict["mediatype"] = self._get_media_type() @@ -266,12 +288,7 @@ def xml_control(self): class RangeQuestion(Question): - """ - This control string is the same for: strings, integers, decimals, - dates, geopoints, barcodes ... - """ - - def xml_control(self): + def build_xml(self): control_dict = self.control label_and_hint = self.xml_label_and_hint() survey = self.get_root() diff --git a/pyxform/survey.py b/pyxform/survey.py index 2600f7ef0..9fbd45fcb 100644 --- a/pyxform/survey.py +++ b/pyxform/survey.py @@ -209,8 +209,8 @@ def xml(self): **nsmap ) - def get_setvalue_actions_for_question_reference(self, question): - return self.setvalues_by_triggering_ref.get("${%s}" % question.name) + def get_setvalues_for_question_name(self, question_name): + return self.setvalues_by_triggering_ref.get("${%s}" % question_name) @staticmethod def _generate_static_instances(list_name, choice_list): @@ -842,7 +842,7 @@ def _var_repl_function(self, matchobj, context, use_current=False): ): xpath, context_xpath = self._xpath[name], context.get_xpath() # share same root i.e repeat_a from /data/repeat_a/... - if xpath.split("/")[2] == context_xpath.split("/")[2]: + if len(context_xpath.split("/")) > 2 and xpath.split("/")[2] == context_xpath.split("/")[2]: # if context xpath and target xpath fall under the same # repeat use relative xpath referencing. steps, ref_path = share_same_repeat_parent(self, xpath, context_xpath) diff --git a/pyxform/survey_element.py b/pyxform/survey_element.py index d1d0c5997..cf5d5527e 100644 --- a/pyxform/survey_element.py +++ b/pyxform/survey_element.py @@ -69,6 +69,7 @@ class SurveyElement(dict): "flat": lambda: False, "action": unicode, "list_name": unicode, + "when": unicode } def _default(self): @@ -408,6 +409,9 @@ def xml_binding(self): # Don't generate bind element for flat groups. return None if bind_dict: + if self.when and "calculate" in self.bind: # the expression goes in a setvalue action + del bind_dict["calculate"] + for k, v in bind_dict.items(): # I think all the binding conversions should be happening on # the xls2json side. diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_when.py index 8bda3fc29..24719572c 100644 --- a/pyxform/tests_v1/test_when.py +++ b/pyxform/tests_v1/test_when.py @@ -7,21 +7,6 @@ class WhenSetvalueTests(PyxformTestCase): - def test_when_without_calculation_gives_error(self): - self.assertPyxformXform( - name="when-missing-ref", - md=""" - | survey | | | | | - | | type | name | label | when | - | | integer | a | A | | - | | dateTime | b | | ${a} | - """, - errored=True, - error__contains=[ - "There should be a calculation for the ${b} field because it specifies a triggering event in the 'when' column." - ], - ) - def test_when_reference_to_nonexistent_node_gives_error(self): self.assertPyxformXform( name="when-missing-ref", @@ -50,81 +35,80 @@ def test_when_with_something_other_than_node_ref_gives_error(self): ] ) - # - # def test_handling_when_column_no_label_and_no_hint(self): - # md = """ - # | survey | | | | | | - # | | type | name | label | calculation | when | - # | | text | a | Enter text | | | - # | | dateTime | b | | now() | ${a} | - # """ - # self.assertPyxformXform( - # md=md, - # name="when-column", - # id_string="id", - # model__contains=["", "",], - # xml__contains=[ - # '', - # '', - # '', - # ], - # xml__excludes=[ - # '' - # '', - # ], - # ) - # - # def test_handling_when_column_with_label_and_hint(self): - # md = """ - # | survey | | | | | | - # | | type | name | label | calculation | when | - # | | text | a | Enter text | | | - # | | dateTime | c | Date of diagnostic | now() | ${a} | - # """ + def test_handling_when_column_no_label_and_no_hint(self): + md = """ + | survey | | | | | | + | | type | name | label | calculation | when | + | | text | a | Enter text | | | + | | dateTime | b | | now() | ${a} | + """ + self.assertPyxformXform( + md=md, + name="when-column", + id_string="id", + model__contains=["", "",], + xml__contains=[ + '', + '', + '', + ], + xml__excludes=[ + '' + '', + ], + ) - # self.assertPyxformXform( - # md=md, - # name="when-column", - # id_string="id", - # model__contains=["", "",], - # xml__contains=[ - # '', - # '', - # '', - # '', - # ], - # xml__excludes=[ - # '' - # ], - # ) + def test_handling_when_column_with_label_and_hint(self): + md = """ + | survey | | | | | | + | | type | name | label | calculation | when | + | | text | a | Enter text | | | + | | dateTime | c | Date of diagnostic | now() | ${a} | + """ - # def test_handling_multiple_when_column(self): - # md = """ - # | survey | | | | | | | - # | | type | name | label | calculation | when | hint | - # | | text | a | Enter text | | | | - # | | integer | b | | 1+1 | ${a} | | - # | | dateTime | c | | now() | ${a} | A hint | - # """ + self.assertPyxformXform( + md=md, + name="when-column", + id_string="id", + model__contains=["", "",], + xml__contains=[ + '', + '', + '', + '', + ], + xml__excludes=[ + '' + ] + ) - # self.assertPyxformXform( - # md=md, - # name="when-column", - # id_string="id", - # model__contains=["", "", "",], - # xml__contains=[ - # '', - # '', - # '', - # '', - # '', - # '', - # ], - # xml__excludes=[ - # '', - # '', - # ], - # ) + def test_handling_multiple_when_column(self): + md = """ + | survey | | | | | | | + | | type | name | label | calculation | when | hint | + | | text | a | Enter text | | | | + | | integer | b | | 1+1 | ${a} | | + | | dateTime | c | | now() | ${a} | A hint | + """ + + self.assertPyxformXform( + md=md, + name="when-column", + id_string="id", + model__contains=["", "", "",], + xml__contains=[ + '', + '', + '', + '', + '', + '', + ], + xml__excludes=[ + '', + '', + ], + ) # def test_handling_when_column_with_no_calculation(self): # md = """ @@ -133,7 +117,7 @@ def test_when_with_something_other_than_node_ref_gives_error(self): # | | text | a | Enter text | | | # | | dateTime | d | Date of something | | ${a} | # """ - + # # self.assertPyxformXform( # md=md, # name="when-column", @@ -147,9 +131,9 @@ def test_when_with_something_other_than_node_ref_gives_error(self): # ], # xml__excludes=[ # '' - # ], + # ],debug=True # ) - + # # def test_handling_when_column_with_no_calculation_no_label_no_hint(self): # md = """ # | survey | | | | | | @@ -157,7 +141,7 @@ def test_when_with_something_other_than_node_ref_gives_error(self): # | | text | a | Enter text | | | # | | decimal | e | | | ${a} | # """ - + # # self.assertPyxformXform( # md=md, # name="when-column", @@ -173,7 +157,7 @@ def test_when_with_something_other_than_node_ref_gives_error(self): # '', # ], # ) - + # # def test_handling_when_column_in_group(self): # md = """ # | survey | | | | | | @@ -183,7 +167,7 @@ def test_when_with_something_other_than_node_ref_gives_error(self): # | | dateTime | c | Date of diagnostic | now() | ${a} | # | | end_group | | | | | # """ - + # # self.assertPyxformXform( # md=md, # name="when-column", From 9f8c694f183079131b3e8a07b15bd91f41f2eea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 21:44:52 -0700 Subject: [PATCH 05/10] Add support for empty calculation --- pyxform/question.py | 11 ++- pyxform/tests_v1/test_when.py | 148 +++++++++++++++++----------------- 2 files changed, 81 insertions(+), 78 deletions(-) diff --git a/pyxform/question.py b/pyxform/question.py index cd7f94aa3..0f0390e81 100644 --- a/pyxform/question.py +++ b/pyxform/question.py @@ -43,11 +43,14 @@ def nest_setvalues(self, xml_node): if nested_setvalues: for setvalue in nested_setvalues: + setvalue_attrs = {"ref": self.get_root().insert_xpaths('${%s}' % setvalue[0], self), + "event": 'xforms-value-changed'} + if not(setvalue[1] == ''): + setvalue_attrs["value"] = setvalue[1] + setvalue_node = node( "setvalue", - ref=self.get_root().insert_xpaths('${%s}' % setvalue[0], self), - value=setvalue[1], - event='xforms-value-changed', + **setvalue_attrs ) xml_node.appendChild(setvalue_node) @@ -63,7 +66,7 @@ class InputQuestion(Question): """ def build_xml(self): - if "calculate" in self.bind and not (self.label or self.hint): + if ("calculate" in self.bind or self.when) and not (self.label or self.hint): return None control_dict = self.control diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_when.py index 24719572c..e6daf19d6 100644 --- a/pyxform/tests_v1/test_when.py +++ b/pyxform/tests_v1/test_when.py @@ -110,77 +110,77 @@ def test_handling_multiple_when_column(self): ], ) - # def test_handling_when_column_with_no_calculation(self): - # md = """ - # | survey | | | | | | - # | | type | name | label | calculation | when | - # | | text | a | Enter text | | | - # | | dateTime | d | Date of something | | ${a} | - # """ - # - # self.assertPyxformXform( - # md=md, - # name="when-column", - # id_string="id", - # model__contains=["", "",], - # xml__contains=[ - # '', - # '', - # '', - # '', - # ], - # xml__excludes=[ - # '' - # ],debug=True - # ) - # - # def test_handling_when_column_with_no_calculation_no_label_no_hint(self): - # md = """ - # | survey | | | | | | - # | | type | name | label | calculation | when | - # | | text | a | Enter text | | | - # | | decimal | e | | | ${a} | - # """ - # - # self.assertPyxformXform( - # md=md, - # name="when-column", - # id_string="id", - # model__contains=["", "",], - # xml__contains=[ - # '', - # '', - # '', - # ], - # xml__excludes=[ - # '', - # '', - # ], - # ) - # - # def test_handling_when_column_in_group(self): - # md = """ - # | survey | | | | | | - # | | type | name | label | calculation | when | - # | | text | a | Enter text | | | - # | | begin_group | grp | | | ${a} | - # | | dateTime | c | Date of diagnostic | now() | ${a} | - # | | end_group | | | | | - # """ - # - # self.assertPyxformXform( - # md=md, - # name="when-column", - # id_string="id", - # model__contains=["", "", "",], - # xml__contains=[ - # '', - # '', - # '', - # '', - # '', - # ], - # xml__excludes=[ - # '', - # ], - # ) + def test_handling_when_column_with_no_calculation(self): + md = """ + | survey | | | | | | + | | type | name | label | calculation | when | + | | text | a | Enter text | | | + | | dateTime | d | Date of something | | ${a} | + """ + + self.assertPyxformXform( + md=md, + name="when-column", + id_string="id", + model__contains=["", "",], + xml__contains=[ + '', + '', + '', + '', + ], + xml__excludes=[ + '' + ] + ) + + def test_handling_when_column_with_no_calculation_no_label_no_hint(self): + md = """ + | survey | | | | | | + | | type | name | label | calculation | when | + | | text | a | Enter text | | | + | | decimal | e | | | ${a} | + """ + + self.assertPyxformXform( + md=md, + name="when-column", + id_string="id", + model__contains=["", "",], + xml__contains=[ + '', + '', + '', + ], + xml__excludes=[ + '', + '', + ], + ) + + def test_handling_when_column_in_group(self): + md = """ + | survey | | | | | | + | | type | name | label | calculation | when | + | | text | a | Enter text | | | + | | begin_group | grp | | | ${a} | + | | dateTime | c | Date of diagnostic | now() | ${a} | + | | end_group | | | | | + """ + + self.assertPyxformXform( + md=md, + name="when-column", + id_string="id", + model__contains=["", "", "",], + xml__contains=[ + '', + '', + '', + '', + '', + ], + xml__excludes=[ + '', + ], + ) From 48a79c16d9977dd3d02ed8f452ec4da97faaac58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 22:00:14 -0700 Subject: [PATCH 06/10] Expand XPath references in setvalue value --- pyxform/question.py | 2 +- pyxform/tests_v1/test_when.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pyxform/question.py b/pyxform/question.py index 0f0390e81..40a7e0bba 100644 --- a/pyxform/question.py +++ b/pyxform/question.py @@ -46,7 +46,7 @@ def nest_setvalues(self, xml_node): setvalue_attrs = {"ref": self.get_root().insert_xpaths('${%s}' % setvalue[0], self), "event": 'xforms-value-changed'} if not(setvalue[1] == ''): - setvalue_attrs["value"] = setvalue[1] + setvalue_attrs["value"] = self.get_root().insert_xpaths(setvalue[1], self) setvalue_node = node( "setvalue", diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_when.py index e6daf19d6..4bc48f407 100644 --- a/pyxform/tests_v1/test_when.py +++ b/pyxform/tests_v1/test_when.py @@ -184,3 +184,15 @@ def test_handling_when_column_in_group(self): '', ], ) + + def test_calculation_with_when_column_should_have_expanded_xpath(self): + self.assertPyxformXform( + name="when-column", + md=""" + | survey | | | | | | + | | type | name | label | calculation | when | + | | dateTime | a | A date | | | + | | integer | b | | decimal-date-time(${a}) | ${a} | + """, + xml__contains=[''] + ) \ No newline at end of file From 76936fdfd3b0d825bf29d8b582f63d43e3d7dc1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 22:10:26 -0700 Subject: [PATCH 07/10] Test setvalue nesting in non-input questions --- pyxform/tests_v1/test_when.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_when.py index 4bc48f407..9750bb1f0 100644 --- a/pyxform/tests_v1/test_when.py +++ b/pyxform/tests_v1/test_when.py @@ -107,7 +107,7 @@ def test_handling_multiple_when_column(self): xml__excludes=[ '', '', - ], + ] ) def test_handling_when_column_with_no_calculation(self): @@ -195,4 +195,20 @@ def test_calculation_with_when_column_should_have_expanded_xpath(self): | | integer | b | | decimal-date-time(${a}) | ${a} | """, xml__contains=[''] - ) \ No newline at end of file + ) + + def test_when_with_trigger_of_type_select_nests_setvalue_in_select(self): + self.assertPyxformXform( + name="when-select_trigger", + md=""" + | survey | | | | | | + | | type | name | label | calculation | when | + | | select_one choices | a | Some choice | | | + | | integer | b | | string-length(${a})| ${a} | + | choices| | | | | | + | | list_name | name | label | + | | choices | a | A | + | | choices | aa | AA | + """, + xml__contains=['\n '] + ) From 36b7152fb9ca372101d7510c7a89e4e819a9a76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 20 May 2020 22:15:15 -0700 Subject: [PATCH 08/10] Run black --- pyxform/builder.py | 8 ++++---- pyxform/question.py | 19 ++++++++++--------- pyxform/survey.py | 11 ++++++++--- pyxform/survey_element.py | 5 +++-- pyxform/tests_v1/test_when.py | 16 ++++++++++------ 5 files changed, 35 insertions(+), 24 deletions(-) diff --git a/pyxform/builder.py b/pyxform/builder.py index 0c998b49b..0696766a6 100644 --- a/pyxform/builder.py +++ b/pyxform/builder.py @@ -127,16 +127,16 @@ def create_survey_element_from_dict(self, d): def _save_when_as_setvalue_and_remove_calculate(self, d): if "when" in d: triggering_ref = re.sub(r"\s+", "", d["when"]) - value = d["bind"]["calculate"] if "bind" in d and "calculate" in d["bind"] else '' + value = "" + if "bind" in d and "calculate" in d["bind"]: + value = d["bind"]["calculate"] if triggering_ref in self.setvalues_by_triggering_ref: self.setvalues_by_triggering_ref[triggering_ref].append( (d["name"], value) ) else: - self.setvalues_by_triggering_ref[triggering_ref] = [ - (d["name"], value) - ] + self.setvalues_by_triggering_ref[triggering_ref] = [(d["name"], value)] @staticmethod def _create_question_from_dict(d, question_type_dictionary, add_none_option=False): diff --git a/pyxform/question.py b/pyxform/question.py index 40a7e0bba..704b91a92 100644 --- a/pyxform/question.py +++ b/pyxform/question.py @@ -43,15 +43,16 @@ def nest_setvalues(self, xml_node): if nested_setvalues: for setvalue in nested_setvalues: - setvalue_attrs = {"ref": self.get_root().insert_xpaths('${%s}' % setvalue[0], self), - "event": 'xforms-value-changed'} - if not(setvalue[1] == ''): - setvalue_attrs["value"] = self.get_root().insert_xpaths(setvalue[1], self) - - setvalue_node = node( - "setvalue", - **setvalue_attrs - ) + setvalue_attrs = { + "ref": self.get_root().insert_xpaths("${%s}" % setvalue[0], self), + "event": "xforms-value-changed", + } + if not (setvalue[1] == ""): + setvalue_attrs["value"] = self.get_root().insert_xpaths( + setvalue[1], self + ) + + setvalue_node = node("setvalue", **setvalue_attrs) xml_node.appendChild(setvalue_node) diff --git a/pyxform/survey.py b/pyxform/survey.py index 9fbd45fcb..69db6efa6 100644 --- a/pyxform/survey.py +++ b/pyxform/survey.py @@ -191,8 +191,10 @@ def xml(self): self._setup_xpath_dictionary() for triggering_reference in self.setvalues_by_triggering_ref.keys(): - if not(re.match(BRACKETED_TAG_REGEX, triggering_reference)): - raise PyXFormError("Only references to other fields are allowed in the 'when' column.") + if not (re.match(BRACKETED_TAG_REGEX, triggering_reference)): + raise PyXFormError( + "Only references to other fields are allowed in the 'when' column." + ) # try to resolve reference and fail if can't self.insert_xpaths(triggering_reference, self) @@ -842,7 +844,10 @@ def _var_repl_function(self, matchobj, context, use_current=False): ): xpath, context_xpath = self._xpath[name], context.get_xpath() # share same root i.e repeat_a from /data/repeat_a/... - if len(context_xpath.split("/")) > 2 and xpath.split("/")[2] == context_xpath.split("/")[2]: + if ( + len(context_xpath.split("/")) > 2 + and xpath.split("/")[2] == context_xpath.split("/")[2] + ): # if context xpath and target xpath fall under the same # repeat use relative xpath referencing. steps, ref_path = share_same_repeat_parent(self, xpath, context_xpath) diff --git a/pyxform/survey_element.py b/pyxform/survey_element.py index cf5d5527e..be6561036 100644 --- a/pyxform/survey_element.py +++ b/pyxform/survey_element.py @@ -69,7 +69,7 @@ class SurveyElement(dict): "flat": lambda: False, "action": unicode, "list_name": unicode, - "when": unicode + "when": unicode, } def _default(self): @@ -409,7 +409,8 @@ def xml_binding(self): # Don't generate bind element for flat groups. return None if bind_dict: - if self.when and "calculate" in self.bind: # the expression goes in a setvalue action + # the expression goes in a setvalue action + if self.when and "calculate" in self.bind: del bind_dict["calculate"] for k, v in bind_dict.items(): diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_when.py index 9750bb1f0..86f9f794a 100644 --- a/pyxform/tests_v1/test_when.py +++ b/pyxform/tests_v1/test_when.py @@ -32,7 +32,7 @@ def test_when_with_something_other_than_node_ref_gives_error(self): errored=True, error__contains=[ "Only references to other fields are allowed in the 'when' column." - ] + ], ) def test_handling_when_column_no_label_and_no_hint(self): @@ -79,7 +79,7 @@ def test_handling_when_column_with_label_and_hint(self): ], xml__excludes=[ '' - ] + ], ) def test_handling_multiple_when_column(self): @@ -107,7 +107,7 @@ def test_handling_multiple_when_column(self): xml__excludes=[ '', '', - ] + ], ) def test_handling_when_column_with_no_calculation(self): @@ -131,7 +131,7 @@ def test_handling_when_column_with_no_calculation(self): ], xml__excludes=[ '' - ] + ], ) def test_handling_when_column_with_no_calculation_no_label_no_hint(self): @@ -194,7 +194,9 @@ def test_calculation_with_when_column_should_have_expanded_xpath(self): | | dateTime | a | A date | | | | | integer | b | | decimal-date-time(${a}) | ${a} | """, - xml__contains=[''] + xml__contains=[ + '' + ], ) def test_when_with_trigger_of_type_select_nests_setvalue_in_select(self): @@ -210,5 +212,7 @@ def test_when_with_trigger_of_type_select_nests_setvalue_in_select(self): | | choices | a | A | | | choices | aa | AA | """, - xml__contains=['\n '] + xml__contains=[ + '\n ' + ], ) From c9b01c8e47caf57c9a7cbf8933ad0a1a8106f434 Mon Sep 17 00:00:00 2001 From: Agus Hilman Date: Thu, 4 Jun 2020 15:05:12 +0700 Subject: [PATCH 09/10] Change 'when' column to 'trigger' column --- pyxform/builder.py | 8 +- pyxform/question.py | 2 +- pyxform/survey.py | 2 +- pyxform/survey_element.py | 4 +- .../{test_when.py => test_trigger.py} | 202 +++++++++--------- 5 files changed, 109 insertions(+), 109 deletions(-) rename pyxform/tests_v1/{test_when.py => test_trigger.py} (52%) diff --git a/pyxform/builder.py b/pyxform/builder.py index 0696766a6..8785bad69 100644 --- a/pyxform/builder.py +++ b/pyxform/builder.py @@ -118,15 +118,15 @@ def create_survey_element_from_dict(self, d): elif d["type"] == "xml-external": return ExternalInstance(**d) else: - self._save_when_as_setvalue_and_remove_calculate(d) + self._save_trigger_as_setvalue_and_remove_calculate(d) return self._create_question_from_dict( d, copy_json_dict(QUESTION_TYPE_DICT), self._add_none_option ) - def _save_when_as_setvalue_and_remove_calculate(self, d): - if "when" in d: - triggering_ref = re.sub(r"\s+", "", d["when"]) + def _save_trigger_as_setvalue_and_remove_calculate(self, d): + if "trigger" in d: + triggering_ref = re.sub(r"\s+", "", d["trigger"]) value = "" if "bind" in d and "calculate" in d["bind"]: value = d["bind"]["calculate"] diff --git a/pyxform/question.py b/pyxform/question.py index 704b91a92..9809eae73 100644 --- a/pyxform/question.py +++ b/pyxform/question.py @@ -67,7 +67,7 @@ class InputQuestion(Question): """ def build_xml(self): - if ("calculate" in self.bind or self.when) and not (self.label or self.hint): + if ("calculate" in self.bind or self.trigger) and not (self.label or self.hint): return None control_dict = self.control diff --git a/pyxform/survey.py b/pyxform/survey.py index 69db6efa6..86245fc56 100644 --- a/pyxform/survey.py +++ b/pyxform/survey.py @@ -193,7 +193,7 @@ def xml(self): for triggering_reference in self.setvalues_by_triggering_ref.keys(): if not (re.match(BRACKETED_TAG_REGEX, triggering_reference)): raise PyXFormError( - "Only references to other fields are allowed in the 'when' column." + "Only references to other fields are allowed in the 'trigger' column." ) # try to resolve reference and fail if can't diff --git a/pyxform/survey_element.py b/pyxform/survey_element.py index be6561036..8d671f8c6 100644 --- a/pyxform/survey_element.py +++ b/pyxform/survey_element.py @@ -69,7 +69,7 @@ class SurveyElement(dict): "flat": lambda: False, "action": unicode, "list_name": unicode, - "when": unicode, + "trigger": unicode, } def _default(self): @@ -410,7 +410,7 @@ def xml_binding(self): return None if bind_dict: # the expression goes in a setvalue action - if self.when and "calculate" in self.bind: + if self.trigger and "calculate" in self.bind: del bind_dict["calculate"] for k, v in bind_dict.items(): diff --git a/pyxform/tests_v1/test_when.py b/pyxform/tests_v1/test_trigger.py similarity index 52% rename from pyxform/tests_v1/test_when.py rename to pyxform/tests_v1/test_trigger.py index 86f9f794a..70155bed2 100644 --- a/pyxform/tests_v1/test_when.py +++ b/pyxform/tests_v1/test_trigger.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- """ -Test handling setvalue of 'when' column in forms +Test handling setvalue of 'trigger' column in forms """ from pyxform.tests_v1.pyxform_test_case import PyxformTestCase -class WhenSetvalueTests(PyxformTestCase): - def test_when_reference_to_nonexistent_node_gives_error(self): +class TriggerSetvalueTests(PyxformTestCase): + def test_trigger_reference_to_nonexistent_node_gives_error(self): self.assertPyxformXform( - name="when-missing-ref", + name="trigger-missing-ref", md=""" - | survey | | | | | | - | | type | name | label | calculation | when | - | | dateTime | b | | now() | ${a} | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | dateTime | b | | now() | ${a} | """, errored=True, error__contains=[ @@ -21,198 +21,198 @@ def test_when_reference_to_nonexistent_node_gives_error(self): ], ) - def test_when_with_something_other_than_node_ref_gives_error(self): + def test_trigger_with_something_other_than_node_ref_gives_error(self): self.assertPyxformXform( - name="when-invalid-ref", + name="trigger-invalid-ref", md=""" - | survey | | | | | | - | | type | name | label | calculation | when | - | | dateTime | b | | now() | 6 | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | dateTime | b | | now() | 6 | """, errored=True, error__contains=[ - "Only references to other fields are allowed in the 'when' column." + "Only references to other fields are allowed in the 'trigger' column." ], ) - def test_handling_when_column_no_label_and_no_hint(self): + def test_handling_trigger_column_no_label_and_no_hint(self): md = """ - | survey | | | | | | - | | type | name | label | calculation | when | - | | text | a | Enter text | | | - | | dateTime | b | | now() | ${a} | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | text | a | Enter text | | | + | | dateTime | b | | now() | ${a} | """ self.assertPyxformXform( md=md, - name="when-column", + name="trigger-column", id_string="id", model__contains=["", "",], xml__contains=[ - '', - '', - '', + '', + '', + '', ], xml__excludes=[ - '' - '', + '' + '', ], ) - def test_handling_when_column_with_label_and_hint(self): + def test_handling_trigger_column_with_label_and_hint(self): md = """ - | survey | | | | | | - | | type | name | label | calculation | when | - | | text | a | Enter text | | | - | | dateTime | c | Date of diagnostic | now() | ${a} | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | text | a | Enter text | | | + | | dateTime | c | Date of diagnostic | now() | ${a} | """ self.assertPyxformXform( md=md, - name="when-column", + name="trigger-column", id_string="id", model__contains=["", "",], xml__contains=[ - '', - '', - '', - '', + '', + '', + '', + '', ], xml__excludes=[ - '' + '' ], ) - def test_handling_multiple_when_column(self): + def test_handling_multiple_trigger_column(self): md = """ - | survey | | | | | | | - | | type | name | label | calculation | when | hint | - | | text | a | Enter text | | | | - | | integer | b | | 1+1 | ${a} | | - | | dateTime | c | | now() | ${a} | A hint | + | survey | | | | | | | + | | type | name | label | calculation | trigger | hint | + | | text | a | Enter text | | | | + | | integer | b | | 1+1 | ${a} | | + | | dateTime | c | | now() | ${a} | A hint | """ self.assertPyxformXform( md=md, - name="when-column", + name="trigger-column", id_string="id", model__contains=["", "", "",], xml__contains=[ - '', - '', - '', - '', - '', - '', + '', + '', + '', + '', + '', + '', ], xml__excludes=[ - '', - '', + '', + '', ], ) - def test_handling_when_column_with_no_calculation(self): + def test_handling_trigger_column_with_no_calculation(self): md = """ - | survey | | | | | | - | | type | name | label | calculation | when | - | | text | a | Enter text | | | - | | dateTime | d | Date of something | | ${a} | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | text | a | Enter text | | | + | | dateTime | d | Date of something | | ${a} | """ self.assertPyxformXform( md=md, - name="when-column", + name="trigger-column", id_string="id", model__contains=["", "",], xml__contains=[ - '', - '', - '', - '', + '', + '', + '', + '', ], xml__excludes=[ - '' + '' ], ) - def test_handling_when_column_with_no_calculation_no_label_no_hint(self): + def test_handling_trigger_column_with_no_calculation_no_label_no_hint(self): md = """ - | survey | | | | | | - | | type | name | label | calculation | when | - | | text | a | Enter text | | | - | | decimal | e | | | ${a} | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | text | a | Enter text | | | + | | decimal | e | | | ${a} | """ self.assertPyxformXform( md=md, - name="when-column", + name="trigger-column", id_string="id", model__contains=["", "",], xml__contains=[ - '', - '', - '', + '', + '', + '', ], xml__excludes=[ - '', - '', + '', + '', ], ) - def test_handling_when_column_in_group(self): + def test_handling_trigger_column_in_group(self): md = """ - | survey | | | | | | - | | type | name | label | calculation | when | - | | text | a | Enter text | | | - | | begin_group | grp | | | ${a} | - | | dateTime | c | Date of diagnostic | now() | ${a} | - | | end_group | | | | | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | text | a | Enter text | | | + | | begin_group | grp | | | ${a} | + | | dateTime | c | Date of diagnostic | now() | ${a} | + | | end_group | | | | | """ self.assertPyxformXform( md=md, - name="when-column", + name="trigger-column", id_string="id", model__contains=["", "", "",], xml__contains=[ - '', - '', - '', - '', - '', + '', + '', + '', + '', + '', ], xml__excludes=[ - '', + '', ], ) - def test_calculation_with_when_column_should_have_expanded_xpath(self): + def test_calculation_with_trigger_column_should_have_expanded_xpath(self): self.assertPyxformXform( - name="when-column", + name="trigger-column", md=""" - | survey | | | | | | - | | type | name | label | calculation | when | - | | dateTime | a | A date | | | - | | integer | b | | decimal-date-time(${a}) | ${a} | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | dateTime | a | A date | | | + | | integer | b | | decimal-date-time(${a}) | ${a} | """, xml__contains=[ - '' + '' ], ) - def test_when_with_trigger_of_type_select_nests_setvalue_in_select(self): + def test_trigger_with_trigger_of_type_select_nests_setvalue_in_select(self): self.assertPyxformXform( - name="when-select_trigger", + name="trigger-select_trigger", md=""" - | survey | | | | | | - | | type | name | label | calculation | when | - | | select_one choices | a | Some choice | | | - | | integer | b | | string-length(${a})| ${a} | - | choices| | | | | | + | survey | | | | | | + | | type | name | label | calculation | trigger | + | | select_one choices | a | Some choice | | | + | | integer | b | | string-length(${a})| ${a} | + | choices| | | | | | | | list_name | name | label | | | choices | a | A | | | choices | aa | AA | """, xml__contains=[ - '\n ' + '\n ' ], ) From 0f1b6b9d99578f5cb7b6877f8df8012d1bce3b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Fri, 5 Jun 2020 14:53:10 -0700 Subject: [PATCH 10/10] Allow no-body calculates of any type --- pyxform/question.py | 6 ++-- pyxform/tests_v1/test_typed_calculates.py | 39 +++++++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/pyxform/question.py b/pyxform/question.py index 9809eae73..c1633e1ae 100644 --- a/pyxform/question.py +++ b/pyxform/question.py @@ -31,6 +31,9 @@ def xml_instance(self, **kwargs): return node(self.name, **attributes) def xml_control(self): + if ("calculate" in self.bind or self.trigger) and not (self.label or self.hint): + return None + xml_node = self.build_xml() if xml_node: @@ -67,9 +70,6 @@ class InputQuestion(Question): """ def build_xml(self): - if ("calculate" in self.bind or self.trigger) and not (self.label or self.hint): - return None - control_dict = self.control label_and_hint = self.xml_label_and_hint() survey = self.get_root() diff --git a/pyxform/tests_v1/test_typed_calculates.py b/pyxform/tests_v1/test_typed_calculates.py index 42e9f0c79..a58ecd6dd 100644 --- a/pyxform/tests_v1/test_typed_calculates.py +++ b/pyxform/tests_v1/test_typed_calculates.py @@ -17,11 +17,25 @@ def test_xls_type_calculate_has_type_string(self): | | calculate| a | | 2 * 2 | """, xml__contains=[ - '' + '', ], ) - def test_other_xls_type_with_calculation_is_passed_through(self): + def test_xls_type_calculate_with_label_has_no_body(self): + self.assertPyxformXform( + name="calculate-type", + md=""" + | survey | | | | | + | | type | name | label | calculation | + | | calculate| a | A | 2 * 2 | + """, + xml__contains=[ + '', + "", + ], + ) + + def test_non_calculate_type_with_calculation_is_bind_type(self): self.assertPyxformXform( name="non-calculate-type", md=""" @@ -34,7 +48,7 @@ def test_other_xls_type_with_calculation_is_passed_through(self): ], ) - def test_other_xls_type_with_calculation_and_no_label_has_no_control(self): + def test_non_calculate_type_with_calculation_and_no_label_has_no_control(self): self.assertPyxformXform( name="no-label", md=""" @@ -46,6 +60,25 @@ def test_other_xls_type_with_calculation_and_no_label_has_no_control(self): xml__excludes=["input"], ) + def test_select_type_with_calculation_and_no_label_has_no_control(self): + self.assertPyxformXform( + name="calculate-select", + md=""" + | survey | | | | | + | | type | name | label | calculation | + | | select_one yes_no | a | | concat('a', 'b') | + | choices | | | | | + | | list_name | name | label | | + | | yes_no | yes | Yes | | + | | yes_no | no | No | | + """, + xml__contains=[ + '' + ], + instance__contains=[""], + xml__excludes=[""], + ) + def test_row_without_label_or_calculation_throws_error(self): self.assertPyxformXform( name="no-label",