Author: russellm
Date: 2010-05-11 08:06:03 -0500 (Tue, 11 May 2010)
New Revision: 13232

Modified:
   django/trunk/django/db/models/base.py
   django/trunk/tests/regressiontests/multiple_database/models.py
   django/trunk/tests/regressiontests/multiple_database/tests.py
Log:
Fixed #13513 -- Ensured that queries collecting deleted objects are issued on 
the right database, especially when dealing with m2m intermediate tables. 
Thanks to gavoja for the report.

Modified: django/trunk/django/db/models/base.py
===================================================================
--- django/trunk/django/db/models/base.py       2010-05-11 06:10:58 UTC (rev 
13231)
+++ django/trunk/django/db/models/base.py       2010-05-11 13:06:03 UTC (rev 
13232)
@@ -588,20 +588,22 @@
 
         for related in self._meta.get_all_related_many_to_many_objects():
             if related.field.rel.through:
+                db = router.db_for_write(related.field.rel.through.__class__, 
instance=self)
                 opts = related.field.rel.through._meta
                 reverse_field_name = related.field.m2m_reverse_field_name()
                 nullable = opts.get_field(reverse_field_name).null
                 filters = {reverse_field_name: self}
-                for sub_obj in 
related.field.rel.through._base_manager.filter(**filters):
+                for sub_obj in 
related.field.rel.through._base_manager.using(db).filter(**filters):
                     sub_obj._collect_sub_objects(seen_objs, self, nullable)
 
         for f in self._meta.many_to_many:
             if f.rel.through:
+                db = router.db_for_write(f.rel.through.__class__, 
instance=self)
                 opts = f.rel.through._meta
                 field_name = f.m2m_field_name()
                 nullable = opts.get_field(field_name).null
                 filters = {field_name: self}
-                for sub_obj in f.rel.through._base_manager.filter(**filters):
+                for sub_obj in 
f.rel.through._base_manager.using(db).filter(**filters):
                     sub_obj._collect_sub_objects(seen_objs, self, nullable)
             else:
                 # m2m-ish but with no through table? GenericRelation: cascade 
delete
@@ -627,7 +629,6 @@
 
     def delete(self, using=None):
         using = using or router.db_for_write(self.__class__, instance=self)
-        connection = connections[using]
         assert self._get_pk_val() is not None, "%s object can't be deleted 
because its %s attribute is set to None." % (self._meta.object_name, 
self._meta.pk.attname)
 
         # Find all the objects than need to be deleted.

Modified: django/trunk/tests/regressiontests/multiple_database/models.py
===================================================================
--- django/trunk/tests/regressiontests/multiple_database/models.py      
2010-05-11 06:10:58 UTC (rev 13231)
+++ django/trunk/tests/regressiontests/multiple_database/models.py      
2010-05-11 13:06:03 UTC (rev 13232)
@@ -44,6 +44,16 @@
     class Meta:
         ordering = ('title',)
 
+class Pet(models.Model):
+    name = models.CharField(max_length=100)
+    owner = models.ForeignKey(Person)
+
+    def __unicode__(self):
+        return self.name
+
+    class Meta:
+        ordering = ('name',)
+
 class UserProfile(models.Model):
     user = models.OneToOneField(User, null=True)
     flavor = models.CharField(max_length=100)

Modified: django/trunk/tests/regressiontests/multiple_database/tests.py
===================================================================
--- django/trunk/tests/regressiontests/multiple_database/tests.py       
2010-05-11 06:10:58 UTC (rev 13231)
+++ django/trunk/tests/regressiontests/multiple_database/tests.py       
2010-05-11 13:06:03 UTC (rev 13232)
@@ -10,7 +10,7 @@
 from django.db.utils import ConnectionRouter
 from django.test import TestCase
 
-from models import Book, Person, Review, UserProfile
+from models import Book, Person, Pet, Review, UserProfile
 
 try:
     # we only have these models if the user is using multi-db, it's safe the
@@ -321,6 +321,66 @@
         except ValueError:
             pass
 
