PageRenderTime 1038ms CodeModel.GetById 182ms app.highlight 732ms RepoModel.GetById 106ms app.codeStats 1ms

/tests/regressiontests/multiple_database/tests.py

https://code.google.com/p/mango-py/
Python | 1884 lines | 1212 code | 385 blank | 287 comment | 27 complexity | b122edd90b14e13b1de9fd9eb7363263 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1import datetime
   2import pickle
   3import sys
   4from StringIO import StringIO
   5
   6from django.conf import settings
   7from django.contrib.auth.models import User
   8from django.core import management
   9from django.db import connections, router, DEFAULT_DB_ALIAS
  10from django.db.models import signals
  11from django.db.utils import ConnectionRouter
  12from django.test import TestCase
  13
  14from models import Book, Person, Pet, Review, UserProfile
  15
  16try:
  17    # we only have these models if the user is using multi-db, it's safe the
  18    # run the tests without them though.
  19    from models import Article, article_using
  20except ImportError:
  21    pass
  22
  23class QueryTestCase(TestCase):
  24    multi_db = True
  25
  26    def test_db_selection(self):
  27        "Check that querysets will use the default database by default"
  28        self.assertEqual(Book.objects.db, DEFAULT_DB_ALIAS)
  29        self.assertEqual(Book.objects.all().db, DEFAULT_DB_ALIAS)
  30
  31        self.assertEqual(Book.objects.using('other').db, 'other')
  32
  33        self.assertEqual(Book.objects.db_manager('other').db, 'other')
  34        self.assertEqual(Book.objects.db_manager('other').all().db, 'other')
  35
  36    def test_default_creation(self):
  37        "Objects created on the default database don't leak onto other databases"
  38        # Create a book on the default database using create()
  39        Book.objects.create(title="Pro Django",
  40                            published=datetime.date(2008, 12, 16))
  41
  42        # Create a book on the default database using a save
  43        dive = Book()
  44        dive.title="Dive into Python"
  45        dive.published = datetime.date(2009, 5, 4)
  46        dive.save()
  47
  48        # Check that book exists on the default database, but not on other database
  49        try:
  50            Book.objects.get(title="Pro Django")
  51            Book.objects.using('default').get(title="Pro Django")
  52        except Book.DoesNotExist:
  53            self.fail('"Dive Into Python" should exist on default database')
  54
  55        self.assertRaises(Book.DoesNotExist,
  56            Book.objects.using('other').get,
  57            title="Pro Django"
  58        )
  59
  60        try:
  61            Book.objects.get(title="Dive into Python")
  62            Book.objects.using('default').get(title="Dive into Python")
  63        except Book.DoesNotExist:
  64            self.fail('"Dive into Python" should exist on default database')
  65
  66        self.assertRaises(Book.DoesNotExist,
  67            Book.objects.using('other').get,
  68            title="Dive into Python"
  69        )
  70
  71
  72    def test_other_creation(self):
  73        "Objects created on another database don't leak onto the default database"
  74        # Create a book on the second database
  75        Book.objects.using('other').create(title="Pro Django",
  76                                           published=datetime.date(2008, 12, 16))
  77
  78        # Create a book on the default database using a save
  79        dive = Book()
  80        dive.title="Dive into Python"
  81        dive.published = datetime.date(2009, 5, 4)
  82        dive.save(using='other')
  83
  84        # Check that book exists on the default database, but not on other database
  85        try:
  86            Book.objects.using('other').get(title="Pro Django")
  87        except Book.DoesNotExist:
  88            self.fail('"Dive Into Python" should exist on other database')
  89
  90        self.assertRaises(Book.DoesNotExist,
  91            Book.objects.get,
  92            title="Pro Django"
  93        )
  94        self.assertRaises(Book.DoesNotExist,
  95            Book.objects.using('default').get,
  96            title="Pro Django"
  97        )
  98
  99        try:
 100            Book.objects.using('other').get(title="Dive into Python")
 101        except Book.DoesNotExist:
 102            self.fail('"Dive into Python" should exist on other database')
 103
 104        self.assertRaises(Book.DoesNotExist,
 105            Book.objects.get,
 106            title="Dive into Python"
 107        )
 108        self.assertRaises(Book.DoesNotExist,
 109            Book.objects.using('default').get,
 110            title="Dive into Python"
 111        )
 112
 113    def test_basic_queries(self):
 114        "Queries are constrained to a single database"
 115        dive = Book.objects.using('other').create(title="Dive into Python",
 116                                                  published=datetime.date(2009, 5, 4))
 117
 118        dive =  Book.objects.using('other').get(published=datetime.date(2009, 5, 4))
 119        self.assertEqual(dive.title, "Dive into Python")
 120        self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published=datetime.date(2009, 5, 4))
 121
 122        dive = Book.objects.using('other').get(title__icontains="dive")
 123        self.assertEqual(dive.title, "Dive into Python")
 124        self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__icontains="dive")
 125
 126        dive = Book.objects.using('other').get(title__iexact="dive INTO python")
 127        self.assertEqual(dive.title, "Dive into Python")
 128        self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__iexact="dive INTO python")
 129
 130        dive =  Book.objects.using('other').get(published__year=2009)
 131        self.assertEqual(dive.title, "Dive into Python")
 132        self.assertEqual(dive.published, datetime.date(2009, 5, 4))
 133        self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published__year=2009)
 134
 135        years = Book.objects.using('other').dates('published', 'year')
 136        self.assertEqual([o.year for o in years], [2009])
 137        years = Book.objects.using('default').dates('published', 'year')
 138        self.assertEqual([o.year for o in years], [])
 139
 140        months = Book.objects.using('other').dates('published', 'month')
 141        self.assertEqual([o.month for o in months], [5])
 142        months = Book.objects.using('default').dates('published', 'month')
 143        self.assertEqual([o.month for o in months], [])
 144
 145    def test_m2m_separation(self):
 146        "M2M fields are constrained to a single database"
 147        # Create a book and author on the default database
 148        pro = Book.objects.create(title="Pro Django",
 149                                  published=datetime.date(2008, 12, 16))
 150
 151        marty = Person.objects.create(name="Marty Alchin")
 152
 153        # Create a book and author on the other database
 154        dive = Book.objects.using('other').create(title="Dive into Python",
 155                                                  published=datetime.date(2009, 5, 4))
 156
 157        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 158
 159        # Save the author relations
 160        pro.authors = [marty]
 161        dive.authors = [mark]
 162
 163        # Inspect the m2m tables directly.
 164        # There should be 1 entry in each database
 165        self.assertEqual(Book.authors.through.objects.using('default').count(), 1)
 166        self.assertEqual(Book.authors.through.objects.using('other').count(), 1)
 167
 168        # Check that queries work across m2m joins
 169        self.assertEqual(list(Book.objects.using('default').filter(authors__name='Marty Alchin').values_list('title', flat=True)),
 170                          [u'Pro Django'])
 171        self.assertEqual(list(Book.objects.using('other').filter(authors__name='Marty Alchin').values_list('title', flat=True)),
 172                          [])
 173
 174        self.assertEqual(list(Book.objects.using('default').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
 175                          [])
 176        self.assertEqual(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
 177                          [u'Dive into Python'])
 178
 179        # Reget the objects to clear caches
 180        dive = Book.objects.using('other').get(title="Dive into Python")
 181        mark = Person.objects.using('other').get(name="Mark Pilgrim")
 182
 183        # Retrive related object by descriptor. Related objects should be database-baound
 184        self.assertEqual(list(dive.authors.all().values_list('name', flat=True)),
 185                          [u'Mark Pilgrim'])
 186
 187        self.assertEqual(list(mark.book_set.all().values_list('title', flat=True)),
 188                          [u'Dive into Python'])
 189
 190    def test_m2m_forward_operations(self):
 191        "M2M forward manipulations are all constrained to a single DB"
 192        # Create a book and author on the other database
 193        dive = Book.objects.using('other').create(title="Dive into Python",
 194                                                  published=datetime.date(2009, 5, 4))
 195
 196        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 197
 198        # Save the author relations
 199        dive.authors = [mark]
 200
 201        # Add a second author
 202        john = Person.objects.using('other').create(name="John Smith")
 203        self.assertEqual(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
 204                          [])
 205
 206
 207        dive.authors.add(john)
 208        self.assertEqual(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
 209                          [u'Dive into Python'])
 210        self.assertEqual(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
 211                          [u'Dive into Python'])
 212
 213        # Remove the second author
 214        dive.authors.remove(john)
 215        self.assertEqual(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
 216                          [u'Dive into Python'])
 217        self.assertEqual(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
 218                          [])
 219
 220        # Clear all authors
 221        dive.authors.clear()
 222        self.assertEqual(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
 223                          [])
 224        self.assertEqual(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
 225                          [])
 226
 227        # Create an author through the m2m interface
 228        dive.authors.create(name='Jane Brown')
 229        self.assertEqual(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
 230                          [])
 231        self.assertEqual(list(Book.objects.using('other').filter(authors__name='Jane Brown').values_list('title', flat=True)),
 232                          [u'Dive into Python'])
 233
 234    def test_m2m_reverse_operations(self):
 235        "M2M reverse manipulations are all constrained to a single DB"
 236        # Create a book and author on the other database
 237        dive = Book.objects.using('other').create(title="Dive into Python",
 238                                                  published=datetime.date(2009, 5, 4))
 239
 240        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 241
 242        # Save the author relations
 243        dive.authors = [mark]
 244
 245        # Create a second book on the other database
 246        grease = Book.objects.using('other').create(title="Greasemonkey Hacks",
 247                                                    published=datetime.date(2005, 11, 1))
 248
 249        # Add a books to the m2m
 250        mark.book_set.add(grease)
 251        self.assertEqual(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
 252                          [u'Mark Pilgrim'])
 253        self.assertEqual(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)),
 254                          [u'Mark Pilgrim'])
 255
 256        # Remove a book from the m2m
 257        mark.book_set.remove(grease)
 258        self.assertEqual(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
 259                          [u'Mark Pilgrim'])
 260        self.assertEqual(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)),
 261                          [])
 262
 263        # Clear the books associated with mark
 264        mark.book_set.clear()
 265        self.assertEqual(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
 266                          [])
 267        self.assertEqual(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)),
 268                          [])
 269
 270        # Create a book through the m2m interface
 271        mark.book_set.create(title="Dive into HTML5", published=datetime.date(2020, 1, 1))
 272        self.assertEqual(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
 273                          [])
 274        self.assertEqual(list(Person.objects.using('other').filter(book__title='Dive into HTML5').values_list('name', flat=True)),
 275                          [u'Mark Pilgrim'])
 276
 277    def test_m2m_cross_database_protection(self):
 278        "Operations that involve sharing M2M objects across databases raise an error"
 279        # Create a book and author on the default database
 280        pro = Book.objects.create(title="Pro Django",
 281                                  published=datetime.date(2008, 12, 16))
 282
 283        marty = Person.objects.create(name="Marty Alchin")
 284
 285        # Create a book and author on the other database
 286        dive = Book.objects.using('other').create(title="Dive into Python",
 287                                                  published=datetime.date(2009, 5, 4))
 288
 289        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 290        # Set a foreign key set with an object from a different database
 291        try:
 292            marty.book_set = [pro, dive]
 293            self.fail("Shouldn't be able to assign across databases")
 294        except ValueError:
 295            pass
 296
 297        # Add to an m2m with an object from a different database
 298        try:
 299            marty.book_set.add(dive)
 300            self.fail("Shouldn't be able to assign across databases")
 301        except ValueError:
 302            pass
 303
 304        # Set a m2m with an object from a different database
 305        try:
 306            marty.book_set = [pro, dive]
 307            self.fail("Shouldn't be able to assign across databases")
 308        except ValueError:
 309            pass
 310
 311        # Add to a reverse m2m with an object from a different database
 312        try:
 313            dive.authors.add(marty)
 314            self.fail("Shouldn't be able to assign across databases")
 315        except ValueError:
 316            pass
 317
 318        # Set a reverse m2m with an object from a different database
 319        try:
 320            dive.authors = [mark, marty]
 321            self.fail("Shouldn't be able to assign across databases")
 322        except ValueError:
 323            pass
 324
 325    def test_m2m_deletion(self):
 326        "Cascaded deletions of m2m relations issue queries on the right database"
 327        # Create a book and author on the other database
 328        dive = Book.objects.using('other').create(title="Dive into Python",
 329                                                  published=datetime.date(2009, 5, 4))
 330
 331        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 332        dive.authors = [mark]
 333
 334        # Check the initial state
 335        self.assertEqual(Person.objects.using('default').count(), 0)
 336        self.assertEqual(Book.objects.using('default').count(), 0)
 337        self.assertEqual(Book.authors.through.objects.using('default').count(), 0)
 338
 339        self.assertEqual(Person.objects.using('other').count(), 1)
 340        self.assertEqual(Book.objects.using('other').count(), 1)
 341        self.assertEqual(Book.authors.through.objects.using('other').count(), 1)
 342
 343        # Delete the object on the other database
 344        dive.delete(using='other')
 345
 346        self.assertEqual(Person.objects.using('default').count(), 0)
 347        self.assertEqual(Book.objects.using('default').count(), 0)
 348        self.assertEqual(Book.authors.through.objects.using('default').count(), 0)
 349
 350        # The person still exists ...
 351        self.assertEqual(Person.objects.using('other').count(), 1)
 352        # ... but the book has been deleted
 353        self.assertEqual(Book.objects.using('other').count(), 0)
 354        # ... and the relationship object has also been deleted.
 355        self.assertEqual(Book.authors.through.objects.using('other').count(), 0)
 356
 357        # Now try deletion in the reverse direction. Set up the relation again
 358        dive = Book.objects.using('other').create(title="Dive into Python",
 359                                                  published=datetime.date(2009, 5, 4))
 360        dive.authors = [mark]
 361
 362        # Check the initial state
 363        self.assertEqual(Person.objects.using('default').count(), 0)
 364        self.assertEqual(Book.objects.using('default').count(), 0)
 365        self.assertEqual(Book.authors.through.objects.using('default').count(), 0)
 366
 367        self.assertEqual(Person.objects.using('other').count(), 1)
 368        self.assertEqual(Book.objects.using('other').count(), 1)
 369        self.assertEqual(Book.authors.through.objects.using('other').count(), 1)
 370
 371        # Delete the object on the other database
 372        mark.delete(using='other')
 373
 374        self.assertEqual(Person.objects.using('default').count(), 0)
 375        self.assertEqual(Book.objects.using('default').count(), 0)
 376        self.assertEqual(Book.authors.through.objects.using('default').count(), 0)
 377
 378        # The person has been deleted ...
 379        self.assertEqual(Person.objects.using('other').count(), 0)
 380        # ... but the book still exists
 381        self.assertEqual(Book.objects.using('other').count(), 1)
 382        # ... and the relationship object has been deleted.
 383        self.assertEqual(Book.authors.through.objects.using('other').count(), 0)
 384
 385    def test_foreign_key_separation(self):
 386        "FK fields are constrained to a single database"
 387        # Create a book and author on the default database
 388        pro = Book.objects.create(title="Pro Django",
 389                                  published=datetime.date(2008, 12, 16))
 390
 391        marty = Person.objects.create(name="Marty Alchin")
 392        george = Person.objects.create(name="George Vilches")
 393
 394        # Create a book and author on the other database
 395        dive = Book.objects.using('other').create(title="Dive into Python",
 396                                                  published=datetime.date(2009, 5, 4))
 397
 398        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 399        chris = Person.objects.using('other').create(name="Chris Mills")
 400
 401        # Save the author's favourite books
 402        pro.editor = george
 403        pro.save()
 404
 405        dive.editor = chris
 406        dive.save()
 407
 408        pro = Book.objects.using('default').get(title="Pro Django")
 409        self.assertEqual(pro.editor.name, "George Vilches")
 410
 411        dive = Book.objects.using('other').get(title="Dive into Python")
 412        self.assertEqual(dive.editor.name, "Chris Mills")
 413
 414        # Check that queries work across foreign key joins
 415        self.assertEqual(list(Person.objects.using('default').filter(edited__title='Pro Django').values_list('name', flat=True)),
 416                          [u'George Vilches'])
 417        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Pro Django').values_list('name', flat=True)),
 418                          [])
 419
 420        self.assertEqual(list(Person.objects.using('default').filter(edited__title='Dive into Python').values_list('name', flat=True)),
 421                          [])
 422        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
 423                          [u'Chris Mills'])
 424
 425        # Reget the objects to clear caches
 426        chris = Person.objects.using('other').get(name="Chris Mills")
 427        dive = Book.objects.using('other').get(title="Dive into Python")
 428
 429        # Retrive related object by descriptor. Related objects should be database-baound
 430        self.assertEqual(list(chris.edited.values_list('title', flat=True)),
 431                          [u'Dive into Python'])
 432
 433    def test_foreign_key_reverse_operations(self):
 434        "FK reverse manipulations are all constrained to a single DB"
 435        dive = Book.objects.using('other').create(title="Dive into Python",
 436                                                       published=datetime.date(2009, 5, 4))
 437
 438        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 439        chris = Person.objects.using('other').create(name="Chris Mills")
 440
 441        # Save the author relations
 442        dive.editor = chris
 443        dive.save()
 444
 445        # Add a second book edited by chris
 446        html5 = Book.objects.using('other').create(title="Dive into HTML5", published=datetime.date(2010, 3, 15))
 447        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
 448                          [])
 449
 450        chris.edited.add(html5)
 451        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
 452                          [u'Chris Mills'])
 453        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
 454                          [u'Chris Mills'])
 455
 456        # Remove the second editor
 457        chris.edited.remove(html5)
 458        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
 459                          [])
 460        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
 461                          [u'Chris Mills'])
 462
 463        # Clear all edited books
 464        chris.edited.clear()
 465        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
 466                          [])
 467        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
 468                          [])
 469
 470        # Create an author through the m2m interface
 471        chris.edited.create(title='Dive into Water', published=datetime.date(2010, 3, 15))
 472        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
 473                          [])
 474        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into Water').values_list('name', flat=True)),
 475                          [u'Chris Mills'])
 476        self.assertEqual(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
 477                          [])
 478
 479    def test_foreign_key_cross_database_protection(self):
 480        "Operations that involve sharing FK objects across databases raise an error"
 481        # Create a book and author on the default database
 482        pro = Book.objects.create(title="Pro Django",
 483                                  published=datetime.date(2008, 12, 16))
 484
 485        marty = Person.objects.create(name="Marty Alchin")
 486
 487        # Create a book and author on the other database
 488        dive = Book.objects.using('other').create(title="Dive into Python",
 489                                                  published=datetime.date(2009, 5, 4))
 490
 491        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 492
 493        # Set a foreign key with an object from a different database
 494        try:
 495            dive.editor = marty
 496            self.fail("Shouldn't be able to assign across databases")
 497        except ValueError:
 498            pass
 499
 500        # Set a foreign key set with an object from a different database
 501        try:
 502            marty.edited = [pro, dive]
 503            self.fail("Shouldn't be able to assign across databases")
 504        except ValueError:
 505            pass
 506
 507        # Add to a foreign key set with an object from a different database
 508        try:
 509            marty.edited.add(dive)
 510            self.fail("Shouldn't be able to assign across databases")
 511        except ValueError:
 512            pass
 513
 514        # BUT! if you assign a FK object when the base object hasn't
 515        # been saved yet, you implicitly assign the database for the
 516        # base object.
 517        chris = Person(name="Chris Mills")
 518        html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15))
 519        # initially, no db assigned
 520        self.assertEqual(chris._state.db, None)
 521        self.assertEqual(html5._state.db, None)
 522
 523        # old object comes from 'other', so the new object is set to use 'other'...
 524        dive.editor = chris
 525        html5.editor = mark
 526        self.assertEqual(chris._state.db, 'other')
 527        self.assertEqual(html5._state.db, 'other')
 528        # ... but it isn't saved yet
 529        self.assertEqual(list(Person.objects.using('other').values_list('name',flat=True)),
 530                          [u'Mark Pilgrim'])
 531        self.assertEqual(list(Book.objects.using('other').values_list('title',flat=True)),
 532                           [u'Dive into Python'])
 533
 534        # When saved (no using required), new objects goes to 'other'
 535        chris.save()
 536        html5.save()
 537        self.assertEqual(list(Person.objects.using('default').values_list('name',flat=True)),
 538                          [u'Marty Alchin'])
 539        self.assertEqual(list(Person.objects.using('other').values_list('name',flat=True)),
 540                          [u'Chris Mills', u'Mark Pilgrim'])
 541        self.assertEqual(list(Book.objects.using('default').values_list('title',flat=True)),
 542                          [u'Pro Django'])
 543        self.assertEqual(list(Book.objects.using('other').values_list('title',flat=True)),
 544                          [u'Dive into HTML5', u'Dive into Python'])
 545
 546        # This also works if you assign the FK in the constructor
 547        water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark)
 548        self.assertEqual(water._state.db, 'other')
 549        # ... but it isn't saved yet
 550        self.assertEqual(list(Book.objects.using('default').values_list('title',flat=True)),
 551                          [u'Pro Django'])
 552        self.assertEqual(list(Book.objects.using('other').values_list('title',flat=True)),
 553                          [u'Dive into HTML5', u'Dive into Python'])
 554
 555        # When saved, the new book goes to 'other'
 556        water.save()
 557        self.assertEqual(list(Book.objects.using('default').values_list('title',flat=True)),
 558                          [u'Pro Django'])
 559        self.assertEqual(list(Book.objects.using('other').values_list('title',flat=True)),
 560                          [u'Dive into HTML5', u'Dive into Python', u'Dive into Water'])
 561
 562    def test_foreign_key_deletion(self):
 563        "Cascaded deletions of Foreign Key relations issue queries on the right database"
 564        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 565        fido = Pet.objects.using('other').create(name="Fido", owner=mark)
 566
 567        # Check the initial state
 568        self.assertEqual(Person.objects.using('default').count(), 0)
 569        self.assertEqual(Pet.objects.using('default').count(), 0)
 570
 571        self.assertEqual(Person.objects.using('other').count(), 1)
 572        self.assertEqual(Pet.objects.using('other').count(), 1)
 573
 574        # Delete the person object, which will cascade onto the pet
 575        mark.delete(using='other')
 576
 577        self.assertEqual(Person.objects.using('default').count(), 0)
 578        self.assertEqual(Pet.objects.using('default').count(), 0)
 579
 580        # Both the pet and the person have been deleted from the right database
 581        self.assertEqual(Person.objects.using('other').count(), 0)
 582        self.assertEqual(Pet.objects.using('other').count(), 0)
 583
 584    def test_foreign_key_validation(self):
 585        "ForeignKey.validate() uses the correct database"
 586        mickey = Person.objects.using('other').create(name="Mickey")
 587        pluto = Pet.objects.using('other').create(name="Pluto", owner=mickey)
 588        self.assertEqual(None, pluto.full_clean())
 589
 590    def test_o2o_separation(self):
 591        "OneToOne fields are constrained to a single database"
 592        # Create a user and profile on the default database
 593        alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
 594        alice_profile = UserProfile.objects.using('default').create(user=alice, flavor='chocolate')
 595
 596        # Create a user and profile on the other database
 597        bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
 598        bob_profile = UserProfile.objects.using('other').create(user=bob, flavor='crunchy frog')
 599
 600        # Retrieve related objects; queries should be database constrained
 601        alice = User.objects.using('default').get(username="alice")
 602        self.assertEqual(alice.userprofile.flavor, "chocolate")
 603
 604        bob = User.objects.using('other').get(username="bob")
 605        self.assertEqual(bob.userprofile.flavor, "crunchy frog")
 606
 607        # Check that queries work across joins
 608        self.assertEqual(list(User.objects.using('default').filter(userprofile__flavor='chocolate').values_list('username', flat=True)),
 609                          [u'alice'])
 610        self.assertEqual(list(User.objects.using('other').filter(userprofile__flavor='chocolate').values_list('username', flat=True)),
 611                          [])
 612
 613        self.assertEqual(list(User.objects.using('default').filter(userprofile__flavor='crunchy frog').values_list('username', flat=True)),
 614                          [])
 615        self.assertEqual(list(User.objects.using('other').filter(userprofile__flavor='crunchy frog').values_list('username', flat=True)),
 616                          [u'bob'])
 617
 618        # Reget the objects to clear caches
 619        alice_profile = UserProfile.objects.using('default').get(flavor='chocolate')
 620        bob_profile = UserProfile.objects.using('other').get(flavor='crunchy frog')
 621
 622        # Retrive related object by descriptor. Related objects should be database-baound
 623        self.assertEqual(alice_profile.user.username, 'alice')
 624        self.assertEqual(bob_profile.user.username, 'bob')
 625
 626    def test_o2o_cross_database_protection(self):
 627        "Operations that involve sharing FK objects across databases raise an error"
 628        # Create a user and profile on the default database
 629        alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
 630
 631        # Create a user and profile on the other database
 632        bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
 633
 634        # Set a one-to-one relation with an object from a different database
 635        alice_profile = UserProfile.objects.using('default').create(user=alice, flavor='chocolate')
 636        try:
 637            bob.userprofile = alice_profile
 638            self.fail("Shouldn't be able to assign across databases")
 639        except ValueError:
 640            pass
 641
 642        # BUT! if you assign a FK object when the base object hasn't
 643        # been saved yet, you implicitly assign the database for the
 644        # base object.
 645        bob_profile = UserProfile.objects.using('other').create(user=bob, flavor='crunchy frog')
 646
 647        new_bob_profile = UserProfile(flavor="spring surprise")
 648
 649        charlie = User(username='charlie',email='charlie@example.com')
 650        charlie.set_unusable_password()
 651
 652        # initially, no db assigned
 653        self.assertEqual(new_bob_profile._state.db, None)
 654        self.assertEqual(charlie._state.db, None)
 655
 656        # old object comes from 'other', so the new object is set to use 'other'...
 657        new_bob_profile.user = bob
 658        charlie.userprofile = bob_profile
 659        self.assertEqual(new_bob_profile._state.db, 'other')
 660        self.assertEqual(charlie._state.db, 'other')
 661
 662        # ... but it isn't saved yet
 663        self.assertEqual(list(User.objects.using('other').values_list('username',flat=True)),
 664                          [u'bob'])
 665        self.assertEqual(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
 666                           [u'crunchy frog'])
 667
 668        # When saved (no using required), new objects goes to 'other'
 669        charlie.save()
 670        bob_profile.save()
 671        new_bob_profile.save()
 672        self.assertEqual(list(User.objects.using('default').values_list('username',flat=True)),
 673                          [u'alice'])
 674        self.assertEqual(list(User.objects.using('other').values_list('username',flat=True)),
 675                          [u'bob', u'charlie'])
 676        self.assertEqual(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
 677                           [u'chocolate'])
 678        self.assertEqual(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
 679                           [u'crunchy frog', u'spring surprise'])
 680
 681        # This also works if you assign the O2O relation in the constructor
 682        denise = User.objects.db_manager('other').create_user('denise','denise@example.com')
 683        denise_profile = UserProfile(flavor="tofu", user=denise)
 684
 685        self.assertEqual(denise_profile._state.db, 'other')
 686        # ... but it isn't saved yet
 687        self.assertEqual(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
 688                           [u'chocolate'])
 689        self.assertEqual(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
 690                           [u'crunchy frog', u'spring surprise'])
 691
 692        # When saved, the new profile goes to 'other'
 693        denise_profile.save()
 694        self.assertEqual(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
 695                           [u'chocolate'])
 696        self.assertEqual(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
 697                           [u'crunchy frog', u'spring surprise', u'tofu'])
 698
 699    def test_generic_key_separation(self):
 700        "Generic fields are constrained to a single database"
 701        # Create a book and author on the default database
 702        pro = Book.objects.create(title="Pro Django",
 703                                  published=datetime.date(2008, 12, 16))
 704
 705        review1 = Review.objects.create(source="Python Monthly", content_object=pro)
 706
 707        # Create a book and author on the other database
 708        dive = Book.objects.using('other').create(title="Dive into Python",
 709                                                  published=datetime.date(2009, 5, 4))
 710
 711        review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
 712
 713        review1 = Review.objects.using('default').get(source="Python Monthly")
 714        self.assertEqual(review1.content_object.title, "Pro Django")
 715
 716        review2 = Review.objects.using('other').get(source="Python Weekly")
 717        self.assertEqual(review2.content_object.title, "Dive into Python")
 718
 719        # Reget the objects to clear caches
 720        dive = Book.objects.using('other').get(title="Dive into Python")
 721
 722        # Retrive related object by descriptor. Related objects should be database-bound
 723        self.assertEqual(list(dive.reviews.all().values_list('source', flat=True)),
 724                          [u'Python Weekly'])
 725
 726    def test_generic_key_reverse_operations(self):
 727        "Generic reverse manipulations are all constrained to a single DB"
 728        dive = Book.objects.using('other').create(title="Dive into Python",
 729                                                  published=datetime.date(2009, 5, 4))
 730
 731        temp = Book.objects.using('other').create(title="Temp",
 732                                                  published=datetime.date(2009, 5, 4))
 733
 734        review1 = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
 735        review2 = Review.objects.using('other').create(source="Python Monthly", content_object=temp)
 736
 737        self.assertEqual(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
 738                          [])
 739        self.assertEqual(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
 740                          [u'Python Weekly'])
 741
 742        # Add a second review
 743        dive.reviews.add(review2)
 744        self.assertEqual(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
 745                          [])
 746        self.assertEqual(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
 747                          [u'Python Monthly', u'Python Weekly'])
 748
 749        # Remove the second author
 750        dive.reviews.remove(review1)
 751        self.assertEqual(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
 752                          [])
 753        self.assertEqual(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
 754                          [u'Python Monthly'])
 755
 756        # Clear all reviews
 757        dive.reviews.clear()
 758        self.assertEqual(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
 759                          [])
 760        self.assertEqual(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
 761                          [])
 762
 763        # Create an author through the generic interface
 764        dive.reviews.create(source='Python Daily')
 765        self.assertEqual(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
 766                          [])
 767        self.assertEqual(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
 768                          [u'Python Daily'])
 769
 770    def test_generic_key_cross_database_protection(self):
 771        "Operations that involve sharing generic key objects across databases raise an error"
 772        # Create a book and author on the default database
 773        pro = Book.objects.create(title="Pro Django",
 774                                  published=datetime.date(2008, 12, 16))
 775
 776        review1 = Review.objects.create(source="Python Monthly", content_object=pro)
 777
 778        # Create a book and author on the other database
 779        dive = Book.objects.using('other').create(title="Dive into Python",
 780                                                  published=datetime.date(2009, 5, 4))
 781
 782        review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
 783
 784        # Set a foreign key with an object from a different database
 785        try:
 786            review1.content_object = dive
 787            self.fail("Shouldn't be able to assign across databases")
 788        except ValueError:
 789            pass
 790
 791        # Add to a foreign key set with an object from a different database
 792        try:
 793            dive.reviews.add(review1)
 794            self.fail("Shouldn't be able to assign across databases")
 795        except ValueError:
 796            pass
 797
 798        # BUT! if you assign a FK object when the base object hasn't
 799        # been saved yet, you implicitly assign the database for the
 800        # base object.
 801        review3 = Review(source="Python Daily")
 802        # initially, no db assigned
 803        self.assertEqual(review3._state.db, None)
 804
 805        # Dive comes from 'other', so review3 is set to use 'other'...
 806        review3.content_object = dive
 807        self.assertEqual(review3._state.db, 'other')
 808        # ... but it isn't saved yet
 809        self.assertEqual(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)),
 810                          [u'Python Monthly'])
 811        self.assertEqual(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),
 812                          [u'Python Weekly'])
 813
 814        # When saved, John goes to 'other'
 815        review3.save()
 816        self.assertEqual(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)),
 817                          [u'Python Monthly'])
 818        self.assertEqual(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),
 819                          [u'Python Daily', u'Python Weekly'])
 820
 821    def test_generic_key_deletion(self):
 822        "Cascaded deletions of Generic Key relations issue queries on the right database"
 823        dive = Book.objects.using('other').create(title="Dive into Python",
 824                                                  published=datetime.date(2009, 5, 4))
 825        review = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
 826
 827        # Check the initial state
 828        self.assertEqual(Book.objects.using('default').count(), 0)
 829        self.assertEqual(Review.objects.using('default').count(), 0)
 830
 831        self.assertEqual(Book.objects.using('other').count(), 1)
 832        self.assertEqual(Review.objects.using('other').count(), 1)
 833
 834        # Delete the Book object, which will cascade onto the pet
 835        dive.delete(using='other')
 836
 837        self.assertEqual(Book.objects.using('default').count(), 0)
 838        self.assertEqual(Review.objects.using('default').count(), 0)
 839
 840        # Both the pet and the person have been deleted from the right database
 841        self.assertEqual(Book.objects.using('other').count(), 0)
 842        self.assertEqual(Review.objects.using('other').count(), 0)
 843
 844    def test_ordering(self):
 845        "get_next_by_XXX commands stick to a single database"
 846        pro = Book.objects.create(title="Pro Django",
 847                                  published=datetime.date(2008, 12, 16))
 848
 849        dive = Book.objects.using('other').create(title="Dive into Python",
 850                                                  published=datetime.date(2009, 5, 4))
 851
 852        learn = Book.objects.using('other').create(title="Learning Python",
 853                                                   published=datetime.date(2008, 7, 16))
 854
 855        self.assertEqual(learn.get_next_by_published().title, "Dive into Python")
 856        self.assertEqual(dive.get_previous_by_published().title, "Learning Python")
 857
 858    def test_raw(self):
 859        "test the raw() method across databases"
 860        dive = Book.objects.using('other').create(title="Dive into Python",
 861            published=datetime.date(2009, 5, 4))
 862        val = Book.objects.db_manager("other").raw('SELECT id FROM multiple_database_book')
 863        self.assertEqual(map(lambda o: o.pk, val), [dive.pk])
 864
 865        val = Book.objects.raw('SELECT id FROM multiple_database_book').using('other')
 866        self.assertEqual(map(lambda o: o.pk, val), [dive.pk])
 867
 868    def test_select_related(self):
 869        "Database assignment is retained if an object is retrieved with select_related()"
 870        # Create a book and author on the other database
 871        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 872        dive = Book.objects.using('other').create(title="Dive into Python",
 873                                                  published=datetime.date(2009, 5, 4),
 874                                                  editor=mark)
 875
 876        # Retrieve the Person using select_related()
 877        book = Book.objects.using('other').select_related('editor').get(title="Dive into Python")
 878
 879        # The editor instance should have a db state
 880        self.assertEqual(book.editor._state.db, 'other')
 881
 882    def test_subquery(self):
 883        """Make sure as_sql works with subqueries and master/slave."""
 884        sub = Person.objects.using('other').filter(name='fff')
 885        qs = Book.objects.filter(editor__in=sub)
 886
 887        # When you call __str__ on the query object, it doesn't know about using
 888        # so it falls back to the default. If the subquery explicitly uses a
 889        # different database, an error should be raised.
 890        self.assertRaises(ValueError, str, qs.query)
 891
 892        # Evaluating the query shouldn't work, either
 893        try:
 894            for obj in qs:
 895                pass
 896            self.fail('Iterating over query should raise ValueError')
 897        except ValueError:
 898            pass
 899
 900    def test_related_manager(self):
 901        "Related managers return managers, not querysets"
 902        mark = Person.objects.using('other').create(name="Mark Pilgrim")
 903
 904        # extra_arg is removed by the BookManager's implementation of
 905        # create(); but the BookManager's implementation won't get called
 906        # unless edited returns a Manager, not a queryset
 907        mark.book_set.create(title="Dive into Python",
 908                             published=datetime.date(2009, 5, 4),
 909                             extra_arg=True)
 910
 911        mark.book_set.get_or_create(title="Dive into Python",
 912                                    published=datetime.date(2009, 5, 4),
 913                                    extra_arg=True)
 914
 915        mark.edited.create(title="Dive into Water",
 916                           published=datetime.date(2009, 5, 4),
 917                           extra_arg=True)
 918
 919        mark.edited.get_or_create(title="Dive into Water",
 920                                  published=datetime.date(2009, 5, 4),
 921                                  extra_arg=True)
 922
 923class TestRouter(object):
 924    # A test router. The behaviour is vaguely master/slave, but the
 925    # databases aren't assumed to propagate changes.
 926    def db_for_read(self, model, instance=None, **hints):
 927        if instance:
 928            return instance._state.db or 'other'
 929        return 'other'
 930
 931    def db_for_write(self, model, **hints):
 932        return DEFAULT_DB_ALIAS
 933
 934    def allow_relation(self, obj1, obj2, **hints):
 935        return obj1._state.db in ('default', 'other') and obj2._state.db in ('default', 'other')
 936
 937    def allow_syncdb(self, db, model):
 938        return True
 939
 940class AuthRouter(object):
 941    """A router to control all database operations on models in
 942    the contrib.auth application"""
 943
 944    def db_for_read(self, model, **hints):
 945        "Point all read operations on auth models to 'default'"
 946        if model._meta.app_label == 'auth':
 947            # We use default here to ensure we can tell the difference
 948            # between a read request and a write request for Auth objects
 949            return 'default'
 950        return None
 951
 952    def db_for_write(self, model, **hints):
 953        "Point all operations on auth models to 'other'"
 954        if model._meta.app_label == 'auth':
 955            return 'other'
 956        return None
 957
 958    def allow_relation(self, obj1, obj2, **hints):
 959        "Allow any relation if a model in Auth is involved"
 960        if obj1._meta.app_label == 'auth' or obj2._meta.app_label == 'auth':
 961            return True
 962        return None
 963
 964    def allow_syncdb(self, db, model):
 965        "Make sure the auth app only appears on the 'other' db"
 966        if db == 'other':
 967            return model._meta.app_label == 'auth'
 968        elif model._meta.app_label == 'auth':
 969            return False
 970        return None
 971
 972class WriteRouter(object):
 973    # A router that only expresses an opinion on writes
 974    def db_for_write(self, model, **hints):
 975        return 'writer'
 976
 977class RouterTestCase(TestCase):
 978    multi_db = True
 979
 980    def setUp(self):
 981        # Make the 'other' database appear to be a slave of the 'default'
 982        self.old_routers = router.routers
 983        router.routers = [TestRouter()]
 984
 985    def tearDown(self):
 986        # Restore the 'other' database as an independent database
 987        router.routers = self.old_routers
 988
 989    def test_db_selection(self):
 990        "Check that querysets obey the router for db suggestions"
 991        self.assertEqual(Book.objects.db, 'other')
 992        self.assertEqual(Book.objects.all().db, 'other')
 993
 994        self.assertEqual(Book.objects.using('default').db, 'default')
 995
 996        self.assertEqual(Book.objects.db_manager('default').db, 'default')
 997        self.assertEqual(Book.objects.db_manager('default').all().db, 'default')
 998
 999    def test_syncdb_selection(self):
