From d1a4979fa55071581411b7d62eb1ee20f2380ae3 Mon Sep 17 00:00:00 2001 From: Mridul Dhall Date: Tue, 16 Sep 2025 21:50:06 +0100 Subject: [PATCH 1/2] Fixed #36594 -- Improved UniqueConstraint's nulls_distinct system check message. Clarified that the nulls_distinct argument is not supported, as opposed to certain values for the argument. Thanks Russell Owen for the report. --- django/db/models/constraints.py | 4 ++-- docs/ref/checks.txt | 4 ++-- tests/invalid_models_tests/test_models.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 93251ff322e3..428631997806 100644 --- a/django/db/models/constraints.py +++ b/django/db/models/constraints.py @@ -408,8 +408,8 @@ def check(self, model, connection): ): errors.append( checks.Warning( - f"{connection.display_name} does not support unique constraints " - "with nulls distinct.", + f"{connection.display_name} does not support " + "UniqueConstraint.nulls_distinct.", hint=( "A constraint won't be created. Silence this warning if you " "don't care about it." diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 7dfa7c61d24b..1e56464a6f18 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -447,8 +447,8 @@ Models expression and won't be validated during the model ``full_clean()``. * **models.W046**: ```` does not support comments on tables (``db_table_comment``). -* **models.W047**: ```` does not support unique constraints with - nulls distinct. +* **models.W047**: ```` does not support + ``UniqueConstraint.nulls_distinct``. * **models.E048**: ``constraints/indexes/unique_together`` refers to a ``CompositePrimaryKey`` ````, but ``CompositePrimaryKey``\s are not supported for that option. diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py index fe3c8126152f..679afa3dc67d 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -2891,8 +2891,8 @@ class Meta: ] warn = Warning( - f"{connection.display_name} does not support unique constraints with nulls " - "distinct.", + f"{connection.display_name} does not support " + "UniqueConstraint.nulls_distinct.", hint=( "A constraint won't be created. Silence this warning if you don't care " "about it." From 6cc1231285a20b11058143f8cb0a6b4b3999b23a Mon Sep 17 00:00:00 2001 From: Clifford Gama Date: Mon, 15 Dec 2025 23:36:24 +0200 Subject: [PATCH 2/2] Fixed #36800 -- Restored ManyToManyField renaming in BaseDatabaseSchemaEditor.alter_field(). Regression in f9a44cc0fac653f8e0c2ab1cdfb12b2cc5c63fc2. Now that ManyToManyField is no longer concrete the decision of whether or not it should be altered, which is also relied on by field renaming, should take into consideration name changes even if it doesn't have a column associated with it, as auto-created many-to-many relationship table names are a base of it. Note that there is room for optimization here where a rename can be entirely avoided if ManyToManyField.db_table remains stable between .name changes, just like we do with Field.db_column remaining stable, but since this is a regression and meant to be backported the current patch focuses on correctness over further improvements. Thanks Josik for the report. Co-authored-by: Simon Charette --- django/db/backends/base/schema.py | 16 +++++++++++---- docs/releases/6.0.1.txt | 4 ++++ tests/schema/tests.py | 34 +++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 4a59a6fe6786..491b80107b40 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -1690,7 +1690,7 @@ def _field_should_be_altered(self, old_field, new_field, ignore=None): _, old_path, old_args, old_kwargs = old_field.deconstruct() _, new_path, new_args, new_kwargs = new_field.deconstruct() # Don't alter when: - # - changing only a field name + # - changing only a field name (unless it's a many-to-many) # - changing an attribute that doesn't affect the schema # - changing an attribute in the provided set of ignored attributes # - adding only a db_column and the column name is not changed @@ -1708,7 +1708,7 @@ def _field_should_be_altered(self, old_field, new_field, ignore=None): ): old_kwargs.pop("to", None) new_kwargs.pop("to", None) - # db_default can take many form but result in the same SQL. + # db_default can take many forms but result in the same SQL. if ( old_kwargs.get("db_default") and new_kwargs.get("db_default") @@ -1716,11 +1716,19 @@ def _field_should_be_altered(self, old_field, new_field, ignore=None): ): old_kwargs.pop("db_default") new_kwargs.pop("db_default") - return ( + if ( old_field.concrete and new_field.concrete and (self.quote_name(old_field.column) != self.quote_name(new_field.column)) - ) or (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs) + ): + return True + if ( + old_field.many_to_many + and new_field.many_to_many + and old_field.name != new_field.name + ): + return True + return (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs) def _field_should_be_indexed(self, model, field): return field.db_index and not field.unique diff --git a/docs/releases/6.0.1.txt b/docs/releases/6.0.1.txt index 72c35b3e6952..a84d49b07a9a 100644 --- a/docs/releases/6.0.1.txt +++ b/docs/releases/6.0.1.txt @@ -12,3 +12,7 @@ Bugfixes * Fixed a regression in Django 6.0 where :ttag:`querystring` mishandled multi-value :class:`~django.http.QueryDict` keys, both by only preserving the last value and by incorrectly handling ``None`` values (:ticket:`36783`). + +* Fixed a regression in Django 6.0 that prevented changing the name of a + :class:`~django.db.models.ManyToManyField` from taking effect when applying + migrations (:ticket:`36800`). diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 12c14563e048..ab6cd60c0782 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -2889,6 +2889,40 @@ def test_m2m_repoint_custom(self): def test_m2m_repoint_inherited(self): self._test_m2m_repoint(InheritedManyToManyField) + def test_m2m_rename(self): + class LocalBook(Model): + authors = ManyToManyField("schema.Author") + + class Meta: + app_label = "schema" + apps = new_apps + + self.local_models = [LocalBook] + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(LocalBook) + old_field = LocalBook._meta.get_field("authors") + new_field = ManyToManyField("schema.Author") + new_field.contribute_to_class(LocalBook, "writers") + with connection.schema_editor() as editor: + editor.alter_field(LocalBook, old_field, new_field, strict=True) + # Ensure old M2M is gone. + with self.assertRaises(DatabaseError): + self.column_classes( + LocalBook._meta.get_field("authors").remote_field.through + ) + if connection.features.supports_foreign_keys: + self.assertForeignKeyExists( + new_field.remote_field.through, + "author_id", + "schema_author", + ) + new_through_table = new_field.remote_field.through._meta.db_table + self.assertIn("writers", new_through_table) + self.assertNotIn("authors", new_through_table) + # Remove the old field from meta for tearDown(). + LocalBook._meta.local_many_to_many.remove(old_field) + @isolate_apps("schema") def test_m2m_rename_field_in_target_model(self): class LocalTagM2MTest(Model):