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:
parent
29345390b8
commit
12385a5f86
@ -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)
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user