mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #35479 -- Dropped support for PostgreSQL 13 and PostGIS 3.0.
This commit is contained in:
		
				
					committed by
					
						 Sarah Boyce
						Sarah Boyce
					
				
			
			
				
	
			
			
			
						parent
						
							bcbc4b9b8a
						
					
				
				
					commit
					b049bec7cf
				
			
							
								
								
									
										4
									
								
								.github/workflows/schedule_tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/schedule_tests.yml
									
									
									
									
										vendored
									
									
								
							| @@ -90,7 +90,7 @@ jobs: | ||||
|     continue-on-error: true | ||||
|     services: | ||||
|       postgres: | ||||
|         image: postgres:13-alpine | ||||
|         image: postgres:14-alpine | ||||
|         env: | ||||
|           POSTGRES_DB: django | ||||
|           POSTGRES_USER: user | ||||
| @@ -163,7 +163,7 @@ jobs: | ||||
|     name: Selenium tests, PostgreSQL | ||||
|     services: | ||||
|       postgres: | ||||
|         image: postgres:13-alpine | ||||
|         image: postgres:14-alpine | ||||
|         env: | ||||
|           POSTGRES_DB: django | ||||
|           POSTGRES_USER: user | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/selenium.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/selenium.yml
									
									
									
									
										vendored
									
									
								
							| @@ -43,7 +43,7 @@ jobs: | ||||
