From a02b4ce19fca026bcb9ed3841205d3c972b65bd8 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 17 Aug 2024 17:52:15 +0200 Subject: [PATCH] Fixed #30049 -- Initialized GIS widgets when admin inlines are added. --- .../contrib/gis/static/gis/js/OLMapWidget.js | 26 +++++++++ .../contrib/gis/templates/gis/openlayers.html | 4 +- tests/gis_tests/geoadmin/models.py | 20 +++++++ tests/gis_tests/geoadmin/tests.py | 58 ++++++++++++++++--- tests/gis_tests/geoadmin/urls.py | 7 ++- tests/gis_tests/test_geoforms.py | 3 +- 6 files changed, 104 insertions(+), 14 deletions(-) diff --git a/django/contrib/gis/static/gis/js/OLMapWidget.js b/django/contrib/gis/static/gis/js/OLMapWidget.js index a545036c9f..56b33462c6 100644 --- a/django/contrib/gis/static/gis/js/OLMapWidget.js +++ b/django/contrib/gis/static/gis/js/OLMapWidget.js @@ -231,3 +231,29 @@ class MapWidget { document.getElementById(this.options.id).value = jsonFormat.writeGeometry(geometry); } } + +{ + function initMapWidgetAfterInlineAdd(event) { + const formsetIndex = event.srcElement.id.split('-').pop(); + event.target.querySelectorAll(".dj_map_wrapper").forEach((wrapper) => { + wrapper.querySelector(".dj_map").innerHTML = ""; + const options = window[wrapper.dataset.widgetoptions]; + const newOptions = {}; + for (const key in options) { + if (options[key].includes && options[key].includes("__prefix__")) { + newOptions[key] = options[key].replace( "__prefix__", formsetIndex); + } else { + newOptions[key] = options[key]; + } + } + new MapWidget(newOptions); + }); + } + document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll(".dj_map_wrapper").forEach((wrapper) => { + const options = window[wrapper.dataset.widgetoptions]; + new MapWidget(options); + }); + document.addEventListener('formset:added', initMapWidgetAfterInlineAdd); + }); +} diff --git a/django/contrib/gis/templates/gis/openlayers.html b/django/contrib/gis/templates/gis/openlayers.html index f9f7e5fa51..7657fa0026 100644 --- a/django/contrib/gis/templates/gis/openlayers.html +++ b/django/contrib/gis/templates/gis/openlayers.html @@ -1,6 +1,6 @@ {% load i18n l10n %} -
+
{% if not disabled %}{% translate "Delete all Features" %}{% endif %} {% if display_raw %}

{% translate "Debugging window (serialized value)" %}

