From 67637188bc62998b7f174a406a49bdb7b8d7b05a Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Thu, 12 Dec 2024 17:13:01 +0100 Subject: [PATCH] :boom: [#2177] Handling geojson in plugins and map formatter --- src/openforms/formio/constants.py | 8 ++++++++ src/openforms/formio/formatters/custom.py | 16 ++++++++++++++-- .../contrib/objects_api/handlers/v2.py | 14 ++++++++------ .../objects_api/submission_registration.py | 16 ++++++++++++---- .../registrations/contrib/stuf_zds/plugin.py | 17 +++++++++++++---- .../registrations/contrib/stuf_zds/typing.py | 5 +++++ .../registrations/contrib/zgw_apis/plugin.py | 16 ++++++++++++---- 7 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/openforms/formio/constants.py b/src/openforms/formio/constants.py index 69e21071ce..d193827a03 100644 --- a/src/openforms/formio/constants.py +++ b/src/openforms/formio/constants.py @@ -1,3 +1,5 @@ +from django.db import models + COMPONENT_DATATYPES = { "date": "date", "time": "time", @@ -11,3 +13,9 @@ "editgrid": "array", "datetime": "datetime", } + + +class GeoJsonGeometryTypes(models.TextChoices): + point = "Point", "Point" + polygon = "Polygon", "Polygon" + line_string = "LineString", "LineString" diff --git a/src/openforms/formio/formatters/custom.py b/src/openforms/formio/formatters/custom.py index 6c87744fc9..a7ffe0e0da 100644 --- a/src/openforms/formio/formatters/custom.py +++ b/src/openforms/formio/formatters/custom.py @@ -6,6 +6,7 @@ from django.utils.html import format_html from django.utils.safestring import mark_safe +from ..constants import GeoJsonGeometryTypes from ..typing import AddressNLComponent, Component, MapComponent from .base import FormatterBase @@ -21,10 +22,21 @@ def format(self, component: Component, value: str) -> str: return f"{fmt_date(parsed_value)} {fmt_time(parsed_value, 'H:i')}" +class GeoJsonGeometry(TypedDict): + type: str + coordinates: list[float] + + +class GeoJson(TypedDict): + type: GeoJsonGeometryTypes + properties: dict[str, str] + geometry: GeoJsonGeometry + + class MapFormatter(FormatterBase): - def format(self, component: MapComponent, value: list[float]) -> str: + def format(self, component: MapComponent, value: GeoJson) -> str: # use a comma here since its a single data element - return ", ".join((str(x) for x in value)) + return ", ".join((str(x) for x in value["geometry"]["coordinates"])) class AddressValue(TypedDict): diff --git a/src/openforms/registrations/contrib/objects_api/handlers/v2.py b/src/openforms/registrations/contrib/objects_api/handlers/v2.py index 279236be92..ac1628afcd 100644 --- a/src/openforms/registrations/contrib/objects_api/handlers/v2.py +++ b/src/openforms/registrations/contrib/objects_api/handlers/v2.py @@ -9,6 +9,7 @@ from glom import Assign, Path, glom from openforms.api.utils import underscore_to_camel +from openforms.formio.constants import GeoJsonGeometryTypes from openforms.formio.typing import Component from openforms.typing import JSONObject, JSONValue @@ -105,12 +106,13 @@ def process_mapped_variable( value = value[0] if value else "" case {"type": "map"}: - # Currently we only support Point coordinates - assert isinstance(value, list) and len(value) == 2 - value = { - "type": "Point", - "coordinates": [value[0], value[1]], - } + assert isinstance(value, dict) + if ( + (geometry := value.get("geometry")) + and geometry.get("type", None) in GeoJsonGeometryTypes + and len(geometry.get("coordinates", [])) == 2 + ): + value = geometry # not a component or standard behaviour where no transformation is necessary case None | _: diff --git a/src/openforms/registrations/contrib/objects_api/submission_registration.py b/src/openforms/registrations/contrib/objects_api/submission_registration.py index 6c3a31d895..c49067b607 100644 --- a/src/openforms/registrations/contrib/objects_api/submission_registration.py +++ b/src/openforms/registrations/contrib/objects_api/submission_registration.py @@ -33,6 +33,8 @@ create_csv_document, create_report_document, ) +from openforms.formio.constants import GeoJsonGeometryTypes +from openforms.formio.formatters.custom import GeoJson, GeoJsonGeometry from openforms.formio.service import FormioData from openforms.formio.typing import Component from openforms.registrations.exceptions import RegistrationFailed @@ -69,10 +71,16 @@ logger = logging.getLogger(__name__) -def _point_coordinate(value: Any) -> dict[str, Any] | object: - if not isinstance(value, list) or len(value) != 2: - return SKIP - return {"type": "Point", "coordinates": [value[0], value[1]]} +def _point_coordinate(value: GeoJson) -> GeoJsonGeometry | object: + return ( + geometry + if ( + (geometry := value.get("geometry")) + and geometry.get("type", None) in GeoJsonGeometryTypes + and len(geometry.get("coordinates", [])) == 2 + ) + else SKIP + ) def _resolve_documenttype( diff --git a/src/openforms/registrations/contrib/stuf_zds/plugin.py b/src/openforms/registrations/contrib/stuf_zds/plugin.py index 11077daf60..e82be0586d 100644 --- a/src/openforms/registrations/contrib/stuf_zds/plugin.py +++ b/src/openforms/registrations/contrib/stuf_zds/plugin.py @@ -11,6 +11,8 @@ from json_logic.typing import Primitive +from openforms.formio.constants import GeoJsonGeometryTypes +from openforms.formio.formatters.custom import GeoJson from openforms.plugins.exceptions import InvalidPluginConfiguration from openforms.registrations.base import BasePlugin, PreRegistrationResult from openforms.registrations.constants import ( @@ -38,7 +40,7 @@ from ...utils import execute_unless_result_exists from .options import ZaakOptionsSerializer from .registration_variables import register as variables_registry -from .typing import RegistrationOptions +from .typing import PointCoordinate, RegistrationOptions from .utils import flatten_data if TYPE_CHECKING: @@ -111,10 +113,17 @@ def _safe_int(num): ) -def _point_coordinate(value): - if not value or not isinstance(value, list) or len(value) != 2: +def _point_coordinate(value: GeoJson) -> PointCoordinate | object: + if ( + not (geometry := value.get("geometry")) + or geometry.get("type") not in GeoJsonGeometryTypes + or len(geometry.get("coordinates", [])) != 2 + ): return SKIP - return {"lat": value[0], "lng": value[1]} + + # GeoJson geometry uses [lng, lat] format for the coordinates + coordinates = geometry.get("coordinates") + return {"lat": coordinates[1], "lng": coordinates[0]} def _gender_choices(value): diff --git a/src/openforms/registrations/contrib/stuf_zds/typing.py b/src/openforms/registrations/contrib/stuf_zds/typing.py index 7e56285f46..2682469d1d 100644 --- a/src/openforms/registrations/contrib/stuf_zds/typing.py +++ b/src/openforms/registrations/contrib/stuf_zds/typing.py @@ -23,3 +23,8 @@ class RegistrationOptions(TypedDict): "OPENBAAR", ] payment_status_update_mapping: NotRequired[list[MappingItem]] + + +class PointCoordinate(TypedDict): + lat: float + lng: float diff --git a/src/openforms/registrations/contrib/zgw_apis/plugin.py b/src/openforms/registrations/contrib/zgw_apis/plugin.py index 27f02b905a..24e11600a4 100644 --- a/src/openforms/registrations/contrib/zgw_apis/plugin.py +++ b/src/openforms/registrations/contrib/zgw_apis/plugin.py @@ -26,6 +26,8 @@ create_attachment_document, create_report_document, ) +from openforms.formio.constants import GeoJsonGeometryTypes +from openforms.formio.formatters.custom import GeoJson, GeoJsonGeometry from openforms.submissions.mapping import SKIP, FieldConf, apply_data_mapping from openforms.submissions.models import Submission, SubmissionReport from openforms.utils.date import datetime_in_amsterdam @@ -75,10 +77,16 @@ def get_property_mappings_from_submission( return property_mappings -def _point_coordinate(value): - if not value or not isinstance(value, list) or len(value) != 2: - return SKIP - return {"type": "Point", "coordinates": [value[0], value[1]]} +def _point_coordinate(value: GeoJson) -> GeoJsonGeometry | object: + return ( + geometry + if ( + (geometry := value.get("geometry")) + and geometry.get("type", None) in GeoJsonGeometryTypes + and len(geometry.get("coordinates", [])) == 2 + ) + else SKIP + ) def _gender_choices(value):