1
0
mirror of https://github.com/django/django.git synced 2025-06-16 00:49:12 +00:00

Fixed #25706 -- Refactored geometry widgets to remove inline JavaScript.

Refactored GIS-related JavaScript initialization to eliminate inline
scripts from templates. Added support for specifying a base layer using
the new `base_layer_name` attribute on `BaseGeometryWidget`, allowing
custom map tile providers via user-defined JavaScript.

As a result, the `gis/openlayers-osm.html` template was removed.

Thanks Sarah Boyce for reviews.

Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
This commit is contained in:
Claude Paroz 2024-08-18 15:29:30 +02:00 committed by nessita
parent e80b33ae4d
commit f2f6046c0f
8 changed files with 308 additions and 96 deletions

View File

@ -1,11 +1,9 @@
import logging import logging
from django.conf import settings
from django.contrib.gis import gdal from django.contrib.gis import gdal
from django.contrib.gis.geometry import json_regex from django.contrib.gis.geometry import json_regex
from django.contrib.gis.geos import GEOSException, GEOSGeometry from django.contrib.gis.geos import GEOSException, GEOSGeometry
from django.forms.widgets import Widget from django.forms.widgets import Widget
from django.utils import translation
logger = logging.getLogger("django.contrib.gis") logger = logging.getLogger("django.contrib.gis")
@ -16,6 +14,7 @@ class BaseGeometryWidget(Widget):
Render a map using the WKT of the geometry. Render a map using the WKT of the geometry.
""" """
base_layer = None
geom_type = "GEOMETRY" geom_type = "GEOMETRY"
map_srid = 4326 map_srid = 4326
display_raw = False display_raw = False
@ -24,9 +23,10 @@ class BaseGeometryWidget(Widget):
template_name = "" # set on subclasses template_name = "" # set on subclasses
def __init__(self, attrs=None): def __init__(self, attrs=None):
self.attrs = {} self.attrs = {
for key in ("geom_type", "map_srid", "display_raw"): key: getattr(self, key)
self.attrs[key] = getattr(self, key) for key in ("base_layer", "geom_type", "map_srid", "display_raw")
}
if attrs: if attrs:
self.attrs.update(attrs) self.attrs.update(attrs)
@ -61,26 +61,16 @@ class BaseGeometryWidget(Widget):
self.map_srid, self.map_srid,
err, err,
) )
context["serialized"] = self.serialize(value)
geom_type = gdal.OGRGeomType(self.attrs["geom_type"]).name geom_type = gdal.OGRGeomType(self.attrs["geom_type"]).name
context.update( context["widget"]["attrs"]["geom_name"] = (
self.build_attrs( "Geometry" if geom_type == "Unknown" else geom_type
self.attrs,
{
"name": name,
"module": "geodjango_%s" % name.replace("-", "_"), # JS-safe
"serialized": self.serialize(value),
"geom_type": "Geometry" if geom_type == "Unknown" else geom_type,
"STATIC_URL": settings.STATIC_URL,
"LANGUAGE_BIDI": translation.get_language_bidi(),
**(attrs or {}),
},
)
) )
return context return context
class OpenLayersWidget(BaseGeometryWidget): class OpenLayersWidget(BaseGeometryWidget):
base_layer = "nasaWorldview"
template_name = "gis/openlayers.html" template_name = "gis/openlayers.html"
map_srid = 3857 map_srid = 3857
@ -112,14 +102,15 @@ class OSMWidget(OpenLayersWidget):
An OpenLayers/OpenStreetMap-based widget. An OpenLayers/OpenStreetMap-based widget.
""" """
template_name = "gis/openlayers-osm.html" base_layer = "osm"
default_lon = 5 default_lon = 5
default_lat = 47 default_lat = 47
default_zoom = 12 default_zoom = 12
def __init__(self, attrs=None): def __init__(self, attrs=None):
super().__init__() if attrs is None:
for key in ("default_lon", "default_lat", "default_zoom"): attrs = {}
self.attrs[key] = getattr(self, key) attrs.setdefault("default_lon", self.default_lon)
if attrs: attrs.setdefault("default_lat", self.default_lat)
self.attrs.update(attrs) attrs.setdefault("default_zoom", self.default_zoom)
super().__init__(attrs=attrs)

