Browse Source

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.
Adam Johnson 5 years ago
parent
commit
9e1b6b8a66

+ 5 - 2
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))

+ 2 - 6
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

+ 19 - 19
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")

+ 17 - 1
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