diff options
| author | Simon Charette <charette.s@gmail.com> | 2017-12-04 20:35:33 -0500 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-12-22 16:09:49 -0500 |
| commit | b40a1d774d6e7dc0a3663a69c3a8a9b8793d31ff (patch) | |
| tree | c8fcbdb1c5f6eec3b0a8c454ece91c73d578eb51 /tests/schema/tests.py | |
| parent | 17987b5511f666ba0f39a8136cd5315e7634f52c (diff) | |
[2.0.x] Fixed #28884 -- Fixed crash on SQLite when renaming a field in a model referenced by a ManyToManyField.
Introspected database constraints instead of relying on _meta.related_objects
to determine whether or not a table or a column is referenced on rename
operations.
This has the side effect of ignoring both db_constraint=False and virtual
fields such as GenericRelation which aren't backend by database level
constraints and thus shouldn't prevent the rename operations from being
performed in a transaction.
Regression in 095c1aaa898bed40568009db836aa8434f1b983d.
Thanks Tim for the additional tests and edits, and Mariusz for the review.
Backport of 9f7772e098439f9edea3d25ab127539fc514eeb2 from master
Diffstat (limited to 'tests/schema/tests.py')
| -rw-r--r-- | tests/schema/tests.py | 100 |
1 files changed, 99 insertions, 1 deletions
diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 4781a6bb3c..1fa715da51 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -61,6 +61,9 @@ class SchemaTests(TransactionTestCase): # local_models should contain test dependent model classes that will be # automatically removed from the app cache on test tear down. self.local_models = [] + # isolated_local_models contains models that are in test methods + # decorated with @isolate_apps. + self.isolated_local_models = [] def tearDown(self): # Delete any tables made for our models @@ -75,6 +78,10 @@ class SchemaTests(TransactionTestCase): if through and through._meta.auto_created: del new_apps.all_models['schema'][through._meta.model_name] del new_apps.all_models['schema'][model._meta.model_name] + if self.isolated_local_models: + with connection.schema_editor() as editor: + for model in self.isolated_local_models: + editor.delete_model(model) def delete_tables(self): "Deletes all model tables for our models for a clean test environment" @@ -1420,6 +1427,38 @@ class SchemaTests(TransactionTestCase): def test_m2m_repoint_inherited(self): self._test_m2m_repoint(InheritedManyToManyField) + @isolate_apps('schema') + def test_m2m_rename_field_in_target_model(self): + class TagM2MTest(Model): + title = CharField(max_length=255) + + class Meta: + app_label = 'schema' + + class LocalM2M(Model): + tags = ManyToManyField(TagM2MTest) + + class Meta: + app_label = 'schema' + + # Create the tables. + with connection.schema_editor() as editor: + editor.create_model(LocalM2M) + editor.create_model(TagM2MTest) + # Ensure the m2m table is there. + self.assertEqual(len(self.column_classes(LocalM2M)), 1) + # Alter a field in TagM2MTest. + old_field = TagM2MTest._meta.get_field('title') + new_field = CharField(max_length=254) + new_field.contribute_to_class(TagM2MTest, 'title1') + # @isolate_apps() and inner models are needed to have the model + # relations populated, otherwise this doesn't act as a regression test. + self.assertEqual(len(new_field.model._meta.related_objects), 1) + with connection.schema_editor() as editor: + editor.alter_field(TagM2MTest, old_field, new_field, strict=True) + # Ensure the m2m table is still there. + self.assertEqual(len(self.column_classes(LocalM2M)), 1) + @skipUnlessDBFeature('supports_column_check_constraints') def test_check_constraints(self): """ @@ -2373,6 +2412,7 @@ class SchemaTests(TransactionTestCase): new_field.set_attributes_from_name('id') with connection.schema_editor() as editor: editor.alter_field(Node, old_field, new_field, strict=True) + self.assertForeignKeyExists(Node, 'parent_id', Node._meta.db_table) @mock.patch('django.db.backends.base.schema.datetime') @mock.patch('django.db.backends.base.schema.timezone') @@ -2481,7 +2521,8 @@ class SchemaTests(TransactionTestCase): doc.students.add(student) def test_rename_table_renames_deferred_sql_references(self): - with connection.schema_editor() as editor: + atomic_rename = connection.features.supports_atomic_references_rename + with connection.schema_editor(atomic=atomic_rename) as editor: editor.create_model(Author) editor.create_model(Book) editor.alter_db_table(Author, 'schema_author', 'schema_renamed_author') @@ -2508,3 +2549,60 @@ class SchemaTests(TransactionTestCase): for statement in editor.deferred_sql: self.assertIs(statement.references_column('book', 'title'), False) self.assertIs(statement.references_column('book', 'author_id'), False) + + @isolate_apps('schema') + def test_referenced_field_without_constraint_rename_inside_atomic_block(self): + """ + Foreign keys without database level constraint don't prevent the field + they reference from being renamed in an atomic block. + """ + class Foo(Model): + field = CharField(max_length=255, unique=True) + + class Meta: + app_label = 'schema' + + class Bar(Model): + foo = ForeignKey(Foo, CASCADE, to_field='field', db_constraint=False) + + class Meta: + app_label = 'schema' + + self.isolated_local_models = [Foo, Bar] + with connection.schema_editor() as editor: + editor.create_model(Foo) + editor.create_model(Bar) + + new_field = CharField(max_length=255, unique=True) + new_field.set_attributes_from_name('renamed') + with connection.schema_editor(atomic=True) as editor: + editor.alter_field(Foo, Foo._meta.get_field('field'), new_field) + + @isolate_apps('schema') + def test_referenced_table_without_constraint_rename_inside_atomic_block(self): + """ + Foreign keys without database level constraint don't prevent the table + they reference from being renamed in an atomic block. + """ + class Foo(Model): + field = CharField(max_length=255, unique=True) + + class Meta: + app_label = 'schema' + + class Bar(Model): + foo = ForeignKey(Foo, CASCADE, to_field='field', db_constraint=False) + + class Meta: + app_label = 'schema' + + self.isolated_local_models = [Foo, Bar] + with connection.schema_editor() as editor: + editor.create_model(Foo) + editor.create_model(Bar) + + new_field = CharField(max_length=255, unique=True) + new_field.set_attributes_from_name('renamed') + with connection.schema_editor(atomic=True) as editor: + editor.alter_db_table(Foo, Foo._meta.db_table, 'renamed_table') + Foo._meta.db_table = 'renamed_table' |
