@@ -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' ,
'[email protected] ' )
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' ,
'[email protected] ' )
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' ,
'[email protected] ' )
541
+
542
+ # Create a user and profile on the other database
543
+ bob = User .
objects .
db_manager (
'other' ).
create_user (
'bob' ,
'[email protected] ' )
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 = '[email protected] ' )
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' ,
'[email protected] ' )
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' ,
'[email protected] ' )
1219
+
1220
+ # Create a user and profile on the other database
1221
+ bob = User .
objects .
db_manager (
'other' ).
create_user (
'bob' ,
'[email protected] ' )
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 commit comments