1
0
mirror of https://github.com/django/django.git synced 2025-09-24 23:49:12 +00:00

Fixed #35453 -- Made ManyToManyField.concrete False.

ManyToManyField was already excluded from fields, concrete_fields,
and local_concrete_fields in Options.
This commit is contained in:
Ryan P Kilby 2025-06-27 13:03:16 -07:00 committed by Jacob Walls
parent dce1b9c2de
commit f9a44cc0fa
8 changed files with 31 additions and 33 deletions

View File

@ -1660,7 +1660,9 @@ class BaseDatabaseSchemaEditor:
return output return output
def _field_should_be_altered(self, old_field, new_field, ignore=None): def _field_should_be_altered(self, old_field, new_field, ignore=None):
if not old_field.concrete and not new_field.concrete: if (not (old_field.concrete or old_field.many_to_many)) and (
not (new_field.concrete or new_field.many_to_many)
):
return False return False
ignore = ignore or set() ignore = ignore or set()
_, old_path, old_args, old_kwargs = old_field.deconstruct() _, old_path, old_args, old_kwargs = old_field.deconstruct()
@ -1692,8 +1694,10 @@ class BaseDatabaseSchemaEditor:
): ):
old_kwargs.pop("db_default") old_kwargs.pop("db_default")
new_kwargs.pop("db_default") new_kwargs.pop("db_default")
return self.quote_name(old_field.column) != self.quote_name( return (
new_field.column 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) ) or (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs)
def _field_should_be_indexed(self, model, field): def _field_should_be_indexed(self, model, field):

View File

@ -144,7 +144,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
# Choose a default and insert it into the copy map # Choose a default and insert it into the copy map
if ( if (
not create_field.has_db_default() not create_field.has_db_default()
and not (create_field.many_to_many or create_field.generated) and not create_field.generated
and create_field.concrete and create_field.concrete
): ):
mapping[create_field.column] = self.prepare_default( mapping[create_field.column] = self.prepare_default(

View File

@ -1849,6 +1849,10 @@ class ManyToManyField(RelatedField):
) )
return name, path, args, kwargs return name, path, args, kwargs
def get_attname_column(self):
attname, _ = super().get_attname_column()
return attname, None
def _get_path_info(self, direct=False, filtered_relation=None): def _get_path_info(self, direct=False, filtered_relation=None):
"""Called by both direct and indirect m2m traversal.""" """Called by both direct and indirect m2m traversal."""
int_model = self.remote_field.through int_model = self.remote_field.through

View File

