summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2022-02-16 21:09:24 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-02-16 21:10:30 +0100
commit760b7e7f4f62eafdc79a2188d2a7739abaa6ca8b (patch)
tree8d425313a9d68758049d55e80e089ffae627f26b
parent11881ad69f9915f64638d55e639770922049a375 (diff)
[4.0.x] Fixed #33515 -- Prevented recreation of migration for ManyToManyField to lowercased swappable setting.
Thanks Chris Lee for the report. Regression in 43289707809c814a70f0db38ca4f82f35f43dbfd. Refs #23916. Backport of 1e2e1be02bdf0fe4add0d0279dbca1d74ae28ad7 from main
-rw-r--r--django/db/models/fields/related.py10
-rw-r--r--docs/releases/4.0.3.txt4
-rw-r--r--tests/field_deconstruction/tests.py20
-rw-r--r--tests/migrations/test_autodetector.py25
4 files changed, 45 insertions, 14 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 50f18116b9..6f08c27aba 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -1711,11 +1711,15 @@ class ManyToManyField(RelatedField):
kwargs["db_table"] = self.db_table
if self.remote_field.db_constraint is not True:
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):
- 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:
+ kwargs["to"] = self.remote_field.model.lower()
else:
- kwargs["to"] = self.remote_field.model._meta.label
+ kwargs["to"] = self.remote_field.model._meta.label_lower
if getattr(self.remote_field, "through", None) is not None:
if isinstance(self.remote_field.through, str):
kwargs["through"] = self.remote_field.through
diff --git a/docs/releases/4.0.3.txt b/docs/releases/4.0.3.txt
index 2ef642fe5e..17e9f65074 100644
--- a/docs/releases/4.0.3.txt
+++ b/docs/releases/4.0.3.txt
@@ -12,4 +12,6 @@ reformatted with `black`_.
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`).
diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py
index 64b90953f1..c78ed62876 100644
--- a/tests/field_deconstruction/tests.py
+++ b/tests/field_deconstruction/tests.py
@@ -475,34 +475,34 @@ class FieldDeconstructionTests(SimpleTestCase):
name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, [])
- self.assertEqual(kwargs, {"to": "auth.Permission"})
+ self.assertEqual(kwargs, {"to": "auth.permission"})
self.assertFalse(hasattr(kwargs["to"], "setting_name"))
# Test swappable
field = models.ManyToManyField("auth.User")
name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, [])
- self.assertEqual(kwargs, {"to": "auth.User"})
+ self.assertEqual(kwargs, {"to": "auth.user"})
self.assertEqual(kwargs["to"].setting_name, "AUTH_USER_MODEL")
# Test through
field = models.ManyToManyField("auth.Permission", through="auth.Group")
name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField")
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
field = models.ManyToManyField("auth.Permission", db_table="custom_table")
name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField")
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
field = models.ManyToManyField("auth.Permission", related_name="custom_table")
name, path, args, kwargs = field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, [])
self.assertEqual(
- kwargs, {"to": "auth.Permission", "related_name": "custom_table"}
+ kwargs, {"to": "auth.permission", "related_name": "custom_table"}
)
# Test related_query_name
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(args, [])
self.assertEqual(
- kwargs, {"to": "auth.Permission", "related_query_name": "foobar"}
+ kwargs, {"to": "auth.permission", "related_query_name": "foobar"}
)
# Test limit_choices_to
field = models.ManyToManyField(
@@ -520,7 +520,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, [])
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")
@@ -533,7 +533,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, [])
- self.assertEqual(kwargs, {"to": "auth.Permission"})
+ self.assertEqual(kwargs, {"to": "auth.permission"})
self.assertEqual(kwargs["to"].setting_name, "AUTH_USER_MODEL")
def test_many_to_many_field_related_name(self):
@@ -551,7 +551,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(args, [])
# deconstruct() should not include attributes which were not passed to
# the field during initialization.
- self.assertEqual(kwargs, {"to": "field_deconstruction.MyModel"})
+ self.assertEqual(kwargs, {"to": "field_deconstruction.mymodel"})
# Passed attributes.
name, path, args, kwargs = MyModel.m2m_related_name.field.deconstruct()
self.assertEqual(path, "django.db.models.ManyToManyField")
@@ -559,7 +559,7 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(
kwargs,
{
- "to": "field_deconstruction.MyModel",
+ "to": "field_deconstruction.mymodel",
"related_query_name": "custom_query_name",
"limit_choices_to": {"flag": True},
},
diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py
index cc23d2f810..58d2bde0d0 100644
--- a/tests/migrations/test_autodetector.py
+++ b/tests/migrations/test_autodetector.py
@@ -3325,6 +3325,31 @@ class AutodetectorTests(TestCase):
[("__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):
with isolate_lru_cache(apps.get_swappable_settings_name):
before = self.make_project_state([self.custom_user, self.author_with_user])