1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

Fixed #21763 -- Added an error msg for missing methods on ManyRelatedManager.

Attempting to add() and remove() an object related by a 'through' model
now raises more descriptive AttributeErrors, in line with set and
create().
This commit is contained in:
Robert Stapenhurst 2014-02-09 13:54:46 +00:00 committed by Tim Graham
parent 29345390b8
commit 12385a5f86
2 changed files with 77 additions and 27 deletions

View File

@ -867,20 +867,29 @@ def create_many_related_manager(superclass, rel):
False, False,
self.prefetch_cache_name) self.prefetch_cache_name)
# If the ManyToMany relation has an intermediary model, def add(self, *objs):
# the add and remove methods do not exist. if not rel.through._meta.auto_created:
if rel.through._meta.auto_created: opts = self.through._meta
def add(self, *objs): raise AttributeError(
self._add_items(self.source_field_name, self.target_field_name, *objs) "Cannot use add() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." %
(opts.app_label, opts.object_name)
)
self._add_items(self.source_field_name, self.target_field_name, *objs)
# If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
if self.symmetrical: if self.symmetrical:
self._add_items(self.target_field_name, self.source_field_name, *objs) self._add_items(self.target_field_name, self.source_field_name, *objs)
add.alters_data = True add.alters_data = True
def remove(self, *objs): def remove(self, *objs):
self._remove_items(self.source_field_name, self.target_field_name, *objs) if not rel.through._meta.auto_created:
remove.alters_data = True opts = self.through._meta
raise AttributeError(
"Cannot use remove() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." %
(opts.app_label, opts.object_name)
)
self._remove_items(self.source_field_name, self.target_field_name, *objs)
remove.alters_data = True
def clear(self): def clear(self):
db = router.db_for_write(self.through, instance=self.instance) db = router.db_for_write(self.through, instance=self.instance)

View File

@ -68,12 +68,24 @@ class M2mThroughTests(TestCase):
def test_forward_descriptors(self): def test_forward_descriptors(self):
# Due to complications with adding via an intermediary model, # Due to complications with adding via an intermediary model,
# the add method is not provided. # the add method raises an error.
self.assertRaises(AttributeError, lambda: self.rock.members.add(self.bob)) self.assertRaisesMessage(
AttributeError,
'Cannot use add() on a ManyToManyField which specifies an intermediary model',
lambda: self.rock.members.add(self.bob)
)
# Create is also disabled as it suffers from the same problems as add. # Create is also disabled as it suffers from the same problems as add.
self.assertRaises(AttributeError, lambda: self.rock.members.create(name='Anne')) self.assertRaisesMessage(
# Remove has similar complications, and is not provided either. AttributeError,
self.assertRaises(AttributeError, lambda: self.rock.members.remove(self.jim)) 'Cannot use create() on a ManyToManyField which specifies an intermediary model',
lambda: self.rock.members.create(name='Anne')
)
# Remove has similar complications, and it also raises an error.
self.assertRaisesMessage(
AttributeError,
'Cannot use remove() on a ManyToManyField which specifies an intermediary model',
lambda: self.rock.members.remove(self.jim)
)
m1 = Membership.objects.create(person=self.jim, group=self.rock) m1 = Membership.objects.create(person=self.jim, group=self.rock)
m2 = Membership.objects.create(person=self.jane, group=self.rock) m2 = Membership.objects.create(person=self.jane, group=self.rock)
@ -93,9 +105,17 @@ class M2mThroughTests(TestCase):
[] []
) )
# Assignment should not work with models specifying a through model for many of # Assignment should not work with models specifying a through model for
# the same reasons as adding. # many of the same reasons as adding.
self.assertRaises(AttributeError, setattr, self.rock, "members", backup) self.assertRaisesMessage(
AttributeError,
'Cannot set values on a ManyToManyField which specifies an intermediary model',
setattr,
self.rock,
"members",
backup
)
# Let's re-save those instances that we've cleared. # Let's re-save those instances that we've cleared.
m1.save() m1.save()
m2.save() m2.save()
@ -111,11 +131,25 @@ class M2mThroughTests(TestCase):
def test_reverse_descriptors(self): def test_reverse_descriptors(self):
# Due to complications with adding via an intermediary model, # Due to complications with adding via an intermediary model,
# the add method is not provided. # the add method is not provided.
self.assertRaises(AttributeError, lambda: self.bob.group_set.add(self.rock)) self.assertRaisesMessage(
AttributeError,
'Cannot use add() on a ManyToManyField which specifies an intermediary model',
lambda: self.bob.group_set.add(self.rock)
)
# Create is also disabled as it suffers from the same problems as add. # Create is also disabled as it suffers from the same problems as add.
self.assertRaises(AttributeError, lambda: self.bob.group_set.create(name="funk")) self.assertRaisesMessage(
AttributeError,
'Cannot use create() on a ManyToManyField which specifies an intermediary model',
lambda: self.bob.group_set.create(name="funk")
)
# Remove has similar complications, and is not provided either. # Remove has similar complications, and is not provided either.
self.assertRaises(AttributeError, lambda: self.jim.group_set.remove(self.rock)) self.assertRaisesMessage(
AttributeError,
'Cannot use remove() on a ManyToManyField which specifies an intermediary model',
lambda: self.jim.group_set.remove(self.rock)
)
m1 = Membership.objects.create(person=self.jim, group=self.rock) m1 = Membership.objects.create(person=self.jim, group=self.rock)
m2 = Membership.objects.create(person=self.jim, group=self.roll) m2 = Membership.objects.create(person=self.jim, group=self.roll)
@ -133,11 +167,18 @@ class M2mThroughTests(TestCase):
self.jim.group_set.all(), self.jim.group_set.all(),
[] []
) )
# Assignment should not work with models specifying a through model for many of # Assignment should not work with models specifying a through model for
# the same reasons as adding. # many of the same reasons as adding.
self.assertRaises(AttributeError, setattr, self.jim, "group_set", backup) self.assertRaisesMessage(
# Let's re-save those instances that we've cleared. AttributeError,
'Cannot set values on a ManyToManyField which specifies an intermediary model',
setattr,
self.jim,
"group_set",
backup
)
# Let's re-save those instances that we've cleared.
m1.save() m1.save()
m2.save() m2.save()
# Verifying that those instances were re-saved successfully. # Verifying that those instances were re-saved successfully.