Skip to content

Commit

Permalink
🐛 [#4980] Update options of radio, select, and selectboxes component …
Browse files Browse the repository at this point in the history
…when the data source is another form variable

This data is not available in the component.as_json_schema methods based on the form alone, because all user defined variables and calculated values are saved in the DB at the moment of submission. So just the form is not enough to get this data, and we need to do some post-processing in the plugin where we have the submission available
  • Loading branch information
viktorvanwijk committed Jan 15, 2025
1 parent 3797ff6 commit c39493d
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 4 deletions.
71 changes: 67 additions & 4 deletions src/openforms/registrations/contrib/json_dump/plugin.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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]:
Expand All @@ -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
187 changes: 187 additions & 0 deletions src/openforms/registrations/contrib/json_dump/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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", ""],
)

0 comments on commit c39493d

Please sign in to comment.