Skip to content

Commit

Permalink
Merge pull request #4123 from open-formulieren/issue/dh-673-null-output
Browse files Browse the repository at this point in the history
Fix Objects API JSON data for missing values
  • Loading branch information
sergei-maertens authored Apr 5, 2024
2 parents 9b54457 + 44e9a48 commit 6f4d0cd
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 4 deletions.
4 changes: 3 additions & 1 deletion src/openforms/formio/rendering/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ class ContainerMixin:
def is_visible(self) -> bool:
# fieldset/editgrid components do not support the showInFoo properties, so we don't use the super
# class.
if self.mode == RenderModes.export:
# In registration mode, we need to treat layout/container nodes as visible so
# that their children are emitted too.
if self.mode in {RenderModes.export, RenderModes.registration}:
return True

# We only pass the step data, since frontend logic only has access to the current step data.
Expand Down
8 changes: 7 additions & 1 deletion src/openforms/formio/rendering/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ..service import format_value
from ..typing import Component
from ..utils import (
get_component_empty_value,
is_layout_component,
is_visible_in_frontend,
iterate_components_with_configuration_path,
Expand Down Expand Up @@ -186,7 +187,12 @@ def value(self) -> Any:
"""
path = Path(self.path, self.key_as_path) if self.path else self.key_as_path

value = glom(self.step_data, path, default=None)
empty_value = (
get_component_empty_value(self.component)
if self.renderer.mode == RenderModes.registration
else None
)
value = glom(self.step_data, path, default=empty_value)
return value

@property
Expand Down
37 changes: 36 additions & 1 deletion src/openforms/formio/rendering/tests/test_component_node.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from unittest.mock import patch

from django.test import TestCase
from django.test import TestCase, tag

from openforms.submissions.rendering import Renderer, RenderModes
from openforms.submissions.tests.factories import SubmissionFactory

from ..constants import RenderConfigurationOptions
from ..nodes import ComponentNode
from ..registry import Registry
from ..structured import render_json


class FormNodeTests(TestCase):
Expand Down Expand Up @@ -363,6 +364,40 @@ def test_explicitly_hidden_component_not_skipped_when_registration(self):
self.assertEqual(len(nodelist), 2)
self.assertEqual(nodelist[0].label, "A container without visible children")

@tag("dh-673")
def test_visible_component_inside_hidden_fieldset_not_skipped(self):
submission = SubmissionFactory.from_components(
[
{
"type": "fieldset",
"key": "fieldset",
"label": "Hidden fieldset",
"hidden": True,
"components": [
{
"type": "textfield",
"key": "textfield",
"label": "Text field",
"hidden": False,
},
],
}
],
)

rendered = render_json(submission)

self.assertEqual(
rendered,
{
submission.steps[0].form_step.slug: {
"fieldset": {
"textfield": "",
},
}
},
)

def test_export_always_emits_all_nodes(self):
renderer = Renderer(self.submission, mode=RenderModes.export, as_html=False)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest.mock import patch

from django.core.exceptions import SuspiciousOperation
from django.test import TestCase, override_settings
from django.test import TestCase, override_settings, tag

import requests_mock
import tablib
Expand All @@ -14,6 +14,7 @@
SubmissionFactory,
SubmissionFileAttachmentFactory,
)
from openforms.submissions.tests.mixins import SubmissionsMixin

from ..models import ObjectsAPIConfig
from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration
Expand Down Expand Up @@ -305,3 +306,99 @@ def test_submission_with_objects_api_content_json_not_valid_json(self):
):
with self.assertRaises(RuntimeError):
plugin.register_submission(submission, {})


class JSONTemplatingRegressionTests(SubmissionsMixin, TestCase):
@tag("dh-673")
def test_object_nulls_regression(self):
submission = SubmissionFactory.from_components(
components_list=[
{
"type": "radio",
"key": "radio",
"label": "Radio",
"values": [
{"label": "1", "value": "1"},
{"label": "2", "value": "2"},
],
"defaultValue": None,
"validate": {"required": True},
"openForms": {"dataSrc": "manual"},
},
{
"type": "textfield",
"key": "tekstveld",
"label": "Tekstveld",
"hidden": True,
"validate": {"required": True},
"conditional": {"eq": "1", "show": True, "when": "radio"},
"defaultValue": None,
"clearOnHide": True,
},
{
"type": "currency",
"currency": "EUR",
"key": "bedrag",
"label": "Bedrag",
"hidden": True,
"validate": {"required": True},
"conditional": {"eq": "1", "show": True, "when": "radio"},
"defaultValue": None,
"clearOnHide": True,
},
],
with_report=True,
submitted_data={"radio": "2"},
form_definition_kwargs={"slug": "stepwithnulls"},
)
config = ObjectsAPIConfig(
objects_service=ServiceFactory.build(),
drc_service=ServiceFactory.build(),
content_json="{% json_summary %}",
)
plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER)
prefix = "openforms.registrations.contrib.objects_api"

with (
patch(
f"{prefix}.models.ObjectsAPIConfig.get_solo",
return_value=config,
),
patch(f"{prefix}.plugin.get_objects_client") as mock_objects_client,
):
_objects_client = mock_objects_client.return_value.__enter__.return_value
_objects_client.create_object.return_value = {"dummy": "response"}

plugin.register_submission(
submission,
{
"version": 1,
"objecttype": "https://objecttypen.nl/api/v1/objecttypes/1",
"objecttype_version": 300,
# skip document uploads
"informatieobjecttype_submission_report": "",
"upload_submission_csv": False,
"informatieobjecttype_attachment": "",
},
)

_objects_client.create_object.mock_assert_called_once()
_objects_client.create_object.call_args
record_data = _objects_client.create_object.call_args[1]["object_data"][
"record"
]["data"]
# for missing values, the empty value (depending on component type) must be used
# Note that the input data was validated against the hidden/visible and
# clearOnHide state - absence of the data implies that the component was not
# visible and its data was cleared (otherwise the value *would* have been sent
# along and be present).
self.assertEqual(
record_data,
{
"stepwithnulls": {
"radio": "2",
"tekstveld": "",
"bedrag": None,
},
},
)

0 comments on commit 6f4d0cd

Please sign in to comment.