Skip to content

Commit

Permalink
🐛 [#4908] Revise how attachments are processed
Browse files Browse the repository at this point in the history
There was a bug with the previous attachment processing, where attachments would get overwritten if multiple were uploaded. Now, an object will be returned with the 'file_name' and 'content' properties in case of a single file component (or 'None' if no file was uploaded). And in case of a multiple files component, it will be a list of these file objects (or an empty list if no file was uploaded). This keeps the data type consistent, and we can give guarantees that an object will always have the file_name and content keys.
  • Loading branch information
viktorvanwijk committed Jan 21, 2025
1 parent 55a4d25 commit 926964f
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 10 deletions.
84 changes: 74 additions & 10 deletions src/openforms/registrations/contrib/json_dump/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

from zgw_consumers.client import build_client

from openforms.submissions.models import Submission
from openforms.formio.typing import Component
from openforms.submissions.models import (
Submission,
SubmissionFileAttachment,
SubmissionValueVariable,
)
from openforms.typing import JSONObject

from ...base import BasePlugin # openforms.registrations.base
Expand Down Expand Up @@ -34,15 +39,8 @@ def register_submission(
if key in options["form_variables"]
}

# Encode (base64) and add attachments to values dict if their form keys were specified in the
# form variables list
for attachment in submission.attachments:
if attachment.form_key not in options["form_variables"]:
continue
options["form_variables"].remove(attachment.form_key)
with attachment.content.open("rb") as f:
f.seek(0)
values[attachment.form_key] = base64.b64encode(f.read()).decode()
# Process attachments
self.process_variables(submission, values)

# Generate schema
# TODO: will be added in #4980. Hardcoded example for now.
Expand Down Expand Up @@ -77,3 +75,69 @@ def register_submission(
def check_config(self) -> None:
# Config checks are not really relevant for this plugin right now
pass

@staticmethod
def process_variables(submission: Submission, values: JSONObject):
"""Process variables.
File components need special treatment, as we send the content of the file
encoded with base64, instead of the output from the serializer.
"""
state = submission.load_submission_value_variables_state()

for key in values.keys():
variable = state.variables.get(key)
if variable is None:
# None for static variables
continue

component = get_component(variable)
if component is None or component["type"] != "file":
continue

encoded_attachments = [
{
"file_name": attachment.original_name,
"content": encode_attachment(attachment),
}
for attachment in submission.attachments
if attachment.form_key == key
]

match (
multiple := component.get("multiple", False),
n_attachments := len(encoded_attachments)
):
case False, 0:
values[key] = None
case False, 1:
values[key] = encoded_attachments[0]
case True, _:
values[key] = encoded_attachments
case _:
raise ValueError(
f"Combination of multiple ({multiple}) and number of "
f"attachments ({n_attachments}) is not allowed."
)

def encode_attachment(attachment: SubmissionFileAttachment) -> str:
"""Encode an attachment using base64
:param attachment: Attachment to encode
:returns: Encoded base64 data as a string
"""
with attachment.content.open("rb") as f:
f.seek(0)
return base64.b64encode(f.read()).decode()


def get_component(variable: SubmissionValueVariable) -> Component:
"""Get the component from a submission value variable.
:param variable: SubmissionValueVariable
:return component: Component
"""
config_wrapper = variable.form_variable.form_definition.configuration_wrapper
component = config_wrapper.component_map[variable.key]

return component
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,65 @@ def test_exception_raised_when_service_returns_unexpected_status_code(self):

with self.assertRaises(RequestException):
json_plugin.register_submission(submission, json_form_options)

def test_multiple_file_uploads(self):
submission = SubmissionFactory.from_components(
[{"key": "file", "type": "file", "multiple": True}],
completed=True,
submitted_data={
"file": [
{
"url": "some://url",
"name": "file1.txt",
"type": "application/text",
"originalName": "file1.txt",
},
{
"url": "some://url",
"name": "file2.txt",
"type": "application/text",
"originalName": "file2.txt",
}
],
},
)

SubmissionFileAttachmentFactory.create(
form_key="file",
submission_step=submission.submissionstep_set.get(),
file_name="file1.txt",
content_type="application/text",
content__data=b"This is example content.",
_component_configuration_path="components.2",
_component_data_path="file",
)

SubmissionFileAttachmentFactory.create(
form_key="file",
submission_step=submission.submissionstep_set.get(),
file_name="file2.txt",
content_type="application/text",
content__data=b"Content example is this.",
_component_configuration_path="components.2",
_component_data_path="file",
)

json_form_options = dict(
service=(ServiceFactory(api_root="http://localhost:80/")),
relative_api_endpoint="json_plugin",
form_variables=["file"],
)
json_plugin = JSONDumpRegistration("json_registration_plugin")
set_submission_reference(submission)

expected_values = {
"file": {
"file1.txt": "VGhpcyBpcyBleGFtcGxlIGNvbnRlbnQu", # This is example content.
"file2.txt": "Q29udGVudCBleGFtcGxlIGlzIHRoaXMu", # Content example is this.
},
}

res = json_plugin.register_submission(submission, json_form_options)
res_json = res["api_response"]

self.assertEqual(res_json["data"]["values"], expected_values)

0 comments on commit 926964f

Please sign in to comment.