View File

@ -58,8 +58,16 @@ class MapWidget {
this.options[property] = options[property]; this.options[property] = options[property];
} }
} }
if (!options.base_layer) {
this.options.base_layer = new ol.layer.Tile({source: new ol.source.OSM()}); // Options' base_layer can be empty, or contain a layerBuilder key, or
// be a layer already constructed.
const base_layer = options.base_layer;
if (typeof base_layer === 'string' && base_layer in MapWidget.layerBuilder) {
this.baseLayer = MapWidget.layerBuilder[base_layer]();
} else if (base_layer && typeof base_layer !== 'string') {
this.baseLayer = base_layer;
} else {
this.baseLayer = MapWidget.layerBuilder.osm();
} }
this.map = this.createMap(); this.map = this.createMap();
@ -120,7 +128,7 @@ class MapWidget {
createMap() { createMap() {
return new ol.Map({ return new ol.Map({
target: this.options.map_id, target: this.options.map_id,
layers: [this.options.base_layer], layers: [this.baseLayer],
view: new ol.View({ view: new ol.View({
zoom: this.options.default_zoom zoom: this.options.default_zoom
}) })
@ -231,3 +239,43 @@ class MapWidget {
document.getElementById(this.options.id).value = jsonFormat.writeGeometry(geometry); document.getElementById(this.options.id).value = jsonFormat.writeGeometry(geometry);
} }
} }
// Static property assignment (ES6-compatible)
MapWidget.layerBuilder = {
nasaWorldview: () => {
return new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: "NASA Worldview",
maxZoom: 8,
url: "https://map1{a-c}.vis.earthdata.nasa.gov/wmts-webmerc/" +
"BlueMarble_ShadedRelief_Bathymetry/default/%7BTime%7D/" +
"GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpg"
})
});
},
osm: () => {
return new ol.layer.Tile({source: new ol.source.OSM()});
}
};
function initMapWidgetInSection(section) {
const maps = [];
section.querySelectorAll(".dj_map_wrapper").forEach((wrapper) => {
// Avoid initializing map widget on an empty form.
if (wrapper.id.includes('__prefix__')) {
return;
}
const options = JSON.parse(wrapper.querySelector("#mapwidget-options").textContent);
options.id = wrapper.querySelector("textarea").id;
options.map_id = wrapper.querySelector(".dj_map").id;
maps.push(new MapWidget(options));
});
return maps;
};
document.addEventListener("DOMContentLoaded", () => {
initMapWidgetInSection(document);
document.addEventListener('formset:added', (ev) => {initMapWidgetInSection(ev.target);});
});

View File

@ -1,12 +0,0 @@
{% extends "gis/openlayers.html" %}
{% load l10n %}
{% block options %}{{ block.super }}
options['default_lon'] = {{ default_lon|unlocalize }};
options['default_lat'] = {{ default_lat|unlocalize }};
options['default_zoom'] = {{ default_zoom|unlocalize }};
{% endblock %}
{% block base_layer %}
var base_layer = new ol.layer.Tile({source: new ol.source.OSM()});
{% endblock %}

View File

