Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #13432 -- Corrected the logic for router use on OneToOne fields…

…; also added a bunch of tests for OneToOneField queries. Thanks to piquadrat for the report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13037 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 848fbdc3e7d9402be096f8f97fb8faa3a36d4324 1 parent 2ebf7fb
Russell Keith-Magee authored April 28, 2010
2  django/db/models/fields/related.py
@@ -222,7 +222,7 @@ def __get__(self, instance, instance_type=None):
5  tests/regressiontests/multiple_database/models.py
@@ -45,6 +45,9 @@ class Meta:
45 45
         ordering = ('title',)
46 46
 
47 47
 class UserProfile(models.Model):
48  
-    user = models.OneToOneField(User)
  48
+    user = models.OneToOneField(User, null=True)
49 49
     flavor = models.CharField(max_length=100)
50 50
 
  51
+    class Meta:
  52
+        ordering = ('flavor',)
  53
+
133  tests/regressiontests/multiple_database/tests.py
@@ -498,6 +498,115 @@ def test_foreign_key_cross_database_protection(self):
498 498
         self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),
499 499
                           [u'Dive into HTML5', u'Dive into Python', u'Dive into Water'])
500 500
 
  501
+    def test_o2o_separation(self):
  502
+        "OneToOne fields are constrained to a single database"
  503
+        # Create a user and profile on the default database
  504
+        alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
  505
+        alice_profile = UserProfile.objects.using('default').create(user=alice, flavor='chocolate')
  506
+
  507
+        # Create a user and profile on the other database
  508
+        bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
  509
+        bob_profile = UserProfile.objects.using('other').create(user=bob, flavor='crunchy frog')
  510
+
  511
+        # Retrieve related objects; queries should be database constrained
  512
+        alice = User.objects.using('default').get(username="alice")
  513
+        self.assertEquals(alice.userprofile.flavor, "chocolate")
  514
+
  515
+        bob = User.objects.using('other').get(username="bob")
  516
+        self.assertEquals(bob.userprofile.flavor, "crunchy frog")
  517
+
  518
+        # Check that queries work across joins
  519
+        self.assertEquals(list(User.objects.using('default').filter(userprofile__flavor='chocolate').values_list('username', flat=True)),
  520
+                          [u'alice'])
  521
+        self.assertEquals(list(User.objects.using('other').filter(userprofile__flavor='chocolate').values_list('username', flat=True)),
  522
+                          [])
  523
+
  524
+        self.assertEquals(list(User.objects.using('default').filter(userprofile__flavor='crunchy frog').values_list('username', flat=True)),
  525
+                          [])
  526
+        self.assertEquals(list(User.objects.using('other').filter(userprofile__flavor='crunchy frog').values_list('username', flat=True)),
  527
+                          [u'bob'])
  528
+
  529
+        # Reget the objects to clear caches
  530
+        alice_profile = UserProfile.objects.using('default').get(flavor='chocolate')
  531
+        bob_profile = UserProfile.objects.using('other').get(flavor='crunchy frog')
  532
+
  533
+        # Retrive related object by descriptor. Related objects should be database-baound
  534
+        self.assertEquals(alice_profile.user.username, 'alice')
  535
+        self.assertEquals(bob_profile.user.username, 'bob')
  536
+
  537
+    def test_o2o_cross_database_protection(self):
  538
+        "Operations that involve sharing FK objects across databases raise an error"
  539
+        # Create a user and profile on the default database
  540
+        alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
  541
+
  542
+        # Create a user and profile on the other database
  543
+        bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
  544
+
  545
+        # Set a one-to-one relation with an object from a different database
  546
+        alice_profile = UserProfile.objects.using('default').create(user=alice, flavor='chocolate')
  547
+        try:
  548
+            bob.userprofile = alice_profile
  549
+            self.fail("Shouldn't be able to assign across databases")
  550
+        except ValueError:
  551
+            pass
  552
+
  553
+        # BUT! if you assign a FK object when the base object hasn't
  554
+        # been saved yet, you implicitly assign the database for the
  555
+        # base object.
  556
+        bob_profile = UserProfile.objects.using('other').create(user=bob, flavor='crunchy frog')
  557
+
  558
