From 2a2ed0e70a93bfd1a3d41af40870fe963dc2687d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 6 Feb 2018 11:51:41 +0100 Subject: [PATCH] Fixed #29116 -- Fixed OpenLayersWidget deserialization ignoring the widget map's SRID. Regression in 6ecccad711b52f9273b1acb07a57d3f806e93928. --- django/contrib/gis/forms/fields.py | 11 ++++++++--- django/contrib/gis/forms/widgets.py | 10 +++++++++- docs/releases/2.0.4.txt | 3 +++ tests/gis_tests/test_geoforms.py | 19 ++++++++++++++----- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index afb09744159..d05518c9712 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -35,9 +35,14 @@ class GeometryField(forms.Field): return None if not isinstance(value, GEOSGeometry): - try: - value = GEOSGeometry(value) - except (GEOSException, ValueError, TypeError): + if hasattr(self.widget, 'deserialize'): + value = self.widget.deserialize(value) + else: + try: + value = GEOSGeometry(value) + except (GEOSException, ValueError, TypeError): + value = None + if value is None: raise forms.ValidationError(self.error_messages['invalid_geom'], code='invalid_geom') # Try to set the srid diff --git a/django/contrib/gis/forms/widgets.py b/django/contrib/gis/forms/widgets.py index 0ffe2ced778..cd9bff9f68c 100644 --- a/django/contrib/gis/forms/widgets.py +++ b/django/contrib/gis/forms/widgets.py @@ -2,6 +2,7 @@ import logging from django.conf import settings from django.contrib.gis import gdal +from django.contrib.gis.geometry import json_regex from django.contrib.gis.geos import GEOSException, GEOSGeometry from django.forms.widgets import Widget from django.utils import translation @@ -36,7 +37,7 @@ class BaseGeometryWidget(Widget): def deserialize(self, value): try: return GEOSGeometry(value) - except (GEOSException, ValueError) as err: + except (GEOSException, ValueError, TypeError) as err: logger.error("Error creating geometry from value '%s' (%s)", value, err) return None @@ -91,6 +92,13 @@ class OpenLayersWidget(BaseGeometryWidget): def serialize(self, value): return value.json if value else '' + def deserialize(self, value): + geom = super().deserialize(value) + # GeoJSON assumes WGS84 (4326). Use the map's SRID instead. + if geom and json_regex.match(value) and self.map_srid != 4326: + geom.srid = self.map_srid + return geom + class OSMWidget(OpenLayersWidget): """ diff --git a/docs/releases/2.0.4.txt b/docs/releases/2.0.4.txt index 08bd05eb4bd..ea1208562d9 100644 --- a/docs/releases/2.0.4.txt +++ b/docs/releases/2.0.4.txt @@ -29,3 +29,6 @@ Bugfixes * Fixed a regression in Django 1.11 where an empty choice could be initially selected for the ``SelectMultiple`` and ``CheckboxSelectMultiple`` widgets (:ticket:`29273`). + +* Fixed a regression in Django 2.0 where ``OpenLayersWidget`` deserialization + ignored the widget map's SRID and assumed 4326 (WGS84) (:ticket:`29116`). diff --git a/tests/gis_tests/test_geoforms.py b/tests/gis_tests/test_geoforms.py index cdcbe476853..1c221f10bc8 100644 --- a/tests/gis_tests/test_geoforms.py +++ b/tests/gis_tests/test_geoforms.py @@ -1,7 +1,7 @@ import re from django.contrib.gis import forms -from django.contrib.gis.forms import BaseGeometryWidget +from django.contrib.gis.forms import BaseGeometryWidget, OpenLayersWidget from django.contrib.gis.geos import GEOSGeometry from django.forms import ValidationError from django.test import SimpleTestCase, override_settings @@ -31,8 +31,9 @@ class GeometryFieldTest(SimpleTestCase): fld = forms.GeometryField(srid=32140) tol = 0.0000001 xform_geom = GEOSGeometry('POINT (951640.547328465 4219369.26171664)', srid=32140) - # The cleaned geometry should be transformed to 32140. - cleaned_geom = fld.clean('SRID=4326;POINT (-95.363151 29.763374)') + # The cleaned geometry is transformed to 32140 (the widget map_srid is 3857). + cleaned_geom = fld.clean('SRID=3857;POINT (-10615777.40976205 3473169.895707852)') + self.assertEqual(cleaned_geom.srid, 32140) self.assertTrue(xform_geom.equals_exact(cleaned_geom, tol)) def test_null(self): @@ -83,6 +84,11 @@ class GeometryFieldTest(SimpleTestCase): with self.assertRaises(forms.ValidationError): fld.to_python(wkt) + def test_to_python_different_map_srid(self): + f = forms.GeometryField(widget=OpenLayersWidget) + json = '{ "type": "Point", "coordinates": [ 5.0, 23.0 ] }' + self.assertEqual(GEOSGeometry('POINT(5 23)', srid=f.widget.map_srid), f.to_python(json)) + def test_field_with_text_widget(self): class PointForm(forms.Form): pt = forms.PointField(srid=4326, widget=forms.TextInput) @@ -91,6 +97,8 @@ class GeometryFieldTest(SimpleTestCase): cleaned_pt = form.fields['pt'].clean('POINT(5 23)') self.assertEqual(cleaned_pt, GEOSGeometry('POINT(5 23)', srid=4326)) self.assertEqual(4326, cleaned_pt.srid) + with self.assertRaisesMessage(ValidationError, 'Invalid geometry value.'): + form.fields['pt'].clean('POINT(5)') point = GEOSGeometry('SRID=4326;POINT(5 23)') form = PointForm(data={'pt': 'POINT(5 23)'}, initial={'pt': point}) @@ -132,8 +140,9 @@ class GeometryFieldTest(SimpleTestCase): ' rows="10" name="pt3">', output ) - # Only the invalid PNT(0) should trigger an error log entry - self.assertEqual(len(logger_calls), 1) + # Only the invalid PNT(0) triggers an error log entry. + # Deserialization is called in form clean and in widget rendering. + self.assertEqual(len(logger_calls), 2) self.assertEqual( logger_calls[0], "Error creating geometry from value 'PNT(0)' (String input "