Skip to content

Commit 7fcc25f

Browse files
authored
Merge pull request #777 from lindsay-stevens/pyxform-refs
453: pyxform relative references (in repeats) improvements
2 parents 87588ba + 038b34c commit 7fcc25f

File tree

13 files changed

+1021
-178
lines changed

13 files changed

+1021
-178
lines changed

.github/workflows/verify.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ jobs:
4141
matrix:
4242
python: ['3.10', '3.11', '3.12', '3.13']
4343
os: [ubuntu-latest, macos-latest, windows-latest]
44+
PYXFORM_TESTS_RUN_ODK_VALIDATE: ['false']
45+
# Extra job to check PyxformTestCase tests pass with ODK Validate.
46+
# Keep versions sync with default dev version (same as 'lint' job above).
4447
include:
45-
- os: windows-latest
46-
windows_nose_args: --traverse-namespace ./tests
48+
- python: '3.12'
49+
os: ubuntu-latest
50+
PYXFORM_TESTS_RUN_ODK_VALIDATE: 'true'
4751
steps:
4852
- uses: actions/checkout@v4
4953
- name: Set up Python
@@ -67,15 +71,17 @@ jobs:
6771
# Tests.
6872
- name: Run tests
6973
run: python -m unittest --verbose
74+
env:
75+
PYXFORM_TESTS_RUN_ODK_VALIDATE: ${{ matrix.PYXFORM_TESTS_RUN_ODK_VALIDATE }}
7076

7177
# Build and Upload.
7278
- name: Build sdist and wheel.
73-
if: success()
79+
if: success() && matrix.PYXFORM_TESTS_RUN_ODK_VALIDATE == 'false'
7480
run: |
7581
pip install flit==3.9.0
7682
flit --debug build --no-use-vcs
7783
- name: Upload sdist and wheel.
78-
if: success()
84+
if: success() && matrix.PYXFORM_TESTS_RUN_ODK_VALIDATE == 'false'
7985
uses: actions/upload-artifact@v4
8086
with:
8187
name: pyxform--on-${{ matrix.os }}--py${{ matrix.python }}

README.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ Releasing pyxform
139139

140140
pyxform_validator_update odk update ODK-Validate-vx.x.x.jar
141141

142-
2. Run all tests through Validate by setting the default for ``run_odk_validate`` to ``True`` in ``tests/pyxform_test_case.py``.
142+
2. Run all tests through ODK Validate as follows:
143+
144+
PYXFORM_TESTS_RUN_ODK_VALIDATE=true python -m unittest --verbose
145+
143146
3. Draft a new GitHub release with the list of merged PRs. Follow the title and description pattern of the previous release.
144147
4. Checkout a release branch from latest upstream master.
145148
5. Update ``CHANGES.txt`` with the text of the draft release.

pyxform/entities/entity_declaration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def _get_id_bind_node(self, survey, entity_id_expression):
117117

118118
if entity_id_expression:
119119
id_bind["calculate"] = survey.insert_xpaths(
120-
entity_id_expression, context=self
120+
text=entity_id_expression, context=self
121121
)
122122

123123
return node(const.BIND, nodeset=self.get_xpath() + "/@id", **id_bind)
@@ -133,7 +133,7 @@ def _get_id_setvalue_node(self):
133133
return node("setvalue", ref=self.get_xpath() + "/@id", **id_setvalue_attrs)
134134

135135
def _get_bind_node(self, survey, expression, destination):
136-
expr = survey.insert_xpaths(expression, context=self)
136+
expr = survey.insert_xpaths(text=expression, context=self)
137137
bind_attrs = {
138138
"calculate": expr,
139139
const.TYPE: "string",

pyxform/question.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def xml_instance(self, survey: "Survey", **kwargs):
140140
attributes = self.instance
141141
if attributes:
142142
for k, v in attributes.items():
143-
result.setAttribute(k, survey.insert_xpaths(v, self))
143+
result.setAttribute(k, survey.insert_xpaths(text=v, context=self))
144144
return result
145145

146146
def xml_control(self, survey: "Survey"):
@@ -200,7 +200,7 @@ def nest_set_nodes(self, survey, xml_node, tag, nested_items):
200200
"event": "xforms-value-changed",
201201
}
202202
if item[1]:
203-
node_attrs["value"] = survey.insert_xpaths(item[1], self)
203+
node_attrs["value"] = survey.insert_xpaths(text=item[1], context=self)
204204
set_node = node(tag, **node_attrs)
205205
xml_node.appendChild(set_node)
206206

@@ -219,7 +219,7 @@ def _build_xml(self, survey: "Survey") -> DetachableElement | None:
219219
# "tag" is from the question type dict so it can't include references. Also,
220220
# if it did include references, then the node element name would be invalid.
221221
if k != "tag":
222-
result.setAttribute(k, survey.insert_xpaths(v, self))
222+
result.setAttribute(k, survey.insert_xpaths(text=v, context=self))
223223
return result
224224

