Skip to content

Conversation

@NoahStapp
Copy link
Contributor

@NoahStapp NoahStapp commented Nov 19, 2025

Comment on lines 60 to 62
"GIS Union not supported.": {
"gis_tests.geoapp.tests.GeoLookupTest.test_gis_lookups_with_complex_expressions",
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self that we should add @skipUnlessDBFeature("supports_Union_function") and send the patch upstream. I'll hold off in case any more similar changes are identified by this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple other places where tests not marked as requiring a feature use it. Maybe the authors didn't expect a backend to implement only the slice of operations that we do?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. The skipping is generally just best effort... what's needed for the built-in backends.

@NoahStapp
Copy link
Contributor Author

All tests (including some simple ones I added due to many of the Django ones requiring features we don't support) passing, marking as ready for an initial review pass.

@NoahStapp NoahStapp marked this pull request as ready for review November 20, 2025 22:16
timgraham

This comment was marked as resolved.

@timgraham timgraham changed the title Initial GeoSpatial query support INTPYTHON-835 Add support for GIS lookups Nov 21, 2025
@NoahStapp NoahStapp requested a review from timgraham November 24, 2025 16:06
timgraham

This comment was marked as resolved.

@timgraham

This comment was marked as resolved.

@NoahStapp

This comment was marked as resolved.

@NoahStapp NoahStapp requested review from WaVEV and timgraham and removed request for timgraham December 1, 2025 14:21
Comment on lines 63 to 64
qs = City.objects.filter(point__dwithin=(houston.point, 0.2))
self.assertEqual(qs.count(), 5)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about asserting the results and not using count applies to other tests. In this case, you could use City.objects.exclude() to produce fewer results.

def test_within(self):
zipcode = Zipcode.objects.get(code="77002")
qs = City.objects.filter(point__within=zipcode.poly).values_list("name", flat=True)
self.assertEqual(list(qs), ["Houston"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.assertCountEqual(qs, ["Houston"]) also works if you want be consistent with the other tests.

@NoahStapp NoahStapp requested a review from timgraham December 5, 2025 14:26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add tests that check:

  • the MQL structure
  • the query in tandem with other queries

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there's a need to verify the MQL of every query. These assertions tend to add a lot of overhead in the event of query refactors. The Django test suite generally doesn't verify queries since the syntax varies among databases. Do you have a specific concern?

Please clarify what "in tandem with other queries" means. (Subqueries aren't supported.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine on no MQL for the query.

For "in tandem queries" I meant having a query where there are multiple queries:

City.objects.filter(name="text", point__distance_lte=(houston.point, 362826))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very familiar with the django test suite, but is it safe to assume that such a query is covered already?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly yes, so good point e.g.

src/django/tests/gis_tests/geoapp/test_functions.py:        self.assertSequenceEqual(City.objects.filter(point__isempty=True), [empty])
src/django/tests/gis_tests/geoapp/test_functions.py:        qs = City.objects.filter(point__isnull=False).annotate(
src/django/tests/gis_tests/geoapp/test_functions.py:                    .filter(geom__geom_type=geom_type)
src/django/tests/gis_tests/geoapp/tests.py:        query = City.objects.filter(point__within=Polygon.from_bbox((0, 0, 2, 2)))
src/django/tests/gis_tests/geoapp/tests.py:        qs1 = City.objects.filter(point__disjoint=ptown.point)
src/django/tests/gis_tests/geoapp/tests.py:        qs2 = State.objects.filter(poly__disjoint=ptown.point)
src/django/tests/gis_tests/geoapp/tests.py:        qs = City.objects.filter(point__contained=texas.mpoly)
src/django/tests/gis_tests/geoapp/tests.py:            len(Country.objects.filter(mpoly__contains=pueblo.point)), 0
src/django/tests/gis_tests/geoapp/tests.py:            len(Country.objects.filter(mpoly__contains=okcity.point.wkt)), 0
src/django/tests/gis_tests/geoapp/tests.py:        qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
src/django/tests/gis_tests/geoapp/tests.py:                State.objects.filter(name="Kansas", poly__isvalid=False).count(), 1
src/django/tests/gis_tests/geoapp/tests.py:        self.assertEqual(qs.filter(poly__isvalid=False).count(), 1)
src/django/tests/gis_tests/geoapp/tests.py:        self.assertEqual(qs.filter(poly__isvalid=True).count(), qs.count() - 1)
src/django/tests/gis_tests/geoapp/tests.py:        qs = City.objects.filter(point__right=co_border)
src/django/tests/gis_tests/geoapp/tests.py:        qs = City.objects.filter(point__right=ks_border)
src/django/tests/gis_tests/geoapp/tests.py:        qs = City.objects.filter(point__left=ks_border)
src/django/tests/gis_tests/geoapp/tests.py:            City.objects.filter(point__strictly_above=dallas.point).order_by("name"),
src/django/tests/gis_tests/geoapp/tests.py:            City.objects.filter(point__strictly_below=dallas.point).order_by("name"),
src/django/tests/gis_tests/geoapp/tests.py:        nullqs = State.objects.filter(poly__isnull=True)
src/django/tests/gis_tests/geoapp/tests.py:        validqs = State.objects.filter(poly__isnull=False)
src/django/tests/gis_tests/geoapp/tests.py:                    null, State.objects.filter(**{"poly__%s" % lookup: geom})
src/django/tests/gis_tests/geoapp/tests.py:            State.objects.filter(poly__intersects="LINESTRING(0 0, 1 1, 5 5)")
src/django/tests/gis_tests/geoapp/tests.py:        qs = State.objects.filter(poly__coveredby=small_poly)
src/django/tests/gis_tests/geoapp/tests.py:        qs = State.objects.filter(poly__coveredby=large_poly)
src/django/tests/gis_tests/geoapp/tests.py:            qs = State.objects.filter(poly__coveredby=poly)
src/django/tests/gis_tests/geoapp/tests.py:        qs = State.objects.filter(poly__covers=small_poly)
src/django/tests/gis_tests/geoapp/tests.py:        qs = State.objects.filter(poly__covers=large_poly)
src/django/tests/gis_tests/geoapp/tests.py:            qs = State.objects.filter(poly__covers=poly)
src/django/tests/gis_tests/geoapp/tests.py:            Country.objects.filter(mpoly__relate=(23, "foo"))
src/django/tests/gis_tests/geoapp/tests.py:            qs = Country.objects.filter(mpoly__relate=bad_args)
src/django/tests/gis_tests/geoapp/tests.py:        qs = City.objects.filter(name__in=("Houston", "Dallas"))
src/django/tests/gis_tests/geoapp/tests.py:        qs = City.objects.filter(point__within=tx)
src/django/tests/gis_tests/geoapp/tests.py:                    City.objects.filter(point__within=tx.mpoly)
src/django/tests/gis_tests/geoapp/tests.py:                City.objects.filter(point__within=tx).aggregate(
src/django/tests/gis_tests/geoapp/tests.py:            City.objects.filter(point__within=tx).aggregate(
src/django/tests/gis_tests/geoapp/tests.py:            point__within=Country.objects.filter(name="Texas").values("mpoly")
src/django/tests/gis_tests/geoapp/test_regress.py:        state = State.objects.filter(poly__contains=pueblo.point)
src/django/tests/gis_tests/geoapp/test_regress.py:        cities_within_state = City.objects.filter(id__in=state)
src/django/tests/gis_tests/layermap/tests.py:        self.assertEqual(HasNulls.objects.filter(num__isnull=True).count(), 1)
src/django/tests/gis_tests/layermap/tests.py:        self.assertEqual(HasNulls.objects.filter(name__isnull=True).count(), 1)
src/django/tests/gis_tests/layermap/tests.py:        self.assertEqual(HasNulls.objects.filter(boolean__isnull=True).count(), 1)
src/django/tests/gis_tests/layermap/tests.py:            HasNulls.objects.filter(datetime__lt=datetime.date(1994, 8, 15)).count(), 1
src/django/tests/gis_tests/layermap/tests.py:        self.assertEqual(HasNulls.objects.filter(datetime__isnull=True).count(), 1)
src/django/tests/gis_tests/geogapp/tests.py:            City.objects.filter(point__distance_lte=(z.poly, D(mi=500)))
src/django/tests/gis_tests/geogapp/tests.py:            City.objects.filter(point__dwithin=(z.poly, D(mi=500)))
src/django/tests/gis_tests/geogapp/tests.py:        qs = City.objects.filter(point__within=z.poly)
src/django/tests/gis_tests/geogapp/tests.py:        qs = City.objects.filter(point__contained=z.poly)
src/django/tests/gis_tests/geogapp/tests.py:        qs = City.objects.filter(point__exact=htown.point)
src/django/tests/gis_tests/geogapp/tests.py:        res = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(
src/django/tests/gis_tests/relatedapp/tests.py:        qs = Parcel.objects.filter(center1__within=F("border1"))
src/django/tests/gis_tests/relatedapp/tests.py:        qs = Parcel.objects.filter(center2__within=F("border1"))
src/django/tests/gis_tests/relatedapp/tests.py:        qs = Parcel.objects.filter(center1=F("city__location__point"))
src/django/tests/gis_tests/relatedapp/tests.py:        qs = Parcel.objects.filter(border2__contains=F("city__location__point"))
src/django/tests/gis_tests/relatedapp/tests.py:        qs1 = City.objects.filter(location__point__within=buf1)
src/django/tests/gis_tests/relatedapp/tests.py:        qs2 = City.objects.filter(location__point__within=buf2)
src/django/tests/gis_tests/relatedapp/tests.py:        qs = Author.objects.annotate(num_books=Count("books")).filter(num_books__gt=1)
src/django/tests/gis_tests/relatedapp/tests.py:            .filter(num_books__gt=1)
src/django/tests/gis_tests/relatedapp/tests.py:        coll = City.objects.filter(state="TX").aggregate(Collect("location__point"))[
src/django/tests/gis_tests/relatedapp/tests.py:                filter=~Q(parcel__name__icontains="ignore"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=Q(parcel__name__icontains="nonexistent"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=Q(parcel__name__contains="Alpha"),
src/django/tests/gis_tests/relatedapp/tests.py:                    filter=~Q(parcel__name__icontains="ignore"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=~Q(parcel__name__icontains="ignore"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=Q(parcel__name__icontains="nonexistent"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=~Q(parcel__name__icontains="ignore"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=Q(parcel__name__icontains="nonexistent"),
src/django/tests/gis_tests/relatedapp/tests.py:            parcel_border_no_filter=Extent("parcel__border1"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=~Q(parcel__name__icontains="ignore"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=Q(parcel__name__icontains="nonexistent"),
src/django/tests/gis_tests/relatedapp/tests.py:                filter=Q(parcel__name__contains="Alpha"),
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rastprojected__dwithin=(rast, D(km=1)))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rastprojected__dwithin=(JSON_RASTER, D(km=1)))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__dwithin=(rast, 40))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__1__dwithin=(rast, 1, 40))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__1__dwithin=(rast, 40))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__dwithin=(rast, 1, 40))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__dwithin=(stx_pnt, 500))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rastprojected__dwithin=(stx_pnt, D(km=10000)))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__dwithin=(stx_pnt, 5))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rastprojected__dwithin=(stx_pnt, D(km=100)))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(geom__dwithin=(rast, 500))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterRelatedModel.objects.filter(rastermodel__rast__dwithin=(rast, 40))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterRelatedModel.objects.filter(rastermodel__rast__1__dwithin=(rast, 40))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rastprojected__bbcontains=rast)
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:            RasterModel.objects.filter(rast__bbcontains=(rast, 1, 2))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__bbcontains=(rast, 1))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__isvalid=True)
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__left=GEOSGeometry("POINT (1 0)", 4326))
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__strictly_below=rast)
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(rast__strictly_below=rast)
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(geom__intersects=rast)
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:        qs = RasterModel.objects.filter(geom__intersects=rast)
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:            RasterModel.objects.filter(geom__intersects=obj)
src/django/tests/gis_tests/rasterapp/test_rasterfield.py:            RasterModel.objects.filter(geom__intersects=obj)
src/django/tests/gis_tests/geo3d/tests.py:            ll_cities=Extent3D("point", filter=Q(name__contains="ll"))
src/django/tests/gis_tests/distapp/tests.py:            qs1 = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist1))
src/django/tests/gis_tests/distapp/tests.py:            qs2 = SouthTexasCityFt.objects.filter(point__dwithin=(self.stx_pnt, dist2))
src/django/tests/gis_tests/distapp/tests.py:                        self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))),
src/django/tests/gis_tests/distapp/tests.py:            qs = model.objects.filter(point__distance_gte=(stx_pnt, D(km=7))).filter(
src/django/tests/gis_tests/distapp/tests.py:        dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100)))
src/django/tests/gis_tests/distapp/tests.py:            len(AustraliaCity.objects.filter(point__distance_lte=("POINT(5 23)",)))
src/django/tests/gis_tests/distapp/tests.py:            .filter(name__in=("San Antonio", "Pearland"))
src/django/tests/gis_tests/distapp/tests.py:            .filter(length__gt=4000)

