From 6891fa1e88227463e89a496e49b3cae53897d793 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Thu, 21 Nov 2024 11:16:26 +0100 Subject: [PATCH 1/8] :sparkles: [#2173] Add map tile layer admin configuration --- src/openforms/config/admin.py | 16 +++++- .../config/migrations/0069_maptilelayer.py | 56 +++++++++++++++++++ src/openforms/config/models/__init__.py | 2 + src/openforms/config/models/map.py | 36 ++++++++++++ .../fixtures/default_admin_index.json | 4 ++ 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/openforms/config/migrations/0069_maptilelayer.py create mode 100644 src/openforms/config/models/map.py diff --git a/src/openforms/config/admin.py b/src/openforms/config/admin.py index b050ba22af..7a8bb08a8d 100644 --- a/src/openforms/config/admin.py +++ b/src/openforms/config/admin.py @@ -9,7 +9,7 @@ from .admin_views import ThemePreviewView from .forms import GlobalConfigurationAdminForm, ThemeAdminForm -from .models import CSPSetting, GlobalConfiguration, RichTextColor, Theme +from .models import CSPSetting, GlobalConfiguration, MapTileLayer, RichTextColor, Theme @admin.register(GlobalConfiguration) @@ -221,6 +221,20 @@ class RichTextColorAdmin(admin.ModelAdmin): ] +@admin.register(MapTileLayer) +class MapTileLayerAdmin(admin.ModelAdmin): + fields = ( + "label", + "identifier", + "url", + ) + list_display = ( + "label", + "identifier", + "url", + ) + + @admin.register(CSPSetting) class CSPSettingAdmin(admin.ModelAdmin): readonly_fields = ("content_type_link",) diff --git a/src/openforms/config/migrations/0069_maptilelayer.py b/src/openforms/config/migrations/0069_maptilelayer.py new file mode 100644 index 0000000000..f14b8cb3a2 --- /dev/null +++ b/src/openforms/config/migrations/0069_maptilelayer.py @@ -0,0 +1,56 @@ +# Generated by Django 4.2.17 on 2024-12-17 12:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("config", "0068_update_summary_tags"), + ] + + operations = [ + migrations.CreateModel( + name="MapTileLayer", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "identifier", + models.SlugField( + help_text="A unique identifier for the tile layer.", + unique=True, + verbose_name="identifier", + ), + ), + ( + "url", + models.URLField( + help_text="URL to the tile layer image, used to define the map component background. To ensure correct functionality of the map, EPSG 28992 projection should be used. Example value: https://service.pdok.nl/brt/achtergrondkaart/wmts/v2_0/standaard/EPSG:28992/{z}/{x}/{y}.png", + max_length=255, + verbose_name="tile layer url", + ), + ), + ( + "label", + models.CharField( + help_text="An easily recognizable name for the tile layer, used to identify it.", + max_length=100, + verbose_name="label", + ), + ), + ], + options={ + "verbose_name": "map tile layer", + "verbose_name_plural": "map tile layers", + "ordering": ("label",), + }, + ), + ] diff --git a/src/openforms/config/models/__init__.py b/src/openforms/config/models/__init__.py index ae0efffa9d..9e082fe22c 100644 --- a/src/openforms/config/models/__init__.py +++ b/src/openforms/config/models/__init__.py @@ -1,11 +1,13 @@ from .color import RichTextColor from .config import GlobalConfiguration from .csp import CSPSetting +from .map import MapTileLayer from .theme import Theme __all__ = [ "CSPSetting", "GlobalConfiguration", "RichTextColor", + "MapTileLayer", "Theme", ] diff --git a/src/openforms/config/models/map.py b/src/openforms/config/models/map.py new file mode 100644 index 0000000000..1d22532fa2 --- /dev/null +++ b/src/openforms/config/models/map.py @@ -0,0 +1,36 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class MapTileLayer(models.Model): + identifier = models.SlugField( + _("identifier"), + unique=True, + max_length=50, + help_text=_("A unique identifier for the tile layer."), + ) + url = models.URLField( + _("tile layer url"), + max_length=255, + help_text=_( + "URL to the tile layer image, used to define the map component " + "background. To ensure correct functionality of the map, " + "EPSG 28992 projection should be used. " + "Example value: https://service.pdok.nl/brt/achtergrondkaart/wmts/v2_0/standaard/EPSG:28992/{z}/{x}/{y}.png" + ), + ) + label = models.CharField( + _("label"), + max_length=100, + help_text=_( + "An easily recognizable name for the tile layer, used to identify it." + ), + ) + + class Meta: + verbose_name = _("map tile layer") + verbose_name_plural = _("map tile layers") + ordering = ("label",) + + def __str__(self): + return self.label diff --git a/src/openforms/fixtures/default_admin_index.json b/src/openforms/fixtures/default_admin_index.json index 5728763f45..24c1df32a8 100644 --- a/src/openforms/fixtures/default_admin_index.json +++ b/src/openforms/fixtures/default_admin_index.json @@ -162,6 +162,10 @@ "config", "globalconfiguration" ], + [ + "config", + "maptilelayer" + ], [ "config", "theme" From cfe9972056d2bf3e820b8f4fa431ad748120aa52 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Mon, 25 Nov 2024 13:49:05 +0100 Subject: [PATCH 2/8] :sparkles: [#2173] Populate WebformBuilder getMapTileLayers --- src/openforms/forms/admin/mixins.py | 7 ++++++- .../templates/admin/forms/includes/formio_config.html | 1 + .../js/components/formio_builder/WebformBuilder.js | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/openforms/forms/admin/mixins.py b/src/openforms/forms/admin/mixins.py index 84000b2003..2f8361f86d 100644 --- a/src/openforms/forms/admin/mixins.py +++ b/src/openforms/forms/admin/mixins.py @@ -5,7 +5,7 @@ from zgw_consumers.api_models.constants import VertrouwelijkheidsAanduidingen from openforms.config.constants import UploadFileType -from openforms.config.models import GlobalConfiguration, RichTextColor +from openforms.config.models import GlobalConfiguration, MapTileLayer, RichTextColor def get_rich_text_colors(): @@ -17,6 +17,10 @@ def get_rich_text_colors(): return colors +def get_map_tile_layers(): + return list(MapTileLayer.objects.values("identifier", "url", "label")) + + class FormioConfigMixin: def render_change_form( self, request, context, add=False, change=False, form_url="", obj=None @@ -26,6 +30,7 @@ def render_change_form( { "required_default": config.form_fields_required_default, "rich_text_colors": get_rich_text_colors(), + "map_tile_layers": get_map_tile_layers(), "upload_filetypes": [ {"label": label, "value": value} for value, label in UploadFileType.choices diff --git a/src/openforms/forms/templates/admin/forms/includes/formio_config.html b/src/openforms/forms/templates/admin/forms/includes/formio_config.html index f512ba493a..51ce7aaecb 100644 --- a/src/openforms/forms/templates/admin/forms/includes/formio_config.html +++ b/src/openforms/forms/templates/admin/forms/includes/formio_config.html @@ -1,5 +1,6 @@ {{ feature_flags|json_script:"feature-flags" }} {{ required_default|json_script:'config-REQUIRED_DEFAULT' }} {{ rich_text_colors|json_script:'config-RICH_TEXT_COLORS' }} +{{ map_tile_layers|json_script:'config-MAP_TILE_LAYERS' }} {{ upload_filetypes|json_script:'config-UPLOAD_FILETYPES' }} {{ confidentiality_levels|json_script:'CONFIDENTIALITY_LEVELS' }} diff --git a/src/openforms/js/components/formio_builder/WebformBuilder.js b/src/openforms/js/components/formio_builder/WebformBuilder.js index fe598f4abe..c48b1e2811 100644 --- a/src/openforms/js/components/formio_builder/WebformBuilder.js +++ b/src/openforms/js/components/formio_builder/WebformBuilder.js @@ -37,6 +37,7 @@ const CONFIDENTIALITY_LEVELS = jsonScriptToVar('CONFIDENTIALITY_LEVELS', {defaul const FILE_TYPES = jsonScriptToVar('config-UPLOAD_FILETYPES', {default: []}); const MAX_FILE_UPLOAD_SIZE = jsonScriptToVar('setting-MAX_FILE_UPLOAD_SIZE', {default: 'unknown'}); const RICH_TEXT_COLORS = jsonScriptToVar('config-RICH_TEXT_COLORS', {default: []}); +const MAP_TILE_LAYERS = jsonScriptToVar('config-MAP_TILE_LAYERS', {default: []}); const WebformBuilderFormio = Formio.Builders.builders.webform; @@ -162,6 +163,7 @@ class WebformBuilder extends WebformBuilderFormio { supportedLanguageCodes={LANGUAGES} theme={currentTheme.getValue()} richTextColors={RICH_TEXT_COLORS} + getMapTileLayers={async () => MAP_TILE_LAYERS} getFormComponents={() => this.webform.form.components} getValidatorPlugins={getValidatorPlugins} getRegistrationAttributes={getRegistrationAttributes} From 824b2d247f9e9ba4dd8859084ab82014ef079f5d Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Mon, 25 Nov 2024 14:19:04 +0100 Subject: [PATCH 3/8] :sparkles: [#2173] Define map tile layer url on request The map configuration can be edited at any moment. To make sure that we always return the correct tile layer url, fetch the url and add it to the component object dynamically. Using the mutate_config_dynamically we can now use the tile_layer_url for form steps and the form summary. --- src/openforms/formio/components/custom.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/openforms/formio/components/custom.py b/src/openforms/formio/components/custom.py index e7521c482b..7f78b6fc19 100644 --- a/src/openforms/formio/components/custom.py +++ b/src/openforms/formio/components/custom.py @@ -14,7 +14,7 @@ from rest_framework.request import Request from openforms.authentication.service import AuthAttribute -from openforms.config.models import GlobalConfiguration +from openforms.config.models import GlobalConfiguration, MapTileLayer from openforms.submissions.models import Submission from openforms.typing import DataMapping from openforms.utils.date import TIMEZONE_AMS, datetime_in_amsterdam, format_date_value @@ -189,6 +189,15 @@ def build_serializer_field( class Map(BasePlugin[Component]): formatter = MapFormatter + def mutate_config_dynamically( + self, component, submission: Submission, data: DataMapping + ) -> None: + if (identifier := component.get("tileLayerIdentifier")) is not None: + tile_layer = MapTileLayer.objects.filter(identifier=identifier).first() + if tile_layer is not None: + # Add the tile layer url information + component["tileLayerUrl"] = tile_layer.url + @staticmethod def rewrite_for_request(component, request: Request): if component.get("useConfigDefaultMapSettings", False): From ecaa45fe048d0e957d776a3f3d60eeaf6218f910 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Mon, 25 Nov 2024 14:23:26 +0100 Subject: [PATCH 4/8] :sparkles: [#2173] Show map background in form design view --- src/openforms/js/components/form/map.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/openforms/js/components/form/map.js b/src/openforms/js/components/form/map.js index af1ffbd2fa..f9aa737ed8 100644 --- a/src/openforms/js/components/form/map.js +++ b/src/openforms/js/components/form/map.js @@ -5,6 +5,8 @@ import {CRS_RD, TILE_LAYER_RD} from '@open-formulieren/leaflet-tools'; import * as L from 'leaflet'; import {Formio} from 'react-formio'; +import jsonScriptToVar from 'utils/json-script'; + import {localiseSchema} from './i18n'; const TextFieldComponent = Formio.Components.components.textfield; @@ -17,6 +19,10 @@ const MAP_DEFAULTS = { zoom: 3, }; +const MAP_TILE_LAYERS = jsonScriptToVar('config-MAP_TILE_LAYERS', { + default: [], +}); + export default class Map extends TextFieldComponent { static schema(...extend) { const schema = TextFieldComponent.schema( @@ -70,8 +76,23 @@ export default class Map extends TextFieldComponent { const map = L.map(`map-${this.id}`, MAP_DEFAULTS); - const {url: tileUrl, ...options} = TILE_LAYER_RD; - const tiles = L.tileLayer(tileUrl, options); + const tiles = L.tileLayer( + this.getTileLayerUrl(this.originalComponent?.tileLayerIdentifier), + TILE_LAYER_RD + ); map.addLayer(tiles); } + + // Try to get the tile layer url for the component. + // If it cannot be found, return the default url. + getTileLayerUrl(tileLayerIdentifier) { + if (!Array.isArray(MAP_TILE_LAYERS) || MAP_TILE_LAYERS.length === 0 || !tileLayerIdentifier) { + return TILE_LAYER_RD.url; + } + + return ( + MAP_TILE_LAYERS.find(tileLayer => tileLayer?.identifier === tileLayerIdentifier)?.url ?? + TILE_LAYER_RD.url + ); + } } From d7eb7bd9d2a70558390f7efbefe888266c669e30 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Mon, 2 Dec 2024 17:20:08 +0100 Subject: [PATCH 5/8] :sparkles: [#2173] Add default map tile layer fixtures --- INSTALL.rst | 13 +++++++++--- ...enerate_default_map_tile_layers_fixture.sh | 11 ++++++++++ .../fixtures/default_map_tile_layers.json | 20 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100755 bin/generate_default_map_tile_layers_fixture.sh create mode 100644 src/openforms/fixtures/default_map_tile_layers.json diff --git a/INSTALL.rst b/INSTALL.rst index fbd52c59d6..b717880660 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -432,11 +432,18 @@ After configuring the application groups in the admin through point-and-click, y call this script to dump the configuration into a fixture which will be loaded on all other installations. -``bin/generate_default_groups_fixtures.sh`` -------------------------------------------- +``bin/generate_default_groups_fixture.sh`` +------------------------------------------ After configuring the user groups with the appropriate permissions in the admin, -you can this script to dump the configuration into a fixture which will be loaded on +you call this script to dump the configuration into a fixture which will be loaded on +all other installations. + +``bin/generate_default_map_tile_layers_fixture.sh`` +----------------------------------------------------------- + +After configuring the map tile layers in the admin, +you call this script to dump the configuration into a fixture which will be loaded on all other installations. ``bin/generate_oas.sh`` diff --git a/bin/generate_default_map_tile_layers_fixture.sh b/bin/generate_default_map_tile_layers_fixture.sh new file mode 100755 index 0000000000..6293f8bc60 --- /dev/null +++ b/bin/generate_default_map_tile_layers_fixture.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Dump the current (local database) config MapTileLayer to a JSON fixture. +# This overwrites the existing one. +# +# You can load this fixture with: +# $ src/manage.py loaddata default_map_tile_layers +# +# Run this script from the root of the repository + +src/manage.py dumpdata --indent=4 --natural-foreign --natural-primary config.MapTileLayer > src/openforms/fixtures/default_map_tile_layers.json diff --git a/src/openforms/fixtures/default_map_tile_layers.json b/src/openforms/fixtures/default_map_tile_layers.json new file mode 100644 index 0000000000..ece464ffb0 --- /dev/null +++ b/src/openforms/fixtures/default_map_tile_layers.json @@ -0,0 +1,20 @@ +[ +{ + "model": "config.maptilelayer", + "pk": 1, + "fields": { + "identifier": "brt", + "url": "https://service.pdok.nl/brt/achtergrondkaart/wmts/v2_0/standaard/EPSG:28992/{z}/{x}/{y}.png", + "label": "BRT" + } +}, +{ + "model": "config.maptilelayer", + "pk": 2, + "fields": { + "identifier": "luchtfoto", + "url": "https://service.pdok.nl/hwh/luchtfotorgb/wmts/v1_0/Actueel_orthoHR/EPSG:28992/{z}/{x}/{y}.png", + "label": "Luchtfoto" + } +} +] From 7815e2f8fff06f01c8a0f458c0a65c71896b4550 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Tue, 3 Dec 2024 11:38:18 +0100 Subject: [PATCH 6/8] :white_check_mark: [#2173] Added tests for map component --- src/openforms/config/tests/factories.py | 9 + src/openforms/config/tests/test_admin.py | 23 +- .../formio/tests/test_dynamic_config.py | 292 ++++++++++++++++++ 3 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/openforms/formio/tests/test_dynamic_config.py diff --git a/src/openforms/config/tests/factories.py b/src/openforms/config/tests/factories.py index 4cec877d42..08476e714b 100644 --- a/src/openforms/config/tests/factories.py +++ b/src/openforms/config/tests/factories.py @@ -14,3 +14,12 @@ class ThemeFactory(factory.django.DjangoModelFactory): class Meta: model = "config.Theme" + + +class MapTileLayerFactory(factory.django.DjangoModelFactory): + identifier = factory.Faker("word") + url = factory.Sequence(lambda n: f"http://example-{n}.com") + label = factory.Faker("word") + + class Meta: + model = "config.MapTileLayer" diff --git a/src/openforms/config/tests/test_admin.py b/src/openforms/config/tests/test_admin.py index cc924ed832..2f60b53bd7 100644 --- a/src/openforms/config/tests/test_admin.py +++ b/src/openforms/config/tests/test_admin.py @@ -5,7 +5,7 @@ from openforms.accounts.tests.factories import SuperUserFactory -from .factories import RichTextColorFactory +from .factories import MapTileLayerFactory, RichTextColorFactory @disable_admin_mfa() @@ -27,3 +27,24 @@ def test_color_detail(self): response = self.app.get(url, user=user) self.assertEqual(response.status_code, 200) + + +@disable_admin_mfa() +class MapTileLayerTests(WebTest): + def test_map_tile_layer_changelist(self): + MapTileLayerFactory.create_batch(9) + url = reverse("admin:config_maptilelayer_changelist") + user = SuperUserFactory.create() + + response = self.app.get(url, user=user) + + self.assertEqual(response.status_code, 200) + + def test_map_tile_layer_detail(self): + map = MapTileLayerFactory.create() + url = reverse("admin:config_maptilelayer_change", args=(map.pk,)) + user = SuperUserFactory.create() + + response = self.app.get(url, user=user) + + self.assertEqual(response.status_code, 200) diff --git a/src/openforms/formio/tests/test_dynamic_config.py b/src/openforms/formio/tests/test_dynamic_config.py new file mode 100644 index 0000000000..5395377403 --- /dev/null +++ b/src/openforms/formio/tests/test_dynamic_config.py @@ -0,0 +1,292 @@ +from unittest.mock import Mock, patch + +from django.test import TestCase + +from rest_framework.test import APIRequestFactory + +from openforms.config.models import GlobalConfiguration +from openforms.config.tests.factories import MapTileLayerFactory +from openforms.formio.datastructures import FormioConfigurationWrapper +from openforms.formio.dynamic_config import ( + rewrite_formio_components, + rewrite_formio_components_for_request, +) +from openforms.submissions.tests.factories import SubmissionFactory + +rf = APIRequestFactory() + + +class DynamicConfigTests(TestCase): + @patch("openforms.formio.components.vanilla.GlobalConfiguration.get_solo") + def test_map_without_default_map_config(self, m_solo: Mock): + m_solo.return_value = GlobalConfiguration( + form_map_default_zoom_level=8, + form_map_default_latitude=55.123, + form_map_default_longitude=56.456, + ) + + configuration = { + "components": [ + { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + } + ] + } + formio_configuration = FormioConfigurationWrapper(configuration) + submission = SubmissionFactory.create() + rewrite_formio_components(formio_configuration, submission) + + request = rf.get("/dummy") + rewrite_formio_components_for_request(formio_configuration, request) + + expected = { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + } + self.assertEqual(configuration["components"][0], expected) + + @patch("openforms.formio.components.vanilla.GlobalConfiguration.get_solo") + def test_map_with_default_map_config(self, m_solo: Mock): + m_solo.return_value = GlobalConfiguration( + form_map_default_zoom_level=8, + form_map_default_latitude=55.123, + form_map_default_longitude=56.456, + ) + + configuration = { + "components": [ + { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": True, + } + ] + } + formio_configuration = FormioConfigurationWrapper(configuration) + submission = SubmissionFactory.create() + rewrite_formio_components(formio_configuration, submission) + + request = rf.get("/dummy") + rewrite_formio_components_for_request(formio_configuration, request) + + expected = { + "type": "map", + "key": "map", + "defaultZoom": 8, + "initialCenter": { + "lat": 55.123, + "lng": 56.456, + }, + "useConfigDefaultMapSettings": True, + } + self.assertEqual(configuration["components"][0], expected) + + def test_map_without_tile_layer_identifier(self): + configuration = { + "components": [ + { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": None, + } + ] + } + formio_configuration = FormioConfigurationWrapper(configuration) + submission = SubmissionFactory.create() + rewrite_formio_components(formio_configuration, submission) + + request = rf.get("/dummy") + rewrite_formio_components_for_request(formio_configuration, request) + + expected = { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": None, + } + self.assertEqual(configuration["components"][0], expected) + + def test_map_with_invalid_tile_layer_identifier(self): + configuration = { + "components": [ + { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": "", + } + ] + } + formio_configuration = FormioConfigurationWrapper(configuration) + submission = SubmissionFactory.create() + rewrite_formio_components(formio_configuration, submission) + + request = rf.get("/dummy") + rewrite_formio_components_for_request(formio_configuration, request) + + expected = { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": "", + } + self.assertEqual(configuration["components"][0], expected) + + def test_map_with_valid_unknown_tile_layer_identifier(self): + configuration = { + "components": [ + { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": "identifier", + } + ] + } + formio_configuration = FormioConfigurationWrapper(configuration) + submission = SubmissionFactory.create() + rewrite_formio_components(formio_configuration, submission) + + request = rf.get("/dummy") + rewrite_formio_components_for_request(formio_configuration, request) + + expected = { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": "identifier", + } + self.assertEqual(configuration["components"][0], expected) + + def test_map_with_valid_known_tile_layer_identifier(self): + map = MapTileLayerFactory.create(identifier="identifier") + configuration = { + "components": [ + { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": "identifier", + } + ] + } + formio_configuration = FormioConfigurationWrapper(configuration) + submission = SubmissionFactory.create() + rewrite_formio_components(formio_configuration, submission) + + request = rf.get("/dummy") + rewrite_formio_components_for_request(formio_configuration, request) + + expected = { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": False, + "tileLayerIdentifier": "identifier", + "tileLayerUrl": map.url, + } + self.assertEqual(configuration["components"][0], expected) + + @patch("openforms.formio.components.vanilla.GlobalConfiguration.get_solo") + def test_map_with_valid_known_tile_layer_identifier_and_use_config_default_map_settings( + self, m_solo: Mock + ): + m_solo.return_value = GlobalConfiguration( + form_map_default_zoom_level=8, + form_map_default_latitude=55.123, + form_map_default_longitude=56.456, + ) + map = MapTileLayerFactory.create(identifier="identifier") + configuration = { + "components": [ + { + "type": "map", + "key": "map", + "defaultZoom": 3, + "initialCenter": { + "lat": 43.23, + "lng": 41.23, + }, + "useConfigDefaultMapSettings": True, + "tileLayerIdentifier": "identifier", + } + ] + } + formio_configuration = FormioConfigurationWrapper(configuration) + submission = SubmissionFactory.create() + rewrite_formio_components(formio_configuration, submission) + + request = rf.get("/dummy") + rewrite_formio_components_for_request(formio_configuration, request) + + expected = { + "type": "map", + "key": "map", + "defaultZoom": 8, + "initialCenter": { + "lat": 55.123, + "lng": 56.456, + }, + "useConfigDefaultMapSettings": True, + "tileLayerIdentifier": "identifier", + "tileLayerUrl": map.url, + } + self.assertEqual(configuration["components"][0], expected) From 9f90a8fb367997a2a8d21fc9b52257c15dc8b65d Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Mon, 16 Dec 2024 09:18:55 +0100 Subject: [PATCH 7/8] :arrow_up: [#2173] Upgraded formio-builder dependency --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f7278b025..5a02c8508f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.1.1", "@open-formulieren/design-tokens": "^0.53.0", - "@open-formulieren/formio-builder": "^0.33.0", + "@open-formulieren/formio-builder": "^0.34.0", "@open-formulieren/leaflet-tools": "^1.0.0", "@open-formulieren/monaco-json-editor": "^0.2.0", "@tinymce/tinymce-react": "^4.3.2", @@ -4526,9 +4526,10 @@ "integrity": "sha512-3Pv32ULCuFOJZ2GaqcpvB45u6xScr0lmW5ETB9P1Ox9TG5nvMcVSwuwYe/GwxbzmvtZgiMQRMKRFT9lNYLeREQ==" }, "node_modules/@open-formulieren/formio-builder": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.33.0.tgz", - "integrity": "sha512-mNozndNckQIO/mxKJYuqbqvvKmVU3EZCFKm0raunpS7gKEPyJl8vNV5fF9o8gNghh9xwBFA4usjNuCSySJwFqQ==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.34.0.tgz", + "integrity": "sha512-inWjRzAdTCSfkeSQbtF5I9XA1EqfaAx1tLCAzZ+XRnrtfK2+/md0dp+dbKCsRwZUDDzzaojECtn5LGeIkubYRA==", + "license": "EUPL-1.2", "dependencies": { "@ckeditor/ckeditor5-react": "^6.2.0", "@floating-ui/react": "^0.26.4", @@ -23105,9 +23106,9 @@ "integrity": "sha512-3Pv32ULCuFOJZ2GaqcpvB45u6xScr0lmW5ETB9P1Ox9TG5nvMcVSwuwYe/GwxbzmvtZgiMQRMKRFT9lNYLeREQ==" }, "@open-formulieren/formio-builder": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.33.0.tgz", - "integrity": "sha512-mNozndNckQIO/mxKJYuqbqvvKmVU3EZCFKm0raunpS7gKEPyJl8vNV5fF9o8gNghh9xwBFA4usjNuCSySJwFqQ==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.34.0.tgz", + "integrity": "sha512-inWjRzAdTCSfkeSQbtF5I9XA1EqfaAx1tLCAzZ+XRnrtfK2+/md0dp+dbKCsRwZUDDzzaojECtn5LGeIkubYRA==", "requires": { "@ckeditor/ckeditor5-react": "^6.2.0", "@floating-ui/react": "^0.26.4", diff --git a/package.json b/package.json index c2a8f3c2a2..ac2c6b996a 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.1.1", "@open-formulieren/design-tokens": "^0.53.0", - "@open-formulieren/formio-builder": "^0.33.0", + "@open-formulieren/formio-builder": "^0.34.0", "@open-formulieren/leaflet-tools": "^1.0.0", "@open-formulieren/monaco-json-editor": "^0.2.0", "@tinymce/tinymce-react": "^4.3.2", From b58cddfcd38a17df8ee905c0456704c8f27e8d92 Mon Sep 17 00:00:00 2001 From: robinvandermolen Date: Tue, 17 Dec 2024 14:13:30 +0100 Subject: [PATCH 8/8] :label: [#2173] Add MapComponent type definition --- src/openforms/formio/components/custom.py | 16 +++++++++++----- src/openforms/formio/formatters/custom.py | 4 ++-- src/openforms/formio/typing/__init__.py | 3 ++- src/openforms/formio/typing/custom.py | 10 ++++++++++ src/openforms/formio/typing/map.py | 6 ++++++ 5 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 src/openforms/formio/typing/map.py diff --git a/src/openforms/formio/components/custom.py b/src/openforms/formio/components/custom.py index 7f78b6fc19..e36f585677 100644 --- a/src/openforms/formio/components/custom.py +++ b/src/openforms/formio/components/custom.py @@ -31,7 +31,13 @@ ) from ..formatters.formio import DefaultFormatter, TextFieldFormatter from ..registry import BasePlugin, register -from ..typing import AddressNLComponent, Component, DateComponent, DatetimeComponent +from ..typing import ( + AddressNLComponent, + Component, + DateComponent, + DatetimeComponent, + MapComponent, +) from ..utils import conform_to_mask from .np_family_members.constants import FamilyMembersDataAPIChoices from .np_family_members.haal_centraal import get_np_family_members_haal_centraal @@ -186,11 +192,11 @@ def build_serializer_field( @register("map") -class Map(BasePlugin[Component]): +class Map(BasePlugin[MapComponent]): formatter = MapFormatter def mutate_config_dynamically( - self, component, submission: Submission, data: DataMapping + self, component: MapComponent, submission: Submission, data: DataMapping ) -> None: if (identifier := component.get("tileLayerIdentifier")) is not None: tile_layer = MapTileLayer.objects.filter(identifier=identifier).first() @@ -199,7 +205,7 @@ def mutate_config_dynamically( component["tileLayerUrl"] = tile_layer.url @staticmethod - def rewrite_for_request(component, request: Request): + def rewrite_for_request(component: MapComponent, request: Request): if component.get("useConfigDefaultMapSettings", False): config = GlobalConfiguration.get_solo() component["defaultZoom"] = config.form_map_default_zoom_level @@ -207,7 +213,7 @@ def rewrite_for_request(component, request: Request): component["initialCenter"]["lat"] = config.form_map_default_latitude component["initialCenter"]["lng"] = config.form_map_default_longitude - def build_serializer_field(self, component: Component) -> serializers.ListField: + def build_serializer_field(self, component: MapComponent) -> serializers.ListField: validate = component.get("validate", {}) required = validate.get("required", False) base = serializers.FloatField( diff --git a/src/openforms/formio/formatters/custom.py b/src/openforms/formio/formatters/custom.py index a76a4cec6d..6c87744fc9 100644 --- a/src/openforms/formio/formatters/custom.py +++ b/src/openforms/formio/formatters/custom.py @@ -6,7 +6,7 @@ from django.utils.html import format_html from django.utils.safestring import mark_safe -from ..typing import AddressNLComponent, Component +from ..typing import AddressNLComponent, Component, MapComponent from .base import FormatterBase @@ -22,7 +22,7 @@ def format(self, component: Component, value: str) -> str: class MapFormatter(FormatterBase): - def format(self, component: Component, value: list[float]) -> str: + def format(self, component: MapComponent, value: list[float]) -> str: # use a comma here since its a single data element return ", ".join((str(x) for x in value)) diff --git a/src/openforms/formio/typing/__init__.py b/src/openforms/formio/typing/__init__.py index a086a4a252..8283e31bee 100644 --- a/src/openforms/formio/typing/__init__.py +++ b/src/openforms/formio/typing/__init__.py @@ -7,7 +7,7 @@ """ from .base import Component, FormioConfiguration, OptionDict -from .custom import AddressNLComponent, DateComponent +from .custom import AddressNLComponent, DateComponent, MapComponent from .vanilla import ( Column, ColumnsComponent, @@ -43,5 +43,6 @@ # special "EditGridComponent", "AddressNLComponent", + "MapComponent", # deprecated ] diff --git a/src/openforms/formio/typing/custom.py b/src/openforms/formio/typing/custom.py index c1db889011..76792272da 100644 --- a/src/openforms/formio/typing/custom.py +++ b/src/openforms/formio/typing/custom.py @@ -2,6 +2,7 @@ from .base import Component from .dates import DatePickerConfig, DatePickerCustomOptions +from .map import MapInitialCenter class DateComponent(Component): @@ -11,3 +12,12 @@ class DateComponent(Component): class AddressNLComponent(Component): deriveAddress: bool + + +class MapComponent(Component): + useConfigDefaultMapSettings: bool + defaultZoom: NotRequired[int] + initialCenter: NotRequired[MapInitialCenter] + tileLayerIdentifier: NotRequired[str] + # The tileLayerUrl will be dynamically generated from the tileLayerIdentifier + tileLayerUrl: NotRequired[str] diff --git a/src/openforms/formio/typing/map.py b/src/openforms/formio/typing/map.py new file mode 100644 index 0000000000..cfc712f65d --- /dev/null +++ b/src/openforms/formio/typing/map.py @@ -0,0 +1,6 @@ +from typing import NotRequired, TypedDict + + +class MapInitialCenter(TypedDict): + lat: NotRequired[float] + lng: NotRequired[float]