|     name: PostgreSQL | ||||
|     services: | ||||
|       postgres: | ||||
|         image: postgres:13-alpine | ||||
|         image: postgres:14-alpine | ||||
|         env: | ||||
|           POSTGRES_DB: django | ||||
|           POSTGRES_USER: user | ||||
|   | ||||
| @@ -203,7 +203,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): | ||||
|                 raise ImproperlyConfigured( | ||||
|                     'Cannot determine PostGIS version for database "%s" ' | ||||
|                     'using command "SELECT postgis_lib_version()". ' | ||||
|                     "GeoDjango requires at least PostGIS version 3.0. " | ||||
|                     "GeoDjango requires at least PostGIS version 3.1. " | ||||
|                     "Was the database created from a spatial database " | ||||
|                     "template?" % self.connection.settings_dict["NAME"] | ||||
|                 ) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from types import NoneType | ||||
|  | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.db import DEFAULT_DB_ALIAS, NotSupportedError | ||||
| from django.db import DEFAULT_DB_ALIAS | ||||
| from django.db.backends.ddl_references import Expressions, Statement, Table | ||||
| from django.db.models import BaseConstraint, Deferrable, F, Q | ||||
| from django.db.models.expressions import Exists, ExpressionList | ||||
| @@ -114,7 +114,6 @@ class ExclusionConstraint(BaseConstraint): | ||||
|         ) | ||||
|  | ||||
|     def create_sql(self, model, schema_editor): | ||||
|         self.check_supported(schema_editor) | ||||
|         return Statement( | ||||
|             "ALTER TABLE %(table)s ADD %(constraint)s", | ||||
|             table=Table(model._meta.db_table, schema_editor.quote_name), | ||||
| @@ -128,17 +127,6 @@ class ExclusionConstraint(BaseConstraint): | ||||
|             schema_editor.quote_name(self.name), | ||||
|         ) | ||||
|  | ||||
|     def check_supported(self, schema_editor): | ||||
|         if ( | ||||
|             self.include | ||||
|             and self.index_type.lower() == "spgist" | ||||
|             and not schema_editor.connection.features.supports_covering_spgist_indexes | ||||
|         ): | ||||
|             raise NotSupportedError( | ||||
|                 "Covering exclusion constraints using an SP-GiST index " | ||||
|                 "require PostgreSQL 14+." | ||||
|             ) | ||||
|  | ||||
|     def deconstruct(self): | ||||
|         path, args, kwargs = super().deconstruct() | ||||
|         kwargs["expressions"] = self.expressions | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from django.db import NotSupportedError | ||||
| from django.db.models import Func, Index | ||||
| from django.utils.functional import cached_property | ||||
|  | ||||
| @@ -234,13 +233,6 @@ class SpGistIndex(PostgresIndex): | ||||
|             with_params.append("fillfactor = %d" % self.fillfactor) | ||||
|         return with_params | ||||
|  | ||||
|     def check_supported(self, schema_editor): | ||||
|         if ( | ||||
|             self.include | ||||
|             and not schema_editor.connection.features.supports_covering_spgist_indexes | ||||
|         ): | ||||
|             raise NotSupportedError("Covering SP-GiST indexes require PostgreSQL 14+.") | ||||
|  | ||||
|  | ||||
| class OpClass(Func): | ||||
|     template = "%(expressions)s %(name)s" | ||||
|   | ||||
| @@ -7,7 +7,7 @@ from django.utils.functional import cached_property | ||||
|  | ||||
|  | ||||
| class DatabaseFeatures(BaseDatabaseFeatures): | ||||
|     minimum_database_version = (13,) | ||||
|     minimum_database_version = (14,) | ||||
|     allows_group_by_selected_pks = True | ||||
|     can_return_columns_from_insert = True | ||||
|     can_return_rows_from_bulk_insert = True | ||||
| @@ -152,10 +152,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): | ||||
|             "PositiveSmallIntegerField": "SmallIntegerField", | ||||
|         } | ||||
|  | ||||
|     @cached_property | ||||
|     def is_postgresql_14(self): | ||||
|         return self.connection.pg_version >= 140000 | ||||
|  | ||||
|     @cached_property | ||||
|     def is_postgresql_15(self): | ||||
|         return self.connection.pg_version >= 150000 | ||||
| @@ -164,8 +160,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): | ||||
|     def is_postgresql_16(self): | ||||
|         return self.connection.pg_version >= 160000 | ||||
|  | ||||
|     has_bit_xor = property(operator.attrgetter("is_postgresql_14")) | ||||
|     supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14")) | ||||
|     supports_unlimited_charfield = True | ||||
|     supports_nulls_distinct_unique_constraints = property( | ||||
|         operator.attrgetter("is_postgresql_15") | ||||
|   | ||||
| @@ -12,7 +12,7 @@ Program                   Description                           Required | ||||
| `PROJ`_                   Cartographic Projections library      Yes (PostgreSQL and SQLite only)  9.x, 8.x, 7.x, 6.x | ||||
| :doc:`GDAL <../gdal>`     Geospatial Data Abstraction Library   Yes                               3.8, 3.7, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0 | ||||
| :doc:`GeoIP <../geoip2>`  IP-based geolocation library          No                                2 | ||||
| `PostGIS`__               Spatial extensions for PostgreSQL     Yes (PostgreSQL only)             3.4, 3.3, 3.2, 3.1, 3.0 | ||||
| `PostGIS`__               Spatial extensions for PostgreSQL     Yes (PostgreSQL only)             3.4, 3.3, 3.2, 3.1 | ||||
| `SpatiaLite`__            Spatial extensions for SQLite         Yes (SQLite only)                 5.1, 5.0, 4.3 | ||||
| ========================  ====================================  ================================  =========================================== | ||||
|  | ||||
| @@ -35,7 +35,6 @@ totally fine with GeoDjango. Your mileage may vary. | ||||
|     GDAL 3.6.0 2022-11-03 | ||||
|     GDAL 3.7.0 2023-05-10 | ||||
|     GDAL 3.8.0 2023-11-13 | ||||
|     PostGIS 3.0.0 2019-10-20 | ||||
|     PostGIS 3.1.0 2020-12-18 | ||||
|     PostGIS 3.2.0 2021-12-18 | ||||
|     PostGIS 3.3.0 2022-08-27 | ||||
|   | ||||
| @@ -56,7 +56,7 @@ supported versions, and any notes for each of the supported database backends: | ||||
| ==================  ==============================  ==================  ========================================= | ||||
| Database            Library Requirements            Supported Versions  Notes | ||||
| ==================  ==============================  ==================  ========================================= | ||||
| PostgreSQL          GEOS, GDAL, PROJ, PostGIS       13+                 Requires PostGIS. | ||||
| PostgreSQL          GEOS, GDAL, PROJ, PostGIS       14+                 Requires PostGIS. | ||||
| MySQL               GEOS, GDAL                      8.0.11+             :ref:`Limited functionality <mysql-spatial-limitations>`. | ||||
| Oracle              GEOS, GDAL                      19+                 XE not supported. | ||||
| SQLite              GEOS, GDAL, PROJ, SpatiaLite    3.31.0+             Requires SpatiaLite 4.3+ | ||||
| @@ -300,7 +300,7 @@ Summary: | ||||
|  | ||||
| .. code-block:: shell | ||||
|  | ||||
|     $ sudo port install postgresql13-server | ||||
|     $ sudo port install postgresql14-server | ||||
|     $ sudo port install geos | ||||
|     $ sudo port install proj6 | ||||
|     $ sudo port install postgis3 | ||||
| @@ -314,14 +314,14 @@ Summary: | ||||
|  | ||||
|     .. code-block:: shell | ||||
|  | ||||
|         export PATH=/opt/local/bin:/opt/local/lib/postgresql13/bin | ||||
|         export PATH=/opt/local/bin:/opt/local/lib/postgresql14/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/postgresql13 | ||||
|         export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql14 | ||||
|  | ||||
| __ https://www.macports.org/ | ||||
|  | ||||
|   | ||||
| @@ -14,12 +14,6 @@ All of these functions are available from the | ||||
|  | ||||
| Returns a version 4 UUID. | ||||
|  | ||||
| On PostgreSQL < 13, the `pgcrypto extension`_ must be installed. You can use | ||||
| the :class:`~django.contrib.postgres.operations.CryptoExtension` migration | ||||
| operation to install it. | ||||
|  | ||||
| .. _pgcrypto extension: https://www.postgresql.org/docs/current/pgcrypto.html | ||||
|  | ||||
| Usage example: | ||||
|  | ||||
| .. code-block:: pycon | ||||
|   | ||||
| @@ -115,7 +115,7 @@ below for information on how to set up your database correctly. | ||||
| PostgreSQL notes | ||||
| ================ | ||||
|  | ||||
| Django supports PostgreSQL 13 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_ | ||||
| Django supports PostgreSQL 14 and higher. `psycopg`_ 3.1.8+ or `psycopg2`_ | ||||
| 2.8.4+ is required, though the latest `psycopg`_ 3.1.8+ is recommended. | ||||
|  | ||||
| .. note:: | ||||
|   | ||||
| @@ -238,6 +238,17 @@ backends. | ||||
|  | ||||
| * ... | ||||
|  | ||||
| :mod:`django.contrib.gis` | ||||
| ------------------------- | ||||
|  | ||||
| * Support for PostGIS 3.0 is removed. | ||||
|  | ||||
| Dropped support for PostgreSQL 13 | ||||
| --------------------------------- | ||||
|  | ||||
| Upstream support for PostgreSQL 13 ends in November 2025. Django 5.2 supports | ||||
| PostgreSQL 14 and higher. | ||||
|  | ||||
| Miscellaneous | ||||
| ------------- | ||||
|  | ||||
|   | ||||
| @@ -548,12 +548,12 @@ class Tests(TestCase): | ||||
|  | ||||
|     def test_get_database_version(self): | ||||
|         new_connection = no_pool_connection() | ||||
|         new_connection.pg_version = 130009 | ||||
|         self.assertEqual(new_connection.get_database_version(), (13, 9)) | ||||
|         new_connection.pg_version = 140009 | ||||
|         self.assertEqual(new_connection.get_database_version(), (14, 9)) | ||||
|  | ||||
|     @mock.patch.object(connection, "get_database_version", return_value=(12,)) | ||||
|     @mock.patch.object(connection, "get_database_version", return_value=(13,)) | ||||
|     def test_check_database_version_supported(self, mocked_get_database_version): | ||||
|         msg = "PostgreSQL 13 or later is required (found 12)." | ||||
|         msg = "PostgreSQL 14 or later is required (found 13)." | ||||
|         with self.assertRaisesMessage(NotSupportedError, msg): | ||||
|             connection.check_database_version_supported() | ||||
|         self.assertTrue(mocked_get_database_version.called) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from django.db import connection, transaction | ||||
| from django.db import transaction | ||||
| from django.db.models import ( | ||||
|     CharField, | ||||
|     F, | ||||
| @@ -13,7 +13,6 @@ from django.db.models import ( | ||||
| ) | ||||
| from django.db.models.fields.json import KeyTextTransform, KeyTransform | ||||
| from django.db.models.functions import Cast, Concat, LPad, Substr | ||||
| from django.test import skipUnlessDBFeature | ||||
| from django.test.utils import Approximate | ||||
| from django.utils import timezone | ||||
|  | ||||
| @@ -95,9 +94,8 @@ class TestGeneralAggregate(PostgreSQLTestCase): | ||||
|             BoolOr("boolean_field"), | ||||
|             JSONBAgg("integer_field"), | ||||
|             StringAgg("char_field", delimiter=";"), | ||||
|             BitXor("integer_field"), | ||||
|         ] | ||||
|         if connection.features.has_bit_xor: | ||||
|             tests.append(BitXor("integer_field")) | ||||
|         for aggregation in tests: | ||||
|             with self.subTest(aggregation=aggregation): | ||||
|                 # Empty result with non-execution optimization. | ||||
| @@ -133,9 +131,8 @@ class TestGeneralAggregate(PostgreSQLTestCase): | ||||
|                 StringAgg("char_field", delimiter=";", default=Value("<empty>")), | ||||
|                 "<empty>", | ||||
|             ), | ||||
|             (BitXor("integer_field", default=0), 0), | ||||
|         ] | ||||
|         if connection.features.has_bit_xor: | ||||
|             tests.append((BitXor("integer_field", default=0), 0)) | ||||
|         for aggregation, expected_result in tests: | ||||
|             with self.subTest(aggregation=aggregation): | ||||
|                 # Empty result with non-execution optimization. | ||||
| @@ -348,7 +345,6 @@ class TestGeneralAggregate(PostgreSQLTestCase): | ||||
|         ) | ||||
|         self.assertEqual(values, {"bitor": 0}) | ||||
|  | ||||
|     @skipUnlessDBFeature("has_bit_xor") | ||||
|     def test_bit_xor_general(self): | ||||
|         AggregateTestModel.objects.create(integer_field=3) | ||||
|         values = AggregateTestModel.objects.filter( | ||||
| @@ -356,14 +352,12 @@ class TestGeneralAggregate(PostgreSQLTestCase): | ||||
|         ).aggregate(bitxor=BitXor("integer_field")) | ||||
|         self.assertEqual(values, {"bitxor": 2}) | ||||
|  | ||||
|     @skipUnlessDBFeature("has_bit_xor") | ||||
|     def test_bit_xor_on_only_true_values(self): | ||||
|         values = AggregateTestModel.objects.filter( | ||||
|             integer_field=1, | ||||
|         ).aggregate(bitxor=BitXor("integer_field")) | ||||
|         self.assertEqual(values, {"bitxor": 1}) | ||||
|  | ||||
|     @skipUnlessDBFeature("has_bit_xor") | ||||
|     def test_bit_xor_on_only_false_values(self): | ||||
|         values = AggregateTestModel.objects.filter( | ||||
|             integer_field=0, | ||||
|   | ||||
| @@ -4,7 +4,7 @@ from unittest import mock | ||||
| from django.contrib.postgres.indexes import OpClass | ||||
| from django.core.checks import Error | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.db import IntegrityError, NotSupportedError, connection, transaction | ||||
| from django.db import IntegrityError, connection, transaction | ||||
| from django.db.models import ( | ||||
|     CASCADE, | ||||
|     CharField, | ||||
| @@ -997,7 +997,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase): | ||||
|         RangesModel.objects.create(ints=(10, 19)) | ||||
|         RangesModel.objects.create(ints=(51, 60)) | ||||
|  | ||||
|     @skipUnlessDBFeature("supports_covering_spgist_indexes") | ||||
|     def test_range_adjacent_spgist_include(self): | ||||
|         constraint_name = "ints_adjacent_spgist_include" | ||||
|         self.assertNotIn( | ||||
| @@ -1034,7 +1033,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase): | ||||
|             editor.add_constraint(RangesModel, constraint) | ||||
|         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) | ||||
|  | ||||
|     @skipUnlessDBFeature("supports_covering_spgist_indexes") | ||||
|     def test_range_adjacent_spgist_include_condition(self): | ||||
|         constraint_name = "ints_adjacent_spgist_include_condition" | ||||
|         self.assertNotIn( | ||||
| @@ -1067,7 +1065,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase): | ||||
|             editor.add_constraint(RangesModel, constraint) | ||||
|         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) | ||||
|  | ||||
|     @skipUnlessDBFeature("supports_covering_spgist_indexes") | ||||
|     def test_range_adjacent_spgist_include_deferrable(self): | ||||
|         constraint_name = "ints_adjacent_spgist_include_deferrable" | ||||
|         self.assertNotIn( | ||||
| @@ -1084,27 +1081,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase): | ||||
|             editor.add_constraint(RangesModel, constraint) | ||||
|         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) | ||||
|  | ||||
|     def test_spgist_include_not_supported(self): | ||||
|         constraint_name = "ints_adjacent_spgist_include_not_supported" | ||||
|         constraint = ExclusionConstraint( | ||||
|             name=constraint_name, | ||||
|             expressions=[("ints", RangeOperators.ADJACENT_TO)], | ||||
|             index_type="spgist", | ||||
|             include=["id"], | ||||
|         ) | ||||
|         msg = ( | ||||
|             "Covering exclusion constraints using an SP-GiST index require " | ||||
|             "PostgreSQL 14+." | ||||
|         ) | ||||
|         with connection.schema_editor() as editor: | ||||
|             with mock.patch( | ||||
|                 "django.db.backends.postgresql.features.DatabaseFeatures." | ||||
|                 "supports_covering_spgist_indexes", | ||||
|                 False, | ||||
|             ): | ||||
|                 with self.assertRaisesMessage(NotSupportedError, msg): | ||||
|                     editor.add_constraint(RangesModel, constraint) | ||||
|  | ||||
|     def test_range_adjacent_opclass(self): | ||||
|         constraint_name = "ints_adjacent_opclass" | ||||
|         self.assertNotIn( | ||||
| @@ -1187,7 +1163,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase): | ||||
|             editor.add_constraint(RangesModel, constraint) | ||||
|         self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) | ||||
|  | ||||
|     @skipUnlessDBFeature("supports_covering_spgist_indexes") | ||||
|     def test_range_adjacent_spgist_opclass_include(self): | ||||
|         constraint_name = "ints_adjacent_spgist_opclass_include" | ||||
|         self.assertNotIn( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| from unittest import mock | ||||
|  | ||||
| from django.contrib.postgres.indexes import ( | ||||
|     BloomIndex, | ||||
|     BrinIndex, | ||||
| @@ -11,10 +9,9 @@ from django.contrib.postgres.indexes import ( | ||||
|     PostgresIndex, | ||||
|     SpGistIndex, | ||||
| ) | ||||
| from django.db import NotSupportedError, connection | ||||
| from django.db import connection | ||||
| from django.db.models import CharField, F, Index, Q | ||||
| from django.db.models.functions import Cast, Collate, Length, Lower | ||||
| from django.test import skipUnlessDBFeature | ||||
| from django.test.utils import register_lookup | ||||
|  | ||||
| from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase | ||||
| @@ -640,7 +637,6 @@ class SchemaTests(PostgreSQLTestCase): | ||||
|             index_name, self.get_constraints(TextFieldModel._meta.db_table) | ||||
|         ) | ||||
|  | ||||
|     @skipUnlessDBFeature("supports_covering_spgist_indexes") | ||||
|     def test_spgist_include(self): | ||||
|         index_name = "scene_spgist_include_setting" | ||||
|         index = SpGistIndex(name=index_name, fields=["scene"], include=["setting"]) | ||||
| @@ -654,20 +650,6 @@ class SchemaTests(PostgreSQLTestCase): | ||||
|             editor.remove_index(Scene, index) | ||||
|         self.assertNotIn(index_name, self.get_constraints(Scene._meta.db_table)) | ||||
|  | ||||
|     def test_spgist_include_not_supported(self): | ||||
|         index_name = "spgist_include_exception" | ||||
|         index = SpGistIndex(fields=["scene"], name=index_name, include=["setting"]) | ||||
|         msg = "Covering SP-GiST indexes require PostgreSQL 14+." | ||||
|         with self.assertRaisesMessage(NotSupportedError, msg): | ||||
|             with mock.patch( | ||||
|                 "django.db.backends.postgresql.features.DatabaseFeatures." | ||||
|                 "supports_covering_spgist_indexes", | ||||
|                 False, | ||||
|             ): | ||||
|                 with connection.schema_editor() as editor: | ||||
|                     editor.add_index(Scene, index) | ||||
|         self.assertNotIn(index_name, self.get_constraints(Scene._meta.db_table)) | ||||
|  | ||||
|     def test_custom_suffix(self): | ||||
|         class CustomSuffixIndex(PostgresIndex): | ||||
|             suffix = "sfx" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user