From 9e1b6b8a66af4c2197e5b1b41eb9dbb36e4f6502 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 13 Jan 2020 16:17:52 +0000 Subject: [PATCH] Fixed #23916 -- Allowed makemigrations to handle related model name case changes. Made autodetector ignore related model name case changes so unnecessary migrations are not created. --- django/db/migrations/autodetector.py | 7 +++-- django/db/models/fields/related.py | 8 ++---- tests/field_deconstruction/tests.py | 38 +++++++++++++-------------- tests/migrations/test_autodetector.py | 18 ++++++++++++- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index b2db84ffcb..73df3a38c1 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -496,10 +496,13 @@ class MigrationAutodetector: dependencies=dependencies, ) self.renamed_models[app_label, model_name] = rem_model_name - renamed_models_rel_key = '%s.%s' % (rem_model_state.app_label, rem_model_state.name) + renamed_models_rel_key = '%s.%s' % ( + rem_model_state.app_label, + rem_model_state.name_lower, + ) self.renamed_models_rel[renamed_models_rel_key] = '%s.%s' % ( model_state.app_label, - model_state.name, + model_state.name_lower, ) self.old_model_keys.remove((rem_app_label, rem_model_name)) self.old_model_keys.add((app_label, model_name)) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 536b3dd601..5ce7fac420 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -581,14 +581,10 @@ class ForeignObject(RelatedField): if self.remote_field.parent_link: kwargs['parent_link'] = self.remote_field.parent_link - # Work out string form of "to" if isinstance(self.remote_field.model, str): - kwargs['to'] = self.remote_field.model + kwargs['to'] = self.remote_field.model.lower() else: - kwargs['to'] = "%s.%s" % ( - self.remote_field.model._meta.app_label, - self.remote_field.model._meta.object_name, - ) + kwargs['to'] = self.remote_field.model._meta.label_lower # If swappable is True, then see if we're actually pointing to the target # of a swap. swappable_setting = self.swappable_setting diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py index a44851fec2..bf00aa44e2 100644 --- a/tests/field_deconstruction/tests.py +++ b/tests/field_deconstruction/tests.py @@ -202,39 +202,39 @@ class FieldDeconstructionTests(SimpleTestCase): name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "on_delete": models.CASCADE}) self.assertFalse(hasattr(kwargs['to'], "setting_name")) # Test swap detection for swappable model field = models.ForeignKey("auth.User", models.CASCADE) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.User", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.user", "on_delete": models.CASCADE}) self.assertEqual(kwargs['to'].setting_name, "AUTH_USER_MODEL") # Test nonexistent (for now) model field = models.ForeignKey("something.Else", models.CASCADE) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "something.Else", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "something.else", "on_delete": models.CASCADE}) # Test on_delete field = models.ForeignKey("auth.User", models.SET_NULL) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.User", "on_delete": models.SET_NULL}) + self.assertEqual(kwargs, {"to": "auth.user", "on_delete": models.SET_NULL}) # Test to_field preservation field = models.ForeignKey("auth.Permission", models.CASCADE, to_field="foobar") name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "to_field": "foobar", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "to_field": "foobar", "on_delete": models.CASCADE}) # Test related_name preservation field = models.ForeignKey("auth.Permission", models.CASCADE, related_name="foobar") name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "related_name": "foobar", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "related_name": "foobar", "on_delete": models.CASCADE}) # Test related_query_name field = models.ForeignKey("auth.Permission", models.CASCADE, related_query_name="foobar") name, path, args, kwargs = field.deconstruct() @@ -242,7 +242,7 @@ class FieldDeconstructionTests(SimpleTestCase): self.assertEqual(args, []) self.assertEqual( kwargs, - {"to": "auth.Permission", "related_query_name": "foobar", "on_delete": models.CASCADE} + {"to": "auth.permission", "related_query_name": "foobar", "on_delete": models.CASCADE} ) # Test limit_choices_to field = models.ForeignKey("auth.Permission", models.CASCADE, limit_choices_to={'foo': 'bar'}) @@ -251,14 +251,14 @@ class FieldDeconstructionTests(SimpleTestCase): self.assertEqual(args, []) self.assertEqual( kwargs, - {"to": "auth.Permission", "limit_choices_to": {'foo': 'bar'}, "on_delete": models.CASCADE} + {"to": "auth.permission", "limit_choices_to": {'foo': 'bar'}, "on_delete": models.CASCADE} ) # Test unique field = models.ForeignKey("auth.Permission", models.CASCADE, unique=True) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "unique": True, "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "unique": True, "on_delete": models.CASCADE}) @override_settings(AUTH_USER_MODEL="auth.Permission") def test_foreign_key_swapped(self): @@ -270,7 +270,7 @@ class FieldDeconstructionTests(SimpleTestCase): self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "on_delete": models.CASCADE}) self.assertEqual(kwargs['to'].setting_name, "AUTH_USER_MODEL") def test_one_to_one(self): @@ -282,39 +282,39 @@ class FieldDeconstructionTests(SimpleTestCase): name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.OneToOneField") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "on_delete": models.CASCADE}) self.assertFalse(hasattr(kwargs['to'], "setting_name")) # Test swap detection for swappable model field = models.OneToOneField("auth.User", models.CASCADE) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.OneToOneField") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.User", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.user", "on_delete": models.CASCADE}) self.assertEqual(kwargs['to'].setting_name, "AUTH_USER_MODEL") # Test nonexistent (for now) model field = models.OneToOneField("something.Else", models.CASCADE) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.OneToOneField") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "something.Else", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "something.else", "on_delete": models.CASCADE}) # Test on_delete field = models.OneToOneField("auth.User", models.SET_NULL) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.OneToOneField") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.User", "on_delete": models.SET_NULL}) + self.assertEqual(kwargs, {"to": "auth.user", "on_delete": models.SET_NULL}) # Test to_field field = models.OneToOneField("auth.Permission", models.CASCADE, to_field="foobar") name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.OneToOneField") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "to_field": "foobar", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "to_field": "foobar", "on_delete": models.CASCADE}) # Test related_name field = models.OneToOneField("auth.Permission", models.CASCADE, related_name="foobar") name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.OneToOneField") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "related_name": "foobar", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "related_name": "foobar", "on_delete": models.CASCADE}) # Test related_query_name field = models.OneToOneField("auth.Permission", models.CASCADE, related_query_name="foobar") name, path, args, kwargs = field.deconstruct() @@ -322,7 +322,7 @@ class FieldDeconstructionTests(SimpleTestCase): self.assertEqual(args, []) self.assertEqual( kwargs, - {"to": "auth.Permission", "related_query_name": "foobar", "on_delete": models.CASCADE} + {"to": "auth.permission", "related_query_name": "foobar", "on_delete": models.CASCADE} ) # Test limit_choices_to field = models.OneToOneField("auth.Permission", models.CASCADE, limit_choices_to={'foo': 'bar'}) @@ -331,14 +331,14 @@ class FieldDeconstructionTests(SimpleTestCase): self.assertEqual(args, []) self.assertEqual( kwargs, - {"to": "auth.Permission", "limit_choices_to": {'foo': 'bar'}, "on_delete": models.CASCADE} + {"to": "auth.permission", "limit_choices_to": {'foo': 'bar'}, "on_delete": models.CASCADE} ) # Test unique field = models.OneToOneField("auth.Permission", models.CASCADE, unique=True) name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.OneToOneField") self.assertEqual(args, []) - self.assertEqual(kwargs, {"to": "auth.Permission", "on_delete": models.CASCADE}) + self.assertEqual(kwargs, {"to": "auth.permission", "on_delete": models.CASCADE}) def test_image_field(self): field = models.ImageField(upload_to="foo/barness", width_field="width", height_field="height") diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index 633d40a26f..daff57fc2c 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -1014,7 +1014,7 @@ class AutodetectorTests(TestCase): 'renamed_foo', 'django.db.models.ForeignKey', [], - {'to': 'app.Foo', 'on_delete': models.CASCADE, 'db_column': 'foo_id'}, + {'to': 'app.foo', 'on_delete': models.CASCADE, 'db_column': 'foo_id'}, )) def test_rename_model(self): @@ -1032,6 +1032,22 @@ class AutodetectorTests(TestCase): # no AlterField for the related field. self.assertNumberMigrations(changes, 'otherapp', 0) + def test_rename_model_case(self): + """ + Model name is case-insensitive. Changing case doesn't lead to any + autodetected operations. + """ + author_renamed = ModelState('testapp', 'author', [ + ('id', models.AutoField(primary_key=True)), + ]) + changes = self.get_changes( + [self.author_empty, self.book], + [author_renamed, self.book], + questioner=MigrationQuestioner({'ask_rename_model': True}), + ) + self.assertNumberMigrations(changes, 'testapp', 0) + self.assertNumberMigrations(changes, 'otherapp', 0) + def test_rename_m2m_through_model(self): """ Tests autodetection of renamed models that are used in M2M relations as