Skip to content

Commit

Permalink
✨ [#4980] Add function to generate JSON schema from a form and list o…
Browse files Browse the repository at this point in the history
…f variables to include

Also updated the JSON dump plugin
  • Loading branch information
viktorvanwijk committed Jan 22, 2025
1 parent 686ad56 commit a9d2ed1
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 21 deletions.
72 changes: 71 additions & 1 deletion src/openforms/forms/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import random
import string
import zipfile
from typing import Any
from typing import Any, Sequence
from uuid import uuid4

from django.conf import settings
Expand All @@ -16,9 +16,13 @@
from rest_framework.test import APIRequestFactory

from openforms.formio.migration_converters import CONVERTERS, DEFINITION_CONVERTERS
from openforms.formio.registry import register as component_registry
from openforms.formio.utils import iter_components
from openforms.typing import JSONObject
from openforms.variables.constants import FormVariableSources
from openforms.variables.registry import (
register_static_variable as static_variable_registry,
)

from .api.datastructures import FormVariableWrapper
from .api.serializers import (
Expand Down Expand Up @@ -129,6 +133,72 @@ def form_to_json(form_id: int) -> dict:
return resources


# TODO-4980: what should the name be? Do we need the submission, or is just the form
# sufficient here?
# TODO-4980: pass form_id instead of the form?
def form_variables_to_json_schema(
form: Form, variables_to_include: Sequence[str]
) -> JSONObject:
"""Generate a JSON schema from a form, for the specified variables.
:param form: The form to generate JSON schema for.
:param variables_to_include: Variables that will be included in the schema.
:returns: A JSON schema representing the form variables.
"""

# Handle static variables
static_var_properties = {
key: static_variable_registry[key].as_json_schema()
for key in variables_to_include
if key in static_variable_registry
}

# Handle form variables
all_form_vars = {var.key: var for var in form.formvariable_set.all()}

# TODO-4980: add to FormVariable?
def get_json_schema_from_form_variable(form_variable):
form_def = form_variable.form_definition

component = form_def.configuration_wrapper.component_map[form_variable.key]
component_plugin = component_registry[component["type"]]

return component_plugin.as_json_schema(component)

form_var_properties = {
key: get_json_schema_from_form_variable(all_form_vars[key])
for key in variables_to_include
if key in all_form_vars
}

# Required
def is_required(form_variable):
form_def = form_variable.form_definition
component = form_def.configuration_wrapper.component_map[form_variable.key]

validate = component.get("validate", {})
return validate.get("required", False)

required_form_variables = [
var for var in form_var_properties.keys() if is_required(all_form_vars[var])
]

required = [*static_var_properties.keys(), *required_form_variables]

# Result
var_properties = {**static_var_properties, **form_var_properties}
schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": var_properties,
"required": required,
"additionalProperties": False,
}

return schema


def export_form(form_id, archive_name=None, response=None):
resources = form_to_json(form_id)

Expand Down
37 changes: 17 additions & 20 deletions src/openforms/registrations/contrib/json_dump/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from zgw_consumers.client import build_client

from openforms.formio.typing import Component
from openforms.forms.utils import form_variables_to_json_schema
from openforms.submissions.models import (
Submission,
SubmissionFileAttachment,
Expand All @@ -32,6 +33,7 @@ def register_submission(
) -> dict:
state = submission.load_submission_value_variables_state()

# Generate values
all_values: JSONObject = {
**state.get_static_data(),
**state.get_data(), # dynamic values from user input
Expand All @@ -42,23 +44,11 @@ def register_submission(
if key in options["variables"]
}

# Process attachments
self.process_variables(submission, values)

# Generate schema
# TODO: will be added in #4980. Hardcoded example for now.
schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"static_var_1": {"type": "string", "pattern": "^cool_pattern$"},
"form_var_1": {"type": "string"},
"form_var_2": {"type": "string"},
"attachment": {"type": "string", "contentEncoding": "base64"},
},
"required": ["static_var_1", "form_var_1", "form_var_2"],
"additionalProperties": False,
}
schema = form_variables_to_json_schema(submission.form, options["variables"])

# Post-processing
self.post_processing(submission, values, schema)

# Send to the service
data = json.dumps({"values": values, "schema": schema}, cls=DjangoJSONEncoder)
Expand All @@ -77,11 +67,17 @@ def check_config(self) -> None:
pass

@staticmethod
def process_variables(submission: Submission, values: JSONObject):
"""Process variables.
def post_processing(
submission: Submission, values: JSONObject, schema: JSONObject
) -> None:
"""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.
:param submission: Submission
:param values: JSONObject
:param schema: JSONObject
"""
state = submission.load_submission_value_variables_state()

Expand All @@ -97,6 +93,7 @@ def process_variables(submission: Submission, values: JSONObject):

component = get_component(variable)
if component is None or component["type"] != "file":
# Only file components need to be processed
continue

encoded_attachments: list[JSONValue] = [
Expand Down Expand Up @@ -136,11 +133,11 @@ def encode_attachment(attachment: SubmissionFileAttachment) -> str:
return base64.b64encode(f.read()).decode()


def get_component(variable: SubmissionValueVariable) -> Component:
def get_component(variable: SubmissionValueVariable) -> Component | None:
"""Get the component from a submission value variable.
:param variable: SubmissionValueVariable
:return component: Component
:return component: None if the form variable has no form definition
"""
config_wrapper = variable.form_variable.form_definition.configuration_wrapper
component = config_wrapper.component_map[variable.key]
Expand Down

0 comments on commit a9d2ed1

Please sign in to comment.