@ -1,32 +1,10 @@
{% load i18n l10n %} {% load i18n %}
<div id="{{ id }}_div_map" class="dj_map_wrapper"> <div id="{{ widget.attrs.id }}_div_map" class="dj_map_wrapper">
<div id="{{ id }}_map" class="dj_map"></div> <div id="{{ widget.attrs.id }}_map" class="dj_map"></div>
{% if not disabled %}<span class="clear_features"><a href="">{% translate "Delete all Features" %}</a></span>{% endif %} {% if not disabled %}<span class="clear_features"><a href="">{% translate "Delete all Features" %}</a></span>{% endif %}
{% if display_raw %}<p>{% translate "Debugging window (serialized value)" %}</p>{% endif %} {% if widget.attrs.display_raw %}<p>{% translate "Debugging window (serialized value)" %}</p>{% endif %}
<textarea id="{{ id }}" class="vSerializedField required" cols="150" rows="10" name="{{ name }}" <textarea id="{{ widget.attrs.id }}" class="vSerializedField required" cols="150" rows="10" name="{{ widget.name }}"
{% if not display_raw %} hidden{% endif %}>{{ serialized }}</textarea> {% if not widget.attrs.display_raw %} hidden{% endif %}>{{ serialized }}</textarea>
<script> {{ widget.attrs|json_script:"mapwidget-options" }}
{% block base_layer %}
var base_layer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: "NASA Worldview",
maxZoom: 8,
url: "https://map1{a-c}.vis.earthdata.nasa.gov/wmts-webmerc/" +
"BlueMarble_ShadedRelief_Bathymetry/default/%7BTime%7D/" +
"GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpg"
})
});
{% endblock %}
{% block options %}var options = {
base_layer: base_layer,
geom_name: '{{ geom_type }}',
id: '{{ id }}',
map_id: '{{ id }}_map',
map_srid: {{ map_srid|unlocalize }},
name: '{{ name }}'
};
{% endblock %}
var {{ module }} = new MapWidget(options);
</script>
</div> </div>

View File

@ -96,6 +96,14 @@ Widget attributes
GeoDjango widgets are template-based, so their attributes are mostly different GeoDjango widgets are template-based, so their attributes are mostly different
from other Django widget attributes. from other Django widget attributes.
.. attribute:: BaseGeometryWidget.base_layer
.. versionadded:: 6.0
A string that specifies the identifier for the default base map layer to be
used by the corresponding JavaScript map widget. It is passed as part of
the widget options when rendering, allowing the ``MapWidget`` to determine
which map tile provider or base layer to initialize (default is ``None``).
.. attribute:: BaseGeometryWidget.geom_type .. attribute:: BaseGeometryWidget.geom_type
@ -137,15 +145,29 @@ Widget classes
This is an abstract base widget containing the logic needed by subclasses. This is an abstract base widget containing the logic needed by subclasses.
You cannot directly use this widget for a geometry field. You cannot directly use this widget for a geometry field.
Note that the rendering of GeoDjango widgets is based on a template, Note that the rendering of GeoDjango widgets is based on a base layer name,
identified by the :attr:`template_name` class attribute. identified by the :attr:`base_layer` class attribute.
``OpenLayersWidget`` ``OpenLayersWidget``
.. class:: OpenLayersWidget .. class:: OpenLayersWidget
This is the default widget used by all GeoDjango form fields. This is the default widget used by all GeoDjango form fields. Attributes
``template_name`` is ``gis/openlayers.html``. are:
.. attribute:: base_layer
.. versionadded:: 6.0
``nasaWorldview``
.. attribute:: template_name
``gis/openlayers.html``.
.. attribute:: map_srid
``3857``
``OpenLayersWidget`` and :class:`OSMWidget` use the ``ol.js`` file hosted ``OpenLayersWidget`` and :class:`OSMWidget` use the ``ol.js`` file hosted
on the ``cdn.jsdelivr.net`` content-delivery network. You can subclass on the ``cdn.jsdelivr.net`` content-delivery network. You can subclass
@ -157,12 +179,14 @@ Widget classes
.. class:: OSMWidget .. class:: OSMWidget
This widget uses an OpenStreetMap base layer to display geographic objects This widget specialized :class:`OpenLayersWidget` and uses an OpenStreetMap
on. Attributes are: base layer to display geographic objects on. Attributes are:
.. attribute:: template_name .. attribute:: base_layer
``gis/openlayers-osm.html`` .. versionadded:: 6.0
``osm``
.. attribute:: default_lat .. attribute:: default_lat
.. attribute:: default_lon .. attribute:: default_lon
@ -179,3 +203,37 @@ Widget classes
tiles. tiles.
.. _FAQ answer: https://help.openstreetmap.org/questions/10920/how-to-embed-a-map-in-my-https-site .. _FAQ answer: https://help.openstreetmap.org/questions/10920/how-to-embed-a-map-in-my-https-site
.. versionchanged:: 6.0
The ``OSMWidget`` no longer uses a custom template. Consequently, the
``gis/openlayers-osm.html`` template was removed.
.. _geometry-widgets-customization:
Customizing the base layer used in OpenLayers-based widgets
-----------------------------------------------------------
.. versionadded:: 6.0
To customize the base layer displayed in OpenLayers-based geometry widgets,
define a new layer builder in a custom JavaScript file. For example:
.. code-block:: javascript
:caption: ``path-to-file.js``
MapWidget.layerBuilder.custom_layer_name = function () {
// Return an OpenLayers layer instance.
return new ol.layer.Tile({source: new ol.source.<ChosenSource>()});
};
Then, subclass a standard geometry widget and set the ``base_layer``::
from django.contrib.gis.forms.widgets import OpenLayersWidget
class YourCustomWidget(OpenLayersWidget):
base_layer = "custom_layer_name"
class Media:
js = ["path-to-file.js"]

