Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add json schema to submitted data of json dump plugin #5007

Merged
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
573492f
:sparkles: [#4980] Add json schema definition to static variables
viktorvanwijk Jan 7, 2025
e0a1760
:sparkles: [#4980] Add json schema definition to formio components
viktorvanwijk Jan 7, 2025
791b151
:sparkles: [#4980] Add function to generate JSON schema from a form a…
viktorvanwijk Jan 8, 2025
e6bb253
:white_check_mark: [#4980] Add tests
viktorvanwijk Jan 8, 2025
4867708
:sparkles: [#4980] Add schema definitions for user-defined variables
viktorvanwijk Jan 10, 2025
0c77904
:sparkles: [#4980] Process file schema to make it represent the data …
viktorvanwijk Jan 10, 2025
1f8ee58
:bug: [#4980] Add empty string to list of choices to account for an u…
viktorvanwijk Jan 10, 2025
92b5db5
:bug: [#4980] Set required properties in select boxes schema to empty…
viktorvanwijk Jan 10, 2025
6000596
:bug: [#4980] Revise as_json_schema for select, select boxes, and rad…
viktorvanwijk Jan 13, 2025
6e11e3b
:sparkles: [#4980] Revise processing of radio, select, and selectboxe…
viktorvanwijk Jan 15, 2025
e07c0a0
:white_check_mark: [#4980] Update tests
viktorvanwijk Jan 20, 2025
de22b21
:truck: [#4980] Make few functions public API
viktorvanwijk Jan 24, 2025
19c8ffc
:art: [#4980] Clean up post-processing
viktorvanwijk Jan 24, 2025
5e4c4e6
:truck: [#4980] Revise and move implementation of converting user-def…
viktorvanwijk Jan 24, 2025
5de478d
:truck: [#4980] Move implementation of JSON schema generation
viktorvanwijk Jan 24, 2025
b157657
:white_check_mark: [#4980] Update tests
viktorvanwijk Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
✨ [#4980] Add json schema definition to formio components
viktorvanwijk committed Jan 28, 2025
commit e0a1760c78d0c21e63bb33271ac327395d54a444
117 changes: 111 additions & 6 deletions src/openforms/formio/components/custom.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
from openforms.authentication.service import AuthAttribute
from openforms.config.models import GlobalConfiguration, MapTileLayer
from openforms.submissions.models import Submission
from openforms.typing import DataMapping
from openforms.typing import DataMapping, JSONObject
from openforms.utils.date import TIMEZONE_AMS, datetime_in_amsterdam, format_date_value
from openforms.utils.validators import BSNValidator, IBANValidator
from openforms.validations.service import PluginValidator
@@ -43,7 +43,7 @@
from .np_family_members.haal_centraal import get_np_family_members_haal_centraal
from .np_family_members.models import FamilyMembersTypeConfig
from .np_family_members.stuf_bg import get_np_family_members_stuf_bg
from .utils import _normalize_pattern, salt_location_message
from .utils import _normalize_pattern, salt_location_message, to_multiple

logger = logging.getLogger(__name__)

@@ -109,6 +109,14 @@ def build_serializer_field(
)
return serializers.ListField(child=base) if multiple else base

@staticmethod
def as_json_schema(component: DateComponent) -> JSONObject:
label = component.get("label", "Date")
multiple = component.get("multiple", False)

base = {"title": label, "format": "date", "type": "string"}
return to_multiple(base) if multiple else base


class FormioDateTimeField(serializers.DateTimeField):
def validate_empty_values(self, data):
@@ -190,6 +198,14 @@ def build_serializer_field(
)
return serializers.ListField(child=base) if multiple else base

@staticmethod
def as_json_schema(component: Component) -> JSONObject:
label = component.get("label", "Date time")
multiple = component.get("multiple", False)

base = {"title": label, "format": "date-time", "type": "string"}
return to_multiple(base) if multiple else base


@register("map")
class Map(BasePlugin[MapComponent]):
@@ -222,6 +238,24 @@ def build_serializer_field(self, component: MapComponent) -> serializers.ListFie
)
return serializers.ListField(child=base, min_length=2, max_length=2)

@staticmethod
def as_json_schema(component: MapComponent) -> JSONObject:
label = component.get("label", "Map coordinate")

base = {
"title": label,
"type": "array",
"prefixItems": [
{"title": "Latitude", "type": "number"},
{"title": "Longitude", "type": "number"},
],
"items": False,
"minItems": 2,
"maxItems": 2,
}

return base
Comment on lines +242 to +258
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has changes substantially with Robin's map work 😬 it now takes GeoJSON.



@register("postcode")
class Postcode(BasePlugin[Component]):
@@ -253,8 +287,8 @@ def build_serializer_field(
# dynamically add in more kwargs based on the component configuration
extra = {}
validators = []
# adding in the validator is more explicit than changing to serialiers.RegexField,
# which essentially does the same.
# adding in the validator is more explicit than changing to
# serializers.RegexField, which essentially does the same.
if pattern := validate.get("pattern"):
validators.append(
RegexValidator(
@@ -274,6 +308,15 @@ def build_serializer_field(
)
return serializers.ListField(child=base) if multiple else base

@staticmethod
def as_json_schema(component: Component) -> JSONObject:
# TODO-4923: add a regex
label = component.get("label", "Postcode")
multiple = component.get("multiple", False)

base = {"title": label, "type": "string"}
viktorvanwijk marked this conversation as resolved.
Show resolved Hide resolved
return to_multiple(base) if multiple else base


class FamilyMembersHandler(Protocol):
def __call__(
@@ -362,6 +405,12 @@ def mutate_config_dynamically(
for value, label in child_choices
]

@staticmethod
def as_json_schema(component: Component) -> JSONObject:
# This component plugin is transformed into a SelectBoxes component, so a schema
# is not relevant here
raise NotImplementedError()


@register("bsn")
class BSN(BasePlugin[Component]):
@@ -393,6 +442,19 @@ def build_serializer_field(
)
return serializers.ListField(child=base) if multiple else base

@staticmethod
def as_json_schema(component: Component) -> JSONObject:
label = component.get("label", "BSN")
multiple = component.get("multiple", False)

base = {
"title": label,
"type": "string",
"pattern": "^\\d{9}",
"format": "nl-bsn",
}
return to_multiple(base) if multiple else base


class AddressValueSerializer(serializers.Serializer):
postcode = serializers.RegexField(
@@ -510,6 +572,25 @@ def build_serializer_field(
**extra,
)

@staticmethod
def as_json_schema(component: AddressNLComponent) -> JSONObject:
label = component.get("label", "Address NL")
base = {
"title": label,
"type": "object",
"properties": {
"city": {"type": "string"},
"houseLetter": {"type": "string"},
"houseNumber": {"type": "string"},
"houseNumberAddition": {"type": "string"},
"postcode": {"type": "string"},
"streetName": {"type": "string"},
},
"required": ["houseNumber", "postcode"],
}

return base


@register("cosign")
class Cosign(BasePlugin):
@@ -520,6 +601,14 @@ def build_serializer_field(self, component: Component) -> serializers.EmailField
required = validate.get("required", False)
return serializers.EmailField(required=required, allow_blank=not required)

@staticmethod
def as_json_schema(component: Component) -> JSONObject:
label = component.get("label", "Cosign email")

base = {"title": label, "type": "string", "format": "email"}

return base


@register("iban")
class Iban(BasePlugin):
@@ -542,6 +631,14 @@ def build_serializer_field(
)
return serializers.ListField(child=base) if multiple else base

@staticmethod
def as_json_schema(component: Component) -> JSONObject:
label = component.get("label", "IBAN")
multiple = component.get("multiple", False)

base = {"title": label, "type": "string"}
return to_multiple(base) if multiple else base


@register("licenseplate")
class LicensePlate(BasePlugin):
@@ -556,8 +653,8 @@ def build_serializer_field(

extra = {}
validators = []
# adding in the validator is more explicit than changing to serialiers.RegexField,
# which essentially does the same.
# adding in the validator is more explicit than changing to
# serializers.RegexField, which essentially does the same.
if pattern := validate.get("pattern"):
validators.append(
RegexValidator(
@@ -579,3 +676,11 @@ def build_serializer_field(
)

return serializers.ListField(child=base) if multiple else base

@staticmethod
def as_json_schema(component: Component) -> JSONObject:
label = component.get("label", "License plate")
multiple = component.get("multiple", False)

base = {"title": label, "type": "string"}
return to_multiple(base) if multiple else base
17 changes: 17 additions & 0 deletions src/openforms/formio/components/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.utils.crypto import salted_hmac

from openforms.typing import JSONObject

from ..typing import Component

def _normalize_pattern(pattern: str) -> str:
"""
@@ -19,3 +22,17 @@ def salt_location_message(message_bits: dict[str, str]) -> str:
computed_message = f"{message_bits['postcode']}/{message_bits['number']}/{message_bits['city']}/{message_bits['street_name']}"
computed_hmac = salted_hmac("location_check", value=computed_message).hexdigest()
return computed_hmac


def to_multiple(schema: JSONObject) -> JSONObject:
"""Convert a JSON schema of a component to a schema of multiple components.

:param schema: JSON schema of a component.
:returns: JSON schema of multiple components.
"""
title = schema.pop("title")
return {
"title": title,
"type": "array",
"items": schema,
}
Loading