From 1e67d1a0614070cb0a8ba75809bdc2fb73375cf9 Mon Sep 17 00:00:00 2001 From: David Smith Date: Sun, 29 Sep 2024 16:53:46 +0100 Subject: [PATCH] Refs #33783 -- Added IsEmpty GIS database function and __isempty lookup on SpatiaLite. --- .../gis/db/backends/spatialite/operations.py | 2 +- docs/ref/contrib/gis/db-api.txt | 4 +-- docs/ref/contrib/gis/functions.txt | 7 +++- docs/ref/contrib/gis/geoquerysets.txt | 7 +++- docs/releases/5.2.txt | 4 +++ tests/gis_tests/geoapp/models.py | 7 ++++ tests/gis_tests/geoapp/test_functions.py | 35 +++++++++++++++++-- 7 files changed, 59 insertions(+), 7 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 3d10a53641..684b4282ec 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -82,7 +82,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): @cached_property def unsupported_functions(self): - unsupported = {"GeometryDistance", "IsEmpty", "MemSize"} + unsupported = {"GeometryDistance", "MemSize"} if not self.geom_lib_version(): unsupported |= {"Azimuth", "GeoHash", "MakeValid"} if self.spatial_version < (5, 1): diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index e33d9a514f..14e985b609 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -359,7 +359,7 @@ Lookup Type PostGIS Oracle MariaDB MySQL [#]_ S :lookup:`equals` X X X X X C :lookup:`exact ` X X X X X B :lookup:`intersects` X X X X X B -:lookup:`isempty` X +:lookup:`isempty` X X :lookup:`isvalid` X X X (≥ 11.7) X X :lookup:`overlaps` X X X X X B :lookup:`relate` X X X X C @@ -408,7 +408,7 @@ Function PostGIS Oracle MariaDB MySQL :class:`FromWKT` X X X X X :class:`GeoHash` X X (≥ 11.7) X X (LWGEOM/RTTOPO) :class:`Intersection` X X X X X -:class:`IsEmpty` X +:class:`IsEmpty` X X :class:`IsValid` X X X (≥ 11.7) X X :class:`Length` X X X X X :class:`LineLocatePoint` X X diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index ff62c17580..03293c6917 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -438,11 +438,16 @@ intersection between them. .. class:: IsEmpty(expr) -*Availability*: `PostGIS `__ +*Availability*: `PostGIS `__, +SpatiaLite Accepts a geographic field or expression and tests if the value is an empty geometry. Returns ``True`` if its value is empty and ``False`` otherwise. +.. versionchanged:: 5.2 + + Support for SpatiaLite was added. + ``IsValid`` =========== diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index 19411b7304..157dfaad9c 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -362,7 +362,8 @@ SpatiaLite ``Intersects(poly, geom)`` ``isempty`` ----------- -*Availability*: `PostGIS `__ +*Availability*: `PostGIS `__, +SpatiaLite Tests if the geometry is empty. @@ -372,6 +373,10 @@ Example:: .. fieldlookup:: isvalid +.. versionchanged:: 5.2 + + Support for SpatiaLite was added. + ``isvalid`` ----------- diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index e103de847b..cafff10014 100644 --- a/docs/releases/5.2.txt +++ b/docs/releases/5.2.txt @@ -108,6 +108,10 @@ Minor features :class:`~django.contrib.gis.db.models.functions.IsValid` database functions are now supported on MariaDB 11.7+. +* The :lookup:`isempty` lookup and + :class:`~django.contrib.gis.db.models.functions.IsEmpty` expression are now + supported on SpatiaLite. + :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/geoapp/models.py b/tests/gis_tests/geoapp/models.py index 2c13c827c6..caa1f56b76 100644 --- a/tests/gis_tests/geoapp/models.py +++ b/tests/gis_tests/geoapp/models.py @@ -28,6 +28,13 @@ class City(NamedModel): app_label = "geoapp" +class Town(NamedModel): + point = models.PointField(null=True) + + class Meta: + app_label = "geoapp" + + # This is an inherited model from City class PennsylvaniaCity(City): county = models.CharField(max_length=30) diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 80b08f8d39..f5fcffd7f1 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -11,7 +11,15 @@ from django.db.models import IntegerField, Sum, Value from django.test import TestCase, skipUnlessDBFeature from ..utils import FuncTestMixin -from .models import City, Country, CountryWebMercator, ManyPointModel, State, Track +from .models import ( + City, + Country, + CountryWebMercator, + ManyPointModel, + State, + Town, + Track, +) class GISFunctionsTests(FuncTestMixin, TestCase): @@ -413,7 +421,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase): self.assertIs(c.inter.empty, True) @skipUnlessDBFeature("supports_empty_geometries", "has_IsEmpty_function") - def test_isempty(self): + def test_isempty_empty_geometry(self): empty = City.objects.create(name="Nowhere", point=Point(srid=4326)) City.objects.create(name="Somewhere", point=Point(6.825, 47.1, srid=4326)) self.assertSequenceEqual( @@ -424,6 +432,29 @@ class GISFunctionsTests(FuncTestMixin, TestCase): ) self.assertSequenceEqual(City.objects.filter(point__isempty=True), [empty]) + @skipUnlessDBFeature("has_IsEmpty_function") + def test_isempty_null_geometry(self): + null_geometry = Town.objects.create(name="Nowhere", point=None) + Town.objects.create(name="Somewhere", point=Point(6.825, 47.1, srid=4326)) + if connection.ops.spatialite: + self.assertSequenceEqual( + Town.objects.annotate(isempty=functions.IsEmpty("point")).filter( + isempty=True + ), + [null_geometry], + ) + self.assertSequenceEqual( + Town.objects.filter(point__isempty=True), [null_geometry] + ) + else: + self.assertSequenceEqual( + Town.objects.annotate(isempty=functions.IsEmpty("point")).filter( + isempty=True + ), + [], + ) + self.assertSequenceEqual(Town.objects.filter(point__isempty=True), []) + @skipUnlessDBFeature("has_IsValid_function") def test_isvalid(self): valid_geom = fromstr("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))")