View File

@ -73,6 +73,9 @@ Minor features
function rotates a geometry by a specified angle around the origin or a function rotates a geometry by a specified angle around the origin or a
specified point. specified point.
* The new :attr:`.BaseGeometryWidget.base_layer` attribute allows specifying a
JavaScript map base layer, enabling customization of map tile providers.
:mod:`django.contrib.messages` :mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -332,6 +335,11 @@ Miscellaneous
refactored to use Python's :py:class:`email.message.Message` for parsing. refactored to use Python's :py:class:`email.message.Message` for parsing.
Input headers exceeding 10000 characters will now raise :exc:`ValueError`. Input headers exceeding 10000 characters will now raise :exc:`ValueError`.
* Widgets from :mod:`django.contrib.gis.forms.widgets` now render without
inline JavaScript in templates. If you have customized any geometry widgets
or their templates, you may need to :ref:`update them
<geometry-widgets-customization>` to match the new layout.
.. _deprecated-features-6.0: .. _deprecated-features-6.0:
Features deprecated in 6.0 Features deprecated in 6.0

View File

@ -1,4 +1,4 @@
/* global QUnit, MapWidget */ /* global QUnit, MapWidget, ol */
'use strict'; 'use strict';
QUnit.module('gis.OLMapWidget'); QUnit.module('gis.OLMapWidget');
@ -91,3 +91,84 @@ QUnit.test('MapWidget.IsCollection', function(assert) {
widget = new MapWidget(options); widget = new MapWidget(options);
assert.ok(widget.options.is_collection); assert.ok(widget.options.is_collection);
}); });
QUnit.test('MapWidget.layerBuilder.osm returns OSM layer', function(assert) {
const layer = MapWidget.layerBuilder.osm();
assert.ok(layer instanceof ol.layer.Tile, 'Layer is Tile');
assert.ok(layer.getSource() instanceof ol.source.OSM, 'Source is OSM');
});
QUnit.test('MapWidget.layerBuilder.nasaWorldview returns XYZ layer', function(assert) {
const layer = MapWidget.layerBuilder.nasaWorldview();
assert.ok(layer instanceof ol.layer.Tile, 'Layer is Tile');
assert.ok(layer.getSource() instanceof ol.source.XYZ, 'Source is XYZ');
assert.ok(layer.getSource().getUrls()[0].includes('earthdata.nasa.gov'), 'URL is NASA-hosted');
});
QUnit.test('MapWidget uses default OSM base layer when none specified', function(assert) {
const widget = new MapWidget({
id: 'id_point',
map_id: 'id_point_map',
geom_name: 'Point'
});
assert.ok(widget.baseLayer.getSource() instanceof ol.source.OSM, 'Default base layer is OSM');
});
QUnit.test('MapWidget uses named base layer from layerBuilder', function(assert) {
const widget = new MapWidget({
id: 'id_point',
map_id: 'id_point_map',
geom_name: 'Point',
base_layer: 'nasaWorldview'
});
assert.ok(widget.baseLayer.getSource() instanceof ol.source.XYZ, 'Uses named base layer from builder');
});
QUnit.test('MapWidget uses passed-in base layer object directly', function(assert) {
const customLayer = new ol.layer.Tile({source: new ol.source.OSM()});
const widget = new MapWidget({
id: 'id_point',
map_id: 'id_point_map',
geom_name: 'Point',
base_layer: customLayer
});
assert.strictEqual(widget.baseLayer, customLayer, 'Uses provided layer object');
});
QUnit.test('initMapWidgetInSection initializes widgets and skips __prefix__', function(assert) {
const wrapper1 = document.createElement('div');
wrapper1.className = 'dj_map_wrapper';
wrapper1.id = 'id_point_map_wrapper';
wrapper1.innerHTML = `
<textarea id="id_point"></textarea>
<div class="dj_map" id="id_point_map"></div>
<script type="application/json" id="mapwidget-options">
{ "geom_name": "Point" }
</script>
`;
document.body.appendChild(wrapper1);
const wrapper2 = document.createElement('div');
wrapper2.className = 'dj_map_wrapper';
wrapper2.id = 'form-__prefix__-map_wrapper';
wrapper2.innerHTML = `
<textarea id="id_fake"></textarea>
<div class="dj_map" id="id_fake_map"></div>
<script type="application/json" id="mapwidget-options">
{ "geom_name": "MultiPoint" }
</script>
`;
document.body.appendChild(wrapper2);
const maps = window.initMapWidgetInSection(document);
assert.equal(maps.length, 1, 'Only one map widget is initialized');
assert.ok(maps[0] instanceof MapWidget, 'Map is instance of MapWidget');
assert.equal(maps[0].options.id, 'id_point', 'Correct widget was initialized');
assert.equal(maps[0].options.map_id, 'id_point_map', 'Map ID is correct');
// Clean up
wrapper1.remove();
wrapper2.remove();
});

