[1.7.x] Fixed #23030 -- Properly handled geometry columns metadata during migrations
Thanks kunitoki for the report and initial patches.
Backport of 8c30df15f1
from master.
This commit is contained in:
parent
86655111c9
commit
ddb5674945
|
@ -10,7 +10,8 @@ from django.utils.encoding import python_2_unicode_compatible
|
||||||
class PostGISGeometryColumns(models.Model):
|
class PostGISGeometryColumns(models.Model):
|
||||||
"""
|
"""
|
||||||
The 'geometry_columns' table from the PostGIS. See the PostGIS
|
The 'geometry_columns' table from the PostGIS. See the PostGIS
|
||||||
documentation at Ch. 4.2.2.
|
documentation at Ch. 4.3.2.
|
||||||
|
On PostGIS 2, this is a view.
|
||||||
"""
|
"""
|
||||||
f_table_catalog = models.CharField(max_length=256)
|
f_table_catalog = models.CharField(max_length=256)
|
||||||
f_table_schema = models.CharField(max_length=256)
|
f_table_schema = models.CharField(max_length=256)
|
||||||
|
|
|
@ -62,13 +62,13 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor):
|
||||||
self.execute(sql)
|
self.execute(sql)
|
||||||
self.geometry_sql = []
|
self.geometry_sql = []
|
||||||
|
|
||||||
def delete_model(self, model):
|
def delete_model(self, model, **kwargs):
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
from django.contrib.gis.db.models.fields import GeometryField
|
||||||
# Drop spatial metadata (dropping the table does not automatically remove them)
|
# Drop spatial metadata (dropping the table does not automatically remove them)
|
||||||
for field in model._meta.local_fields:
|
for field in model._meta.local_fields:
|
||||||
if isinstance(field, GeometryField):
|
if isinstance(field, GeometryField):
|
||||||
self.remove_geometry_metadata(model, field)
|
self.remove_geometry_metadata(model, field)
|
||||||
super(SpatialiteSchemaEditor, self).delete_model(model)
|
super(SpatialiteSchemaEditor, self).delete_model(model, **kwargs)
|
||||||
|
|
||||||
def add_field(self, model, field):
|
def add_field(self, model, field):
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
from django.contrib.gis.db.models.fields import GeometryField
|
||||||
|
@ -81,12 +81,6 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor):
|
||||||
else:
|
else:
|
||||||
super(SpatialiteSchemaEditor, self).add_field(model, field)
|
super(SpatialiteSchemaEditor, self).add_field(model, field)
|
||||||
|
|
||||||
def remove_field(self, model, field):
|
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
|
||||||
if isinstance(field, GeometryField):
|
|
||||||
self.remove_geometry_metadata(model, field)
|
|
||||||
super(SpatialiteSchemaEditor, self).remove_field(model, field)
|
|
||||||
|
|
||||||
def alter_db_table(self, model, old_db_table, new_db_table):
|
def alter_db_table(self, model, old_db_table, new_db_table):
|
||||||
super(SpatialiteSchemaEditor, self).alter_db_table(model, old_db_table, new_db_table)
|
super(SpatialiteSchemaEditor, self).alter_db_table(model, old_db_table, new_db_table)
|
||||||
self.execute(
|
self.execute(
|
||||||
|
@ -95,3 +89,10 @@ class SpatialiteSchemaEditor(DatabaseSchemaEditor):
|
||||||
"new_table": self.quote_name(new_db_table),
|
"new_table": self.quote_name(new_db_table),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
# Also rename spatial index tables
|
||||||
|
for field in model._meta.local_fields:
|
||||||
|
if getattr(field, 'spatial_index', False):
|
||||||
|
self.execute(self.sql_rename_table % {
|
||||||
|
"old_table": self.quote_name("idx_%s_%s" % (old_db_table, field.column)),
|
||||||
|
"new_table": self.quote_name("idx_%s_%s" % (new_db_table, field.column)),
|
||||||
|
})
|
||||||
|
|
|
@ -2,9 +2,10 @@ from django.db import models, migrations
|
||||||
import django.contrib.gis.db.models.fields
|
import django.contrib.gis.db.models.fields
|
||||||
|
|
||||||
|
|
||||||
# Used for regression test of ticket #22001: https://code.djangoproject.com/ticket/22001
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
"""
|
||||||
|
Used for gis.specific migration tests.
|
||||||
|
"""
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Neighborhood',
|
name='Neighborhood',
|
||||||
|
@ -29,5 +30,21 @@ class Migration(migrations.Migration):
|
||||||
options={
|
options={
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
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.Family', null=True),
|
||||||
|
preserve_default=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -34,17 +34,37 @@ class MigrateTests(TransactionTestCase):
|
||||||
Tests basic usage of the migrate command when a model uses Geodjango
|
Tests basic usage of the migrate command when a model uses Geodjango
|
||||||
fields. Regression test for ticket #22001:
|
fields. Regression test for ticket #22001:
|
||||||
https://code.djangoproject.com/ticket/22001
|
https://code.djangoproject.com/ticket/22001
|
||||||
|
|
||||||
|
It's also used to showcase an error in migrations where spatialite is
|
||||||
|
enabled and geo tables are renamed resulting in unique constraint
|
||||||
|
failure on geometry_columns. Regression for ticket #23030:
|
||||||
|
https://code.djangoproject.com/ticket/23030
|
||||||
"""
|
"""
|
||||||
# Make sure no tables are created
|
# Make sure no tables are created
|
||||||
self.assertTableNotExists("migrations_neighborhood")
|
self.assertTableNotExists("migrations_neighborhood")
|
||||||
self.assertTableNotExists("migrations_household")
|
self.assertTableNotExists("migrations_household")
|
||||||
|
self.assertTableNotExists("migrations_family")
|
||||||
# Run the migrations to 0001 only
|
# Run the migrations to 0001 only
|
||||||
call_command("migrate", "gis", "0001", verbosity=0)
|
call_command("migrate", "gis", "0001", verbosity=0)
|
||||||
# Make sure the right tables exist
|
# Make sure the right tables exist
|
||||||
self.assertTableExists("gis_neighborhood")
|
self.assertTableExists("gis_neighborhood")
|
||||||
self.assertTableExists("gis_household")
|
self.assertTableExists("gis_household")
|
||||||
|
self.assertTableExists("gis_family")
|
||||||
# Unmigrate everything
|
# Unmigrate everything
|
||||||
call_command("migrate", "gis", "zero", verbosity=0)
|
call_command("migrate", "gis", "zero", verbosity=0)
|
||||||
# Make sure it's all gone
|
# Make sure it's all gone
|
||||||
self.assertTableNotExists("gis_neighborhood")
|
self.assertTableNotExists("gis_neighborhood")
|
||||||
self.assertTableNotExists("gis_household")
|
self.assertTableNotExists("gis_household")
|
||||||
|
self.assertTableNotExists("gis_family")
|
||||||
|
# Even geometry columns metadata
|
||||||
|
try:
|
||||||
|
GeoColumn = connection.ops.geometry_columns()
|
||||||
|
except NotImplementedError:
|
||||||
|
# Not all GIS backends have geometry columns model
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.assertEqual(
|
||||||
|
GeoColumn.objects.filter(
|
||||||
|
**{'%s__in' % GeoColumn.table_name_col(): ["gis_neighborhood", "gis_household"]}
|
||||||
|
).count(),
|
||||||
|
0)
|
||||||
|
|
|
@ -127,13 +127,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
', '.join(y for x, y in field_maps),
|
', '.join(y for x, y in field_maps),
|
||||||
self.quote_name(model._meta.db_table),
|
self.quote_name(model._meta.db_table),
|
||||||
))
|
))
|
||||||
# Delete the old table (not using self.delete_model to avoid deleting
|
# Delete the old table
|
||||||
# all implicit M2M tables)
|
self.delete_model(model, handle_autom2m=False)
|
||||||
self.execute(self.sql_delete_table % {
|
|
||||||
"table": self.quote_name(model._meta.db_table),
|
|
||||||
})
|
|
||||||
# Rename the new to the old
|
# Rename the new to the old
|
||||||
self.alter_db_table(model, temp_model._meta.db_table, model._meta.db_table)
|
self.alter_db_table(temp_model, temp_model._meta.db_table, model._meta.db_table)
|
||||||
# Run deferred SQL on correct table
|
# Run deferred SQL on correct table
|
||||||
for sql in self.deferred_sql:
|
for sql in self.deferred_sql:
|
||||||
self.execute(sql.replace(temp_model._meta.db_table, model._meta.db_table))
|
self.execute(sql.replace(temp_model._meta.db_table, model._meta.db_table))
|
||||||
|
@ -142,6 +139,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
if restore_pk_field:
|
if restore_pk_field:
|
||||||
restore_pk_field.primary_key = True
|
restore_pk_field.primary_key = True
|
||||||
|
|
||||||
|
def delete_model(self, model, handle_autom2m=True):
|
||||||
|
if handle_autom2m:
|
||||||
|
super(DatabaseSchemaEditor, self).delete_model(model)
|
||||||
|
else:
|
||||||
|
# Delete the table (and only that)
|
||||||
|
self.execute(self.sql_delete_table % {
|
||||||
|
"table": self.quote_name(model._meta.db_table),
|
||||||
|
})
|
||||||
|
|
||||||
def add_field(self, model, field):
|
def add_field(self, model, field):
|
||||||
"""
|
"""
|
||||||
Creates a field on a model.
|
Creates a field on a model.
|
||||||
|
|
Loading…
Reference in New Issue