225225
def build_xml(self, survey: "Survey") -> DetachableElement | None:
@@ -269,7 +269,9 @@ def build_xml(self, survey: "Survey"):
269269
if self.query:
270270
choice_filter = self.choice_filter
271271
if choice_filter:
272-
pred = survey.insert_xpaths(choice_filter, self, True)
272+
pred = survey.insert_xpaths(
273+
text=choice_filter, context=self, use_current=True
274+
)
273275
query = f"""instance('{self.query}')/root/item[{pred}]"""
274276
else:
275277
query = f"""instance('{self.query}')/root/item"""
@@ -417,11 +419,16 @@ def build_xml(self, survey: "Survey"):
417419
choice_filter = self.choice_filter
418420
if choice_filter:
419421
choice_filter = survey.insert_xpaths(
420-
choice_filter, self, True, is_previous_question
422+
text=choice_filter,
423+
context=self,
424+
use_current=True,
425+
reference_parent=is_previous_question,
421426
)
422427
if is_previous_question:
423428
path = (
424-
survey.insert_xpaths(self.itemset, self, reference_parent=True)
429+
survey.insert_xpaths(
430+
text=self.itemset, context=self, reference_parent=True
431+
)
425432
.strip()
426433
.split("/")
427434
)

pyxform/section.py

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def xml_instance(self, survey: "Survey", **kwargs):
119119
attributes.update(self.instance)
120120
# Resolve field references in attributes
121121
for key, value in attributes.items():
122-
attributes[key] = survey.insert_xpaths(value, self)
122+
attributes[key] = survey.insert_xpaths(text=value, context=self)
123123
result = node(self.name, **attributes)
124124

125125
for child in self.children:
@@ -173,6 +173,36 @@ def xml_control(self, survey: "Survey"):
173173

174174

175175
class RepeatingSection(Section):
176+
def __init__(
177+
self,
178+
name: str,
179+
type: str = constants.REPEAT,
180+
label: str | dict | None = None,
181+
hint: str | dict | None = None,
182+
bind: dict | None = None,
183+
control: dict | None = None,
184+
instance: dict | None = None,
185+
media: dict | None = None,
186+
flat: bool | None = None,
187+
sms_field: str | None = None,
188+
fields: tuple[str, ...] | None = None,
189+
**kwargs,
190+
):
191+
super().__init__(
192+
name=name,
193+
type=type,
194+
label=label,
195+
hint=hint,
196+
bind=bind,
197+
control=control,
198+
instance=instance,
199+
media=media,
200+
flat=flat,
201+
sms_field=sms_field,
202+
fields=fields,
203+
**kwargs,
204+
)
205+
176206
def xml_control(self, survey: "Survey"):
177207
"""
178208
<group>
@@ -190,7 +220,7 @@ def xml_control(self, survey: "Survey"):
190220
# Resolve field references in attributes
191221
if self.control:
192222
control_dict = {
193-
key: survey.insert_xpaths(value, self)
223+
key: survey.insert_xpaths(text=value, context=self)
194224
for key, value in self.control.items()
195225
}
196226
repeat_node = node("repeat", nodeset=self.get_xpath(), **control_dict)
@@ -233,17 +263,35 @@ def template_instance(self, survey: "Survey", **kwargs):
233263

234264

235265
class GroupedSection(Section):
236-
# I think this might be a better place for the table-list stuff, however it
237-
# doesn't allow for as good of validation as putting it in xls2json
238-
# def __init__(self, **kwargs):
239-
# control = kwargs.get(u"control")
240-
# if control:
241-
# appearance = control.get(u"appearance")
242-
# if appearance is u"table-list":
243-
# print "HI"
244-
# control[u"appearance"] = "field-list"
245-
# kwargs["children"].insert(0, kwargs["children"][0])
246-
# super(GroupedSection, self).__init__(kwargs)
266+
def __init__(
267+
self,
268+
name: str,
269+
type: str = constants.GROUP,
270+
label: str | dict | None = None,
271+
hint: str | dict | None = None,
272+
bind: dict | None = None,
273+
control: dict | None = None,
274+
instance: dict | None = None,
275+
media: dict | None = None,
276+
flat: bool | None = None,
277+
sms_field: str | None = None,
278+
fields: tuple[str, ...] | None = None,
279+
**kwargs,
280+
):
281+
super().__init__(
282+
name=name,
283+
type=type,
284+
label=label,
285+
hint=hint,
286+
bind=bind,
287+
control=control,
288+
instance=instance,
289+
media=media,
290+
flat=flat,
291+
sms_field=sms_field,
292+
fields=fields,
293+
**kwargs,
294+
)
247295

248296
def xml_control(self, survey: "Survey"):
249297
if self.control and self.control.get("bodyless"):
@@ -254,7 +302,7 @@ def xml_control(self, survey: "Survey"):
254302
# Resolve field references in attributes
255303
if self.control:
256304
attributes = {
257-
key: survey.insert_xpaths(value, self)
305+
key: survey.insert_xpaths(text=value, context=self)
258306
for key, value in self.control.items()
259307
}
260308
if "appearance" in self.control:

0 commit comments

Comments
 (0)