Skip to content

Commit 51569fc

Browse files
committed
Address review
1 parent beba412 commit 51569fc

File tree

4 files changed

+121
-80
lines changed

4 files changed

+121
-80
lines changed

django_mongodb_backend/gis/operations.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,18 @@ class GISOperations(BaseSpatialOperations):
2828
models.Union,
2929
)
3030

31-
operators = {
31+
gis_operators = {
3232
"contains": Contains(),
33-
"intersects": Intersects(),
3433
"disjoint": Disjoint(),
35-
"within": Within(),
3634
"distance_gt": DistanceGT(),
3735
"distance_gte": DistanceGTE(),
3836
"distance_lt": DistanceLT(),
3937
"distance_lte": DistanceLTE(),
4038
"dwithin": DWithin(),
39+
"intersects": Intersects(),
40+
"within": Within(),
4141
}
4242

43-
@property
44-
def gis_operators(self):
45-
return self.operators
46-
4743
unsupported_functions = {
4844
"Area",
4945
"AsGeoJSON",

django_mongodb_backend/gis/operators.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,20 @@ def as_sql(self, connection, lookup, template_params, sql_params):
88
return self.name, []
99

1010

11-
class Within(Operator):
12-
name = "within"
13-
14-
def as_mql(self, field, value, params=None):
15-
return {
16-
field: {
17-
"$geoWithin": {
18-
"$geometry": {
19-
"type": value["type"],
20-
"coordinates": value["coordinates"],
21-
}
22-
}
23-
}
24-
}
25-
26-
27-
class Intersects(Operator):
28-
name = "intersects"
11+
class Contains(Operator):
12+
name = "contains"
2913

3014
def as_mql(self, field, value, params=None):
15+
value_type = value["type"]
16+
if value_type != "Point":
17+
raise NotSupportedError(
18+
"MongoDB does not support contains on non-Point query geometries."
19+
)
3120
return {
3221
field: {
3322
"$geoIntersects": {
3423
"$geometry": {
35-
"type": value["type"],
24+
"type": value_type,
3625
"coordinates": value["coordinates"],
3726
}
3827
}
@@ -58,27 +47,6 @@ def as_mql(self, field, value, params=None):
5847
}
5948

6049

61-
class Contains(Operator):
62-
name = "contains"
63-
64-
def as_mql(self, field, value, params=None):
65-
value_type = value["type"]
66-
if value_type != "Point":
67-
raise NotSupportedError(
68-
"MongoDB does not support contains on non-Point query geometries."
69-
)
70-
return {
71-
field: {
72-
"$geoIntersects": {
73-
"$geometry": {
74-
"type": value_type,
75-
"coordinates": value["coordinates"],
76-
}
77-
}
78-
}
79-
}
80-
81-
8250
class DistanceBase(Operator):
8351
name = "distance_base"
8452

@@ -132,3 +100,35 @@ class DWithin(Operator):
132100

133101
def as_mql(self, field, value, params=None):
134102
return {field: {"$geoWithin": {"$centerSphere": [value["coordinates"], params[0]]}}}
103+
104+
105+
class Intersects(Operator):
106+
name = "intersects"
107+
108+
def as_mql(self, field, value, params=None):
109+
return {
110+
field: {
111+
"$geoIntersects": {
112+
"$geometry": {
113+
"type": value["type"],
114+
"coordinates": value["coordinates"],
115+
}
116+
}
117+
}
118+
}
119+
120+
121+
class Within(Operator):
122+
name = "within"
123+
124+
def as_mql(self, field, value, params=None):
125+
return {
126+
field: {
127+
"$geoWithin": {
128+
"$geometry": {
129+
"type": value["type"],
130+
"coordinates": value["coordinates"],
131+
}
132+
}
133+
}
134+
}

docs/ref/contrib/gis.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@ Limitations
5050
(:attr:`BaseSpatialField.srid
5151
<django.contrib.gis.db.models.BaseSpatialField.srid>`)
5252
besides `4326 (WGS84) <https://spatialreference.org/ref/epsg/4326/>`_.
53+
- QuerySet APIs do not support subqueries or expressions.
5354
- :class:`~django.contrib.gis.db.models.RasterField` isn't supported.

tests/gis_tests_/tests.py

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from django.contrib.gis.geos import Point
1+
from django.contrib.gis.geos import LineString, Point, Polygon
22
from django.contrib.gis.measure import Distance
33
from django.db import NotSupportedError
4+
from django.db.models import Case, CharField, Value, When
45
from django.test import TestCase, skipUnlessDBFeature
56

67
from .models import City, Zipcode
@@ -10,34 +11,45 @@
1011
class LookupTests(TestCase):
1112
fixtures = ["initial"]
1213

13-
def test_unsupported(self):
14-
msg = "MongoDB does not support the 'same_as' lookup."
15-
with self.assertRaisesMessage(NotSupportedError, msg):
16-
City.objects.get(point__same_as=Point(95, 30))
17-
1814
def test_contains(self):
19-
houston = City.objects.get(name="Houston")
20-
qs = City.objects.filter(point__contains=Point(-95.363151, 29.763374))
21-
self.assertCountEqual(qs, [houston])
15+
qs = City.objects.filter(point__contains=Point(-95.363151, 29.763374)).values_list(
16+
"name", flat=True
17+
)
18+
self.assertCountEqual(qs, ["Houston"])
19+
20+
def test_contains_errors_on_non_point(self):
21+
qs = City.objects.filter(point__contains=LineString((0, 0), (1, 1)))
22+
message = "MongoDB does not support contains on non-Point query geometries."
23+
with self.assertRaisesMessage(NotSupportedError, message):
24+
qs.first()
2225

2326
def test_disjoint(self):
24-
houston = City.objects.get(name="Houston")
25-
qs = City.objects.filter(point__disjoint=Point(100, 50))
26-
self.assertIn(houston, qs)
27+
qs = City.objects.filter(point__disjoint=Point(100, 50)).values_list("name", flat=True)
28+
self.assertIn("Houston", qs)
2729

2830
def test_distance_gt(self):
2931
houston = City.objects.get(name="Houston")
30-
dallas = City.objects.get(name="Dallas") # Roughly ~363 km from Houston
31-
qs = City.objects.filter(point__distance_gt=(houston.point, 362826))
32-
self.assertEqual(qs.count(), 6)
33-
self.assertNotIn(dallas, list(qs))
32+
expected = ["Oklahoma City", "Wellington", "Pueblo", "Lawrence", "Chicago", "Victoria"]
33+
qs = City.objects.filter(point__distance_gt=(houston.point, 362826)).values_list(
34+
"name", flat=True
35+
)
36+
self.assertCountEqual(qs, expected)
3437

3538
def test_distance_gte(self):
3639
houston = City.objects.get(name="Houston")
37-
dallas = City.objects.get(name="Dallas") # Roughly ~363 km from Houston
38-
qs = City.objects.filter(point__distance_gte=(houston.point, 362825))
39-
self.assertEqual(qs.count(), 7)
40-
self.assertIn(dallas, list(qs))
40+
expected = [
41+
"Dallas",
42+
"Oklahoma City",
43+
"Wellington",
44+
"Pueblo",
45+
"Lawrence",
46+
"Chicago",
47+
"Victoria",
48+
]
49+
qs = City.objects.filter(point__distance_gte=(houston.point, 362825)).values_list(
50+
"name", flat=True
51+
)
52+
self.assertCountEqual(qs, expected)
4153

4254
def test_distance_lt(self):
4355
houston = City.objects.get(name="Houston")
@@ -46,22 +58,27 @@ def test_distance_lt(self):
4658

4759
def test_distance_lte(self):
4860
houston = City.objects.get(name="Houston")
49-
dallas = City.objects.get(name="Dallas") # Roughly ~363 km from Houston
50-
qs = City.objects.filter(point__distance_lte=(houston.point, 362826))
51-
self.assertCountEqual(list(qs), [houston, dallas])
61+
qs = City.objects.filter(point__distance_lte=(houston.point, 362826)).values_list(
62+
"name", flat=True
63+
)
64+
self.assertCountEqual(qs, ["Houston", "Dallas"]) # Dallas is roughly ~363 km from Houston
5265

5366
def test_distance_units(self):
5467
chicago = City.objects.get(name="Chicago")
55-
lawrence = City.objects.get(name="Lawrence")
56-
qs = City.objects.filter(point__distance_lt=(chicago.point, Distance(km=720)))
57-
self.assertCountEqual(list(qs), [lawrence, chicago])
58-
qs = City.objects.filter(point__distance_lt=(chicago.point, Distance(mi=447)))
59-
self.assertCountEqual(list(qs), [lawrence, chicago])
68+
qs = City.objects.filter(point__distance_lt=(chicago.point, Distance(km=720))).values_list(
69+
"name", flat=True
70+
)
71+
self.assertCountEqual(qs, ["Lawrence", "Chicago"])
72+
qs = City.objects.filter(point__distance_lt=(chicago.point, Distance(mi=447))).values_list(
73+
"name", flat=True
74+
)
75+
self.assertCountEqual(qs, ["Lawrence", "Chicago"])
6076

6177
def test_dwithin(self):
6278
houston = City.objects.get(name="Houston")
63-
qs = City.objects.filter(point__dwithin=(houston.point, 0.2))
64-
self.assertEqual(qs.count(), 5)
79+
expected = ["Houston", "Dallas", "Pueblo", "Oklahoma City", "Lawrence"]
80+
qs = City.objects.filter(point__dwithin=(houston.point, 0.2)).values_list("name", flat=True)
81+
self.assertCountEqual(qs, expected)
6582

6683
def test_dwithin_unsupported_units(self):
6784
qs = City.objects.filter(point__dwithin=(Point(40.7670, -73.9820), Distance(km=1)))
@@ -72,10 +89,37 @@ def test_dwithin_unsupported_units(self):
7289
def test_intersects(self):
7390
city = City.objects.create(point=Point(95, 30))
7491
qs = City.objects.filter(point__intersects=Point(95, 30).buffer(10))
75-
self.assertEqual(qs.count(), 1)
76-
self.assertIn(city, qs)
92+
self.assertCountEqual(qs, [city])
7793

7894
def test_within(self):
7995
zipcode = Zipcode.objects.get(code="77002")
8096
qs = City.objects.filter(point__within=zipcode.poly).values_list("name", flat=True)
81-
self.assertEqual(list(qs), ["Houston"])
97+
self.assertCountEqual(qs, ["Houston"])
98+
99+
def test_unsupported(self):
100+
msg = "MongoDB does not support the 'same_as' lookup."
101+
with self.assertRaisesMessage(NotSupportedError, msg):
102+
City.objects.get(point__same_as=Point(95, 30))
103+
104+
def test_unsupported_expr(self):
105+
downtown_area = Polygon(
106+
(
107+
(-122.4194, 37.7749),
108+
(-122.4194, 37.8049),
109+
(-122.3894, 37.8049),
110+
(-122.3894, 37.7749),
111+
(-122.4194, 37.7749),
112+
)
113+
)
114+
115+
qs = City.objects.annotate(
116+
area_type=Case(
117+
When(point__within=downtown_area, then=Value("Downtown")),
118+
default=Value("Other"),
119+
output_field=CharField(),
120+
)
121+
)
122+
123+
message = "MongoDB does not support GIS lookups as expressions."
124+
with self.assertRaisesMessage(NotSupportedError, message):
125+
qs.first()

0 commit comments

Comments
 (0)