Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions django/db/backends/base/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -1708,19 +1708,27 @@ 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")
and self.db_default_sql(old_field) == self.db_default_sql(new_field)
):
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
Expand Down
4 changes: 2 additions & 2 deletions django/db/models/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
4 changes: 2 additions & 2 deletions docs/ref/checks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,8 @@ Models
expression and won't be validated during the model ``full_clean()``.
* **models.W046**: ``<database>`` does not support comments on tables
(``db_table_comment``).
* **models.W047**: ``<database>`` does not support unique constraints with
nulls distinct.
* **models.W047**: ``<database>`` does not support
``UniqueConstraint.nulls_distinct``.
* **models.E048**: ``constraints/indexes/unique_together`` refers to a
``CompositePrimaryKey`` ``<field name>``, but ``CompositePrimaryKey``\s are
not supported for that option.
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/6.0.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
4 changes: 2 additions & 2 deletions tests/invalid_models_tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
34 changes: 34 additions & 0 deletions tests/schema/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down