diff --git a/src/openforms/registrations/contrib/json_dump/plugin.py b/src/openforms/registrations/contrib/json_dump/plugin.py index 78beeb73a1..e167fe23b4 100644 --- a/src/openforms/registrations/contrib/json_dump/plugin.py +++ b/src/openforms/registrations/contrib/json_dump/plugin.py @@ -1,12 +1,14 @@ import base64 -from typing import Sequence from django.utils.translation import gettext_lazy as _ +from glom import assign, glom from zgw_consumers.client import build_client +from openforms.formio.dynamic_config import rewrite_formio_components from openforms.formio.typing import Component from openforms.forms.utils import form_variables_to_json_schema +from openforms.submissions.logic.datastructures import DataContainer from openforms.submissions.models import ( Submission, SubmissionFileAttachment, @@ -76,7 +78,9 @@ def post_processing( """Post-processing of values and schema. File components need special treatment, as we send the content of the file - encoded with base64, instead of the output from the serializer. + encoded with base64, instead of the output from the serializer. Also, Radio, + Select, and SelectBoxes components need to be updated if their data source is + set to another form variable. :param submission: Submission :param values: JSONObject @@ -94,7 +98,7 @@ def post_processing( # not relevant here continue - component = get_component(variable) + component = get_component(variable, submission) if component is None: continue @@ -127,7 +131,53 @@ def post_processing( if multiple else base ) + case "radio": + data_src = component.get("openForms", {}).get("dataSrc") + if data_src != "variable": + # Only components where another variable is used as a data + # source need to be processed, so skip this one + continue + + choices = [options["value"] for options in component["values"]] + choices.append("") # Take into account an unfilled field + assign(schema, f"properties.{variable.key}.enum", choices) + case "select": + data_src = component.get("openForms", {}).get("dataSrc") + if data_src != "variable": + # Only components where another variable is used as a data + # source need to be processed, so skip this one + continue + + choices = [ + options["value"] for options in component["data"]["values"] + ] + choices.append("") # Take into account an unfilled field + + base_schema_path = f"properties.{variable.key}" + sub_path = ( + "enum" + if glom(schema, f"{base_schema_path}.type") == "string" + else "items.enum" + ) + assign(schema, f"{base_schema_path}.{sub_path}", choices) case "selectboxes": + data_src = component.get("openForms", {}).get("dataSrc") + + # Only components where another variable is used as a data + # source need to be processed + if data_src == "variable": + properties = { + options["value"]: {"type": "boolean"} + for options in component["values"] + } + schema["properties"][variable.key].update( + { + "properties": properties, + "required": list(properties.keys()), + "additionalProperties": False, + } + ) + # If the select boxes component is not filled, set required # properties to empty list if not values[key]: @@ -147,13 +197,26 @@ def encode_attachment(attachment: SubmissionFileAttachment) -> str: return base64.b64encode(f.read()).decode() -def get_component(variable: SubmissionValueVariable) -> Component | None: +def get_component( + variable: SubmissionValueVariable, submission : Submission +) -> Component | None: """Get the component from a submission value variable. :param variable: SubmissionValueVariable + :param submission: Submission :return component: None if the form variable has no form definition """ config_wrapper = variable.form_variable.form_definition.configuration_wrapper + + # Update components. This is necessary to update the options for Select, + # SelectBoxes, and Radio components, which get their options from another form + # variable. + state = submission.load_submission_value_variables_state() + data = DataContainer(state=state) + config_wrapper = rewrite_formio_components( + config_wrapper, submission=submission, data=data.data + ) + component = config_wrapper.component_map[variable.key] return component diff --git a/src/openforms/registrations/contrib/json_dump/tests/test_backend.py b/src/openforms/registrations/contrib/json_dump/tests/test_backend.py index f43cd15411..cb8e7882f2 100644 --- a/src/openforms/registrations/contrib/json_dump/tests/test_backend.py +++ b/src/openforms/registrations/contrib/json_dump/tests/test_backend.py @@ -9,10 +9,12 @@ from openforms.submissions.public_references import set_submission_reference from openforms.submissions.tests.factories import ( + FormVariableFactory, SubmissionFactory, SubmissionFileAttachmentFactory, ) from openforms.utils.tests.vcr import OFVCRMixin +from openforms.variables.constants import FormVariableDataTypes from ..plugin import JSONDumpRegistration @@ -232,3 +234,188 @@ def test_required_in_schema_is_empty_if_select_boxes_component_unfilled(self): glom(res, "api_response.data.schema.properties.selectboxes.required"), [] ) + def test_select_component_with_form_variable_as_data_source(self): + + submission = SubmissionFactory.from_components( + [ + { + "label": "Select", + "key": "select", + "type": "select", + "multiple": True, + "openForms": { + "dataSrc": "variable", + "itemsExpression": {"var": "valuesForSelect"}, + }, + "data": { + "values": [], + "json": "", + "url": "", + "resource": "", + "custom": "", + }, + }, + ], + completed=True, + submitted_data={"select": ["A", "C"]}, + ) + + FormVariableFactory.create( + form=submission.form, + name="Values for select", + key="valuesForSelect", + user_defined=True, + data_type=FormVariableDataTypes.array, + initial_value=["A", "B", "C"], + ) + + json_plugin = JSONDumpRegistration("json_registration_plugin") + set_submission_reference(submission) + + json_form_options = dict( + service=(ServiceFactory(api_root="http://localhost:80/")), + relative_api_endpoint="json_plugin", + form_variables=["select"], + ) + + res = json_plugin.register_submission(submission, json_form_options) + + self.assertEqual( + glom(res, "api_response.data.schema.properties.select.items.enum"), + ["A", "B", "C", ""], + ) + + def test_select_boxes_component_with_form_variable_as_data_source(self): + + submission = SubmissionFactory.from_components( + [ + { + "label": "Select Boxes", + "key": "selectBoxes", + "type": "selectboxes", + "openForms": { + "dataSrc": "variable", + "translations": {}, + "itemsExpression": {"var": "valuesForSelectBoxes"}, + }, + "values": [], + }, + ], + completed=True, + submitted_data={"selectBoxes": {"A": True, "B": False, "C": True}}, + ) + + FormVariableFactory.create( + form=submission.form, + name="Values for select boxes", + key="valuesForSelectBoxes", + user_defined=True, + data_type=FormVariableDataTypes.array, + initial_value=["A", "B", "C"], + ) + + json_plugin = JSONDumpRegistration("json_registration_plugin") + set_submission_reference(submission) + + json_form_options = dict( + service=(ServiceFactory(api_root="http://localhost:80/")), + relative_api_endpoint="json_plugin", + form_variables=["selectBoxes"], + ) + + res = json_plugin.register_submission(submission, json_form_options) + + expected_schema = { + "title": "Select Boxes", + "type": "object", + "additionalProperties": False, + "properties": { + "A": {"type": "boolean"}, + "B": {"type": "boolean"}, + "C": {"type": "boolean"}, + }, + "required": ["A", "B", "C"], + } + + self.assertEqual( + glom(res, "api_response.data.schema.properties.selectBoxes"), + expected_schema + ) + + def test_select_boxes_schema_required_is_empty_when_no_data_is_submitted(self): + submission = SubmissionFactory.from_components( + [ + { + "label": "Select Boxes", + "key": "selectBoxes", + "type": "selectboxes", + "values": [ + {"label": "A", "value": "a"}, + {"label": "B", "value": "b"}, + {"label": "C", "value": "c"}, + ], + }, + ], + completed=True, + submitted_data={"selectBoxes": {}}, + ) + + json_plugin = JSONDumpRegistration("json_registration_plugin") + set_submission_reference(submission) + + json_form_options = dict( + service=(ServiceFactory(api_root="http://localhost:80/")), + relative_api_endpoint="json_plugin", + form_variables=["selectBoxes"], + ) + + res = json_plugin.register_submission(submission, json_form_options) + + self.assertEqual( + glom(res, "api_response.data.schema.properties.selectBoxes.required"), + [], + ) + + def test_radio_component_with_form_variable_as_data_source(self): + submission = SubmissionFactory.from_components( + [ + { + "label": "Radio", + "key": "radio", + "type": "radio", + "openForms": { + "dataSrc": "variable", + "translations": {}, + "itemsExpression": {"var": "valuesForRadio"}, + }, + "values": [], + }, + ], + completed=True, + submitted_data={"radio": "A"}, + ) + + FormVariableFactory.create( + form=submission.form, + name="Values for radio", + key="valuesForRadio", + user_defined=True, + data_type=FormVariableDataTypes.array, + initial_value=["A", "B", "C"], + ) + + json_plugin = JSONDumpRegistration("json_registration_plugin") + set_submission_reference(submission) + + json_form_options = dict( + service=(ServiceFactory(api_root="http://localhost:80/")), + relative_api_endpoint="json_plugin", + form_variables=["radio"], + ) + + res = json_plugin.register_submission(submission, json_form_options) + + self.assertEqual( + glom(res, "api_response.data.schema.properties.radio.enum"), + ["A", "B", "C", ""], + )