{% endif %} @@ -27,6 +27,6 @@ name: '{{ name }}' }; {% endblock %} - var {{ module }} = new MapWidget(options); + window.{{ module }}Options = options;
diff --git a/tests/gis_tests/geoadmin/models.py b/tests/gis_tests/geoadmin/models.py index ea726dd68f..14d1f35015 100644 --- a/tests/gis_tests/geoadmin/models.py +++ b/tests/gis_tests/geoadmin/models.py @@ -3,8 +3,19 @@ from django.contrib.gis.db import models from ..admin import admin +class Country(models.Model): + name = models.CharField(max_length=30) + + class Meta: + app_label = "geoadmin" + + def __str__(self): + return self.name + + class City(models.Model): name = models.CharField(max_length=30) + country = models.ForeignKey(Country, on_delete=models.CASCADE) point = models.PointField() class Meta: @@ -23,11 +34,20 @@ class CityAdminCustomWidgetKwargs(admin.GISModelAdmin): } +class CityInline(admin.TabularInline): + model = City + + +class CountryAdmin(admin.ModelAdmin): + inlines = [CityInline] + + site = admin.AdminSite(name="gis_admin_modeladmin") site.register(City, admin.ModelAdmin) site_gis = admin.AdminSite(name="gis_admin_gismodeladmin") site_gis.register(City, admin.GISModelAdmin) +site_gis.register(Country, CountryAdmin) site_gis_custom = admin.AdminSite(name="gis_admin_gismodeladmin") site_gis_custom.register(City, CityAdminCustomWidgetKwargs) diff --git a/tests/gis_tests/geoadmin/tests.py b/tests/gis_tests/geoadmin/tests.py index e101050464..872d7d6d2b 100644 --- a/tests/gis_tests/geoadmin/tests.py +++ b/tests/gis_tests/geoadmin/tests.py @@ -1,16 +1,31 @@ +from django.contrib.admin.tests import AdminSeleniumTestCase +from django.contrib.auth.models import User from django.contrib.gis.geos import Point -from django.test import SimpleTestCase, override_settings +from django.test import TestCase, modify_settings, override_settings +from django.test.client import RequestFactory +from django.urls import reverse from .models import City, site, site_gis, site_gis_custom -@override_settings(ROOT_URLCONF="django.contrib.gis.tests.geoadmin.urls") -class GeoAdminTest(SimpleTestCase): +@override_settings( + ROOT_URLCONF="django.contrib.gis.tests.geoadmin.urls", + PASSWORD_HASHERS=["django.contrib.auth.hashers.MD5PasswordHasher"], +) +class GeoAdminTest(TestCase): admin_site = site # ModelAdmin + factory = RequestFactory() + + def setUp(self): + user = User.objects.create_superuser( + username="super", password="secret", email="super@example.com" + ) + self.request = self.factory.get("/admin") + self.request.user = user def test_widget_empty_string(self): geoadmin = self.admin_site.get_model_admin(City) - form = geoadmin.get_changelist_form(None)({"point": ""}) + form = geoadmin.get_changelist_form(self.request)({"point": ""}) with self.assertRaisesMessage(AssertionError, "no logs"): with self.assertLogs("django.contrib.gis", "ERROR"): output = str(form["point"]) @@ -22,7 +37,7 @@ class GeoAdminTest(SimpleTestCase): def test_widget_invalid_string(self): geoadmin = self.admin_site.get_model_admin(City) - form = geoadmin.get_changelist_form(None)({"point": "INVALID()"}) + form = geoadmin.get_changelist_form(self.request)({"point": "INVALID()"}) with self.assertLogs("django.contrib.gis", "ERROR") as cm: output = str(form["point"]) self.assertInHTML( @@ -39,7 +54,7 @@ class GeoAdminTest(SimpleTestCase): def test_widget_has_changed(self): geoadmin = self.admin_site.get_model_admin(City) - form = geoadmin.get_changelist_form(None)() + form = geoadmin.get_changelist_form(self.request)() has_changed = form.fields["point"].has_changed initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326) @@ -60,7 +75,7 @@ class GISAdminTests(GeoAdminTest): def test_default_gis_widget_kwargs(self): geoadmin = self.admin_site.get_model_admin(City) - form = geoadmin.get_changelist_form(None)() + form = geoadmin.get_changelist_form(self.request)() widget = form["point"].field.widget self.assertEqual(widget.attrs["default_lat"], 47) self.assertEqual(widget.attrs["default_lon"], 5) @@ -68,8 +83,35 @@ class GISAdminTests(GeoAdminTest): def test_custom_gis_widget_kwargs(self): geoadmin = site_gis_custom.get_model_admin(City) - form = geoadmin.get_changelist_form(None)() + form = geoadmin.get_changelist_form(self.request)() widget = form["point"].field.widget self.assertEqual(widget.attrs["default_lat"], 55) self.assertEqual(widget.attrs["default_lon"], 37) self.assertEqual(widget.attrs["default_zoom"], 12) + + +@override_settings(ROOT_URLCONF="gis_tests.geoadmin.urls") +# GeoDjango admin not yet CSP-compatible with strict values (#25706) +@modify_settings(MIDDLEWARE={"remove": "django.contrib.admin.tests.CSPMiddleware"}) +class GISSeleniumAdminTests(AdminSeleniumTestCase): + available_apps = AdminSeleniumTestCase.available_apps + [ + "django.contrib.gis", + "gis_tests.geoadmin", + ] + + def setUp(self): + User.objects.create_superuser( + username="super", password="secret", email="super@example.com" + ) + + def test_gis_widget_initalized_when_inline_added(self): + from selenium.webdriver.common.by import By + + self.admin_login(username="super", password="secret") + self.selenium.get(self.live_server_url + reverse("admin:geoadmin_country_add")) + self.assertCountSeleniumElements("tr.dynamic-city_set", 3) + add_button = self.selenium.find_element(By.LINK_TEXT, "Add another City") + add_button.click() + self.assertCountSeleniumElements("tr.dynamic-city_set", 4) + map_div = self.selenium.find_element(By.ID, "id_city_set-3-point_map") + self.assertCountSeleniumElements(".ol-layer", 1, root_element=map_div) diff --git a/tests/gis_tests/geoadmin/urls.py b/tests/gis_tests/geoadmin/urls.py index ce237dbfd1..5843bd1213 100644 --- a/tests/gis_tests/geoadmin/urls.py +++ b/tests/gis_tests/geoadmin/urls.py @@ -1,6 +1,7 @@ -from django.contrib import admin -from django.urls import include, path +from django.urls import path + +from .models import site_gis urlpatterns = [ - path("admin/", include(admin.site.urls)), + path("admin/", site_gis.urls), ] diff --git a/tests/gis_tests/test_geoforms.py b/tests/gis_tests/test_geoforms.py index c351edaaad..1589da2fd3 100644 --- a/tests/gis_tests/test_geoforms.py +++ b/tests/gis_tests/test_geoforms.py @@ -257,7 +257,8 @@ class SpecializedFieldTest(SimpleTestCase): """ self.assertTrue(form_instance.is_valid()) rendered = form_instance.as_p() - self.assertIn("new MapWidget(options);", rendered) + field_name = list(form_instance.fields.keys())[0] + self.assertIn(f"window.geodjango_{field_name}Options = options;", rendered) self.assertIn("map_srid: 3857,", rendered) self.assertIn("gis/js/OLMapWidget.js", str(form_instance.media))