Skip to content

Commit 3260eed

Browse files
deSagazGagaro
authored andcommitted
Fix fatal bug with Django => 1.11.2 for non-GIS databases (makinacorpus#196)
* Amend exception handling and add fallback OGRGeomType class Update exception handling (https://code.djangoproject.com/ticket/28178). The OGRGeomType had to be added to this codebase, since Django does not allow using GIS classes anymore without such a service installed (https://code.djangoproject.com/ticket/28160). * Fix version issue with the BaseGeometryWidget class BaseGeometryWidget is now based off Django 1.11.3's version and includes the deprecated render function with a version check. * Update requirements.txt for example * Fix newline * Fixed style errors and faulty exceptions
1 parent 124aaac commit 3260eed

File tree

4 files changed

+195
-46
lines changed

4 files changed

+195
-46
lines changed

example/requirements.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
Django==1.10.2
2-
django-geojson==2.9.1
3-
django-leaflet==0.19.0
4-
jsonfield==1.0.3
5-
Pillow==3.4.1
6-
six==1.10.0
1+
Django==1.11.3
2+
django-geojson==2.10.0
3+
django-leaflet==0.22.0
4+
jsonfield==2.0.2
5+
Pillow==4.2.1
6+
six==1.10.0

leaflet/forms/backport.py

Lines changed: 183 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@
3333
import warnings
3434
import json
3535

36+
from distutils.version import LooseVersion
37+
from django import get_version
3638
from django.conf import settings
37-
from django.contrib.gis import gdal
39+
from django.core.exceptions import ImproperlyConfigured
3840
from django.forms.widgets import Widget
3941
from django.template import loader
4042
from django.utils import six
@@ -45,21 +47,127 @@
4547

4648
try:
4749
from django.contrib.gis.geos import GEOSGeometry
48-
except ImportError:
50+
except (ImportError, ImproperlyConfigured):
4951
from .nogeos import GEOSGeometry
5052

5153
try:
5254
from django.contrib.gis.geos import GEOSException
53-
except ImportError:
55+
except (ImportError, ImproperlyConfigured):
5456
from .nogeos import GEOSException
5557

58+
try:
59+
from django.contrib.gis.gdal import OGRException
60+
except (ImportError, ImproperlyConfigured):
61+
from .nogeos import OGRException
62+
63+
try:
64+
from django.contrib.gis.gdal import OGRGeomType
65+
except (ImportError, ImproperlyConfigured):
66+
class OGRGeomType:
67+
"""
68+
Encapsulate OGR Geometry Types.
69+
Taken from Django GitHub repository:
70+
https://github.com/django/django/commit/5411821e3b8d1427ee63a5914aed1088c04cc1ed
71+
"""
72+
73+
wkb25bit = -2147483648
74+
75+
# Dictionary of acceptable OGRwkbGeometryType s and their string names.
76+
_types = {0: 'Unknown',
77+
1: 'Point',
78+
2: 'LineString',
79+
3: 'Polygon',
80+
4: 'MultiPoint',
81+
5: 'MultiLineString',
82+
6: 'MultiPolygon',
83+
7: 'GeometryCollection',
84+
100: 'None',
85+
101: 'LinearRing',
86+
102: 'PointZ',
87+
1 + wkb25bit: 'Point25D',
88+
2 + wkb25bit: 'LineString25D',
89+
3 + wkb25bit: 'Polygon25D',
90+
4 + wkb25bit: 'MultiPoint25D',
91+
5 + wkb25bit: 'MultiLineString25D',
92+
6 + wkb25bit: 'MultiPolygon25D',
93+
7 + wkb25bit: 'GeometryCollection25D',
94+
}
95+
# Reverse type dictionary, keyed by lower-case of the name.
96+
_str_types = {v.lower(): k for k, v in _types.items()}
97+
98+
def __init__(self, type_input):
99+
"Figure out the correct OGR Type based upon the input."
100+
if isinstance(type_input, OGRGeomType):
101+
num = type_input.num
102+
elif isinstance(type_input, str):
103+
type_input = type_input.lower()
104+
if type_input == 'geometry':
105+
type_input = 'unknown'
106+
num = self._str_types.get(type_input)
107+
if num is None:
108+
raise GEOSException('Invalid OGR String Type "%s"' % type_input)
109+
elif isinstance(type_input, int):
110+
if type_input not in self._types:
111+
raise GEOSException('Invalid OGR Integer Type: %d' % type_input)
112+
num = type_input
113+
else:
114+
raise TypeError('Invalid OGR input type given.')
115+
116+
# Setting the OGR geometry type number.
117+
self.num = num
118+
119+
def __str__(self):
120+
"Return the value of the name property."
121+
return self.name
122+
123+
def __eq__(self, other):
124+
"""
125+
Do an equivalence test on the OGR type with the given
126+
other OGRGeomType, the short-hand string, or the integer.
127+
"""
128+
if isinstance(other, OGRGeomType):
129+
return self.num == other.num
130+
elif isinstance(other, str):
131+
return self.name.lower() == other.lower()
132+
elif isinstance(other, int):
133+
return self.num == other
134+
else:
135+
return False
136+
137+
@property
138+
def name(self):
139+
"Return a short-hand string form of the OGR Geometry type."
140+
return self._types[self.num]
141+
142+
@property
143+
def django(self):
144+
"Return the Django GeometryField for this OGR Type."
145+
s = self.name.replace('25D', '')
146+
if s in ('LinearRing', 'None'):
147+
return None
148+
elif s == 'Unknown':
149+
s = 'Geometry'
150+
elif s == 'PointZ':
151+
s = 'Point'
152+
return s + 'Field'
153+
154+
def to_multi(self):
155+
"""
156+
Transform Point, LineString, Polygon, and their 25D equivalents
157+
to their Multi... counterpart.
158+
"""
159+
if self.name.startswith(('Point', 'LineString', 'Polygon')):
160+
self.num += 3
161+
56162
logger = logging.getLogger('django.contrib.gis')
57163

58164

59165
class BaseGeometryWidget(Widget):
60166
"""
61167
The base class for rich geometry widgets.
62-
Renders a map using the WKT of the geometry.
168+
Render a map using the WKT of the geometry.
169+
Adapted from:
170+
https://github.com/django/django/commit/a7975260b50282b934c78c8e51846d103636ba04
63171
"""
64172
geom_type = 'GEOMETRY'
65173
map_srid = 4326
@@ -82,45 +190,81 @@ def serialize(self, value):
82190

83191
def deserialize(self, value):
84192
try:
193+
# To allow older versions of django-leaflet to work,
194+
# self.map_srid is also returned (unlike the imported Django class)
85195
return GEOSGeometry(value, self.map_srid)
86196
except (GEOSException, ValueError) as err:
87-
logger.error(
88-
"Error creating geometry from value '%s' (%s)" % (value, err)
89-
)
197+
logger.error("Error creating geometry from value '%s' (%s)", value, err)
90198
return None
91199

92-
def render(self, name, value, attrs=None):
93-
# If a string reaches here (via a validation error on another
94-
# field) then just reconstruct the Geometry.
95-
if isinstance(value, six.string_types):
96-
value = self.deserialize(value)
97-
98-
if isinstance(value, dict):
99-
value = GEOSGeometry(json.dumps(value), srid=self.map_srid)
100-
101-
if value:
102-
# Check that srid of value and map match
103-
if value.srid != self.map_srid:
104-
try:
105-
ogr = value.ogr
106-
ogr.transform(self.map_srid)
107-
value = ogr
108-
except gdal.OGRException as err:
109-
logger.error(
110-
"Error transforming geometry from srid '%s' to srid "
111-
"'%s' (%s)" % (value.srid, self.map_srid, err)
112-
)
113-
114-
context = self.build_attrs(
115-
attrs,
116-
name=name,
117-
module='geodjango_%s' % name.replace('-', '_'), # JS-safe
118-
serialized=self.serialize(value),
119-
geom_type=gdal.OGRGeomType(self.attrs['geom_type']),
120-
STATIC_URL=settings.STATIC_URL,
121-
LANGUAGE_BIDI=translation.get_language_bidi(),
122-
)
123-
return loader.render_to_string(self.template_name, context)
200+
if LooseVersion(get_version()) >= LooseVersion('1.11'):
201+
def get_context(self, name, value, attrs):
202+
context = super().get_context(name, value, attrs)
203+
# If a string reaches here (via a validation error on another
204+
# field) then just reconstruct the Geometry.
205+
if value and isinstance(value, str):
206+
value = self.deserialize(value)
207+
208+
if value:
209+
# Check that srid of value and map match
210+
if value.srid and value.srid != self.map_srid:
211+
try:
212+
ogr = value.ogr
213+
ogr.transform(self.map_srid)
214+
value = ogr
215+
except OGRException as err:
216+
logger.error(
217+
"Error transforming geometry from srid '%s' to srid '%s' (%s)",
218+
value.srid, self.map_srid, err
219+
)
220+
221+
if attrs is None:
222+
attrs = {}
223+
224+
build_attrs_kwargs = {
225+
'name': name,
226+
'module': 'geodjango_%s' % name.replace('-', '_'), # JS-safe
227+
'serialized': self.serialize(value),
228+
'geom_type': OGRGeomType(self.attrs['geom_type']),
229+
'STATIC_URL': settings.STATIC_URL,
230+
'LANGUAGE_BIDI': translation.get_language_bidi(),
231+
}
232+
build_attrs_kwargs.update(attrs)
233+
context.update(self.build_attrs(self.attrs, build_attrs_kwargs))
234+
return context
235+
else:
236+
def render(self, name, value, attrs=None):
237+
# If a string reaches here (via a validation error on another
238+
# field) then just reconstruct the Geometry.
239+
if isinstance(value, six.string_types):
240+
value = self.deserialize(value)
241+
242+
if isinstance(value, dict):
243+
value = GEOSGeometry(json.dumps(value), srid=self.map_srid)
244+
245+
if value:
246+
# Check that srid of value and map match
247+
if value.srid != self.map_srid:
248+
try:
249+
ogr = value.ogr
250+
ogr.transform(self.map_srid)
251+
value = ogr
252+
except OGRException as err:
253+
logger.error(
254+
"Error transforming geometry from srid '%s' to srid "
255+
"'%s' (%s)" % (value.srid, self.map_srid, err)
256+
)
257+
258+
context = self.build_attrs(
259+
attrs,
260+
name=name,
261+
module='geodjango_%s' % name.replace('-', '_'), # JS-safe
262+
serialized=self.serialize(value),
263+
geom_type=OGRGeomType(self.attrs['geom_type']),
264+
STATIC_URL=settings.STATIC_URL,
265+
LANGUAGE_BIDI=translation.get_language_bidi(),
266+
)
267+
return loader.render_to_string(self.template_name, context)
124268

125269

126270
class GeometryField(forms.Field):

leaflet/forms/nogeos.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ def __init__(self, geo_input, srid=None):
1414

1515
class GEOSException(Exception):
1616
pass
17+
18+
19+
class OGRException(Exception):
20+
pass

leaflet/forms/widgets.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
from django import get_version
66
from django import forms
77
from django.core import validators
8+
from django.core.exceptions import ImproperlyConfigured
89
from django.template.defaultfilters import slugify
910
try:
1011
from django.contrib.gis.forms.widgets import BaseGeometryWidget
11-
except ImportError:
12+
except (ImportError, ImproperlyConfigured):
1213
from .backport import BaseGeometryWidget
1314

1415
from leaflet import app_settings, PLUGINS, PLUGIN_FORMS

0 commit comments

Comments
 (0)