diff --git a/pyxform/entities/entities_parsing.py b/pyxform/entities/entities_parsing.py index e985ee44..42ee7ac9 100644 --- a/pyxform/entities/entities_parsing.py +++ b/pyxform/entities/entities_parsing.py @@ -17,7 +17,7 @@ def get_entity_declaration(workbook_dict: Dict, warnings: List) -> Dict: return {} elif len(entities_sheet) > 1: raise PyXFormError( - "This version of pyxform only supports declaring a single entity per form. Please make sure your entities sheet only declares one entity." + "Currently, you can only declare a single entity per form. Please make sure your entities sheet only declares one entity." ) entity = entities_sheet[0] @@ -57,7 +57,9 @@ def get_entity_declaration(workbook_dict: Dict, warnings: List) -> Dict: } -def validate_entity_saveto(row: Dict, row_number: int, entity_declaration: Dict): +def validate_entity_saveto( + row: Dict, row_number: int, entity_declaration: Dict, in_repeat: bool +): save_to = row.get("bind", {}).get("entities:saveto", "") if not save_to: return @@ -74,6 +76,11 @@ def validate_entity_saveto(row: Dict, row_number: int, entity_declaration: Dict) f"{constants.ROW_FORMAT_STRING % row_number} Groups and repeats can't be saved as entity properties." ) + if in_repeat: + raise PyXFormError( + f"{constants.ROW_FORMAT_STRING % row_number} Currently, you can't create entities from repeats. You may only specify save_to values for form fields outside of repeats." + ) + error_start = f"{constants.ROW_FORMAT_STRING % row_number} Invalid save_to name:" if save_to == "name" or save_to == "label": diff --git a/pyxform/xls2json.py b/pyxform/xls2json.py index 47c58752..1559bcd9 100644 --- a/pyxform/xls2json.py +++ b/pyxform/xls2json.py @@ -886,7 +886,8 @@ def workbook_to_json( f"{ROW_FORMAT_STRING % row_number} Invalid question name '{question_name}'. Names {XML_IDENTIFIER_ERROR_MESSAGE}" ) - validate_entity_saveto(row, row_number, entity_declaration) + in_repeat = any(ancestor["control_type"] == "repeat" for ancestor in stack) + validate_entity_saveto(row, row_number, entity_declaration, in_repeat) # Try to parse question as begin control statement # (i.e. begin loop/repeat/group): diff --git a/tests/test_entities.py b/tests/test_entities.py index 4b62365a..01a0e705 100644 --- a/tests/test_entities.py +++ b/tests/test_entities.py @@ -3,9 +3,10 @@ class EntitiesTest(PyxformTestCase): - def test_dataset_in_entities_sheet__adds_meta_entity_block(self): + def test_basic_entity_creation_building_blocks(self): self.assertPyxformXform( name="data", + debug=True, md=""" | survey | | | | | | type | name | label | @@ -14,7 +15,19 @@ def test_dataset_in_entities_sheet__adds_meta_entity_block(self): | | dataset | label | | | | trees | a | | """, - xml__xpath_match=["/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity"], + xml__xpath_match=[ + "/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity", + '/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity[@dataset = "trees"]', + # defaults to always creating + '/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity[@create = "1"]', + '/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity[@id = ""]', + '/h:html/h:head/x:model/x:bind[@nodeset = "/data/meta/entity/@id" and @type = "string" and @readonly = "true()"]', + '/h:html/h:head/x:model/x:setvalue[@event = "odk-instance-first-load" and @type = "string" and @ref = "/data/meta/entity/@id" and @value = "uuid()"]', + "/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity/x:label", + '/h:html/h:head/x:model/x:bind[@nodeset = "/data/meta/entity/label" and @type = "string" and @readonly = "true()" and @calculate = "a"]', + '/h:html/h:head/x:model[@entities:entities-version = "2022.1.0"]', + ], + xml__contains=['xmlns:entities="http://www.opendatakit.org/xforms/entities"'], ) def test_multiple_dataset_rows_in_entities_sheet__errors(self): @@ -31,23 +44,7 @@ def test_multiple_dataset_rows_in_entities_sheet__errors(self): """, errored=True, error__contains=[ - "This version of pyxform only supports declaring a single entity per form. Please make sure your entities sheet only declares one entity." - ], - ) - - def test_dataset_in_entities_sheet__adds_dataset_attribute_to_entity(self): - self.assertPyxformXform( - name="data", - md=""" - | survey | | | | - | | type | name | label | - | | text | a | A | - | entities | | | | - | | dataset | label | | - | | trees | a | | - """, - xml__xpath_match=[ - '/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity[@dataset = "trees"]' + "Currently, you can only declare a single entity per form. Please make sure your entities sheet only declares one entity." ], ) @@ -118,22 +115,6 @@ def test_worksheet_name_close_to_entities__produces_warning(self): ], ) - def test_dataset_in_entities_sheet__defaults_to_always_creating(self): - self.assertPyxformXform( - name="data", - md=""" - | survey | | | | - | | type | name | label | - | | text | a | A | - | entities | | | | - | | dataset | label | | - | | trees | a | | - """, - xml__xpath_match=[ - '/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity[@create = "1"]' - ], - ) - def test_create_if_in_entities_sheet__puts_expression_on_bind(self): self.assertPyxformXform( name="data", @@ -151,41 +132,6 @@ def test_create_if_in_entities_sheet__puts_expression_on_bind(self): ], ) - def test_dataset_in_entities_sheet__adds_id_attribute_and_model_nodes_to_entity(self): - self.assertPyxformXform( - name="data", - md=""" - | survey | | | | - | | type | name | label | - | | text | a | A | - | entities | | | | - | | dataset | label | | - | | trees | a | | - """, - xml__xpath_match=[ - '/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity[@id = ""]', - '/h:html/h:head/x:model/x:bind[@nodeset = "/data/meta/entity/@id" and @type = "string" and @readonly = "true()"]', - '/h:html/h:head/x:model/x:setvalue[@event = "odk-instance-first-load" and @type = "string" and @ref = "/data/meta/entity/@id" and @value = "uuid()"]', - ], - ) - - def test_label_in_entities_sheet__adds_label_and_bind_to_entity(self): - self.assertPyxformXform( - name="data", - md=""" - | survey | | | | - | | type | name | label | - | | text | a | A | - | entities | | | | - | | dataset | label | | - | | trees | a | | - """, - xml__xpath_match=[ - "/h:html/h:head/x:model/x:instance/x:data/x:meta/x:entity/x:label", - '/h:html/h:head/x:model/x:bind[@nodeset = "/data/meta/entity/label" and @type = "string" and @readonly = "true()" and @calculate = "a"]', - ], - ) - def test_label_and_create_if_in_entities_sheet__expand_node_selectors_to_xpath(self): self.assertPyxformXform( name="data", @@ -218,20 +164,6 @@ def test_entity_label__required(self): error__contains=["The entities sheet is missing the required label column."], ) - def test_dataset_in_entities_sheet__adds_entities_namespace(self): - self.assertPyxformXform( - name="data", - md=""" - | survey | | | | - | | type | name | label | - | | text | a | A | - | entities | | | | - | | dataset | label | | - | | trees | a | | - """, - xml__contains=['xmlns:entities="http://www.opendatakit.org/xforms/entities"'], - ) - def test_entities_namespace__omitted_if_no_entities_sheet(self): self.assertPyxformXform( name="data", @@ -243,22 +175,6 @@ def test_entities_namespace__omitted_if_no_entities_sheet(self): xml__excludes=['xmlns:entities="http://www.opendatakit.org/xforms/entities"'], ) - def test_dataset_in_entities_sheet__adds_entities_version(self): - self.assertPyxformXform( - name="data", - md=""" - | survey | | | | - | | type | name | label | - | | text | a | A | - | entities | | | | - | | dataset | label | | - | | trees | a | | - """, - xml__xpath_match=[ - '/h:html/h:head/x:model[@entities:entities-version = "2022.1.0"]' - ], - ) - def test_entities_version__omitted_if_no_entities_sheet(self): self.assertPyxformXform( name="data", @@ -385,3 +301,38 @@ def test_saveto_on_group__errors(self): "[row : 2] Groups and repeats can't be saved as entity properties." ], ) + + def test_saveto_in_repeat__errors(self): + self.assertPyxformXform( + name="data", + md=""" + | survey | | | | | + | | type | name | label | save_to | + | | begin_repeat| a | A | | + | | text | size | Size | size | + | | end_repeat | | | | + | entities | | | | | + | | dataset | label | | | + | | trees | ${size}| | | + """, + errored=True, + error__contains=[ + "[row : 3] Currently, you can't create entities from repeats. You may only specify save_to values for form fields outside of repeats." + ], + ) + + def test_saveto_in_group__works(self): + self.assertPyxformXform( + name="data", + md=""" + | survey | | | | | + | | type | name | label | save_to | + | | begin_group | a | A | | + | | text | size | Size | size | + | | end_group | | | | + | entities | | | | | + | | dataset | label | | | + | | trees | ${size}| | | + """, + errored=False, + )