+        new_bob_profile = UserProfile(flavor="spring surprise")
  559
+
  560
+        charlie = User(username='charlie',email='charlie@example.com')
  561
+        charlie.set_unusable_password()
  562
+
  563
+        # initially, no db assigned
  564
+        self.assertEquals(new_bob_profile._state.db, None)
  565
+        self.assertEquals(charlie._state.db, None)
  566
+
  567
+        # old object comes from 'other', so the new object is set to use 'other'...
  568
+        new_bob_profile.user = bob
  569
+        charlie.userprofile = bob_profile
  570
+        self.assertEquals(new_bob_profile._state.db, 'other')
  571
+        self.assertEquals(charlie._state.db, 'other')
  572
+
  573
+        # ... but it isn't saved yet
  574
+        self.assertEquals(list(User.objects.using('other').values_list('username',flat=True)),
  575
+                          [u'bob'])
  576
+        self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
  577
+                           [u'crunchy frog'])
  578
+
  579
+        # When saved (no using required), new objects goes to 'other'
  580
+        charlie.save()
  581
+        bob_profile.save()
  582
+        new_bob_profile.save()
  583
+        self.assertEquals(list(User.objects.using('default').values_list('username',flat=True)),
  584
+                          [u'alice'])
  585
+        self.assertEquals(list(User.objects.using('other').values_list('username',flat=True)),
  586
+                          [u'bob', u'charlie'])
  587
+        self.assertEquals(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
  588
+                           [u'chocolate'])
  589
+        self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
  590
+                           [u'crunchy frog', u'spring surprise'])
  591
+
  592
+        # This also works if you assign the O2O relation in the constructor
  593
+        denise = User.objects.db_manager('other').create_user('denise','denise@example.com')
  594
+        denise_profile = UserProfile(flavor="tofu", user=denise)
  595
+
  596
+        self.assertEquals(denise_profile._state.db, 'other')
  597
+        # ... but it isn't saved yet
  598
+        self.assertEquals(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
  599
+                           [u'chocolate'])
  600
+        self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
  601
+                           [u'crunchy frog', u'spring surprise'])
  602
+
  603
+        # When saved, the new profile goes to 'other'
  604
+        denise_profile.save()
  605
+        self.assertEquals(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
  606
+                           [u'chocolate'])
  607
+        self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
  608
+                           [u'crunchy frog', u'spring surprise', u'tofu'])
  609
+
501 610
     def test_generic_key_separation(self):
502 611
         "Generic fields are constrained to a single database"
503 612
         # Create a book and author on the default database
@@ -1103,6 +1212,30 @@ def test_m2m_cross_database_protection(self):
1103 1212
         bob, created = dive.authors.get_or_create(name='Bob')
1104 1213
         self.assertEquals(bob._state.db, 'default')
1105 1214
 
  1215
+    def test_o2o_cross_database_protection(self):
  1216
+        "Operations that involve sharing FK objects across databases raise an error"
  1217
+        # Create a user and profile on the default database
  1218
+        alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
  1219
+
  1220
+        # Create a user and profile on the other database
  1221
+        bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
  1222
+
  1223
+        # Set a one-to-one relation with an object from a different database
  1224
+        alice_profile = UserProfile.objects.create(user=alice, flavor='chocolate')
  1225
+        try:
  1226
+            bob.userprofile = alice_profile
  1227
+        except ValueError:
  1228
+            self.fail("Assignment across master/slave databases with a common source should be ok")
  1229
+
  1230
+        # Database assignments of original objects haven't changed...
  1231
+        self.assertEquals(alice._state.db, 'default')
  1232
+        self.assertEquals(alice_profile._state.db, 'default')
  1233
+        self.assertEquals(bob._state.db, 'other')
  1234
+
  1235
+        # ... but they will when the affected object is saved.
  1236
+        bob.save()
  1237
+        self.assertEquals(bob._state.db, 'default')
  1238
+
1106 1239
     def test_generic_key_cross_database_protection(self):
1107 1240
         "Generic Key operations can span databases if they share a source"
1108 1241
         # Create a book and author on the default database

0 notes on commit 848fbdc

Please sign in to comment.
Something went wrong with that request. Please try again.