diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 2a8958b89e5df..38ba5b9a0c304 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -151,13 +151,6 @@ def function_names(self): 'BoundingCircle': 'ST_MinimumBoundingCircle', 'NumPoints': 'ST_NPoints', } - if self.spatial_version < (2, 2, 0): - function_names.update({ - 'DistanceSphere': 'ST_distance_sphere', - 'DistanceSpheroid': 'ST_distance_spheroid', - 'LengthSpheroid': 'ST_length_spheroid', - 'MemSize': 'ST_mem_size', - }) if self.spatial_version < (2, 4, 0): function_names['ForcePolygonCW'] = 'ST_ForceRHR' return function_names @@ -185,7 +178,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 2.1. ' + 'GeoDjango requires at least PostGIS version 2.2. ' 'Was the database created from a spatial database ' 'template?' % self.connection.settings_dict['NAME'] ) diff --git a/django/contrib/postgres/indexes.py b/django/contrib/postgres/indexes.py index f8014f87a013d..7a5023c758f71 100644 --- a/django/contrib/postgres/indexes.py +++ b/django/contrib/postgres/indexes.py @@ -55,8 +55,6 @@ def deconstruct(self): return path, args, kwargs def check_supported(self, schema_editor): - if not schema_editor.connection.features.has_brin_index_support: - raise NotSupportedError('BRIN indexes require PostgreSQL 9.5+.') if self.autosummarize and not schema_editor.connection.features.has_brin_autosummarize: raise NotSupportedError('BRIN option autosummarize requires PostgreSQL 10+.') @@ -105,10 +103,6 @@ def deconstruct(self): kwargs['gin_pending_list_limit'] = self.gin_pending_list_limit return path, args, kwargs - def check_supported(self, schema_editor): - if self.gin_pending_list_limit and not schema_editor.connection.features.has_gin_pending_list_limit: - raise NotSupportedError('GIN option gin_pending_list_limit requires PostgreSQL 9.5+.') - def get_with_params(self): with_params = [] if self.gin_pending_list_limit is not None: diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index af3cde88b9716..907ba136fb379 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -16,6 +16,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): has_select_for_update = True has_select_for_update_nowait = True has_select_for_update_of = True + has_select_for_update_skip_locked = True can_release_savepoints = True supports_tablespaces = True supports_transactions = True @@ -55,10 +56,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): supported_explain_formats = {'JSON', 'TEXT', 'XML', 'YAML'} validates_explain_options = False # A query will error on invalid options. - @cached_property - def is_postgresql_9_5(self): - return self.connection.pg_version >= 90500 - @cached_property def is_postgresql_9_6(self): return self.connection.pg_version >= 90600 @@ -67,11 +64,6 @@ def is_postgresql_9_6(self): def is_postgresql_10(self): return self.connection.pg_version >= 100000 - has_select_for_update_skip_locked = property(operator.attrgetter('is_postgresql_9_5')) - has_brin_index_support = property(operator.attrgetter('is_postgresql_9_5')) - has_jsonb_agg = property(operator.attrgetter('is_postgresql_9_5')) has_brin_autosummarize = property(operator.attrgetter('is_postgresql_10')) - has_gin_pending_list_limit = property(operator.attrgetter('is_postgresql_9_5')) - supports_ignore_conflicts = property(operator.attrgetter('is_postgresql_9_5')) has_phraseto_tsquery = property(operator.attrgetter('is_postgresql_9_6')) supports_table_partitions = property(operator.attrgetter('is_postgresql_10')) diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index f52a12c0d702f..2ca69a1e763f6 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -12,7 +12,7 @@ Program Description Required `PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 5.2, 5.1, 5.0, 4.x :doc:`GDAL <../gdal>` Geospatial Data Abstraction Library Yes 2.3, 2.2, 2.1, 2.0, 1.11 :doc:`GeoIP <../geoip2>` IP-based geolocation library No 2 -`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 2.5, 2.4, 2.3, 2.2, 2.1 +`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 2.5, 2.4, 2.3, 2.2 `SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 4.3 ======================== ==================================== ================================ =================================== @@ -30,7 +30,6 @@ totally fine with GeoDjango. Your mileage may vary. GDAL 2.1.0 2016-04 GDAL 2.2.0 2017-05 GDAL 2.3.0 2018-05 - PostGIS 2.1.0 2013-08-17 PostGIS 2.2.0 2015-10-17 PostGIS 2.3.0 2016-09-26 PostGIS 2.4.0 2017-09-30 diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index e588cb285aaf4..be4f036d401f4 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/docs/ref/contrib/gis/install/index.txt @@ -58,7 +58,7 @@ supported versions, and any notes for each of the supported database backends: ================== ============================== ================== ========================================= Database Library Requirements Supported Versions Notes ================== ============================== ================== ========================================= -PostgreSQL GEOS, GDAL, PROJ.4, PostGIS 9.4+ Requires PostGIS. +PostgreSQL GEOS, GDAL, PROJ.4, PostGIS 9.5+ Requires PostGIS. MySQL GEOS, GDAL 5.6+ Not OGC-compliant; :ref:`limited functionality `. Oracle GEOS, GDAL 12.1+ XE not supported. SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.8.3+ Requires SpatiaLite 4.3+ diff --git a/docs/ref/contrib/postgres/aggregates.txt b/docs/ref/contrib/postgres/aggregates.txt index e66b7715243cf..14f839000f511 100644 --- a/docs/ref/contrib/postgres/aggregates.txt +++ b/docs/ref/contrib/postgres/aggregates.txt @@ -90,7 +90,7 @@ General-purpose aggregation functions .. class:: JSONBAgg(expressions, filter=None, **extra) - Returns the input values as a ``JSON`` array. Requires PostgreSQL ≥ 9.5. + Returns the input values as a ``JSON`` array. ``StringAgg`` ------------- diff --git a/docs/ref/contrib/postgres/indexes.txt b/docs/ref/contrib/postgres/indexes.txt index ef19384fb8d98..0c7f115fd4d8d 100644 --- a/docs/ref/contrib/postgres/indexes.txt +++ b/docs/ref/contrib/postgres/indexes.txt @@ -61,7 +61,7 @@ available from the ``django.contrib.postgres.indexes`` module. Provide an integer number of bytes to the gin_pending_list_limit_ parameter to tune the maximum size of the GIN pending list which is used when - ``fastupdate`` is enabled. This parameter requires PostgreSQL ≥ 9.5. + ``fastupdate`` is enabled. .. _GIN Fast Update Technique: https://www.postgresql.org/docs/current/static/gin-implementation.html#GIN-FAST-UPDATE .. _gin_pending_list_limit: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-GIN-PENDING-LIST-LIMIT diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 0c584f7110c24..d8f02576179c3 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -92,7 +92,7 @@ below for information on how to set up your database correctly. PostgreSQL notes ================ -Django supports PostgreSQL 9.4 and higher. `psycopg2`_ 2.5.4 or higher is +Django supports PostgreSQL 9.5 and higher. `psycopg2`_ 2.5.4 or higher is required, though the latest release is recommended. .. _psycopg2: http://initd.org/psycopg/ diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 42b3d94722966..d09618985fa47 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -2073,11 +2073,11 @@ The ``batch_size`` parameter controls how many objects are created in a single query. The default is to create all objects in one batch, except for SQLite where the default is such that at most 999 variables per query are used. -On databases that support it (all except PostgreSQL < 9.5 and Oracle), setting -the ``ignore_conflicts`` parameter to ``True`` tells the database to ignore -failure to insert any rows that fail constraints such as duplicate unique -values. Enabling this parameter disables setting the primary key on each model -instance (if the database normally supports it). +On databases that support it (all but Oracle), setting the ``ignore_conflicts`` +parameter to ``True`` tells the database to ignore failure to insert any rows +that fail constraints such as duplicate unique values. Enabling this parameter +disables setting the primary key on each model instance (if the database +normally supports it). .. versionchanged:: 2.2 diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt index 4814f62e8687d..4eca26bbca664 100644 --- a/docs/releases/3.0.txt +++ b/docs/releases/3.0.txt @@ -225,8 +225,16 @@ backends. :mod:`django.contrib.gis` ------------------------- +* Supported for PostGIS 2.1 is removed. + * Support for SpatiaLite 4.1 and 4.2 is removed. +Dropped support for PostgreSQL 9.4 +---------------------------------- + +Upstream support for PostgreSQL 9.4 ends in December 2019. Django 3.0 supports +PostgreSQL 9.5 and higher. + Miscellaneous ------------- diff --git a/tests/gis_tests/tests.py b/tests/gis_tests/tests.py index 20b66cd39be44..e8ecca9cebaf5 100644 --- a/tests/gis_tests/tests.py +++ b/tests/gis_tests/tests.py @@ -81,21 +81,3 @@ def test_no_version_number(self): ops = FakePostGISOperations() with self.assertRaises(ImproperlyConfigured): ops.spatial_version - - def test_version_dependent_funcs(self): - """ - Resolve names of functions renamed and deprecated in PostGIS 2.2.0 - depending on PostGIS version. - Remove when dropping support for PostGIS 2.1. - """ - ops = FakePostGISOperations('2.2.0') - self.assertEqual(ops.spatial_function_name('DistanceSphere'), 'ST_DistanceSphere') - self.assertEqual(ops.spatial_function_name('DistanceSpheroid'), 'ST_DistanceSpheroid') - self.assertEqual(ops.spatial_function_name('LengthSpheroid'), 'ST_LengthSpheroid') - self.assertEqual(ops.spatial_function_name('MemSize'), 'ST_MemSize') - - ops = FakePostGISOperations('2.1.0') - self.assertEqual(ops.spatial_function_name('DistanceSphere'), 'ST_distance_sphere') - self.assertEqual(ops.spatial_function_name('DistanceSpheroid'), 'ST_distance_spheroid') - self.assertEqual(ops.spatial_function_name('LengthSpheroid'), 'ST_length_spheroid') - self.assertEqual(ops.spatial_function_name('MemSize'), 'ST_mem_size') diff --git a/tests/postgres_tests/test_aggregates.py b/tests/postgres_tests/test_aggregates.py index 85d6f45fd1c8a..ec81f5ad7592e 100644 --- a/tests/postgres_tests/test_aggregates.py +++ b/tests/postgres_tests/test_aggregates.py @@ -1,7 +1,6 @@ import json from django.db.models.expressions import F, Value -from django.test.testcases import skipUnlessDBFeature from django.test.utils import Approximate from . import PostgreSQLTestCase @@ -184,12 +183,10 @@ def test_orderable_agg_alternative_fields(self): ) self.assertEqual(values, {'arrayagg': [0, 1, 0, 2]}) - @skipUnlessDBFeature('has_jsonb_agg') def test_json_agg(self): values = AggregateTestModel.objects.aggregate(jsonagg=JSONBAgg('char_field')) self.assertEqual(values, {'jsonagg': ['Foo1', 'Foo2', 'Foo4', 'Foo3']}) - @skipUnlessDBFeature('has_jsonb_agg') def test_json_agg_empty(self): values = AggregateTestModel.objects.none().aggregate(jsonagg=JSONBAgg('integer_field')) self.assertEqual(values, json.loads('{"jsonagg": []}')) diff --git a/tests/postgres_tests/test_indexes.py b/tests/postgres_tests/test_indexes.py index 3f1018c7b9507..5772069d25755 100644 --- a/tests/postgres_tests/test_indexes.py +++ b/tests/postgres_tests/test_indexes.py @@ -205,7 +205,6 @@ def test_partial_gin_index_with_tablespace(self): editor.remove_index(CharFieldModel, index) self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table)) - @skipUnlessDBFeature('has_gin_pending_list_limit') def test_gin_parameters(self): index_name = 'integer_array_gin_params' index = GinIndex(fields=['field'], name=index_name, fastupdate=True, gin_pending_list_limit=64) @@ -218,17 +217,6 @@ def test_gin_parameters(self): editor.remove_index(IntegerArrayModel, index) self.assertNotIn(index_name, self.get_constraints(IntegerArrayModel._meta.db_table)) - @mock.patch('django.db.backends.postgresql.features.DatabaseFeatures.has_gin_pending_list_limit', False) - def test_gin_parameters_exception(self): - index_name = 'gin_options_exception' - index = GinIndex(fields=['field'], name=index_name, gin_pending_list_limit=64) - msg = 'GIN option gin_pending_list_limit requires PostgreSQL 9.5+.' - with self.assertRaisesMessage(NotSupportedError, msg): - with connection.schema_editor() as editor: - editor.add_index(IntegerArrayModel, index) - self.assertNotIn(index_name, self.get_constraints(IntegerArrayModel._meta.db_table)) - - @skipUnlessDBFeature('has_brin_index_support') def test_brin_index(self): index_name = 'char_field_model_field_brin' index = BrinIndex(fields=['field'], name=index_name, pages_per_range=4) @@ -241,7 +229,7 @@ def test_brin_index(self): editor.remove_index(CharFieldModel, index) self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table)) - @skipUnlessDBFeature('has_brin_index_support', 'has_brin_autosummarize') + @skipUnlessDBFeature('has_brin_autosummarize') def test_brin_parameters(self): index_name = 'char_field_brin_params' index = BrinIndex(fields=['field'], name=index_name, autosummarize=True) @@ -254,16 +242,6 @@ def test_brin_parameters(self): editor.remove_index(CharFieldModel, index) self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table)) - def test_brin_index_not_supported(self): - index_name = 'brin_index_exception' - index = BrinIndex(fields=['field'], name=index_name) - with self.assertRaisesMessage(NotSupportedError, 'BRIN indexes require PostgreSQL 9.5+.'): - with mock.patch('django.db.backends.postgresql.features.DatabaseFeatures.has_brin_index_support', False): - with connection.schema_editor() as editor: - editor.add_index(CharFieldModel, index) - self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table)) - - @skipUnlessDBFeature('has_brin_index_support') def test_brin_autosummarize_not_supported(self): index_name = 'brin_options_exception' index = BrinIndex(fields=['field'], name=index_name, autosummarize=True)