From a5bcce91b669783afbd58071a2989e5e4fa026de Mon Sep 17 00:00:00 2001 From: WinnyTroy Date: Fri, 19 Jul 2019 11:04:28 +0300 Subject: [PATCH 1/2] Include check to throw error if external choices list is not found --- pyxform/constants.py | 2 + .../test_support_external_instances.py | 51 +++++++++++++++++++ pyxform/xls2json.py | 27 ++++++++-- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/pyxform/constants.py b/pyxform/constants.py index 6f42bb4f1..f27f5cf70 100644 --- a/pyxform/constants.py +++ b/pyxform/constants.py @@ -68,6 +68,7 @@ # The following are the possible sheet names: SURVEY = "survey" SETTINGS = "settings" +EXTERNAL_CHOICES = "external_choices" # These sheet names are for list sheets CHOICES_AND_COLUMNS = "choices and columns" CASCADING_CHOICES = "cascades" @@ -84,6 +85,7 @@ COLUMNS, CHOICES_AND_COLUMNS, SETTINGS, + EXTERNAL_CHOICES, OSM, ] SUPPORTED_FILE_EXTENSIONS = [".xls", ".xlsx", ".xlsm"] diff --git a/pyxform/tests_v1/test_support_external_instances.py b/pyxform/tests_v1/test_support_external_instances.py index 1c3714f8b..2cd09330b 100644 --- a/pyxform/tests_v1/test_support_external_instances.py +++ b/pyxform/tests_v1/test_support_external_instances.py @@ -165,3 +165,54 @@ def test_non_existent_itext_reference(self): """ ], ) + + +class ExternalChoicesInstaceTests(PyxformTestCase): + def test_external_choices_sheet_values_required(self): + md = """ + | survey | | | | | + | | type | name | label | choice_filter | + | | text | S1 | s1 | | + | | select_one_external counties | county | county | S1=${S1} | + | | select_one_external cities | city | city | S1=${S1} and county=${county} | + | choices | | | | + | | list name | name | label | + | | list | option a | a | + | external_choices | | | + | | list_name | name | + | | counties | Kajiado | + | | counties | Nakuru | + | | cities | Kisumu | + | | cities | Mombasa | + """ + expected = [ + """ + + """ + ] # noqa + self.assertPyxformXform( + md=md, debug=True, model__contins=[expected], run_odk_validate=True + ) + + def test_list_name_not_in_external_choices_sheet_raises_error(self): + self.assertPyxformXform( + md=""" + | survey | | | | | + | | type | name | label | choice_filter | + | | select_one list | S1 | s1 | | + | | select_one_external counties | county | County | S1=${S1} | + | | select_one_external cities | city | City | S1=${S1} and county=${county} | + | choices | | | | + | | list name | name | label | + | | list | option a | a | + | | list | option b | b | + | | list | option c | c | + | external_choices | | | + | | list_name | name | + | | counties | Kajiado | + | | counties | Nakuru | + """, # noqa + errored=True, + error__contains=["List name not in external choices sheet: cities"], + ) diff --git a/pyxform/xls2json.py b/pyxform/xls2json.py index 9cb681025..3c0fbcf5f 100644 --- a/pyxform/xls2json.py +++ b/pyxform/xls2json.py @@ -406,6 +406,19 @@ def workbook_to_json( # Here the default settings are overridden by those in the settings sheet json_dict.update(settings) + # ########## External Choices sheet ########## + + external_choices_sheet = workbook_dict.get(constants.EXTERNAL_CHOICES, []) + for choice_item in external_choices_sheet: + replace_smart_quotes_in_dict(choice_item) + + external_choices_sheet = dealias_and_group_headers( + external_choices_sheet, aliases.list_header, use_double_colons, default_language + ) + external_choices = group_dictionaries_by_key( + external_choices_sheet, constants.LIST_NAME + ) + # ########## Choices sheet ########## # Columns and "choices and columns" sheets are deprecated, # but we combine them with the choices sheet for backwards-compatibility. @@ -992,16 +1005,24 @@ def replace_prefix(d, prefix): parse_dict = select_parse.groupdict() if parse_dict.get("select_command"): select_type = aliases.select[parse_dict["select_command"]] + list_name = parse_dict["list_name"] + list_file_name, file_extension = os.path.splitext(list_name) if select_type == "select one external" and "choice_filter" not in row: warnings.append( row_format_string % row_number + " select one external is only meant for" " filtered selects." ) + if ( + select_type == "select one external" + and list_name not in external_choices + ): + raise PyXFormError( + row_format_string % row_number + + "List name not in external choices sheet: " + + list_name + ) select_type = aliases.select["select_one"] - list_name = parse_dict["list_name"] - list_file_name, file_extension = os.path.splitext(list_name) - if ( list_name not in choices and select_type != "select one external" From 08f5818aceb832df5d0b84c21ecfce54eb23b4a8 Mon Sep 17 00:00:00 2001 From: WinnyTroy Date: Wed, 31 Jul 2019 10:49:29 +0300 Subject: [PATCH 2/2] Clean up markdown --- .../test_support_external_instances.py | 46 +++++++++---------- pyxform/xls2json.py | 4 +- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/pyxform/tests_v1/test_support_external_instances.py b/pyxform/tests_v1/test_support_external_instances.py index 2cd09330b..60810f260 100644 --- a/pyxform/tests_v1/test_support_external_instances.py +++ b/pyxform/tests_v1/test_support_external_instances.py @@ -166,18 +166,16 @@ def test_non_existent_itext_reference(self): ], ) - -class ExternalChoicesInstaceTests(PyxformTestCase): def test_external_choices_sheet_values_required(self): md = """ - | survey | | | | | - | | type | name | label | choice_filter | - | | text | S1 | s1 | | - | | select_one_external counties | county | county | S1=${S1} | - | | select_one_external cities | city | city | S1=${S1} and county=${county} | - | choices | | | | - | | list name | name | label | - | | list | option a | a | + | survey | | | | | + | | type | name | label | choice_filter | + | | text | S1 | s1 | | + | | select_one_external counties | county | county | S1=${S1} | + | | select_one_external cities | city | city | S1=${S1} and county=${county} | + | choices | | | | + | | list name | name | label | + | | list | option a | a | | external_choices | | | | | list_name | name | | | counties | Kajiado | @@ -198,20 +196,20 @@ def test_external_choices_sheet_values_required(self): def test_list_name_not_in_external_choices_sheet_raises_error(self): self.assertPyxformXform( md=""" - | survey | | | | | - | | type | name | label | choice_filter | - | | select_one list | S1 | s1 | | - | | select_one_external counties | county | County | S1=${S1} | - | | select_one_external cities | city | City | S1=${S1} and county=${county} | - | choices | | | | - | | list name | name | label | - | | list | option a | a | - | | list | option b | b | - | | list | option c | c | - | external_choices | | | - | | list_name | name | - | | counties | Kajiado | - | | counties | Nakuru | + | survey | | | | | + | | type | name | label | choice_filter | + | | select_one list | S1 | s1 | | + | | select_one_external counties | county | County | S1=${S1} | + | | select_one_external cities | city | City | S1=${S1} and county=${county} | + | choices | | | | + | | list name | name | label | + | | list | option a | a | + | | list | option b | b | + | | list | option c | c | + | external_choices | | | + | | list_name | name | + | | counties | Kajiado | + | | counties | Nakuru | """, # noqa errored=True, error__contains=["List name not in external choices sheet: cities"], diff --git a/pyxform/xls2json.py b/pyxform/xls2json.py index 3c0fbcf5f..bda1feb58 100644 --- a/pyxform/xls2json.py +++ b/pyxform/xls2json.py @@ -1005,14 +1005,14 @@ def replace_prefix(d, prefix): parse_dict = select_parse.groupdict() if parse_dict.get("select_command"): select_type = aliases.select[parse_dict["select_command"]] - list_name = parse_dict["list_name"] - list_file_name, file_extension = os.path.splitext(list_name) if select_type == "select one external" and "choice_filter" not in row: warnings.append( row_format_string % row_number + " select one external is only meant for" " filtered selects." ) + list_name = parse_dict["list_name"] + list_file_name, file_extension = os.path.splitext(list_name) if ( select_type == "select one external" and list_name not in external_choices