diff options
| author | Daniel Wiesmann <daniel.wiesmann@gmail.com> | 2015-06-19 16:46:03 +0100 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2015-06-19 14:36:43 -0400 |
| commit | b769bbd4f6a3cd1bcd9ebf3559ec6ea0f9b50565 (patch) | |
| tree | 48cb987ced74d60f75fd86306edc2f87c764362f /tests | |
| parent | d3d66d47222dd8765a20a15fdc754c0ed7635404 (diff) | |
Fixed #23804 -- Added RasterField for PostGIS.
Thanks to Tim Graham and Claude Paroz for the reviews and patches.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/gis_tests/data/rasters/textrasters.py | 6 | ||||
| -rw-r--r-- | tests/gis_tests/gis_migrations/migrations/0001_initial.py | 95 | ||||
| -rw-r--r-- | tests/gis_tests/gis_migrations/test_commands.py | 4 | ||||
| -rw-r--r-- | tests/gis_tests/gis_migrations/test_operations.py | 176 | ||||
| -rw-r--r-- | tests/gis_tests/models.py | 17 | ||||
| -rw-r--r-- | tests/gis_tests/rasterapp/__init__.py | 0 | ||||
| -rw-r--r-- | tests/gis_tests/rasterapp/models.py | 11 | ||||
| -rw-r--r-- | tests/gis_tests/rasterapp/test_rasterfield.py | 72 |
8 files changed, 272 insertions, 109 deletions
diff --git a/tests/gis_tests/data/rasters/textrasters.py b/tests/gis_tests/data/rasters/textrasters.py index b12ce62afc..01b211b3ce 100644 --- a/tests/gis_tests/data/rasters/textrasters.py +++ b/tests/gis_tests/data/rasters/textrasters.py @@ -1,7 +1,11 @@ +""" +Text-based test rasters +""" + JSON_RASTER = """{ "srid": 4326, "origin": [0, 0], - "scale": [1, 1], + "scale": [-1, 1], "skew": [0, 0], "width": 5, "height": 5, diff --git a/tests/gis_tests/gis_migrations/migrations/0001_initial.py b/tests/gis_tests/gis_migrations/migrations/0001_initial.py index bf2966ee79..6615fdf2c4 100644 --- a/tests/gis_tests/gis_migrations/migrations/0001_initial.py +++ b/tests/gis_tests/gis_migrations/migrations/0001_initial.py @@ -1,53 +1,70 @@ -from django.db import migrations, models +from django.db import connection, migrations, models from ...models import models as gis_models +ops = [ + migrations.CreateModel( + name='Neighborhood', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=100, unique=True)), + ('geom', gis_models.MultiPolygonField(srid=4326)), + ], + options={ + 'required_db_features': ['gis_enabled'], + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Household', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('neighborhood', models.ForeignKey(to='gis_migrations.Neighborhood', to_field='id', null=True)), + ('address', models.CharField(max_length=100)), + ('zip_code', models.IntegerField(null=True, blank=True)), + ('geom', gis_models.PointField(srid=4326, geography=True)), + ], + options={ + 'required_db_features': ['gis_enabled'], + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Family', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=100, unique=True)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='household', + name='family', + field=models.ForeignKey(blank=True, to='gis_migrations.Family', null=True), + preserve_default=True, + ) +] -class Migration(migrations.Migration): - """ - Used for gis.specific migration tests. - """ - operations = [ - migrations.CreateModel( - name='Neighborhood', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=100, unique=True)), - ('geom', gis_models.MultiPolygonField(srid=4326)), - ], - options={ - 'required_db_features': ['gis_enabled'], - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Household', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('neighborhood', models.ForeignKey(to='gis_migrations.Neighborhood', to_field='id', null=True)), - ('address', models.CharField(max_length=100)), - ('zip_code', models.IntegerField(null=True, blank=True)), - ('geom', gis_models.PointField(srid=4326, geography=True)), - ], - options={ - 'required_db_features': ['gis_enabled'], - }, - bases=(models.Model,), - ), +if connection.features.gis_enabled and connection.features.supports_raster: + ops += [ migrations.CreateModel( - name='Family', + name='Heatmap', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('name', models.CharField(max_length=100, unique=True)), + ('rast', gis_models.fields.RasterField(srid=4326)), ], options={ }, bases=(models.Model,), ), - migrations.AddField( - model_name='household', - name='family', - field=models.ForeignKey(blank=True, to='gis_migrations.Family', null=True), - preserve_default=True, - ), ] + + +class Migration(migrations.Migration): + """ + Used for gis-specific migration tests. + """ + operations = ops diff --git a/tests/gis_tests/gis_migrations/test_commands.py b/tests/gis_tests/gis_migrations/test_commands.py index b4fe597bca..77bbdf3349 100644 --- a/tests/gis_tests/gis_migrations/test_commands.py +++ b/tests/gis_tests/gis_migrations/test_commands.py @@ -39,12 +39,16 @@ class MigrateTests(TransactionTestCase): self.assertTableExists("gis_migrations_neighborhood") self.assertTableExists("gis_migrations_household") self.assertTableExists("gis_migrations_family") + if connection.features.supports_raster: + self.assertTableExists("gis_migrations_heatmap") # Unmigrate everything call_command("migrate", "gis_migrations", "zero", verbosity=0) # Make sure it's all gone self.assertTableNotExists("gis_migrations_neighborhood") self.assertTableNotExists("gis_migrations_household") self.assertTableNotExists("gis_migrations_family") + if connection.features.supports_raster: + self.assertTableNotExists("gis_migrations_heatmap") # Even geometry columns metadata try: GeoColumn = connection.ops.geometry_columns() diff --git a/tests/gis_tests/gis_migrations/test_operations.py b/tests/gis_tests/gis_migrations/test_operations.py index 7af8da74e5..680742f41d 100644 --- a/tests/gis_tests/gis_migrations/test_operations.py +++ b/tests/gis_tests/gis_migrations/test_operations.py @@ -1,14 +1,17 @@ from __future__ import unicode_literals +from django.contrib.gis.db.models import fields +from django.core.exceptions import ImproperlyConfigured from django.db import connection, migrations, models from django.db.migrations.migration import Migration from django.db.migrations.state import ProjectState -from django.test import TransactionTestCase, skipUnlessDBFeature +from django.test import ( + TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature, +) from ..utils import mysql if connection.features.gis_enabled: - from django.contrib.gis.db.models import fields try: GeometryColumns = connection.ops.geometry_columns() HAS_GEOMETRY_COLUMNS = True @@ -16,13 +19,14 @@ if connection.features.gis_enabled: HAS_GEOMETRY_COLUMNS = False -@skipUnlessDBFeature("gis_enabled") +@skipUnlessDBFeature('gis_enabled') class OperationTests(TransactionTestCase): - available_apps = ["gis_tests.gis_migrations"] + available_apps = ['gis_tests.gis_migrations'] def tearDown(self): # Delete table after testing - self.apply_operations('gis', self.current_state, [migrations.DeleteModel("Neighborhood")]) + if hasattr(self, 'current_state'): + self.apply_operations('gis', self.current_state, [migrations.DeleteModel('Neighborhood')]) super(OperationTests, self).tearDown() def get_table_description(self, table): @@ -41,19 +45,19 @@ class OperationTests(TransactionTestCase): with connection.schema_editor() as editor: return migration.apply(project_state, editor) - def set_up_test_model(self): - operations = [migrations.CreateModel( - "Neighborhood", - [ - ("id", models.AutoField(primary_key=True)), - ('name', models.CharField(max_length=100, unique=True)), - ('geom', fields.MultiPolygonField(srid=4326)), - ], - )] + def set_up_test_model(self, force_raster_creation=False): + test_fields = [ + ('id', models.AutoField(primary_key=True)), + ('name', models.CharField(max_length=100, unique=True)), + ('geom', fields.MultiPolygonField(srid=4326)) + ] + if connection.features.supports_raster or force_raster_creation: + test_fields += [('rast', fields.RasterField(srid=4326))] + operations = [migrations.CreateModel('Neighborhood', test_fields)] return self.apply_operations('gis', ProjectState(), operations) def assertGeometryColumnsCount(self, expected_count): - table_name = "gis_neighborhood" + table_name = 'gis_neighborhood' if connection.features.uppercases_column_names: table_name = table_name.upper() self.assertEqual( @@ -63,91 +67,137 @@ class OperationTests(TransactionTestCase): expected_count ) - def test_add_gis_field(self): - """ - Tests the AddField operation with a GIS-enabled column. - """ + def assertSpatialIndexExists(self, table, column): + with connection.cursor() as cursor: + indexes = connection.introspection.get_indexes(cursor, table) + self.assertIn(column, indexes) + + def alter_gis_model(self, migration_class, model_name, field_name, + blank=False, field_class=None): project_state = self.set_up_test_model() self.current_state = project_state - operation = migrations.AddField( - "Neighborhood", - "path", - fields.LineStringField(srid=4326), - ) + args = [model_name, field_name] + if field_class: + args.append(field_class(srid=4326, blank=blank)) + operation = migration_class(*args) new_state = project_state.clone() - operation.state_forwards("gis", new_state) + operation.state_forwards('gis', new_state) with connection.schema_editor() as editor: - operation.database_forwards("gis", editor, project_state, new_state) + operation.database_forwards('gis', editor, project_state, new_state) self.current_state = new_state - self.assertColumnExists("gis_neighborhood", "path") + + def test_add_geom_field(self): + """ + Test the AddField operation with a geometry-enabled column. + """ + self.alter_gis_model(migrations.AddField, 'Neighborhood', + 'path', False, fields.LineStringField) + self.assertColumnExists('gis_neighborhood', 'path') # Test GeometryColumns when available if HAS_GEOMETRY_COLUMNS: self.assertGeometryColumnsCount(2) + # Test spatial indices when available if self.has_spatial_indexes: - with connection.cursor() as cursor: - indexes = connection.introspection.get_indexes(cursor, "gis_neighborhood") - self.assertIn('path', indexes) + self.assertSpatialIndexExists('gis_neighborhood', 'path') - def test_add_blank_gis_field(self): + @skipUnlessDBFeature('supports_raster') + def test_add_raster_field(self): + """ + Test the AddField operation with a raster-enabled column. + """ + self.alter_gis_model(migrations.AddField, 'Neighborhood', + 'heatmap', False, fields.RasterField) + self.assertColumnExists('gis_neighborhood', 'heatmap') + + # Test spatial indices when available + if self.has_spatial_indexes: + self.assertSpatialIndexExists('gis_neighborhood', 'heatmap') + + @skipIfDBFeature('supports_raster') + def test_create_raster_model_on_db_without_raster_support(self): + """ + Test creating a model with a raster field on a db without raster support. + """ + msg = 'Raster fields require backends with raster support.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + self.set_up_test_model(True) + + @skipIfDBFeature('supports_raster') + def test_add_raster_field_on_db_without_raster_support(self): + """ + Test adding a raster field on a db without raster support. + """ + msg = 'Raster fields require backends with raster support.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + self.alter_gis_model( + migrations.AddField, 'Neighborhood', 'heatmap', + False, fields.RasterField + ) + + def test_add_blank_geom_field(self): """ Should be able to add a GeometryField with blank=True. """ - project_state = self.set_up_test_model() - self.current_state = project_state - operation = migrations.AddField( - "Neighborhood", - "path", - fields.LineStringField(blank=True, srid=4326), - ) - new_state = project_state.clone() - operation.state_forwards("gis", new_state) - with connection.schema_editor() as editor: - operation.database_forwards("gis", editor, project_state, new_state) - self.current_state = new_state - self.assertColumnExists("gis_neighborhood", "path") + self.alter_gis_model(migrations.AddField, 'Neighborhood', + 'path', True, fields.LineStringField) + self.assertColumnExists('gis_neighborhood', 'path') # Test GeometryColumns when available if HAS_GEOMETRY_COLUMNS: self.assertGeometryColumnsCount(2) + # Test spatial indices when available if self.has_spatial_indexes: - with connection.cursor() as cursor: - indexes = connection.introspection.get_indexes(cursor, "gis_neighborhood") - self.assertIn('path', indexes) + self.assertSpatialIndexExists('gis_neighborhood', 'path') - def test_remove_gis_field(self): + @skipUnlessDBFeature('supports_raster') + def test_add_blank_raster_field(self): """ - Tests the RemoveField operation with a GIS-enabled column. + Should be able to add a RasterField with blank=True. """ - project_state = self.set_up_test_model() - self.current_state = project_state - operation = migrations.RemoveField("Neighborhood", "geom") - new_state = project_state.clone() - operation.state_forwards("gis", new_state) - with connection.schema_editor() as editor: - operation.database_forwards("gis", editor, project_state, new_state) - self.current_state = new_state - self.assertColumnNotExists("gis_neighborhood", "geom") + self.alter_gis_model(migrations.AddField, 'Neighborhood', + 'heatmap', True, fields.RasterField) + self.assertColumnExists('gis_neighborhood', 'heatmap') + + # Test spatial indices when available + if self.has_spatial_indexes: + self.assertSpatialIndexExists('gis_neighborhood', 'heatmap') + + def test_remove_geom_field(self): + """ + Test the RemoveField operation with a geometry-enabled column. + """ + self.alter_gis_model(migrations.RemoveField, 'Neighborhood', 'geom') + self.assertColumnNotExists('gis_neighborhood', 'geom') # Test GeometryColumns when available if HAS_GEOMETRY_COLUMNS: self.assertGeometryColumnsCount(0) + @skipUnlessDBFeature('supports_raster') + def test_remove_raster_field(self): + """ + Test the RemoveField operation with a raster-enabled column. + """ + self.alter_gis_model(migrations.RemoveField, 'Neighborhood', 'rast') + self.assertColumnNotExists('gis_neighborhood', 'rast') + def test_create_model_spatial_index(self): self.current_state = self.set_up_test_model() if not self.has_spatial_indexes: - self.skipTest("No support for Spatial indexes") + self.skipTest('No support for Spatial indexes') - with connection.cursor() as cursor: - indexes = connection.introspection.get_indexes(cursor, "gis_neighborhood") - self.assertIn('geom', indexes) + self.assertSpatialIndexExists('gis_neighborhood', 'geom') + + if connection.features.supports_raster: + self.assertSpatialIndexExists('gis_neighborhood', 'rast') @property def has_spatial_indexes(self): if mysql: with connection.cursor() as cursor: - return connection.introspection.supports_spatial_index(cursor, "gis_neighborhood") + return connection.introspection.supports_spatial_index(cursor, 'gis_neighborhood') return True diff --git a/tests/gis_tests/models.py b/tests/gis_tests/models.py index cfb7f96656..f5c694e688 100644 --- a/tests/gis_tests/models.py +++ b/tests/gis_tests/models.py @@ -1,14 +1,18 @@ from django.core.exceptions import ImproperlyConfigured +from django.db import models + + +class DummyField(models.Field): + def __init__(self, dim=None, srid=None, geography=None, spatial_index=True, *args, **kwargs): + super(DummyField, self).__init__(*args, **kwargs) try: from django.contrib.gis.db import models + try: + models.RasterField() + except ImproperlyConfigured: + models.RasterField = DummyField except ImproperlyConfigured: - from django.db import models - - class DummyField(models.Field): - def __init__(self, dim=None, srid=None, geography=None, *args, **kwargs): - super(DummyField, self).__init__(*args, **kwargs) - models.GeoManager = models.Manager models.GeometryField = DummyField models.LineStringField = DummyField @@ -16,3 +20,4 @@ except ImproperlyConfigured: models.MultiPolygonField = DummyField models.PointField = DummyField models.PolygonField = DummyField + models.RasterField = DummyField diff --git a/tests/gis_tests/rasterapp/__init__.py b/tests/gis_tests/rasterapp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/gis_tests/rasterapp/__init__.py diff --git a/tests/gis_tests/rasterapp/models.py b/tests/gis_tests/rasterapp/models.py new file mode 100644 index 0000000000..d7cc4d15e2 --- /dev/null +++ b/tests/gis_tests/rasterapp/models.py @@ -0,0 +1,11 @@ +from ..models import models + + +class RasterModel(models.Model): + rast = models.RasterField(null=True, srid=4326, spatial_index=True, blank=True) + + class Meta: + required_db_features = ['supports_raster'] + + def __str__(self): + return str(self.id) diff --git a/tests/gis_tests/rasterapp/test_rasterfield.py b/tests/gis_tests/rasterapp/test_rasterfield.py new file mode 100644 index 0000000000..7b4413ae0d --- /dev/null +++ b/tests/gis_tests/rasterapp/test_rasterfield.py @@ -0,0 +1,72 @@ +import json + +from django.contrib.gis.shortcuts import numpy +from django.test import TransactionTestCase, skipUnlessDBFeature + +from ..data.rasters.textrasters import JSON_RASTER +from .models import RasterModel + + +@skipUnlessDBFeature('supports_raster') +class RasterFieldTest(TransactionTestCase): + available_apps = ['gis_tests.rasterapp'] + + def test_field_null_value(self): + """ + Test creating a model where the RasterField has a null value. + """ + r = RasterModel.objects.create(rast=None) + r.refresh_from_db() + self.assertIsNone(r.rast) + + def test_model_creation(self): + """ + Test RasterField through a test model. + """ + # Create model instance from JSON raster + r = RasterModel.objects.create(rast=JSON_RASTER) + r.refresh_from_db() + # Test raster metadata properties + self.assertEqual((5, 5), (r.rast.width, r.rast.height)) + self.assertEqual([0.0, -1.0, 0.0, 0.0, 0.0, 1.0], r.rast.geotransform) + self.assertIsNone(r.rast.bands[0].nodata_value) + # Compare srs + self.assertEqual(r.rast.srs.srid, 4326) + # Compare pixel values + band = r.rast.bands[0].data() + # If numpy, convert result to list + if numpy: + band = band.flatten().tolist() + # Loop through rows in band data and assert single + # value is as expected. + self.assertEqual( + [ + 0.0, 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, 9.0, + 10.0, 11.0, 12.0, 13.0, 14.0, + 15.0, 16.0, 17.0, 18.0, 19.0, + 20.0, 21.0, 22.0, 23.0, 24.0 + ], + band + ) + + def test_implicit_raster_transformation(self): + """ + Test automatic transformation of rasters with srid different from the + field srid. + """ + # Parse json raster + rast = json.loads(JSON_RASTER) + # Update srid to another value + rast['srid'] = 3086 + # Save model and get it from db + r = RasterModel.objects.create(rast=rast) + r.refresh_from_db() + # Confirm raster has been transformed to the default srid + self.assertEqual(r.rast.srs.srid, 4326) + # Confirm geotransform is in lat/lon + self.assertEqual( + r.rast.geotransform, + [-87.9298551266551, 9.459646421449934e-06, 0.0, + 23.94249275457565, 0.0, -9.459646421449934e-06] + ) |