That said, off hand I'm not sure how much of the GIS test suite we are running.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminology for this type of QuerySet is that it has "multiple lookups." This sort of test could be useful if there's any doubt that any of the GIS lookups generate MQL that isn't generally composable with other MQL operators like $and, $or, etc.

A quick manual check confirms no issue with a case like this:

City.objects.filter(name="Houston", point__disjoint=Point(100, 50))

{'$match': {'point': {'$not': {'$geoIntersects': {'$geometry': {'type': 'Point', 'coordinates': (100.0, 50.0)}}}}}}, {'$project': {'name': 1}}])

City.objects.filter(point__disjoint=Point(100, 50))

{'$match': {'$and': [{'name': 'Houston'}, {'point': {'$not': {'$geoIntersects': {'$geometry': {'type': 'Point', 'coordinates': (100.0, 50.0)}}}}}]}}.

Is there any reason to doubt other cases?

Comment on lines +55 to +67
if self.name == "distance_gt" or self.name == "distance_gte":
cmd = {
field: {
"$not": {
"$geoWithin": {
"$centerSphere": [
value["coordinates"],
distance / 6378100, # radius of earth in meters
],
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just be moved to DistanceGT and DistanceGTE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more specific? Having it here lets us reduce the code duplication.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see a refactor like this: [Incidentally, is gt vs. gte accounted for elsewhere?)

diff --git a/django_mongodb_backend/gis/operators.py b/django_mongodb_backend/gis/operators.py
index cc24473..3824de5 100644
--- a/django_mongodb_backend/gis/operators.py
+++ b/django_mongodb_backend/gis/operators.py
@@ -52,22 +52,28 @@ class DistanceBase(Operator):
 
     def as_mql(self, field, value, params=None):
         distance = params[0].m if hasattr(params[0], "m") else params[0]
-        if self.name == "distance_gt" or self.name == "distance_gte":
-            cmd = {
-                field: {
-                    "$not": {
-                        "$geoWithin": {
-                            "$centerSphere": [
-                                value["coordinates"],
-                                distance / 6378100,  # radius of earth in meters
-                            ],
-                        }
-                    }
+        return self.get_mql(field, value, distance):
+
+    def get_mql(self, field, value, distance):
+        return  {
+            field: {
+                "$geoWithin": {
+                    "$centerSphere": [
+                        value["coordinates"],
+                        distance / 6378100,  # radius of earth in meters
+                    ],
                 }
             }
+        return self.get_mql(field, value, distance):
+
+    def get_mql(self, field, value, distance):
+        return  {
+            field: {
+                "$geoWithin": {
+                    "$centerSphere": [
+                        value["coordinates"],
+                        distance / 6378100,  # radius of earth in meters
+                    ],
                 }
             }
-        else:
-            cmd = {
-                field: {
+        }
+
+
+class DistanceGT(DistanceBase):
+    name = "distance_gt"
+
+    def get_mql(self, field, value, distance):
+        return {
+            field: {
+                "$not": {
                     "$geoWithin": {
                         "$centerSphere": [
                             value["coordinates"],
@@ -76,14 +82,9 @@ class DistanceBase(Operator):
                     }
                 }
             }
-        return cmd
-
-
-class DistanceGT(DistanceBase):
-    name = "distance_gt"
-
+        }
 
-class DistanceGTE(DistanceBase):
+class DistanceGTE(DistanceGT):
     name = "distance_gte"
 
 

"gis_tests.distapp.tests.DistanceTest.test_dwithin",
},
"Cannot use a non-Point geometry with distance lookups.": {
"gis_tests.distapp.tests.DistanceTest.test_dwithin_with_expression_rhs"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't fail gracefully:

======================================================================
ERROR: test_dwithin_with_expression_rhs (gis_tests.distapp.tests.DistanceTest.test_dwithin_with_expression_rhs)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/gis_tests/utils.py", line 24, in skip_wrapper
    return test_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django/django/test/testcases.py", line 1588, in skip_wrapper
    return test_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django/tests/gis_tests/distapp/tests.py", line 347, in test_dwithin_with_expression_rhs
    self.get_names(qs),
    ^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django/tests/gis_tests/distapp/tests.py", line 52, in get_names
    cities = [c.name for c in qs]
             ^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django/django/db/models/query.py", line 390, in __iter__
    self._fetch_all()
  File "/home/tim/code/django/django/db/models/query.py", line 2000, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django/django/db/models/query.py", line 95, in __iter__
    results = compiler.execute_sql(
              ^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/compiler.py", line 355, in execute_sql
    cursor = query.get_cursor()
             ^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/query.py", line 20, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/query.py", line 79, in get_cursor
    return self.compiler.collection.aggregate(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/collection.py", line 2979, in aggregate
    return self._aggregate(
           ^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/_csot.py", line 125, in csot_wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/collection.py", line 2886, in _aggregate
    return self._database.client._retryable_read(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/mongo_client.py", line 2026, in _retryable_read
    return self._retry_internal(
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/_csot.py", line 125, in csot_wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/mongo_client.py", line 1993, in _retry_internal
    ).run()
      ^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/mongo_client.py", line 2730, in run
    return self._read() if self._is_read else self._write()
           ^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/mongo_client.py", line 2891, in _read
    return self._func(self._session, self._server, conn, read_pref)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/aggregation.py", line 164, in get_cursor
    result = conn.command(
             ^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/helpers.py", line 47, in inner
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/pool.py", line 442, in command
    self._raise_connection_failure(error)
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/pool.py", line 414, in command
    return command(
           ^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/synchronous/network.py", line 148, in command
    request_id, msg, size, max_doc_size = message._op_msg(
                                          ^^^^^^^^^^^^^^^^
  File "/home/tim/.virtualenvs/django312/lib/python3.12/site-packages/pymongo/message.py", line 419, in _op_msg
    return _op_msg_uncompressed(flags, command, identifier, docs, opts)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bson.errors.InvalidDocument: Invalid document {'aggregate': 'distapp_australiacity', 'pipeline': [{'$match': {'point': {'$geoWithin': {'$centerSphere': [((150.902, -34.4245), (138.6, -34.9258)), Col(distapp_australiacity, distapp.AustraliaCity.allowed_distance)]}}}}, {'$addFields': {'name': '$name'}}, {'$sort': SON([('name', 1)])}], 'cursor': {}, 'lsid': {'id': Binary(b'\xea\x82\xe9\x12\x19GO\x0e\x87\xfb\xeb\xd0\xe9\xfd\x9bJ', 4)}, '$clusterTime': {'clusterTime': Timestamp(1765932487, 8), 'signature': {'hash': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'keyId': 0}}, '$db': 'test_django', '$readPreference': {'mode': 'primaryPreferred'}} | cannot encode object: Col(distapp_australiacity, distapp.AustraliaCity.allowed_distance), of type: <class 'django.db.models.expressions.Col'>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed adding the supports_dwithin_distance_expr=False flag here that gives a better error message.

"gis_tests.distapp.tests.DistanceTest.test_dwithin_with_expression_rhs"
},
"Subqueries not supported.": {
"gis_tests.geoapp.tests.GeoLookupTest.test_subquery_annotation",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't fail gracefully:

======================================================================
ERROR: test_subquery_annotation (gis_tests.geoapp.tests.GeoLookupTest.test_subquery_annotation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/gis_tests/utils.py", line 24, in skip_wrapper
    return test_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django/tests/gis_tests/geoapp/tests.py", line 695, in test_subquery_annotation
    self.assertEqual(qs.get(), multifields)
                     ^^^^^^^^
  File "/home/tim/code/django/django/db/models/query.py", line 635, in get
    num = len(clone)
          ^^^^^^^^^^
  File "/home/tim/code/django/django/db/models/query.py", line 372, in __len__
    self._fetch_all()
  File "/home/tim/code/django/django/db/models/query.py", line 2000, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django/django/db/models/query.py", line 95, in __iter__
    results = compiler.execute_sql(
              ^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/compiler.py", line 346, in execute_sql
    query = self.build_query(
            ^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/compiler.py", line 477, in build_query
    match_mql = where.as_mql(self, self.connection) if where else {}
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/query.py", line 354, in where_node
    mql = child.as_mql(compiler, connection, as_expr=as_expr)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/gis/lookups.py", line 16, in gis_lookup
    return rhs_op.as_mql(lhs_mql, rhs_mql, self.rhs_params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tim/code/django-mongodb/django_mongodb_backend/gis/operators.py", line 129, in as_mql
    "type": value["type"],
            ~~~~~^^^^^^^^
TypeError: string indices must be integers, not 'str'

Comment on lines 43 to 45
"gis_tests.distapp.tests.DistanceTest.test_distance_lookups",
"gis_tests.distapp.tests.DistanceTest.test_distance_lookups_with_expression_rhs",
"gis_tests.distapp.tests.DistanceTest.test_distance_annotation_group_by",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed these since they are already skipped by: @skipUnlessDBFeature("supports_distances_lookups") which is an alias for DatabaseFeatures.has_Distance_function (probably the confusion was from when you were figuring things out).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants