Skip to content

Commit 77b340a

Browse files
Merge pull request #4881 from open-formulieren/feature/2173-leaflet-map-background
Add new leaflet map background configuration
2 parents 4f57eb8 + b58cddf commit 77b340a

22 files changed

+558
-24
lines changed

INSTALL.rst

+10-3
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,18 @@ After configuring the application groups in the admin through point-and-click, y
432432
call this script to dump the configuration into a fixture which will be loaded on
433433
all other installations.
434434

435-
``bin/generate_default_groups_fixtures.sh``
436-
-------------------------------------------
435+
``bin/generate_default_groups_fixture.sh``
436+
------------------------------------------
437437

438438
After configuring the user groups with the appropriate permissions in the admin,
439-
you can this script to dump the configuration into a fixture which will be loaded on
439+
you call this script to dump the configuration into a fixture which will be loaded on
440+
all other installations.
441+
442+
``bin/generate_default_map_tile_layers_fixture.sh``
443+
-----------------------------------------------------------
444+
445+
After configuring the map tile layers in the admin,
446+
you call this script to dump the configuration into a fixture which will be loaded on
440447
all other installations.
441448

442449
``bin/generate_oas.sh``
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
#
3+
# Dump the current (local database) config MapTileLayer to a JSON fixture.
4+
# This overwrites the existing one.
5+
#
6+
# You can load this fixture with:
7+
# $ src/manage.py loaddata default_map_tile_layers
8+
#
9+
# Run this script from the root of the repository
10+
11+
src/manage.py dumpdata --indent=4 --natural-foreign --natural-primary config.MapTileLayer > src/openforms/fixtures/default_map_tile_layers.json

package-lock.json

+8-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"dependencies": {
3131
"@fortawesome/fontawesome-free": "^6.1.1",
3232
"@open-formulieren/design-tokens": "^0.53.0",
33-
"@open-formulieren/formio-builder": "^0.33.0",
33+
"@open-formulieren/formio-builder": "^0.34.0",
3434
"@open-formulieren/leaflet-tools": "^1.0.0",
3535
"@open-formulieren/monaco-json-editor": "^0.2.0",
3636
"@tinymce/tinymce-react": "^4.3.2",

src/openforms/config/admin.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from .admin_views import ThemePreviewView
1111
from .forms import GlobalConfigurationAdminForm, ThemeAdminForm
12-
from .models import CSPSetting, GlobalConfiguration, RichTextColor, Theme
12+
from .models import CSPSetting, GlobalConfiguration, MapTileLayer, RichTextColor, Theme
1313

1414

1515
@admin.register(GlobalConfiguration)
@@ -221,6 +221,20 @@ class RichTextColorAdmin(admin.ModelAdmin):
221221
]
222222

223223

224+
@admin.register(MapTileLayer)
225+
class MapTileLayerAdmin(admin.ModelAdmin):
226+
fields = (
227+
"label",
228+
"identifier",
229+
"url",
230+
)
231+
list_display = (
232+
"label",
233+
"identifier",
234+
"url",
235+
)
236+
237+
224238
@admin.register(CSPSetting)
225239
class CSPSettingAdmin(admin.ModelAdmin):
226240
readonly_fields = ("content_type_link",)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Generated by Django 4.2.17 on 2024-12-17 12:42
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("config", "0068_update_summary_tags"),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name="MapTileLayer",
15+
fields=[
16+
(
17+
"id",
18+
models.AutoField(
19+
auto_created=True,
20+
primary_key=True,
21+
serialize=False,
22+
verbose_name="ID",
23+
),
24+
),
25+
(
26+
"identifier",
27+
models.SlugField(
28+
help_text="A unique identifier for the tile layer.",
29+
unique=True,
30+
verbose_name="identifier",
31+
),
32+
),
33+
(
34+
"url",
35+
models.URLField(
36+
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",
37+
max_length=255,
38+
verbose_name="tile layer url",
39+
),
40+
),
41+
(
42+
"label",
43+
models.CharField(
44+
help_text="An easily recognizable name for the tile layer, used to identify it.",
45+
max_length=100,
46+
verbose_name="label",
47+
),
48+
),
49+
],
50+
options={
51+
"verbose_name": "map tile layer",
52+
"verbose_name_plural": "map tile layers",
53+
"ordering": ("label",),
54+
},
55+
),
56+
]
+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from .color import RichTextColor
22
from .config import GlobalConfiguration
33
from .csp import CSPSetting
4+
from .map import MapTileLayer
45
from .theme import Theme
56

67
__all__ = [
78
"CSPSetting",
89
"GlobalConfiguration",
910
"RichTextColor",
11+
"MapTileLayer",
1012
"Theme",
1113
]

src/openforms/config/models/map.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from django.db import models
2+
from django.utils.translation import gettext_lazy as _
3+
4+
5+
class MapTileLayer(models.Model):
6+
identifier = models.SlugField(
7+
_("identifier"),
8+
unique=True,
9+
max_length=50,
10+
help_text=_("A unique identifier for the tile layer."),
11+
)
12+
url = models.URLField(
13+
_("tile layer url"),
14+
max_length=255,
15+
help_text=_(
16+
"URL to the tile layer image, used to define the map component "
17+
"background. To ensure correct functionality of the map, "
18+
"EPSG 28992 projection should be used. "
19+
"Example value: https://service.pdok.nl/brt/achtergrondkaart/wmts/v2_0/standaard/EPSG:28992/{z}/{x}/{y}.png"
20+
),
21+
)
22+
label = models.CharField(
23+
_("label"),
24+
max_length=100,
25+
help_text=_(
26+
"An easily recognizable name for the tile layer, used to identify it."
27+
),
28+
)
29+
30+
class Meta:
31+
verbose_name = _("map tile layer")
32+
verbose_name_plural = _("map tile layers")
33+
ordering = ("label",)
34+
35+
def __str__(self):
36+
return self.label

src/openforms/config/tests/factories.py

+9
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@ class ThemeFactory(factory.django.DjangoModelFactory):
1414

1515
class Meta:
1616
model = "config.Theme"
17+
18+
19+
class MapTileLayerFactory(factory.django.DjangoModelFactory):
20+
identifier = factory.Faker("word")
21+
url = factory.Sequence(lambda n: f"http://example-{n}.com")
22+
label = factory.Faker("word")
23+
24+
class Meta:
25+
model = "config.MapTileLayer"

src/openforms/config/tests/test_admin.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from openforms.accounts.tests.factories import SuperUserFactory
77

8-
from .factories import RichTextColorFactory
8+
from .factories import MapTileLayerFactory, RichTextColorFactory
99

1010

1111
@disable_admin_mfa()
@@ -27,3 +27,24 @@ def test_color_detail(self):
2727
response = self.app.get(url, user=user)
2828

2929
self.assertEqual(response.status_code, 200)
30+
31+
32+
@disable_admin_mfa()
33+
class MapTileLayerTests(WebTest):
34+
def test_map_tile_layer_changelist(self):
35+
MapTileLayerFactory.create_batch(9)
36+
url = reverse("admin:config_maptilelayer_changelist")
37+
user = SuperUserFactory.create()
38+
39+
response = self.app.get(url, user=user)
40+
41+
self.assertEqual(response.status_code, 200)
42+
43+
def test_map_tile_layer_detail(self):
44+
map = MapTileLayerFactory.create()
45+
url = reverse("admin:config_maptilelayer_change", args=(map.pk,))
46+
user = SuperUserFactory.create()
47+
48+
response = self.app.get(url, user=user)
49+
50+
self.assertEqual(response.status_code, 200)

src/openforms/fixtures/default_admin_index.json

