Fixed #33515 -- Prevented recreation of migration for ManyToManyField to lowercased swappable setting.

Thanks Chris Lee for the report.

Regression in 4328970780.

Refs #23916.
This commit is contained in:
Mariusz Felisiak 2022-02-16 21:09:24 +01:00 committed by GitHub
parent 35c2474f16
commit 1e2e1be02b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 45 additions and 14 deletions

View File

@ -1728,11 +1728,15 @@ class ManyToManyField(RelatedField):
kwargs["db_table"] = self.db_table kwargs["db_table"] = self.db_table
if self.remote_field.db_constraint is not True: if self.remote_field.db_constraint is not True:
kwargs["db_constraint"] = self.remote_field.db_constraint kwargs["db_constraint"] = self.remote_field.db_constraint
# Rel needs more work. # Lowercase model names as they should be treated as case-insensitive.
if isinstance(self.remote_field.model, str): if isinstance(self.remote_field.model, str):
kwargs["to"] = self.remote_field.model if "." in self.remote_field.model:
app_label, model_name = self.remote_field.model.split(".")
kwargs["to"] = "%s.%s" % (app_label, model_name.lower())
else: else:
kwargs["to"] = self.remote_field.model._meta.label kwargs["to"] = self.remote_field.model.lower()
else:
kwargs["to"] = self.remote_field.model._meta.label_lower
if getattr(self.remote_field, "through", None) is not None: if getattr(self.remote_field, "through", None) is not None:
if isinstance(self.remote_field.through, str): if isinstance(self.remote_field.through, str):
kwargs["through"] = self.remote_field.through kwargs["through"] = self.remote_field.through

View File

@ -12,4 +12,6 @@ reformatted with `black`_.
Bugfixes Bugfixes
======== ========
* ... * Prevented, following a regression in Django 4.0.1, :djadmin:`makemigrations`
from generating infinite migrations for a model with ``ManyToManyField`` to
a lowercased swappable model such as ``'auth.user'`` (:ticket:`33515`).

View File

@ -475,34 +475,34 @@ class FieldDeconstructionTests(SimpleTestCase):
name, path, args, kwargs = field.deconstruct() name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual(kwargs, {"to": "auth.Permission"}) self.assertEqual(kwargs, {"to": "auth.permission"})
self.assertFalse(hasattr(kwargs["to"], "setting_name")) self.assertFalse(hasattr(kwargs["to"], "setting_name"))
# Test swappable # Test swappable
field = models.ManyToManyField("auth.User") field = models.ManyToManyField("auth.User")
name, path, args, kwargs = field.deconstruct() name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual(kwargs, {"to": "auth.User"}) self.assertEqual(kwargs, {"to": "auth.user"})
self.assertEqual(kwargs["to"].setting_name, "AUTH_USER_MODEL") self.assertEqual(kwargs["to"].setting_name, "AUTH_USER_MODEL")
# Test through # Test through
field = models.ManyToManyField("auth.Permission", through="auth.Group") field = models.ManyToManyField("auth.Permission", through="auth.Group")
name, path, args, kwargs = field.deconstruct() name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual(kwargs, {"to": "auth.Permission", "through": "auth.Group"}) self.assertEqual(kwargs, {"to": "auth.permission", "through": "auth.Group"})
# Test custom db_table # Test custom db_table
field = models.ManyToManyField("auth.Permission", db_table="custom_table") field = models.ManyToManyField("auth.Permission", db_table="custom_table")
name, path, args, kwargs = field.deconstruct() name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual(kwargs, {"to": "auth.Permission", "db_table": "custom_table"}) self.assertEqual(kwargs, {"to": "auth.permission", "db_table": "custom_table"})
# Test related_name # Test related_name
field = models.ManyToManyField("auth.Permission", related_name="custom_table") field = models.ManyToManyField("auth.Permission", related_name="custom_table")
name, path, args, kwargs = field.deconstruct() name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual( self.assertEqual(
kwargs, {"to": "auth.Permission", "related_name": "custom_table"} kwargs, {"to": "auth.permission", "related_name": "custom_table"}
) )
# Test related_query_name # Test related_query_name
field = models.ManyToManyField("auth.Permission", related_query_name="foobar") field = models.ManyToManyField("auth.Permission", related_query_name="foobar")
@ -510,7 +510,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual( self.assertEqual(
kwargs, {"to": "auth.Permission", "related_query_name": "foobar"} kwargs, {"to": "auth.permission", "related_query_name": "foobar"}
) )
# Test limit_choices_to # Test limit_choices_to
field = models.ManyToManyField( field = models.ManyToManyField(
@ -520,7 +520,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual( self.assertEqual(
kwargs, {"to": "auth.Permission", "limit_choices_to": {"foo": "bar"}} kwargs, {"to": "auth.permission", "limit_choices_to": {"foo": "bar"}}
) )
@override_settings(AUTH_USER_MODEL="auth.Permission") @override_settings(AUTH_USER_MODEL="auth.Permission")
@ -533,7 +533,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, []) self.assertEqual(args, [])
self.assertEqual(kwargs, {"to": "auth.Permission"}) self.assertEqual(kwargs, {"to": "auth.permission"})
self.assertEqual(kwargs["to"].setting_name, "AUTH_USER_MODEL") self.assertEqual(kwargs["to"].setting_name, "AUTH_USER_MODEL")
def test_many_to_many_field_related_name(self): def test_many_to_many_field_related_name(self):
@ -551,7 +551,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(args, []) self.assertEqual(args, [])
# deconstruct() should not include attributes which were not passed to # deconstruct() should not include attributes which were not passed to
# the field during initialization. # the field during initialization.
self.assertEqual(kwargs, {"to": "field_deconstruction.MyModel"}) self.assertEqual(kwargs, {"to": "field_deconstruction.mymodel"})
# Passed attributes. # Passed attributes.
name, path, args, kwargs = MyModel.m2m_related_name.field.deconstruct() name, path, args, kwargs = MyModel.m2m_related_name.field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField") self.assertEqual(path, "django.db.models.ManyToManyField")
@ -559,7 +559,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual( self.assertEqual(
kwargs, kwargs,
{ {
"to": "field_deconstruction.MyModel", "to": "field_deconstruction.mymodel",
"related_query_name": "custom_query_name", "related_query_name": "custom_query_name",
"limit_choices_to": {"flag": True}, "limit_choices_to": {"flag": True},
}, },

View File

@ -3279,6 +3279,31 @@ class AutodetectorTests(TestCase):
[("__setting__", "AUTH_USER_MODEL")], [("__setting__", "AUTH_USER_MODEL")],
) )
@override_settings(AUTH_USER_MODEL="thirdapp.CustomUser")
def test_swappable_many_to_many_model_case(self):
document_lowercase = ModelState(
"testapp",
"Document",
[
("id", models.AutoField(primary_key=True)),
("owners", models.ManyToManyField(settings.AUTH_USER_MODEL.lower())),
],
)
document = ModelState(
"testapp",
"Document",
[
("id", models.AutoField(primary_key=True)),
("owners", models.ManyToManyField(settings.AUTH_USER_MODEL)),
],
)
with isolate_lru_cache(apps.get_swappable_settings_name):
changes = self.get_changes(
[self.custom_user, document_lowercase],
[self.custom_user, document],
)
self.assertEqual(len(changes), 0)
def test_swappable_changed(self): def test_swappable_changed(self):
with isolate_lru_cache(apps.get_swappable_settings_name): with isolate_lru_cache(apps.get_swappable_settings_name):
before = self.make_project_state([self.custom_user, self.author_with_user]) before = self.make_project_state([self.custom_user, self.author_with_user])