diff --git a/django/contrib/gis/db/backend/postgis/query.py b/django/contrib/gis/db/backend/postgis/query.py index 1b8c3fa029..2610173673 100644 --- a/django/contrib/gis/db/backend/postgis/query.py +++ b/django/contrib/gis/db/backend/postgis/query.py @@ -228,7 +228,7 @@ def get_geo_where_clause(lookup_type, table_prefix, field, value): if lookup_type == 'relate': op = op(value[1]) elif lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin': - if field._unit_name == 'degree': + if field.geodetic: # Geodetic distances are only availble from Points to PointFields. if field._geom != 'POINT': raise TypeError('PostGIS spherical operations are only valid on PointFields.') diff --git a/django/contrib/gis/db/models/fields/__init__.py b/django/contrib/gis/db/models/fields/__init__.py index 8948854b35..264ba40509 100644 --- a/django/contrib/gis/db/models/fields/__init__.py +++ b/django/contrib/gis/db/models/fields/__init__.py @@ -79,15 +79,22 @@ class GeometryField(SpatialBackend.Field): super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function ### Routines specific to GeometryField ### - def get_distance(self, dist): + @property + def geodetic(self): + return self._unit_name in self.geodetic_units + + def get_distance(self, dist, lookup_type): """ Returns a distance number in units of the field. For example, if `D(km=1)` was passed in and the units of the field were in meters, then 1000 would be returned. """ - + postgis = SpatialBackend.name == 'postgis' if isinstance(dist, Distance): - if self._unit_name in self.geodetic_units: + if self.geodetic: + # Won't allow Distance objects w/DWithin lookups on PostGIS. + if postgis and lookup_type == 'dwithin': + raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.') # Spherical distance calculation parameter should be in meters. dist_param = dist.m else: @@ -97,7 +104,7 @@ class GeometryField(SpatialBackend.Field): dist_param = dist # Sphereical distance query; returning meters. - if SpatialBackend.name == 'postgis' and self._unit_name in self.geodetic_units: + if postgis and self.geodetic and lookup_type != 'dwithin': return [gqn(self._spheroid), dist_param] else: return [dist_param] @@ -170,7 +177,7 @@ class GeometryField(SpatialBackend.Field): if isinstance(value, (tuple, list)): if lookup_type in SpatialBackend.distance_functions: # Getting the distance parameter in the units of the field. - where += self.get_distance(value[1]) + where += self.get_distance(value[1], lookup_type) elif lookup_type in SpatialBackend.limited_where: pass else: diff --git a/django/contrib/gis/tests/distapp/tests.py b/django/contrib/gis/tests/distapp/tests.py index 0f5b0349dd..3bedb19b4c 100644 --- a/django/contrib/gis/tests/distapp/tests.py +++ b/django/contrib/gis/tests/distapp/tests.py @@ -17,7 +17,9 @@ class DistanceTest(unittest.TestCase): # the coordinate system of the field, EPSG:32140 (Texas South Central # w/units in meters) stx_pnt = GEOSGeometry('POINT (-95.370401017314293 29.704867409475465)', 4326) - + # Another one for Australia + au_pnt = GEOSGeometry('POINT (150.791 -34.4919)', 4326) + def get_cities(self, qs): cities = [c.name for c in qs] cities.sort() @@ -43,8 +45,17 @@ class DistanceTest(unittest.TestCase): dists = [7000, D(km=7), D(mi=4.349)] for dist in dists: qs = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist)) - cities = self.get_cities(qs) - self.assertEqual(cities, ['Downtown Houston', 'Southside Place']) + self.assertEqual(['Downtown Houston', 'Southside Place'], self.get_cities(qs)) + + if isinstance(dist, D): + # A TypeError should be raised when trying to pass Distance objects + # into a DWithin query using a geodetic field. + qs = AustraliaCity.objects.filter(point__dwithin=(self.au_pnt, dist)) + self.assertRaises(TypeError, qs.count) + else: + # Actually using a distance value of 0.5 degrees. + qs = AustraliaCity.objects.filter(point__dwithin=(self.au_pnt, 0.5)).order_by('name') + self.assertEqual(['Mittagong', 'Shellharbour', 'Thirroul', 'Wollongong'], self.get_cities(qs)) def test03_distance_aggregate(self): "Testing the `distance` GeoQuerySet method."