From e3d8f557ff3bab2b37a55db83f71ceb90bc4ebc8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 25 Nov 2025 03:03:17 -0500 Subject: [PATCH 1/3] Refs #21961 -- Added required_db_features to delete's RelatedDbOptionParent. --- tests/delete/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/delete/models.py b/tests/delete/models.py index d8f81827be66..c754b41a9110 100644 --- a/tests/delete/models.py +++ b/tests/delete/models.py @@ -48,6 +48,9 @@ class RelatedDbOptionGrandParent(models.Model): class RelatedDbOptionParent(models.Model): p = models.ForeignKey(RelatedDbOptionGrandParent, models.DB_CASCADE, null=True) + class Meta: + required_db_features = {"supports_on_delete_db_cascade"} + class CascadeDbModel(models.Model): name = models.CharField(max_length=30) From d86802f13fd821f006371b95d888d294297874fd Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 23 Sep 2024 18:21:02 +0100 Subject: [PATCH 2/3] Fixed #35783 -- Added NumDimensions GIS database function and __num_dimensions lookup. --- .../gis/db/backends/base/operations.py | 1 + .../gis/db/backends/mysql/operations.py | 1 + .../gis/db/backends/oracle/operations.py | 1 + .../gis/db/backends/postgis/operations.py | 1 + .../gis/db/backends/spatialite/operations.py | 1 + django/contrib/gis/db/models/functions.py | 7 ++++++ docs/ref/contrib/gis/db-api.txt | 2 ++ docs/ref/contrib/gis/functions.txt | 13 ++++++++++ docs/ref/contrib/gis/geoquerysets.txt | 22 ++++++++++++++++ docs/releases/6.1.txt | 5 ++++ tests/gis_tests/geoapp/test_functions.py | 25 ++++++++++++++++++- 11 files changed, 78 insertions(+), 1 deletion(-) diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py index b3bf4383adee..e468c6f610c8 100644 --- a/django/contrib/gis/db/backends/base/operations.py +++ b/django/contrib/gis/db/backends/base/operations.py @@ -62,6 +62,7 @@ def select_extent(self): "LineLocatePoint", "MakeValid", "MemSize", + "NumDimensions", "NumGeometries", "NumPoints", "Perimeter", diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index b82bd16abb95..f48e05b67d7a 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -98,6 +98,7 @@ def unsupported_functions(self): "LineLocatePoint", "MakeValid", "MemSize", + "NumDimensions", "Perimeter", "PointOnSurface", "Reverse", diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 467fc530b3f8..1c0928180974 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -132,6 +132,7 @@ def unsupported_functions(self): "LineLocatePoint", "MakeValid", "MemSize", + "NumDimensions", "Rotate", "Scale", "SnapToGrid", diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 5a403fd7fe55..e0a6dee7bee1 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -176,6 +176,7 @@ def function_names(self): "BoundingCircle": "ST_MinimumBoundingCircle", "FromWKB": "ST_GeomFromWKB", "FromWKT": "ST_GeomFromText", + "NumDimensions": "ST_NDims", "NumPoints": "ST_NPoints", "GeometryType": "GeometryType", } diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 54ec249f07a0..51a04d5c502b 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -76,6 +76,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): "IsEmpty": "ST_IsEmpty", "Length": "ST_Length", "LineLocatePoint": "ST_Line_Locate_Point", + "NumDimensions": "ST_NDims", "NumPoints": "ST_NPoints", "Reverse": "ST_Reverse", "Scale": "ScaleCoords", diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index b6ff35858fec..54505b136ac5 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -521,6 +521,13 @@ class MemSize(GeoFunc): arity = 1 +@BaseSpatialField.register_lookup +class NumDimensions(GeoFuncMixin, Transform): + lookup_name = "num_dimensions" + output_field = IntegerField() + arity = 1 + + class NumGeometries(GeoFunc): output_field = IntegerField() arity = 1 diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index b51001ecd1f9..8f062e503da1 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -365,6 +365,7 @@ Lookup Type PostGIS Oracle MariaDB MySQL [#]_ :lookup:`intersects` X X X X X B :lookup:`isempty` X X :lookup:`isvalid` X X X (≥ 12.0.1) X X +:lookup:`num_dimensions` X X :lookup:`overlaps` X X X X X B :lookup:`relate` X X X X C :lookup:`same_as` X X X X X B @@ -420,6 +421,7 @@ Function PostGIS Oracle MariaDB MySQL :class:`LineLocatePoint` X X :class:`MakeValid` X X (LWGEOM/RTTOPO) :class:`MemSize` X +:class:`NumDimensions` X X :class:`NumGeometries` X X X X X :class:`NumPoints` X X X X X :class:`Perimeter` X X X diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index ab540627eb1e..2c4d344867ac 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -669,6 +669,19 @@ Accepts a geographic field or expression and returns its geometry type. Accepts a single geographic field or expression and returns the memory size (number of bytes) that the geometry field takes. +``NumDimensions`` +----------------- + +.. versionadded:: 6.1 + +.. class:: NumDimensions(expression, **extra) + +*Availability*: `PostGIS `__, +SpatiaLite + +Accepts a single geometry field or expression and returns the number of +dimensions used by the geometry. + ``NumGeometries`` ----------------- diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index 6109bafb4f23..70ac9d4d28e4 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -425,6 +425,28 @@ Oracle ``SDO_GEOMETRY.GET_GTYPE(geom)`` SpatiaLite ``GeometryType(geom)`` ========== ========================== +.. fieldlookup:: num_dimensions + +``num_dimensions`` +------------------ + +.. versionadded:: 6.1 + +*Availability*: `PostGIS `__, +SpatiaLite + +Returns the number of dimensions used by the geometry. + +Example:: + + Zipcode.objects.filter(geom__num_dimensions=3) + +=================== ================== +Backend SQL Equivalent +=================== ================== +PostGIS, SpatiaLite ``ST_NDims(geom)`` +=================== ================== + .. fieldlookup:: overlaps ``overlaps`` diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt index ae588a3fdb9f..0d1b6ccdcae8 100644 --- a/docs/releases/6.1.txt +++ b/docs/releases/6.1.txt @@ -118,6 +118,11 @@ Minor features :class:`IsEmpty() ` database function are now supported on SpatiaLite. +* The new :lookup:`num_dimensions` lookup and :class:`NumDimensions() + ` database function + allow filtering geometries by the number of dimensions on PostGIS and + SpatiaLite. + :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 917e63cd5762..a6b072542ceb 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -17,7 +17,7 @@ from django.contrib.gis.geos.libgeos import geos_version_tuple from django.contrib.gis.measure import Area from django.db import NotSupportedError, connection -from django.db.models import IntegerField, Sum, Value +from django.db.models import F, IntegerField, Sum, Value from django.test import TestCase, skipUnlessDBFeature from ..utils import FuncTestMixin @@ -28,6 +28,7 @@ Feature, ManyPointModel, State, + ThreeDimensionalFeature, Track, ) @@ -608,6 +609,28 @@ def test_num_geom(self): else: self.assertEqual(1, city.num_geom) + @skipUnlessDBFeature("has_NumDimensions_function") + def test_num_dimensions(self): + for c in Country.objects.annotate(num_dims=functions.NumDimensions("mpoly")): + self.assertEqual(2, c.num_dims) + + ThreeDimensionalFeature.objects.create( + name="London", geom=Point(-0.126418, 51.500832, 0) + ) + qs = ThreeDimensionalFeature.objects.annotate( + num_dims=functions.NumDimensions("geom") + ) + self.assertEqual(qs[0].num_dims, 3) + + qs = ThreeDimensionalFeature.objects.annotate( + num_dims=F("geom__num_dimensions") + ) + self.assertEqual(qs[0].num_dims, 3) + + msg = "'NumDimensions' takes exactly 1 argument (2 given)" + with self.assertRaisesMessage(TypeError, msg): + Country.objects.annotate(num_dims=functions.NumDimensions("point", "error")) + @skipUnlessDBFeature("has_NumPoints_function") def test_num_points(self): coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)] From 70e93bc17bc00c4d1ade9ba600ffe180a6973d66 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 25 Nov 2025 11:21:13 +0100 Subject: [PATCH 3/3] Fixed #36756 -- Dropped support for GDAL 3.1 and 3.2. --- django/contrib/gis/gdal/libgdal.py | 4 ---- django/contrib/gis/gdal/prototypes/geom.py | 24 +++++++--------------- docs/ref/contrib/gis/install/geolibs.txt | 10 ++++----- docs/releases/6.1.txt | 2 ++ 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index c68d175e094a..a9f6351059ec 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -32,8 +32,6 @@ "gdal305", "gdal304", "gdal303", - "gdal302", - "gdal301", ] elif os.name == "posix": # *NIX library names. @@ -50,8 +48,6 @@ "gdal3.5.0", "gdal3.4.0", "gdal3.3.0", - "gdal3.2.0", - "gdal3.1.0", ] else: raise ImproperlyConfigured('GDAL is unsupported on OS "%s".' % os.name) diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index 9066dbd423c9..2f89e663ba52 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -1,7 +1,7 @@ from ctypes import POINTER, c_char_p, c_double, c_int, c_void_p from django.contrib.gis.gdal.envelope import OGREnvelope -from django.contrib.gis.gdal.libgdal import GDAL_VERSION, lgdal +from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.prototypes.errcheck import check_envelope from django.contrib.gis.gdal.prototypes.generation import ( bool_output, @@ -54,18 +54,11 @@ def topology_func(f): getm = pnt_func(lgdal.OGR_G_GetM) # Geometry creation routines. -if GDAL_VERSION >= (3, 3): - from_wkb = geom_output( - lgdal.OGR_G_CreateFromWkbEx, - [c_char_p, c_void_p, POINTER(c_void_p), c_int], - offset=-2, - ) -else: - from_wkb = geom_output( - lgdal.OGR_G_CreateFromWkb, - [c_char_p, c_void_p, POINTER(c_void_p), c_int], - offset=-2, - ) +from_wkb = geom_output( + lgdal.OGR_G_CreateFromWkbEx, + [c_char_p, c_void_p, POINTER(c_void_p), c_int], + offset=-2, +) from_wkt = geom_output( lgdal.OGR_G_CreateFromWkt, [POINTER(c_char_p), c_void_p, POINTER(c_void_p)], @@ -114,10 +107,7 @@ def topology_func(f): to_gml = string_output( lgdal.OGR_G_ExportToGML, [c_void_p], str_result=True, decoding="ascii" ) -if GDAL_VERSION >= (3, 3): - get_wkbsize = int_output(lgdal.OGR_G_WkbSizeEx, [c_void_p]) -else: - get_wkbsize = int_output(lgdal.OGR_G_WkbSize, [c_void_p]) +get_wkbsize = int_output(lgdal.OGR_G_WkbSizeEx, [c_void_p]) # Geometry spatial-reference related routines. assign_srs = void_output( diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 630b60510340..e5e4fb8499ac 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -10,16 +10,16 @@ Geospatial libraries GeoDjango uses and/or provides interfaces for the following open source geospatial libraries: -============================== ==================================== ================================ ============================================================= +============================== ==================================== ================================ =================================================== Program Description Required Supported Versions -============================== ==================================== ================================ ============================================================= +============================== ==================================== ================================ =================================================== :ref:`GEOS ` Geometry Engine Open Source Yes 3.14, 3.13, 3.12, 3.11, 3.10, 3.9 `PROJ`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 9.x, 8.x, 7.x, 6.x -:ref:`GDAL ` Geospatial Data Abstraction Library Yes 3.12, 3.11, 3.10, 3.9, 3.8, 3.7, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1 +:ref:`GDAL ` Geospatial Data Abstraction Library Yes 3.12, 3.11, 3.10, 3.9, 3.8, 3.7, 3.6, 3.5, 3.4, 3.3 :ref:`GeoIP ` IP-based geolocation library No 2 `PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 3.6, 3.5, 3.4, 3.3, 3.2 `SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 5.1, 5.0, 4.3 -============================== ==================================== ================================ ============================================================= +============================== ==================================== ================================ =================================================== Note that older or more recent versions of these libraries *may* also work totally fine with GeoDjango. Your mileage may vary. @@ -32,8 +32,6 @@ totally fine with GeoDjango. Your mileage may vary. GEOS 3.12.0 2023-06-27 GEOS 3.13.0 2024-09-06 GEOS 3.14.0 2025-08-21 - GDAL 3.1.0 2020-05-07 - GDAL 3.2.0 2020-11-02 GDAL 3.3.0 2021-05-03 GDAL 3.4.0 2021-11-04 GDAL 3.5.0 2022-05-13 diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt index 0d1b6ccdcae8..1c533f1341fe 100644 --- a/docs/releases/6.1.txt +++ b/docs/releases/6.1.txt @@ -345,6 +345,8 @@ backends. * Support for GEOS 3.8 is removed. +* Support for GDAL 3.1 and 3.2 is removed. + :mod:`django.contrib.postgres` ------------------------------