summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Stapenhurst <rob@secondsync.com>2014-02-09 13:54:46 +0000
committerTim Graham <timograham@gmail.com>2014-02-09 16:01:17 -0500
commit12385a5f868ee697266756511a85434627ae7ca6 (patch)
tree4dc86b6e784a421c5cafd620aad820ba3fd43398
parent29345390b81a3bfce22af43617cabd0d08de3632 (diff)
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().
-rw-r--r--django/db/models/fields/related.py33
-rw-r--r--tests/m2m_through/tests.py71
2 files changed, 77 insertions, 27 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 75e5f20fea..6bd91817dd 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -867,20 +867,29 @@ def create_many_related_manager(superclass, rel):
False,
self.prefetch_cache_name)
- # If the ManyToMany relation has an intermediary model,
- # the add and remove methods do not exist.
- if rel.through._meta.auto_created:
- def add(self, *objs):
- self._add_items(self.source_field_name, self.target_field_name, *objs)
+ def add(self, *objs):
+ if not rel.through._meta.auto_created:
+ opts = self.through._meta
+ raise AttributeError(
+ "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 self.symmetrical:
- self._add_items(self.target_field_name, self.source_field_name, *objs)
- add.alters_data = True
+ # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
+ if self.symmetrical:
+ self._add_items(self.target_field_name, self.source_field_name, *objs)
+ add.alters_data = True
- def remove(self, *objs):
- self._remove_items(self.source_field_name, self.target_field_name, *objs)
- remove.alters_data = True
+ def remove(self, *objs):
+ if not rel.through._meta.auto_created:
+ 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):
db = router.db_for_write(self.through, instance=self.instance)
diff --git a/tests/m2m_through/tests.py b/tests/m2m_through/tests.py
index 1e1b32f9eb..0fa3d4aa55 100644
--- a/tests/m2m_through/tests.py
+++ b/tests/m2m_through/tests.py
@@ -68,12 +68,24 @@ class M2mThroughTests(TestCase):
def test_forward_descriptors(self):
# Due to complications with adding via an intermediary model,
- # the add method is not provided.
- self.assertRaises(AttributeError, lambda: self.rock.members.add(self.bob))
+ # the add method raises an error.
+ 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.
- self.assertRaises(AttributeError, lambda: self.rock.members.create(name='Anne'))
- # Remove has similar complications, and is not provided either.
- self.assertRaises(AttributeError, lambda: self.rock.members.remove(self.jim))
+ self.assertRaisesMessage(
+ AttributeError,
+ '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)
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
- # the same reasons as adding.
- self.assertRaises(AttributeError, setattr, self.rock, "members", backup)
+ # Assignment should not work with models specifying a through model for
+ # many of the same reasons as adding.
+ 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.
m1.save()
m2.save()
@@ -111,11 +131,25 @@ class M2mThroughTests(TestCase):
def test_reverse_descriptors(self):
# Due to complications with adding via an intermediary model,
# 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.
- 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.
- 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)
m2 = Membership.objects.create(person=self.jim, group=self.roll)
@@ -133,11 +167,18 @@ class M2mThroughTests(TestCase):
self.jim.group_set.all(),
[]
)
- # Assignment should not work with models specifying a through model for many of
- # the same reasons as adding.
- self.assertRaises(AttributeError, setattr, self.jim, "group_set", backup)
- # Let's re-save those instances that we've cleared.
+ # Assignment should not work with models specifying a through model for
+ # many of the same reasons as adding.
+ self.assertRaisesMessage(
+ 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()
m2.save()
# Verifying that those instances were re-saved successfully.