diff --git a/django/contrib/gis/db/backends/postgis/schema.py b/django/contrib/gis/db/backends/postgis/schema.py index 4ee46cb3ff..d88901b50d 100644 --- a/django/contrib/gis/db/backends/postgis/schema.py +++ b/django/contrib/gis/db/backends/postgis/schema.py @@ -6,6 +6,9 @@ class PostGISSchemaEditor(DatabaseSchemaEditor): geom_index_ops_nd = 'GIST_GEOMETRY_OPS_ND' rast_index_wrapper = 'ST_ConvexHull(%s)' + sql_alter_column_to_3d = "ALTER COLUMN %(column)s TYPE %(type)s USING ST_Force3D(%(column)s)::%(type)s" + sql_alter_column_to_2d = "ALTER COLUMN %(column)s TYPE %(type)s USING ST_Force2D(%(column)s)::%(type)s" + def geo_quote_name(self, name): return self.connection.ops.geo_quote_name(name) @@ -36,3 +39,29 @@ class PostGISSchemaEditor(DatabaseSchemaEditor): "columns": field_column, "extra": '', } + + def _alter_column_type_sql(self, table, old_field, new_field, new_type): + """ + Special case when dimension changed. + """ + if not hasattr(old_field, 'dim') or not hasattr(new_field, 'dim'): + return super(PostGISSchemaEditor, self)._alter_column_type_sql( + table, old_field, new_field, new_type + ) + + if old_field.dim == 2 and new_field.dim == 3: + sql_alter = self.sql_alter_column_to_3d + elif old_field.dim == 3 and new_field.dim == 2: + sql_alter = self.sql_alter_column_to_2d + else: + sql_alter = self.sql_alter_column_type + return ( + ( + sql_alter % { + "column": self.quote_name(new_field.column), + "type": new_type, + }, + [], + ), + [], + ) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 75407ceed8..73240c76ac 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -141,6 +141,8 @@ Minor features ``https://cdnjs.cloudflare.com`` which is more suitable for production use than the the old ``http://openlayers.org`` source. +* PostGIS migrations can now change field dimensions. + :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/gis_migrations/test_operations.py b/tests/gis_tests/gis_migrations/test_operations.py index 99dc1295d9..26ff16feed 100644 --- a/tests/gis_tests/gis_migrations/test_operations.py +++ b/tests/gis_tests/gis_migrations/test_operations.py @@ -1,6 +1,9 @@ from __future__ import unicode_literals +from unittest import skipIf + from django.contrib.gis.db.models import fields +from django.contrib.gis.geos import MultiPolygon, Polygon from django.core.exceptions import ImproperlyConfigured from django.db import connection, migrations, models from django.db.migrations.migration import Migration @@ -9,7 +12,7 @@ from django.test import ( TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature, ) -from ..utils import mysql +from ..utils import mysql, spatialite if connection.features.gis_enabled: try: @@ -59,7 +62,7 @@ class OperationTestCase(TransactionTestCase): ('geom', fields.MultiPolygonField(srid=4326)) ] if connection.features.supports_raster or force_raster_creation: - test_fields += [('rast', fields.RasterField(srid=4326))] + test_fields += [('rast', fields.RasterField(srid=4326, null=True))] operations = [migrations.CreateModel('Neighborhood', test_fields)] self.current_state = self.apply_operations('gis', ProjectState(), operations) @@ -187,6 +190,25 @@ class OperationTests(OperationTestCase): if connection.features.supports_raster: self.assertSpatialIndexExists('gis_neighborhood', 'rast', raster=True) + @skipUnlessDBFeature("supports_3d_storage") + @skipIf(spatialite, "Django currently doesn't support altering Spatialite geometry fields") + def test_alter_geom_field_dim(self): + Neighborhood = self.current_state.apps.get_model('gis', 'Neighborhood') + p1 = Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0))) + Neighborhood.objects.create(name='TestDim', geom=MultiPolygon(p1, p1)) + # Add 3rd dimension. + self.alter_gis_model( + migrations.AlterField, 'Neighborhood', 'geom', False, + fields.MultiPolygonField, field_class_kwargs={'srid': 4326, 'dim': 3} + ) + self.assertTrue(Neighborhood.objects.first().geom.hasz) + # Rewind to 2 dimensions. + self.alter_gis_model( + migrations.AlterField, 'Neighborhood', 'geom', False, + fields.MultiPolygonField, field_class_kwargs={'srid': 4326, 'dim': 2} + ) + self.assertFalse(Neighborhood.objects.first().geom.hasz) + @skipIfDBFeature('supports_raster') class NoRasterSupportTests(OperationTestCase):