@ -725,7 +725,7 @@ class QuerySet(AltersData):
"Unique fields that can trigger the upsert must be provided." "Unique fields that can trigger the upsert must be provided."
) )
# Updating primary keys and non-concrete fields is forbidden. # Updating primary keys and non-concrete fields is forbidden.
if any(not f.concrete or f.many_to_many for f in update_fields): if any(not f.concrete for f in update_fields):
raise ValueError( raise ValueError(
"bulk_create() can only be used with concrete fields in " "bulk_create() can only be used with concrete fields in "
"update_fields." "update_fields."
@ -736,7 +736,7 @@ class QuerySet(AltersData):
"update_fields." "update_fields."
) )
if unique_fields: if unique_fields:
if any(not f.concrete or f.many_to_many for f in unique_fields): if any(not f.concrete for f in unique_fields):
raise ValueError( raise ValueError(
"bulk_create() can only be used with concrete fields " "bulk_create() can only be used with concrete fields "
"in unique_fields." "in unique_fields."
@ -916,7 +916,7 @@ class QuerySet(AltersData):
raise ValueError("All bulk_update() objects must have a primary key set.") raise ValueError("All bulk_update() objects must have a primary key set.")
opts = self.model._meta opts = self.model._meta
fields = [opts.get_field(name) for name in fields] fields = [opts.get_field(name) for name in fields]
if any(not f.concrete or f.many_to_many for f in fields): if any(not f.concrete for f in fields):
raise ValueError("bulk_update() can only be used with concrete fields.") raise ValueError("bulk_update() can only be used with concrete fields.")
all_pk_fields = set(opts.pk_fields) all_pk_fields = set(opts.pk_fields)
for parent in opts.all_parents: for parent in opts.all_parents:

View File

@ -92,10 +92,10 @@ class UpdateQuery(Query):
raise FieldError( raise FieldError(
"Composite primary key fields must be updated individually." "Composite primary key fields must be updated individually."
) )
if not field.concrete or (field.is_relation and field.many_to_many): if not field.concrete:
raise FieldError( raise FieldError(
"Cannot update model field %r (only non-relations and " "Cannot update model field %r (only concrete fields are permitted)."
"foreign keys permitted)." % field % field
) )
if model is not self.get_meta().concrete_model: if model is not self.get_meta().concrete_model:
self.add_related_update(model, field, val) self.add_related_update(model, field, val)

View File

@ -172,7 +172,7 @@ class CompositePKUpdateTests(TestCase):
def test_cant_update_relation(self): def test_cant_update_relation(self):
msg = ( msg = (
"Cannot update model field <django.db.models.fields.related.ForeignObject: " "Cannot update model field <django.db.models.fields.related.ForeignObject: "
"user> (only non-relations and foreign keys permitted)" "user> (only concrete fields are permitted)"
) )
with self.assertRaisesMessage(FieldError, msg): with self.assertRaisesMessage(FieldError, msg):

View File

@ -6,6 +6,7 @@ from .models import AllFieldsModel
NON_CONCRETE_FIELDS = ( NON_CONCRETE_FIELDS = (
models.ForeignObject, models.ForeignObject,
models.ManyToManyField,
GenericForeignKey, GenericForeignKey,
GenericRelation, GenericRelation,
) )
@ -209,7 +210,7 @@ class FieldFlagsTests(test.SimpleTestCase):
def test_model_and_reverse_model_should_equal_on_relations(self): def test_model_and_reverse_model_should_equal_on_relations(self):
for field in AllFieldsModel._meta.get_fields(): for field in AllFieldsModel._meta.get_fields():
is_concrete_forward_field = field.concrete and field.related_model is_concrete_forward_field = field.concrete and field.related_model
if is_concrete_forward_field: if is_concrete_forward_field or field.many_to_many:
reverse_field = field.remote_field reverse_field = field.remote_field
self.assertEqual(field.model, reverse_field.related_model) self.assertEqual(field.model, reverse_field.related_model)
self.assertEqual(field.related_model, reverse_field.model) self.assertEqual(field.related_model, reverse_field.model)

View File

@ -157,43 +157,32 @@ class AdvancedTests(TestCase):
self.assertEqual(bar_qs[0].foo_id, b_foo.target) self.assertEqual(bar_qs[0].foo_id, b_foo.target)
def test_update_m2m_field(self): def test_update_m2m_field(self):
msg = ( rel = "<django.db.models.fields.related.ManyToManyField: m2m_foo>"
"Cannot update model field " msg = f"Cannot update model field {rel} (only concrete fields are permitted)."
"<django.db.models.fields.related.ManyToManyField: m2m_foo> "
"(only non-relations and foreign keys permitted)."
)
with self.assertRaisesMessage(FieldError, msg): with self.assertRaisesMessage(FieldError, msg):
Bar.objects.update(m2m_foo="whatever") Bar.objects.update(m2m_foo="whatever")
def test_update_reverse_m2m_descriptor(self): def test_update_reverse_m2m_descriptor(self):
msg = ( rel = "<ManyToManyRel: update.bar>"
"Cannot update model field <ManyToManyRel: update.bar> " msg = f"Cannot update model field {rel} (only concrete fields are permitted)."
"(only non-relations and foreign keys permitted)."
)
with self.assertRaisesMessage(FieldError, msg): with self.assertRaisesMessage(FieldError, msg):
Foo.objects.update(m2m_foo="whatever") Foo.objects.update(m2m_foo="whatever")
def test_update_reverse_fk_descriptor(self): def test_update_reverse_fk_descriptor(self):
msg = ( rel = "<ManyToOneRel: update.bar>"
"Cannot update model field <ManyToOneRel: update.bar> " msg = f"Cannot update model field {rel} (only concrete fields are permitted)."
"(only non-relations and foreign keys permitted)."
)
with self.assertRaisesMessage(FieldError, msg): with self.assertRaisesMessage(FieldError, msg):
Foo.objects.update(bar="whatever") Foo.objects.update(bar="whatever")
def test_update_reverse_o2o_descriptor(self): def test_update_reverse_o2o_descriptor(self):
msg = ( rel = "<OneToOneRel: update.bar>"
"Cannot update model field <OneToOneRel: update.bar> " msg = f"Cannot update model field {rel} (only concrete fields are permitted)."
"(only non-relations and foreign keys permitted)."
)
with self.assertRaisesMessage(FieldError, msg): with self.assertRaisesMessage(FieldError, msg):
Foo.objects.update(o2o_bar="whatever") Foo.objects.update(o2o_bar="whatever")
def test_update_reverse_mti_parent_link_descriptor(self): def test_update_reverse_mti_parent_link_descriptor(self):
msg = ( rel = "<OneToOneRel: update.uniquenumberchild>"
"Cannot update model field <OneToOneRel: update.uniquenumberchild> " msg = f"Cannot update model field {rel} (only concrete fields are permitted)."
"(only non-relations and foreign keys permitted)."
)
with self.assertRaisesMessage(FieldError, msg): with self.assertRaisesMessage(FieldError, msg):
UniqueNumber.objects.update(uniquenumberchild="whatever") UniqueNumber.objects.update(uniquenumberchild="whatever")