1000        "Synchronization behaviour is predicatable"
1001
1002        self.assertTrue(router.allow_syncdb('default', User))
1003        self.assertTrue(router.allow_syncdb('default', Book))
1004
1005        self.assertTrue(router.allow_syncdb('other', User))
1006        self.assertTrue(router.allow_syncdb('other', Book))
1007
1008        # Add the auth router to the chain.
1009        # TestRouter is a universal synchronizer, so it should have no effect.
1010        router.routers = [TestRouter(), AuthRouter()]
1011
1012        self.assertTrue(router.allow_syncdb('default', User))
1013        self.assertTrue(router.allow_syncdb('default', Book))
1014
1015        self.assertTrue(router.allow_syncdb('other', User))
1016        self.assertTrue(router.allow_syncdb('other', Book))
1017
1018        # Now check what happens if the router order is the other way around
1019        router.routers = [AuthRouter(), TestRouter()]
1020
1021        self.assertFalse(router.allow_syncdb('default', User))
1022        self.assertTrue(router.allow_syncdb('default', Book))
1023
1024        self.assertTrue(router.allow_syncdb('other', User))
1025        self.assertFalse(router.allow_syncdb('other', Book))
1026
1027    def test_partial_router(self):
1028        "A router can choose to implement a subset of methods"
1029        dive = Book.objects.using('other').create(title="Dive into Python",
1030                                                  published=datetime.date(2009, 5, 4))
1031
1032        # First check the baseline behaviour
1033
1034        self.assertEqual(router.db_for_read(User), 'other')
1035        self.assertEqual(router.db_for_read(Book), 'other')
1036
1037        self.assertEqual(router.db_for_write(User), 'default')
1038        self.assertEqual(router.db_for_write(Book), 'default')
1039
1040        self.assertTrue(router.allow_relation(dive, dive))
1041
1042        self.assertTrue(router.allow_syncdb('default', User))
1043        self.assertTrue(router.allow_syncdb('default', Book))
1044
1045        router.routers = [WriteRouter(), AuthRouter(), TestRouter()]
1046
1047        self.assertEqual(router.db_for_read(User), 'default')
1048        self.assertEqual(router.db_for_read(Book), 'other')
1049
1050        self.assertEqual(router.db_for_write(User), 'writer')
1051        self.assertEqual(router.db_for_write(Book), 'writer')
1052
1053        self.assertTrue(router.allow_relatio

Large files files are truncated, but you can click here to view the full file