+4
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@
162162
"config",
163163
"globalconfiguration"
164164
],
165+
[
166+
"config",
167+
"maptilelayer"
168+
],
165169
[
166170
"config",
167171
"theme"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"model": "config.maptilelayer",
4+
"pk": 1,
5+
"fields": {
6+
"identifier": "brt",
7+
"url": "https://service.pdok.nl/brt/achtergrondkaart/wmts/v2_0/standaard/EPSG:28992/{z}/{x}/{y}.png",
8+
"label": "BRT"
9+
}
10+
},
11+
{
12+
"model": "config.maptilelayer",
13+
"pk": 2,
14+
"fields": {
15+
"identifier": "luchtfoto",
16+
"url": "https://service.pdok.nl/hwh/luchtfotorgb/wmts/v1_0/Actueel_orthoHR/EPSG:28992/{z}/{x}/{y}.png",
17+
"label": "Luchtfoto"
18+
}
19+
}
20+
]

src/openforms/formio/components/custom.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from rest_framework.request import Request
1515

1616
from openforms.authentication.service import AuthAttribute
17-
from openforms.config.models import GlobalConfiguration
17+
from openforms.config.models import GlobalConfiguration, MapTileLayer
1818
from openforms.submissions.models import Submission
1919
from openforms.typing import DataMapping
2020
from openforms.utils.date import TIMEZONE_AMS, datetime_in_amsterdam, format_date_value
@@ -31,7 +31,13 @@
3131
)
3232
from ..formatters.formio import DefaultFormatter, TextFieldFormatter
3333
from ..registry import BasePlugin, register
34-
from ..typing import AddressNLComponent, Component, DateComponent, DatetimeComponent
34+
from ..typing import (
35+
AddressNLComponent,
36+
Component,
37+
DateComponent,
38+
DatetimeComponent,
39+
MapComponent,
40+
)
3541
from ..utils import conform_to_mask
3642
from .np_family_members.constants import FamilyMembersDataAPIChoices
3743
from .np_family_members.haal_centraal import get_np_family_members_haal_centraal
@@ -186,19 +192,28 @@ def build_serializer_field(
186192

187193

188194
@register("map")
189-
class Map(BasePlugin[Component]):
195+
class Map(BasePlugin[MapComponent]):
190196
formatter = MapFormatter
191197

198+
def mutate_config_dynamically(
199+
self, component: MapComponent, submission: Submission, data: DataMapping
200+
) -> None:
201+
if (identifier := component.get("tileLayerIdentifier")) is not None:
202+
tile_layer = MapTileLayer.objects.filter(identifier=identifier).first()
203+
if tile_layer is not None:
204+
# Add the tile layer url information
205+
component["tileLayerUrl"] = tile_layer.url
206+
192207
@staticmethod
193-
def rewrite_for_request(component, request: Request):
208+
def rewrite_for_request(component: MapComponent, request: Request):
194209
if component.get("useConfigDefaultMapSettings", False):
195210
config = GlobalConfiguration.get_solo()
196211
component["defaultZoom"] = config.form_map_default_zoom_level
197212
component.setdefault("initialCenter", {})
198213
component["initialCenter"]["lat"] = config.form_map_default_latitude
199214
component["initialCenter"]["lng"] = config.form_map_default_longitude
200215

201-
def build_serializer_field(self, component: Component) -> serializers.ListField:
216+
def build_serializer_field(self, component: MapComponent) -> serializers.ListField:
202217
validate = component.get("validate", {})
203218
required = validate.get("required", False)
204219
base = serializers.FloatField(

src/openforms/formio/formatters/custom.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.utils.html import format_html
77
from django.utils.safestring import mark_safe
88

9-
from ..typing import AddressNLComponent, Component
9+
from ..typing import AddressNLComponent, Component, MapComponent
1010
from .base import FormatterBase
1111

1212

@@ -22,7 +22,7 @@ def format(self, component: Component, value: str) -> str:
2222

2323

2424
class MapFormatter(FormatterBase):
25-
def format(self, component: Component, value: list[float]) -> str:
25+
def format(self, component: MapComponent, value: list[float]) -> str:
2626
# use a comma here since its a single data element
2727
return ", ".join((str(x) for x in value))
2828

0 commit comments

Comments
 (0)