View File

@ -4,6 +4,7 @@ from django.contrib.gis import forms
from django.contrib.gis.forms import BaseGeometryWidget, OpenLayersWidget from django.contrib.gis.forms import BaseGeometryWidget, OpenLayersWidget
from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.geos import GEOSGeometry
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.template.defaultfilters import json_script
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase, override_settings
from django.utils.html import escape from django.utils.html import escape
@ -183,6 +184,37 @@ class GeometryFieldTest(SimpleTestCase):
"unrecognized as WKT EWKT, and HEXEWKB.)", "unrecognized as WKT EWKT, and HEXEWKB.)",
) )
def test_override_attrs(self):
self.assertIsNone(forms.BaseGeometryWidget.base_layer)
self.assertEqual(forms.BaseGeometryWidget.geom_type, "GEOMETRY")
self.assertEqual(forms.BaseGeometryWidget.map_srid, 4326)
self.assertIs(forms.BaseGeometryWidget.display_raw, False)
class PointForm(forms.Form):
p = forms.PointField(
widget=forms.OpenLayersWidget(
attrs={
"base_layer": "some-test-file",
"map_srid": 1234,
}
),
)
form = PointForm()
rendered = form.as_p()
attrs = {
"base_layer": "some-test-file",
"geom_type": "POINT",
"map_srid": 1234,
"display_raw": False,
"required": True,
"id": "id_p",
"geom_name": "Point",
}
expected = json_script(attrs, "mapwidget-options")
self.assertInHTML(expected, rendered)
class SpecializedFieldTest(SimpleTestCase): class SpecializedFieldTest(SimpleTestCase):
def setUp(self): def setUp(self):
@ -250,15 +282,29 @@ class SpecializedFieldTest(SimpleTestCase):
), ),
} }
def assertMapWidget(self, form_instance): def assertMapWidget(self, form_instance, geom_name):
""" """
Make sure the MapWidget js is passed in the form media and a MapWidget Make sure the MapWidget js is passed in the form media and a MapWidget
is actually created is actually created
""" """
self.assertTrue(form_instance.is_valid()) self.assertTrue(form_instance.is_valid())
rendered = form_instance.as_p() rendered = form_instance.as_p()
self.assertIn("new MapWidget(options);", rendered)
self.assertIn("map_srid: 3857,", rendered) map_fields = [
f for f in form_instance if isinstance(f.field, forms.GeometryField)
]
for map_field in map_fields:
attrs = {
"base_layer": "nasaWorldview",
"geom_type": map_field.field.geom_type,
"map_srid": 3857,
"display_raw": False,
"required": True,
"id": map_field.id_for_label,
"geom_name": geom_name,
}
expected = json_script(attrs, "mapwidget-options")
self.assertInHTML(expected, rendered)
self.assertIn("gis/js/OLMapWidget.js", str(form_instance.media)) self.assertIn("gis/js/OLMapWidget.js", str(form_instance.media))
def assertTextarea(self, geom, rendered): def assertTextarea(self, geom, rendered):
@ -279,7 +325,7 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries["point"] geom = self.geometries["point"]
form = PointForm(data={"p": geom}) form = PointForm(data={"p": geom})
self.assertTextarea(geom, form.as_p()) self.assertTextarea(geom, form.as_p())
self.assertMapWidget(form) self.assertMapWidget(form, "Point")
self.assertFalse(PointForm().is_valid()) self.assertFalse(PointForm().is_valid())
invalid = PointForm(data={"p": "some invalid geom"}) invalid = PointForm(data={"p": "some invalid geom"})
self.assertFalse(invalid.is_valid()) self.assertFalse(invalid.is_valid())
@ -295,7 +341,7 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries["multipoint"] geom = self.geometries["multipoint"]
form = PointForm(data={"p": geom}) form = PointForm(data={"p": geom})
self.assertTextarea(geom, form.as_p()) self.assertTextarea(geom, form.as_p())
self.assertMapWidget(form) self.assertMapWidget(form, "MultiPoint")
self.assertFalse(PointForm().is_valid()) self.assertFalse(PointForm().is_valid())
for invalid in [ for invalid in [
@ -310,7 +356,7 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries["linestring"] geom = self.geometries["linestring"]
form = LineStringForm(data={"f": geom}) form = LineStringForm(data={"f": geom})
self.assertTextarea(geom, form.as_p()) self.assertTextarea(geom, form.as_p())
self.assertMapWidget(form) self.assertMapWidget(form, "LineString")
self.assertFalse(LineStringForm().is_valid()) self.assertFalse(LineStringForm().is_valid())
for invalid in [ for invalid in [
@ -325,7 +371,7 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries["multilinestring"] geom = self.geometries["multilinestring"]
form = LineStringForm(data={"f": geom}) form = LineStringForm(data={"f": geom})
self.assertTextarea(geom, form.as_p()) self.assertTextarea(geom, form.as_p())
self.assertMapWidget(form) self.assertMapWidget(form, "MultiLineString")
self.assertFalse(LineStringForm().is_valid()) self.assertFalse(LineStringForm().is_valid())
for invalid in [ for invalid in [
@ -340,7 +386,7 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries["polygon"] geom = self.geometries["polygon"]
form = PolygonForm(data={"p": geom}) form = PolygonForm(data={"p": geom})
self.assertTextarea(geom, form.as_p()) self.assertTextarea(geom, form.as_p())
self.assertMapWidget(form) self.assertMapWidget(form, "Polygon")
self.assertFalse(PolygonForm().is_valid()) self.assertFalse(PolygonForm().is_valid())
for invalid in [ for invalid in [
@ -355,7 +401,7 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries["multipolygon"] geom = self.geometries["multipolygon"]
form = PolygonForm(data={"p": geom}) form = PolygonForm(data={"p": geom})
self.assertTextarea(geom, form.as_p()) self.assertTextarea(geom, form.as_p())
self.assertMapWidget(form) self.assertMapWidget(form, "MultiPolygon")
self.assertFalse(PolygonForm().is_valid()) self.assertFalse(PolygonForm().is_valid())
for invalid in [ for invalid in [
@ -370,7 +416,7 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries["geometrycollection"] geom = self.geometries["geometrycollection"]
form = GeometryForm(data={"g": geom}) form = GeometryForm(data={"g": geom})
self.assertTextarea(geom, form.as_p()) self.assertTextarea(geom, form.as_p())
self.assertMapWidget(form) self.assertMapWidget(form, "GeometryCollection")
self.assertFalse(GeometryForm().is_valid()) self.assertFalse(GeometryForm().is_valid())
for invalid in [ for invalid in [
@ -393,8 +439,8 @@ class OSMWidgetTest(SimpleTestCase):
form = PointForm(data={"p": geom}) form = PointForm(data={"p": geom})
rendered = form.as_p() rendered = form.as_p()
self.assertIn("ol.source.OSM()", rendered) self.assertIn('"base_layer": "osm"', rendered)
self.assertIn("id: 'id_p',", rendered) self.assertIn('<textarea id="id_p"', rendered)
def test_default_lat_lon(self): def test_default_lat_lon(self):
self.assertEqual(forms.OSMWidget.default_lon, 5) self.assertEqual(forms.OSMWidget.default_lon, 5)
@ -415,9 +461,20 @@ class OSMWidgetTest(SimpleTestCase):
form = PointForm() form = PointForm()
rendered = form.as_p() rendered = form.as_p()
self.assertIn("options['default_lon'] = 20;", rendered) attrs = {
self.assertIn("options['default_lat'] = 30;", rendered) "base_layer": "osm",
self.assertIn("options['default_zoom'] = 17;", rendered) "geom_type": "POINT",
"map_srid": 3857,
"display_raw": False,
"default_lon": 20,
"default_lat": 30,
"default_zoom": 17,
"required": True,
"id": "id_p",
"geom_name": "Point",
}
expected = json_script(attrs, "mapwidget-options")
self.assertInHTML(expected, rendered)
class GeometryWidgetTests(SimpleTestCase): class GeometryWidgetTests(SimpleTestCase):
@ -425,15 +482,15 @@ class GeometryWidgetTests(SimpleTestCase):
# The Widget.get_context() attrs argument overrides self.attrs. # The Widget.get_context() attrs argument overrides self.attrs.
widget = BaseGeometryWidget(attrs={"geom_type": "POINT"}) widget = BaseGeometryWidget(attrs={"geom_type": "POINT"})
context = widget.get_context("point", None, attrs={"geom_type": "POINT2"}) context = widget.get_context("point", None, attrs={"geom_type": "POINT2"})
self.assertEqual(context["geom_type"], "POINT2") self.assertEqual(context["widget"]["attrs"]["geom_type"], "POINT2")
# Widget.get_context() returns expected name for geom_type. # Widget.get_context() returns expected name for geom_type.
widget = BaseGeometryWidget(attrs={"geom_type": "POLYGON"}) widget = BaseGeometryWidget(attrs={"geom_type": "POLYGON"})
context = widget.get_context("polygon", None, None) context = widget.get_context("polygon", None, None)
self.assertEqual(context["geom_type"], "Polygon") self.assertEqual(context["widget"]["attrs"]["geom_name"], "Polygon")
# Widget.get_context() returns 'Geometry' instead of 'Unknown'. # Widget.get_context() returns 'Geometry' instead of 'Unknown'.
widget = BaseGeometryWidget(attrs={"geom_type": "GEOMETRY"}) widget = BaseGeometryWidget(attrs={"geom_type": "GEOMETRY"})
context = widget.get_context("geometry", None, None) context = widget.get_context("geometry", None, None)
self.assertEqual(context["geom_type"], "Geometry") self.assertEqual(context["widget"]["attrs"]["geom_name"], "Geometry")
def test_subwidgets(self): def test_subwidgets(self):
widget = forms.BaseGeometryWidget() widget = forms.BaseGeometryWidget()
@ -443,7 +500,10 @@ class GeometryWidgetTests(SimpleTestCase):
{ {
"is_hidden": False, "is_hidden": False,
"attrs": { "attrs": {
"base_layer": None,
"display_raw": False,
"map_srid": 4326, "map_srid": 4326,
"geom_name": "Geometry",
"geom_type": "GEOMETRY", "geom_type": "GEOMETRY",
"display_raw": False, "display_raw": False,
}, },