+    def test_m2m_deletion(self):
+        "Cascaded deletions of m2m relations issue queries on the right 
database"
+        # Create a book and author on the other database
+        dive = Book.objects.using('other').create(title="Dive into Python",
+                                                  
published=datetime.date(2009, 5, 4))
+
+        mark = Person.objects.using('other').create(name="Mark Pilgrim")
+        dive.authors = [mark]
+
+        # Check the initial state
+        self.assertEquals(Person.objects.using('default').count(), 0)
+        self.assertEquals(Book.objects.using('default').count(), 0)
+        
self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+        self.assertEquals(Person.objects.using('other').count(), 1)
+        self.assertEquals(Book.objects.using('other').count(), 1)
+        self.assertEquals(Book.authors.through.objects.using('other').count(), 
1)
+
+        # Delete the object on the other database
+        dive.delete(using='other')
+
+        self.assertEquals(Person.objects.using('default').count(), 0)
+        self.assertEquals(Book.objects.using('default').count(), 0)
+        
self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+        # The person still exists ...
+        self.assertEquals(Person.objects.using('other').count(), 1)
+        # ... but the book has been deleted
+        self.assertEquals(Book.objects.using('other').count(), 0)
+        # ... and the relationship object has also been deleted.
+        self.assertEquals(Book.authors.through.objects.using('other').count(), 
0)
+
+        # Now try deletion in the reverse direction. Set up the relation again
+        dive = Book.objects.using('other').create(title="Dive into Python",
+                                                  
published=datetime.date(2009, 5, 4))
+        dive.authors = [mark]
+
+        # Check the initial state
+        self.assertEquals(Person.objects.using('default').count(), 0)
+        self.assertEquals(Book.objects.using('default').count(), 0)
+        
self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+        self.assertEquals(Person.objects.using('other').count(), 1)
+        self.assertEquals(Book.objects.using('other').count(), 1)
+        self.assertEquals(Book.authors.through.objects.using('other').count(), 
1)
+
+        # Delete the object on the other database
+        mark.delete(using='other')
+
+        self.assertEquals(Person.objects.using('default').count(), 0)
+        self.assertEquals(Book.objects.using('default').count(), 0)
+        
self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+        # The person has been deleted ...
+        self.assertEquals(Person.objects.using('other').count(), 0)
+        # ... but the book still exists
+        self.assertEquals(Book.objects.using('other').count(), 1)
+        # ... and the relationship object has been deleted.
+        self.assertEquals(Book.authors.through.objects.using('other').count(), 
0)
+
     def test_foreign_key_separation(self):
         "FK fields are constrained to a single database"
         # Create a book and author on the default database
@@ -498,6 +558,28 @@
         
self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),
                           [u'Dive into HTML5', u'Dive into Python', u'Dive 
into Water'])
 
+    def test_foreign_key_deletion(self):
+        "Cascaded deletions of Foreign Key relations issue queries on the 
right database"
+        mark = Person.objects.using('other').create(name="Mark Pilgrim")
+        fido = Pet.objects.using('other').create(name="Fido", owner=mark)
+
+        # Check the initial state
+        self.assertEquals(Person.objects.using('default').count(), 0)
+        self.assertEquals(Pet.objects.using('default').count(), 0)
+
+        self.assertEquals(Person.objects.using('other').count(), 1)
+        self.assertEquals(Pet.objects.using('other').count(), 1)
+
+        # Delete the person object, which will cascade onto the pet
+        mark.delete(using='other')
+
+        self.assertEquals(Person.objects.using('default').count(), 0)
+        self.assertEquals(Pet.objects.using('default').count(), 0)
+
+        # Both the pet and the person have been deleted from the right database
+        self.assertEquals(Person.objects.using('other').count(), 0)
+        self.assertEquals(Pet.objects.using('other').count(), 0)
+
     def test_o2o_separation(self):
         "OneToOne fields are constrained to a single database"
         # Create a user and profile on the default database
@@ -729,6 +811,29 @@
         
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),
                           [u'Python Daily', u'Python Weekly'])
 
+    def test_generic_key_deletion(self):
+        "Cascaded deletions of Generic Key relations issue queries on the 
right database"
+        dive = Book.objects.using('other').create(title="Dive into Python",
+                                                  
published=datetime.date(2009, 5, 4))
+        review = Review.objects.using('other').create(source="Python Weekly", 
content_object=dive)
+
+        # Check the initial state
+        self.assertEquals(Book.objects.using('default').count(), 0)
+        self.assertEquals(Review.objects.using('default').count(), 0)
+
+        self.assertEquals(Book.objects.using('other').count(), 1)
+        self.assertEquals(Review.objects.using('other').count(), 1)
+
+        # Delete the Book object, which will cascade onto the pet
+        dive.delete(using='other')
+
+        self.assertEquals(Book.objects.using('default').count(), 0)
+        self.assertEquals(Review.objects.using('default').count(), 0)
+
+        # Both the pet and the person have been deleted from the right database
+        self.assertEquals(Book.objects.using('other').count(), 0)
+        self.assertEquals(Review.objects.using('other').count(), 0)
+
     def test_ordering(self):
         "get_next_by_XXX commands stick to a single database"
         pro = Book.objects.create(title="Pro Django",

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to