diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 3452736162..f2adc7af0c 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -555,7 +555,7 @@ def create_many_related_manager(superclass, rel=False): raise TypeError("'%s' instance expected" % self.model._meta.object_name) else: new_ids.add(obj) - db = router.db_for_write(self.through.__class__, instance=self.instance) + db = router.db_for_write(self.through, instance=self.instance) vals = self.through._default_manager.using(db).values_list(target_field_name, flat=True) vals = vals.filter(**{ source_field_name: self._pk_val, @@ -597,7 +597,7 @@ def create_many_related_manager(superclass, rel=False): else: old_ids.add(obj) # Work out what DB we're operating on - db = router.db_for_write(self.through.__class__, instance=self.instance) + db = router.db_for_write(self.through, instance=self.instance) # Send a signal to the other end if need be. if self.reverse or source_field_name == self.source_field_name: # Don't send the signal when we are deleting the @@ -618,7 +618,7 @@ def create_many_related_manager(superclass, rel=False): model=self.model, pk_set=old_ids, using=db) def _clear_items(self, source_field_name): - db = router.db_for_write(self.through.__class__, instance=self.instance) + db = router.db_for_write(self.through, instance=self.instance) # source_col_name: the PK colname in join_table for the source object if self.reverse or source_field_name == self.source_field_name: # Don't send the signal when we are clearing the diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index 04ab2bb925..69bb7efd85 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -1791,3 +1791,56 @@ class SignalTests(TestCase): b.authors.clear() self._write_to_default() self.assertEqual(receiver._database, "other") + +class AttributeErrorRouter(object): + "A router to test the exception handling of ConnectionRouter" + def db_for_read(self, model, **hints): + raise AttributeError + + def db_for_write(self, model, **hints): + raise AttributeError + +class RouterAttributeErrorTestCase(TestCase): + multi_db = True + + def setUp(self): + self.old_routers = router.routers + router.routers = [AttributeErrorRouter()] + + def tearDown(self): + router.routers = self.old_routers + + def test_attribute_error(self): + "Check that the AttributeError from AttributeErrorRouter bubbles up" + dive = Book() + dive.title="Dive into Python" + dive.published = datetime.date(2009, 5, 4) + self.assertRaises(AttributeError, dive.save) + +class ModelMetaRouter(object): + "A router to ensure model arguments are real model classes" + def db_for_write(self, model, **hints): + if not hasattr(model, '_meta'): + raise ValueError + +class RouterM2MThroughTestCase(TestCase): + multi_db = True + + def setUp(self): + self.old_routers = router.routers + router.routers = [ModelMetaRouter()] + + def tearDown(self): + router.routers = self.old_routers + + def test_m2m_through(self): + b = Book.objects.create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + p = Person.objects.create(name="Marty Alchin") + # test add + b.authors.add(p) + # test remove + b.authors.remove(p) + # test clear + b.authors.clear()