From 82f34e2f7290d98ae2d1fe80919b2f0e21b18369 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 19 Sep 2026 16:32:57 -0400 Subject: [PATCH 1/2] Refs #36947 -- Added supports_comments skip to test_alter_generated_field_base_field_comment. --- tests/schema/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 16fbe11078dc..b761ef933217 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1113,6 +1113,7 @@ class Meta: @isolate_apps("schema") @skipUnlessDBFeature( + "supports_comments", "supports_stored_generated_columns", "supports_independent_comment_alteration", ) From 446d9cf602a5c42862da118b714a5679bb61cf27 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 13 Sep 2026 12:53:01 +0200 Subject: [PATCH 2/2] Fixed #37114 -- Dropped support for PostgreSQL 15 and PostGIS 3.2. --- .github/workflows/check-migrations.yml | 2 +- .github/workflows/playwright.yml | 2 +- .github/workflows/schedule_tests.yml | 4 +-- .../gis/db/backends/postgis/operations.py | 2 +- django/db/backends/postgresql/features.py | 8 ++---- django/db/models/functions/json.py | 26 +++---------------- docs/ref/contrib/gis/install/geolibs.txt | 3 +-- docs/ref/contrib/gis/install/index.txt | 18 ++++++------- docs/ref/databases.txt | 2 +- docs/releases/6.2.txt | 8 ++++++ tests/backends/postgresql/tests.py | 8 +++--- tests/queries/test_explain.py | 3 +-- 12 files changed, 34 insertions(+), 52 deletions(-) diff --git a/.github/workflows/check-migrations.yml b/.github/workflows/check-migrations.yml index 8c17f6d68939..21e51d08661d 100644 --- a/.github/workflows/check-migrations.yml +++ b/.github/workflows/check-migrations.yml @@ -26,7 +26,7 @@ jobs: services: postgres: - image: postgres:15-alpine + image: postgres:16-alpine env: POSTGRES_DB: django POSTGRES_USER: user diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a3f526154929..15905fe8c23d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -51,7 +51,7 @@ jobs: timeout-minutes: 60 services: postgres: - image: postgres:15-alpine + image: postgres:16-alpine env: POSTGRES_DB: django POSTGRES_USER: user diff --git a/.github/workflows/schedule_tests.yml b/.github/workflows/schedule_tests.yml index e6ddfccabafd..eec0533a877a 100644 --- a/.github/workflows/schedule_tests.yml +++ b/.github/workflows/schedule_tests.yml @@ -126,7 +126,7 @@ jobs: timeout-minutes: 60 services: postgres: - image: postgres:15-alpine + image: postgres:16-alpine env: POSTGRES_DB: django POSTGRES_USER: user @@ -169,7 +169,7 @@ jobs: strategy: fail-fast: false matrix: - version: [16, 17, 18] + version: [17, 18] server_side_bindings: [0, 1] runs-on: ubuntu-latest name: PostgreSQL Versions diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index dcbb5e18c831..38b4d3d67949 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -211,7 +211,7 @@ def spatial_version(self): raise ImproperlyConfigured( 'Cannot determine PostGIS version for database "%s" ' 'using command "SELECT postgis_lib_version()". ' - "GeoDjango requires at least PostGIS version 3.2. " + "GeoDjango requires at least PostGIS version 3.3. " "Was the database created from a spatial database " "template?" % self.connection.settings_dict["NAME"] ) diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index fb12c798dda3..0d63b41b8e6c 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -7,7 +7,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): - minimum_database_version = (15,) + minimum_database_version = (16,) allows_group_by_selected_pks = True can_return_columns_from_insert = True can_return_rows_from_bulk_insert = True @@ -174,10 +174,6 @@ def introspected_field_types(self): "PositiveSmallIntegerField": "SmallIntegerField", } - @cached_property - def is_postgresql_16(self): - return self.connection.pg_version >= 160000 - @cached_property def is_postgresql_17(self): return self.connection.pg_version >= 170000 @@ -187,7 +183,7 @@ def is_postgresql_18(self): return self.connection.pg_version >= 180000 supports_unlimited_charfield = True - supports_any_value = property(operator.attrgetter("is_postgresql_16")) + supports_any_value = True supports_virtual_generated_columns = property( operator.attrgetter("is_postgresql_18") ) diff --git a/django/db/models/functions/json.py b/django/db/models/functions/json.py index fee7dd05f4ea..8465d28377f0 100644 --- a/django/db/models/functions/json.py +++ b/django/db/models/functions/json.py @@ -48,17 +48,8 @@ def as_postgresql(self, compiler, connection, **extra_context): for expression in casted_obj.get_source_expressions() ] ) - - if connection.features.is_postgresql_16: - return casted_obj.as_native( - compiler, connection, returning="JSONB", **extra_context - ) - - return casted_obj.as_sql( - compiler, - connection, - function="JSONB_BUILD_ARRAY", - **extra_context, + return casted_obj.as_native( + compiler, connection, returning="JSONB", **extra_context ) def as_oracle(self, compiler, connection, **extra_context): @@ -107,18 +98,7 @@ def as_postgresql(self, compiler, connection, **extra_context): for index, expression in enumerate(copy.get_source_expressions()) ] ) - - if connection.features.is_postgresql_16: - return copy.as_native( - compiler, connection, returning="JSONB", **extra_context - ) - - return super(JSONObject, copy).as_sql( - compiler, - connection, - function="JSONB_BUILD_OBJECT", - **extra_context, - ) + return copy.as_native(compiler, connection, returning="JSONB", **extra_context) def as_oracle(self, compiler, connection, **extra_context): return self.as_native(compiler, connection, returning="CLOB", **extra_context) diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 6d48df2bc4a0..f4733ae1762c 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -17,7 +17,7 @@ Program Description Required `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 -`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 3.6, 3.5, 3.4, 3.3, 3.2 +`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 3.6, 3.5, 3.4, 3.3 `SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 5.1, 5.0, 4.3 ============================== ==================================== ================================ ========================================================= @@ -42,7 +42,6 @@ totally fine with GeoDjango. Your mileage may vary. GDAL 3.11.0 2025-05-09 GDAL 3.12.0 2025-11-07 GDAL 3.13.0 2026-05-08 - PostGIS 3.2.0 2021-12-18 PostGIS 3.3.0 2022-08-27 PostGIS 3.4.0 2023-08-15 PostGIS 3.5.0 2024-09-25 diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index eaba7f3fafc8..642d414a8495 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/docs/ref/contrib/gis/install/index.txt @@ -57,7 +57,7 @@ supported versions, and any notes for each of the supported database backends: ================== ============================== ================== ========================================= Database Library Requirements Supported Versions Notes ================== ============================== ================== ========================================= -PostgreSQL GEOS, GDAL, PROJ, PostGIS 15+ Requires PostGIS. +PostgreSQL GEOS, GDAL, PROJ, PostGIS 16+ Requires PostGIS. MySQL GEOS, GDAL 8.4+ :ref:`Limited functionality `. Oracle GEOS, GDAL 19+ XE not supported. SQLite GEOS, GDAL, PROJ, SpatiaLite 3.37.0+ Requires SpatiaLite 4.3+ @@ -304,7 +304,7 @@ Summary: .. code-block:: shell - $ sudo port install postgresql15-server + $ sudo port install postgresql16-server $ sudo port install geos $ sudo port install proj6 $ sudo port install postgis3 @@ -317,14 +317,14 @@ Summary: .. code-block:: shell - export PATH=/opt/local/bin:/opt/local/lib/postgresql15/bin + export PATH=/opt/local/bin:/opt/local/lib/postgresql16/bin In addition, add the ``DYLD_FALLBACK_LIBRARY_PATH`` setting so that the libraries can be found by Python: .. code-block:: shell - export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql15 + export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql16 __ https://www.macports.org/ @@ -346,7 +346,7 @@ for further information. PostgreSQL ~~~~~~~~~~ -Download the latest `PostgreSQL 15.x installer`__ from the +Download the latest `PostgreSQL 16.x installer`__ from the `EnterpriseDB`__ website. After downloading, run the installer, follow the on-screen directions, and keep the default options unless you know the consequences of changing them. @@ -363,7 +363,7 @@ keep this checked, as it is necessary to install :ref:`postgisasb`. If installed successfully, the PostgreSQL server will run in the background each time the system is started as a Windows service. A - :menuselection:`PostgreSQL 15` start menu group will created and contains + :menuselection:`PostgreSQL 16` start menu group will created and contains shortcuts for the Application Stack Builder (ASB) as well as the 'SQL Shell', which will launch a ``psql`` command window. @@ -376,11 +376,11 @@ PostGIS ~~~~~~~ From within the Stack Builder (to run outside of the installer, -:menuselection:`Start --> PostgreSQL 15 --> Application Stack Builder`), select -:menuselection:`PostgreSQL 15 (x64) on port 5432` from the drop down +:menuselection:`Start --> PostgreSQL 16 --> Application Stack Builder`), select +:menuselection:`PostgreSQL 16 (x64) on port 5432` from the drop down menu and click next. Expand the :menuselection:`Categories --> Spatial Extensions` menu tree and select -:menuselection:`PostGIS X.Y for PostgreSQL 15`. +:menuselection:`PostGIS X.Y for PostgreSQL 16`. After clicking next, you will be prompted to confirm the selected package and "Download directory". Click next again, this will download PostGIS and you will diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 62c2617ded3e..14205b8a7bd7 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -119,7 +119,7 @@ below for information on how to set up your database correctly. PostgreSQL notes ================ -Django supports PostgreSQL 15 and higher. `psycopg`_ 3.1.12+ or `psycopg2`_ +Django supports PostgreSQL 16 and higher. `psycopg`_ 3.1.12+ or `psycopg2`_ 2.9.9+ is required, though the latest `psycopg`_ 3.1.12+ is recommended. .. note:: diff --git a/docs/releases/6.2.txt b/docs/releases/6.2.txt index 5b9e9c70ac79..2995b049f5c2 100644 --- a/docs/releases/6.2.txt +++ b/docs/releases/6.2.txt @@ -306,6 +306,8 @@ backends. :meth:`~django.contrib.gis.gdal.Field.as_datetime` is now a ``c_float`` rather than a ``c_int``. +* Support for PostGIS 3.2 is removed. + Models ------ @@ -322,6 +324,12 @@ Tests * The undocumented ``django.test.selenium`` module was removed. +Dropped support for PostgreSQL 15 +--------------------------------- + +Upstream support for PostgreSQL 15 ends in November 2027. Django 6.2 supports +PostgreSQL 16 and higher. + Miscellaneous ------------- diff --git a/tests/backends/postgresql/tests.py b/tests/backends/postgresql/tests.py index 9afa6020a375..f31c43da5c79 100644 --- a/tests/backends/postgresql/tests.py +++ b/tests/backends/postgresql/tests.py @@ -579,12 +579,12 @@ def test_copy_cursors(self): def test_get_database_version(self): new_connection = no_pool_connection() - new_connection.pg_version = 150009 - self.assertEqual(new_connection.get_database_version(), (15, 9)) + new_connection.pg_version = 160009 + self.assertEqual(new_connection.get_database_version(), (16, 9)) - @mock.patch.object(connection, "get_database_version", return_value=(14,)) + @mock.patch.object(connection, "get_database_version", return_value=(15,)) def test_check_database_version_supported(self, mocked_get_database_version): - msg = "PostgreSQL 15 or later is required (found 14)." + msg = "PostgreSQL 16 or later is required (found 15)." with self.assertRaisesMessage(NotSupportedError, msg): connection.check_database_version_supported() self.assertTrue(mocked_get_database_version.called) diff --git a/tests/queries/test_explain.py b/tests/queries/test_explain.py index 59bd0e8d0852..cb9e636d0c02 100644 --- a/tests/queries/test_explain.py +++ b/tests/queries/test_explain.py @@ -87,9 +87,8 @@ def test_postgres_options(self): {"summary": True}, {"settings": True}, {"analyze": True, "wal": True}, + {"generic_plan": True}, ] - if connection.features.is_postgresql_16: - test_options.append({"generic_plan": True}) if connection.features.is_postgresql_17: test_options.append({"memory": True}) test_options.append({"serialize": "TEXT", "analyze": True})