Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trigger column #447

Merged
merged 11 commits into from
Jun 8, 2020
28 changes: 26 additions & 2 deletions pyxform/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
import copy
import os
import re

from pyxform import file_utils, utils
from pyxform.errors import PyXFormError
Expand Down Expand Up @@ -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
Expand All @@ -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.setvalues_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":
Expand All @@ -109,10 +118,26 @@ def create_survey_element_from_dict(self, d):
elif d["type"] == "xml-external":
return ExternalInstance(**d)
else:
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_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"]

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):
question_type_str = d["type"]
Expand Down Expand Up @@ -353,7 +378,6 @@ def create_survey(
if title is not None:
survey.title = title
survey.def_lang = default_language

return survey


Expand Down
46 changes: 35 additions & 11 deletions pyxform/question.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ 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_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)

def build_xml(self):
return None


Expand All @@ -40,7 +66,10 @@ class InputQuestion(Question):
dates, geopoints, barcodes ...
"""

def xml_control(self):
def build_xml(self):
if ("calculate" in self.bind or self.trigger) and not (self.label or self.hint):
lognaturel marked this conversation as resolved.
Show resolved Hide resolved
return None

control_dict = self.control
label_and_hint = self.xml_label_and_hint()
survey = self.get_root()
Expand All @@ -66,7 +95,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
Expand All @@ -80,7 +109,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
Expand Down Expand Up @@ -133,7 +162,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()
Expand Down Expand Up @@ -250,7 +279,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()
Expand All @@ -263,12 +292,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()
Expand Down
19 changes: 18 additions & 1 deletion pyxform/survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -188,6 +189,16 @@ def xml(self):
"""
self.validate()
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 'trigger' column."
)

# try to resolve reference and fail if can't
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)
Expand All @@ -200,6 +211,9 @@ def xml(self):
**nsmap
)

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):
"""
Expand Down Expand Up @@ -830,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 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)
Expand Down
5 changes: 5 additions & 0 deletions pyxform/survey_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class SurveyElement(dict):
"flat": lambda: False,
"action": unicode,
"list_name": unicode,
"trigger": unicode,
}

def _default(self):
Expand Down Expand Up @@ -408,6 +409,10 @@ def xml_binding(self):
# Don't generate bind element for flat groups.
return None
if bind_dict:
# the expression goes in a setvalue action
if self.trigger and "calculate" in self.bind:
del bind_dict["calculate"]

for k, v in bind_dict.items():
# I think all the binding conversions should be happening on
# the xls2json side.
Expand Down
Loading