From 89768ae1ab909ae77cbecc2e57927530dbe8584f Mon Sep 17 00:00:00 2001 From: "Md. Saikat Islam" Date: Tue, 8 Sep 2026 16:51:30 +0600 Subject: [PATCH 1/2] Fixed #37331 -- Dropped support for GEOS 3.10. --- docs/ref/contrib/gis/install/geolibs.txt | 3 +-- docs/releases/6.2.txt | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 4d2c2356b10a..6d48df2bc4a0 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -13,7 +13,7 @@ geospatial libraries: ============================== ==================================== ================================ ========================================================= Program Description Required Supported Versions ============================== ==================================== ================================ ========================================================= -:ref:`GEOS ` Geometry Engine Open Source Yes 3.15, 3.14, 3.13, 3.12, 3.11, 3.10 +:ref:`GEOS ` Geometry Engine Open Source Yes 3.15, 3.14, 3.13, 3.12, 3.11 `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.13, 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 @@ -26,7 +26,6 @@ totally fine with GeoDjango. Your mileage may vary. .. Libs release dates: - GEOS 3.10.0 2021-10-20 GEOS 3.11.0 2022-07-01 GEOS 3.12.0 2023-06-27 GEOS 3.13.0 2024-09-06 diff --git a/docs/releases/6.2.txt b/docs/releases/6.2.txt index 6fc57a0e2446..1bd58f0aac04 100644 --- a/docs/releases/6.2.txt +++ b/docs/releases/6.2.txt @@ -293,6 +293,8 @@ backends. :mod:`django.contrib.gis` ------------------------- +* Support for GEOS 3.10 is removed. + * The seconds value returned by :meth:`~django.contrib.gis.gdal.Field.as_datetime` is now a ``c_float`` rather than a ``c_int``. From fc4eaaabf419508ea39d750ca5ca801d933e0b16 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 3 Sep 2026 15:59:07 -0400 Subject: [PATCH 2/2] Fixed #37289 -- Avoided deleting network rasters. GDALRaster.__del__() issues an unlink call to GDAL to remove temporary files from the in-memory virtual filesystem. When #32670 allowed using any virtual filesystem, including commercial storage ones like vsis3 for Amazon S3, this meant network rasters could be unintentionally deleted. Thanks "garden_" for the report and Sarah Boyce for the review. --- django/contrib/gis/gdal/raster/source.py | 10 ++++++---- docs/ref/contrib/gis/gdal.txt | 4 +++- docs/releases/5.2.18.txt | 13 +++++++++++++ docs/releases/6.0.9.txt | 13 +++++++++++++ docs/releases/6.1.2.txt | 5 +++-- docs/releases/index.txt | 2 ++ tests/gis_tests/gdal_tests/test_raster.py | 19 ++++++++++++++++++- 7 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 docs/releases/5.2.18.txt create mode 100644 docs/releases/6.0.9.txt diff --git a/django/contrib/gis/gdal/raster/source.py b/django/contrib/gis/gdal/raster/source.py index 42ab1e3a7091..53a608ec6d25 100644 --- a/django/contrib/gis/gdal/raster/source.py +++ b/django/contrib/gis/gdal/raster/source.py @@ -217,7 +217,7 @@ def __init__(self, ds_input, write=False): ) def __del__(self): - if self.is_vsi_based: + if self._is_vsimem_based: # Remove the temporary file from the VSI in-memory filesystem. capi.unlink_vsi_file(force_bytes(self.name)) super().__del__() @@ -276,9 +276,7 @@ def _flush(self): @property def vsi_buffer(self): - if not ( - self.is_vsi_based and self.name.startswith(VSI_MEM_FILESYSTEM_BASE_PATH) - ): + if not self._is_vsimem_based: return None # Prepare an integer that will contain the buffer length. out_length = c_int() @@ -295,6 +293,10 @@ def vsi_buffer(self): def is_vsi_based(self): return self._ptr and self.name.startswith(VSI_FILESYSTEM_PREFIX) + @cached_property + def _is_vsimem_based(self): + return self._ptr and self.name.startswith(VSI_MEM_FILESYSTEM_BASE_PATH) + @property def name(self): """ diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index 99433553c1a4..ee1850be5ee1 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -1688,7 +1688,7 @@ bands: one for red, one for green, and one for blue. .. attribute:: vsi_buffer A ``bytes`` representation of this raster. Returns ``None`` for rasters - that are not stored in GDAL's virtual filesystem. + that are not stored in GDAL's in-memory virtual filesystem. .. attribute:: is_vsi_based @@ -2116,6 +2116,8 @@ can directly access compressed files using the ``/vsizip/``, ``/vsigzip/``, or >>> rst = GDALRaster("/vsigzip/path/to/your/file.gz") >>> rst = GDALRaster("/vsitar/path/to/your/file.tar/path/to/raster.tif") +.. _gdal-raster-network: + Network rasters ^^^^^^^^^^^^^^^ diff --git a/docs/releases/5.2.18.txt b/docs/releases/5.2.18.txt new file mode 100644 index 000000000000..a1ecd78690e8 --- /dev/null +++ b/docs/releases/5.2.18.txt @@ -0,0 +1,13 @@ +=========================== +Django 5.2.18 release notes +=========================== + +*Expected October 6, 2026* + +Django 5.2.18 fixes one data loss issue in 4.0. + +Bugfixes +======== + +* Fixed a data loss issue in Django 4.0 where :ref:`network rasters + ` were deleted by GeoDjango when closed. diff --git a/docs/releases/6.0.9.txt b/docs/releases/6.0.9.txt new file mode 100644 index 000000000000..64f691ea21e2 --- /dev/null +++ b/docs/releases/6.0.9.txt @@ -0,0 +1,13 @@ +========================== +Django 6.0.9 release notes +========================== + +*Expected October 6, 2026* + +Django 6.0.9 fixes one data loss issue in 4.0. + +Bugfixes +======== + +* Fixed a data loss issue in Django 4.0 where :ref:`network rasters + ` were deleted by GeoDjango when closed. diff --git a/docs/releases/6.1.2.txt b/docs/releases/6.1.2.txt index 2ea851219af5..810d81d62682 100644 --- a/docs/releases/6.1.2.txt +++ b/docs/releases/6.1.2.txt @@ -4,9 +4,10 @@ Django 6.1.2 release notes *Expected October 6, 2026* -Django 6.1.2 fixes several bugs in 6.1.1. +Django 6.1.2 fixes one data loss issue in 4.0 and several bugs in 6.1.1. Bugfixes ======== -* ... +* Fixed a data loss issue in Django 4.0 where :ref:`network rasters + ` were deleted by GeoDjango when closed. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 0a27ee307c56..42b88b69c388 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -41,6 +41,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 6.0.9 6.0.8 6.0.7 6.0.6 @@ -56,6 +57,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 5.2.18 5.2.17 5.2.16 5.2.15 diff --git a/tests/gis_tests/gdal_tests/test_raster.py b/tests/gis_tests/gdal_tests/test_raster.py index ca7251914b8d..48c2ef476b49 100644 --- a/tests/gis_tests/gdal_tests/test_raster.py +++ b/tests/gis_tests/gdal_tests/test_raster.py @@ -8,6 +8,7 @@ from django.contrib.gis.gdal import GDAL_VERSION, GDALRaster, SpatialReference from django.contrib.gis.gdal.error import GDALException +from django.contrib.gis.gdal.prototypes import raster as capi from django.contrib.gis.gdal.raster.band import GDALBand from django.contrib.gis.shortcuts import numpy from django.core.files.temp import NamedTemporaryFile @@ -192,7 +193,10 @@ def test_file_based_raster_creation(self): def test_nonexistent_file(self): msg = 'Unable to read raster source input "nonexistent.tif".' - with self.assertRaisesMessage(GDALException, msg): + with ( + self.assertNoLogs("django.contrib.gis", "ERROR"), + self.assertRaisesMessage(GDALException, msg), + ): GDALRaster("nonexistent.tif") def test_vsi_raster_creation(self): @@ -284,6 +288,19 @@ def test_vsi_vsizip_filesystem(self): self.assertIs(rst.is_vsi_based, True) self.assertIsNone(rst.vsi_buffer) + def test_non_vsimem_raster_not_unlinked(self): + """Closing a non-/vsimem/ raster doesn't unlink its source.""" + rst_zipfile = NamedTemporaryFile(suffix=".zip") + self.addCleanup(rst_zipfile.close) + with zipfile.ZipFile(rst_zipfile, mode="w") as zf: + zf.write(self.rs_path, "raster.tif") + rst_path = "/vsizip/" + os.path.join(rst_zipfile.name, "raster.tif") + rst = GDALRaster(rst_path) + self.assertIs(rst._is_vsimem_based, False) + with mock.patch.object(capi, "unlink_vsi_file") as unlink_vsi_file: + del rst + unlink_vsi_file.assert_not_called() + def test_offset_size_and_shape_on_raster_creation(self): rast = GDALRaster( {