From f2604c331da724e7842ade39d6fe0f44be99dc36 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 23 Nov 2009 16:42:56 +0000 Subject: [PATCH] [soc2009/multidb] Updated testing services to handle multiple databases better. Includes extra tests (some failing) for multiple database support. Patch from Russell Keith-Magee. git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11764 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- TODO | 4 - django/db/backends/__init__.py | 5 +- django/db/backends/creation.py | 8 +- django/db/utils.py | 18 +- django/test/simple.py | 2 +- django/test/testcases.py | 59 ++-- docs/internals/contributing.txt | 40 ++- docs/ref/settings.txt | 8 +- docs/topics/testing.txt | 33 ++ .../fixtures/multidb-common.json | 10 + .../fixtures/multidb.default.json | 10 + .../fixtures/multidb.other.json | 10 + .../multiple_database/models.py | 14 +- .../multiple_database/tests.py | 285 ++++++++++++++---- 14 files changed, 375 insertions(+), 131 deletions(-) create mode 100644 tests/regressiontests/multiple_database/fixtures/multidb-common.json create mode 100644 tests/regressiontests/multiple_database/fixtures/multidb.default.json create mode 100644 tests/regressiontests/multiple_database/fixtures/multidb.other.json diff --git a/TODO b/TODO index 51aa6ad933..23ce75c8d6 100644 --- a/TODO +++ b/TODO @@ -13,10 +13,6 @@ Required for v1.2 * Resolve the public facing UI issues around using multi-db * Should we take the opportunity to modify DB backends to use fully qualified paths? * Meta.using? Is is still required/desirable? - * Testing infrastructure - * Most tests don't need multidb. Some absolutely require it, but only to prove you - can write to a different db. Second DB could be a SQLite temp file. Need to have - test infrastructure to allow creation of the temp database. * Cleanup of new API entry points * validate() on a field * name/purpose clash with Honza? diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index f71016b5ca..24caf67c14 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -26,7 +26,7 @@ class BaseDatabaseWrapper(local): """ ops = None - def __init__(self, settings_dict): + def __init__(self, settings_dict, alias='default'): # `settings_dict` should be a dictionary containing keys such as # DATABASE_NAME, DATABASE_USER, etc. It's called `settings_dict` # instead of `settings` to disambiguate it from Django settings @@ -34,6 +34,7 @@ class BaseDatabaseWrapper(local): self.connection = None self.queries = [] self.settings_dict = settings_dict + self.alias = alias def __eq__(self, other): return self.settings_dict == other.settings_dict @@ -117,7 +118,7 @@ class BaseDatabaseOperations(object): row. """ compiler_module = "django.db.models.sql.compiler" - + def __init__(self): self._cache = {} diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 2608f153b3..6de9484324 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -316,13 +316,13 @@ class BaseDatabaseCreation(object): output.append(ds) return output - def create_test_db(self, verbosity=1, autoclobber=False, alias=None): + def create_test_db(self, verbosity=1, autoclobber=False): """ Creates a test database, prompting the user for confirmation if the database already exists. Returns the name of the test database created. """ if verbosity >= 1: - print "Creating test database..." + print "Creating test database '%s'..." % self.connection.alias test_database_name = self._create_test_db(verbosity, autoclobber) @@ -334,7 +334,7 @@ class BaseDatabaseCreation(object): # FIXME we end up loading the same fixture into the default DB for each # DB we have, this causes various test failures, but can't really be # fixed until we have an API for saving to a specific DB - call_command('syncdb', verbosity=verbosity, interactive=False, database=alias) + call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias) if settings.CACHE_BACKEND.startswith('db://'): from django.core.cache import parse_backend_uri @@ -404,7 +404,7 @@ class BaseDatabaseCreation(object): database already exists. Returns the name of the test database created. """ if verbosity >= 1: - print "Destroying test database..." + print "Destroying test database '%s'..." % self.connection.alias self.connection.close() test_database_name = self.connection.settings_dict['DATABASE_NAME'] self.connection.settings_dict['DATABASE_NAME'] = old_database_name diff --git a/django/db/utils.py b/django/db/utils.py index c9effde28d..aeee8bbfcb 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -67,7 +67,7 @@ class ConnectionHandler(object): self.ensure_defaults(alias) db = self.databases[alias] backend = load_backend(db['DATABASE_ENGINE']) - conn = backend.DatabaseWrapper(db) + conn = backend.DatabaseWrapper(db, alias) self._connections[alias] = conn return conn @@ -76,19 +76,3 @@ class ConnectionHandler(object): def all(self): return [self[alias] for alias in self] - - def alias_for_connection(self, connection): - """ - Returns the alias for the given connection object. - """ - return self.alias_for_settings(connection.settings_dict) - - def alias_for_settings(self, settings_dict): - """ - Returns the alias for the given settings dictionary. - """ - for alias in self: - conn_settings = self.databases[alias] - if conn_settings == settings_dict: - return alias - return None diff --git a/django/test/simple.py b/django/test/simple.py index 2f74f3b0c2..c850d0634d 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -191,7 +191,7 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]): for alias in connections: connection = connections[alias] old_names.append((connection, connection.settings_dict['DATABASE_NAME'])) - connection.creation.create_test_db(verbosity, autoclobber=not interactive, alias=alias) + connection.creation.create_test_db(verbosity, autoclobber=not interactive) result = unittest.TextTestRunner(verbosity=verbosity).run(suite) for connection, old_name in old_names: connection.creation.destroy_test_db(old_name, verbosity) diff --git a/django/test/testcases.py b/django/test/testcases.py index adae5d4494..d3bc1eaeba 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -7,7 +7,7 @@ from django.conf import settings from django.core import mail from django.core.management import call_command from django.core.urlresolvers import clear_url_caches -from django.db import transaction, connections +from django.db import transaction, connections, DEFAULT_DB_ALIAS from django.http import QueryDict from django.test import _doctest as doctest from django.test.client import Client @@ -225,11 +225,19 @@ class TransactionTestCase(unittest.TestCase): mail.outbox = [] def _fixture_setup(self): - call_command('flush', verbosity=0, interactive=False) - if hasattr(self, 'fixtures'): - # We have to use this slightly awkward syntax due to the fact - # that we're using *args and **kwargs together. - call_command('loaddata', *self.fixtures, **{'verbosity': 0}) + # If the test case has a multi_db=True flag, flush all databases. + # Otherwise, just flush default. + if getattr(self, 'multi_db', False): + databases = connections + else: + databases = [DEFAULT_DB_ALIAS] + for db in databases: + call_command('flush', verbosity=0, interactive=False, database=db) + + if hasattr(self, 'fixtures'): + # We have to use this slightly awkward syntax due to the fact + # that we're using *args and **kwargs together. + call_command('loaddata', *self.fixtures, **{'verbosity': 0, 'database': db}) def _urlconf_setup(self): if hasattr(self, 'urls'): @@ -453,27 +461,44 @@ class TestCase(TransactionTestCase): if not connections_support_transactions(): return super(TestCase, self)._fixture_setup() - for conn in connections: - transaction.enter_transaction_management(using=conn) - transaction.managed(True, using=conn) + # If the test case has a multi_db=True flag, setup all databases. + # Otherwise, just use default. + if getattr(self, 'multi_db', False): + databases = connections + else: + databases = [DEFAULT_DB_ALIAS] + + for db in databases: + transaction.enter_transaction_management(using=db) + transaction.managed(True, using=db) disable_transaction_methods() from django.contrib.sites.models import Site Site.objects.clear_cache() - if hasattr(self, 'fixtures'): - call_command('loaddata', *self.fixtures, **{ - 'verbosity': 0, - 'commit': False - }) + for db in databases: + if hasattr(self, 'fixtures'): + call_command('loaddata', *self.fixtures, **{ + 'verbosity': 0, + 'commit': False, + 'database': db + }) def _fixture_teardown(self): if not connections_support_transactions(): return super(TestCase, self)._fixture_teardown() + # If the test case has a multi_db=True flag, teardown all databases. + # Otherwise, just teardown default. + if getattr(self, 'multi_db', False): + databases = connections + else: + databases = [DEFAULT_DB_ALIAS] + restore_transaction_methods() - for conn in connections: - transaction.rollback(using=conn) - transaction.leave_transaction_management(using=conn) + for db in databases: + transaction.rollback(using=db) + transaction.leave_transaction_management(using=db) + for connection in connections.all(): connection.close() diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 46255d439b..30d8ed75fd 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -752,20 +752,46 @@ To run the tests, ``cd`` to the ``tests/`` directory and type: ./runtests.py --settings=path.to.django.settings Yes, the unit tests need a settings module, but only for database connection -info, with the ``DATABASES`` setting. +info. Your :setting:`DATABASES` setting needs to define two databases: -If you're using the ``sqlite3`` database backend, no further settings are -needed. A temporary database will be created in memory when running the tests. + * A ``default`` database. This database should use the backend that + you want to use for primary testing -If you're using another backend: + * A database with the alias ``other``. The ``other`` database is + used to establish that queries can be directed to different + databases. As a result, this database can use any backend you + want. It doesn't need to use the same backend as the ``default`` + database (although it can use the same backend if you want to). - * Your the ``DATABASE_USER`` option for each of your databases needs to +If you're using the ``sqlite3`` database backend, you need to define +:setting:`DATABASE_ENGINE` for both databases, plus a +:setting:`TEST_DATABASE_NAME` for the ``other`` database. The +following is a minimal settings file that can be used to test SQLite:: + + DATABASES = { + 'default': { + 'DATABASE_ENGINE': 'sqlite3' + }, + 'other': { + 'DATABASE_ENGINE': 'sqlite3', + 'TEST_DATABASE_NAME: 'other_db' + } + } + + +If you're using another backend, you will need to provide other details for +each database: + + * The :setting:`DATABASE_USER` option for each of your databases needs to specify an existing user account for the database. - * The ``DATABASE_NAME`` option must be the name of an existing database to + * The :setting:`DATABASE_PASSWORD` option needs to provide the password for + the :setting:`DATABASE_USER` that has been specified. + + * The :setting:`DATABASE_NAME` option must be the name of an existing database to which the given user has permission to connect. The unit tests will not touch this database; the test runner creates a new database whose name is - ``DATABASE_NAME`` prefixed with ``test_``, and this test database is + :setting:`DATABASE_NAME` prefixed with ``test_``, and this test database is deleted when the tests are finished. This means your user account needs permission to execute ``CREATE DATABASE``. diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index a2c4e54363..6205777087 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -202,11 +202,11 @@ Django. It is a nested dictionary who's contents maps aliases to a dictionary containing the options for an individual database. The following inner options are used: -.. admonition:: Note +.. deprecated: 1.2 - In versions of Django prior to 1.2 each of the following were individual - settings, the usage of those has been deprecated but will be supported - until Django 1.4. + In versions of Django prior to 1.2 each of the following were + individual settings. The usage of the standalone database settings + has been deprecated, and will be removed in Django 1.4. .. setting:: DATABASE_ENGINE diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 269ef9599f..c8d7819c7b 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1037,6 +1037,39 @@ URLconf for the duration of the test case. .. _emptying-test-outbox: +Multi-database support +~~~~~~~~~~~~~~~~~~~~~~ + +.. attribute:: TestCase.multi_db + +.. versionadded:: 1.2 + +Django sets up a test database corresponding to every database that is +defined in the :setting:``DATABASES`` definition in your settings +file. However, a big part of the time taken to run a Django TestCase +is consumed by the call to ``flush`` that ensures that you have a +clean database at the start of each test run. If you have multiple +databases, multiple flushes are required (one for each database), +which can be a time consuming activity -- especially if your tests +don't need to test multi-database activity. + +As an optimization, Django only flushes the ``default`` database at +the start of each test run. If your setup contains multiple databases, +and you have a test that requires every database to be clean, you can +use the ``multi_db`` attribute on the test suite to request a full +flush. + +For example:: + + class TestMyViews(TestCase): + multi_db = True + + def testIndexPageView(self): + call_some_test_code() + +This test case will flush *all* the test databases before running +``testIndexPageView``. + Emptying the test outbox ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/multiple_database/fixtures/multidb-common.json b/tests/regressiontests/multiple_database/fixtures/multidb-common.json new file mode 100644 index 0000000000..33134173b9 --- /dev/null +++ b/tests/regressiontests/multiple_database/fixtures/multidb-common.json @@ -0,0 +1,10 @@ +[ + { + "pk": 1, + "model": "multiple_database.book", + "fields": { + "title": "The Definitive Guide to Django", + "published": "2009-7-8" + } + } +] \ No newline at end of file diff --git a/tests/regressiontests/multiple_database/fixtures/multidb.default.json b/tests/regressiontests/multiple_database/fixtures/multidb.default.json new file mode 100644 index 0000000000..eaa600ae73 --- /dev/null +++ b/tests/regressiontests/multiple_database/fixtures/multidb.default.json @@ -0,0 +1,10 @@ +[ + { + "pk": 2, + "model": "multiple_database.book", + "fields": { + "title": "Dive into Python", + "published": "2009-5-4" + } + } +] \ No newline at end of file diff --git a/tests/regressiontests/multiple_database/fixtures/multidb.other.json b/tests/regressiontests/multiple_database/fixtures/multidb.other.json new file mode 100644 index 0000000000..1377212efb --- /dev/null +++ b/tests/regressiontests/multiple_database/fixtures/multidb.other.json @@ -0,0 +1,10 @@ +[ + { + "pk": 2, + "model": "multiple_database.book", + "fields": { + "title": "Pro Django", + "published": "2008-12-16" + } + } +] \ No newline at end of file diff --git a/tests/regressiontests/multiple_database/models.py b/tests/regressiontests/multiple_database/models.py index 7808358da6..c7f1c95100 100644 --- a/tests/regressiontests/multiple_database/models.py +++ b/tests/regressiontests/multiple_database/models.py @@ -14,19 +14,7 @@ class Book(models.Model): class Author(models.Model): name = models.CharField(max_length=100) + favourite_book = models.ForeignKey(Book, null=True, related_name='favourite_of') def __unicode__(self): return self.name - -if len(settings.DATABASES) > 1: - article_using = filter(lambda o: o != DEFAULT_DB_ALIAS, settings.DATABASES.keys())[0] - class Article(models.Model): - title = models.CharField(max_length=100) - author = models.ForeignKey(Author) - - def __unicode__(self): - return self.title - - class Meta: - ordering = ('title',) - using = article_using diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index bc1be2cf94..b863ab378c 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -14,82 +14,243 @@ try: except ImportError: pass -class ConnectionHandlerTestCase(TestCase): - def test_alias_for_connection(self): - for db in connections: - self.assertEqual(db, connections.alias_for_connection(connections[db])) - - class QueryTestCase(TestCase): + multi_db = True + + def test_default_creation(self): + "Objects created on the default database don't leak onto other databases" + # Create a book on the default database using create() + Book.objects.create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + # Create a book on the default database using a save + pro = Book() + pro.title="Pro Django" + pro.published = datetime.date(2008, 12, 16) + pro.save() + + # Check that book exists on the default database, but not on other database + try: + Book.objects.get(title="Dive into Python") + Book.objects.using('default').get(title="Dive into Python") + except Book.DoesNotExist: + self.fail('"Dive Into Python" should exist on default database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.using('other').get, + title="Dive into Python" + ) + + try: + Book.objects.get(title="Pro Django") + Book.objects.using('default').get(title="Pro Django") + except Book.DoesNotExist: + self.fail('"Pro Django" should exist on default database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.using('other').get, + title="Pro Django" + ) + + + def test_other_creation(self): + "Objects created on another database don't leak onto the default database" + # Create a book on the second database + Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + # Create a book on the default database using a save + pro = Book() + pro.title="Pro Django" + pro.published = datetime.date(2008, 12, 16) + pro.save(using='other') + + # Check that book exists on the default database, but not on other database + try: + Book.objects.using('other').get(title="Dive into Python") + except Book.DoesNotExist: + self.fail('"Dive Into Python" should exist on other database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.get, + title="Dive into Python" + ) + self.assertRaises(Book.DoesNotExist, + Book.objects.using('default').get, + title="Dive into Python" + ) + + try: + Book.objects.using('other').get(title="Pro Django") + except Book.DoesNotExist: + self.fail('"Pro Django" should exist on other database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.get, + title="Pro Django" + ) + self.assertRaises(Book.DoesNotExist, + Book.objects.using('default').get, + title="Pro Django" + ) + def test_basic_queries(self): - for db in connections: - self.assertRaises(Book.DoesNotExist, - lambda: Book.objects.using(db).get(title="Dive into Python")) - Book.objects.using(db).create(title="Dive into Python", - published=datetime.date(2009, 5, 4)) + "Queries are constrained to a single database" + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) - for db in connections: - books = Book.objects.all().using(db) - self.assertEqual(books.count(), 1) - self.assertEqual(len(books), 1) - self.assertEqual(books[0].title, "Dive into Python") - self.assertEqual(books[0].published, datetime.date(2009, 5, 4)) + dive = Book.objects.using('other').get(published=datetime.date(2009, 5, 4)) + self.assertEqual(dive.title, "Dive into Python") + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published=datetime.date(2009, 5, 4)) - for db in connections: - self.assertRaises(Book.DoesNotExist, - lambda: Book.objects.using(db).get(title="Pro Django")) - book = Book(title="Pro Django", published=datetime.date(2008, 12, 16)) - book.save(using=db) + dive = Book.objects.using('other').get(title__icontains="dive") + self.assertEqual(dive.title, "Dive into Python") + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__icontains="dive") - for db in connections: - books = Book.objects.all().using(db) - self.assertEqual(books.count(), 2) - self.assertEqual(len(books), 2) - self.assertEqual(books[0].title, "Dive into Python") - self.assertEqual(books[1].title, "Pro Django") + dive = Book.objects.using('other').get(title__iexact="dive INTO python") + self.assertEqual(dive.title, "Dive into Python") + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__iexact="dive INTO python") - pro = Book.objects.using(db).get(published=datetime.date(2008, 12, 16)) - self.assertEqual(pro.title, "Pro Django") + dive = Book.objects.using('other').get(published__year=2009) + self.assertEqual(dive.title, "Dive into Python") + self.assertEqual(dive.published, datetime.date(2009, 5, 4)) + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published__year=2009) - dive = Book.objects.using(db).get(title__icontains="dive") - self.assertEqual(dive.title, "Dive into Python") + years = Book.objects.using('other').dates('published', 'year') + self.assertEqual([o.year for o in years], [2009]) + years = Book.objects.using('default').dates('published', 'year') + self.assertEqual([o.year for o in years], []) - dive = Book.objects.using(db).get(title__iexact="dive INTO python") - self.assertEqual(dive.title, "Dive into Python") + months = Book.objects.using('other').dates('published', 'month') + self.assertEqual([o.month for o in months], [5]) + months = Book.objects.using('default').dates('published', 'month') + self.assertEqual([o.month for o in months], []) - pro = Book.objects.using(db).get(published__year=2008) - self.assertEqual(pro.title, "Pro Django") - self.assertEqual(pro.published, datetime.date(2008, 12, 16)) + def test_m2m(self): + "M2M fields are constrained to a single database" + # Create a book and author on the default database + dive = Book.objects.create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) - years = Book.objects.using(db).dates('published', 'year') - self.assertEqual([o.year for o in years], [2008, 2009]) + mark = Author.objects.create(name="Mark Pilgrim") + + # Create a book and author on the other database + pro = Book.objects.using('other').create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + marty = Author.objects.using('other').create(name="Marty Alchin") + + # Save the author relations + dive.authors = [mark] + pro.authors = [marty] + + # Inspect the m2m tables directly. + # There should be 1 entry in each database + self.assertEquals(Book.authors.through.objects.using('default').count(), 1) + self.assertEquals(Book.authors.through.objects.using('other').count(), 1) + + # Check that queries work across m2m joins + self.assertEquals(Book.objects.using('default').filter(authors__name='Mark Pilgrim').values_list('title', flat=True), + ['Dive into Python']) + self.assertEquals(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True), + []) + + self.assertEquals(Book.objects.using('default').filter(authors__name='Marty Alchin').values_list('title', flat=True), + []) + self.assertEquals(Book.objects.using('other').filter(authors__name='Marty Alchin').values_list('title', flat=True), + ['Pro Django']) + + def test_foreign_key(self): + "FK fields are constrained to a single database" + # Create a book and author on the default database + dive = Book.objects.create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + mark = Author.objects.create(name="Mark Pilgrim") + + # Create a book and author on the other database + pro = Book.objects.using('other').create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + marty = Author.objects.using('other').create(name="Marty Alchin") + + # Save the author's favourite books + mark.favourite_book = dive + mark.save() + + marty.favourite_book = pro + marty.save() # FIXME Should this be save(using=alias)? + + mark = Author.objects.using('default').get(name="Mark Pilgrim") + self.assertEquals(mark.favourite_book.title, "Dive into Python") + + marty = Author.objects.using('other').get(name='Marty Alchin') + self.assertEquals(marty.favourite_book.title, "Dive into Python") + + try: + mark.favourite_book = marty + self.fail("Shouldn't be able to assign across databases") + except Exception: # FIXME - this should be more explicit + pass + + # Check that queries work across foreign key joins + self.assertEquals(Book.objects.using('default').filter(favourite_of__name='Mark Pilgrim').values_list('title', flat=True), + ['Dive into Python']) + self.assertEquals(Book.objects.using('other').filter(favourite_of__name='Mark Pilgrim').values_list('title', flat=True), + []) + + self.assertEquals(Book.objects.using('default').filter(favourite_of__name='Marty Alchin').values_list('title', flat=True), + []) + self.assertEquals(Book.objects.using('other').filter(favourite_of__name='Marty Alchin').values_list('title', flat=True), + ['Pro Django']) + +class FixtureTestCase(TestCase): + multi_db = True + fixtures = ['multidb-common', 'multidb'] + + def test_fixture_loading(self): + "Multi-db fixtures are loaded correctly" + # Check that "Dive into Python" exists on the default database, but not on other database + try: + Book.objects.get(title="Dive into Python") + Book.objects.using('default').get(title="Dive into Python") + except Book.DoesNotExist: + self.fail('"Dive Into Python" should exist on default database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.using('other').get, + title="Dive into Python" + ) + + # Check that "Pro Django" exists on the default database, but not on other database + try: + Book.objects.using('other').get(title="Pro Django") + except Book.DoesNotExist: + self.fail('"Pro Django" should exist on other database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.get, + title="Pro Django" + ) + self.assertRaises(Book.DoesNotExist, + Book.objects.using('default').get, + title="Pro Django" + ) + + # Check that "Definitive Guide" exists on the both databases + try: + Book.objects.get(title="The Definitive Guide to Django") + Book.objects.using('default').get(title="The Definitive Guide to Django") + Book.objects.using('other').get(title="The Definitive Guide to Django") + except Book.DoesNotExist: + self.fail('"The Definitive Guide to Django" should exist on both databases') - months = Book.objects.dates('published', 'month').using(db) - self.assertEqual(sorted(o.month for o in months), [5, 12]) class PickleQuerySetTestCase(TestCase): + multi_db = True + def test_pickling(self): for db in connections: + Book.objects.using(db).create(title='Pro Django', published=datetime.date(2008, 12, 16)) qs = Book.objects.all() self.assertEqual(qs._using, pickle.loads(pickle.dumps(qs))._using) - - -if len(settings.DATABASES) > 1: - class MetaUsingTestCase(TestCase): - def test_meta_using_queries(self): - auth = Author.objects.create(name="Zed Shaw") - a = Article.objects.create(title="Django Rules!", author=auth) - self.assertEqual(Article.objects.get(title="Django Rules!"), a) - for db in connections: - if db == article_using: - a1 = Article.objects.using(db).get(title="Django Rules!") - self.assertEqual(a1, a) - self.assertEqual(a1.author, auth) - else: - self.assertRaises(Article.DoesNotExist, - lambda: Article.objects.using(db).get(title="Django Rules!")) - a.delete() - self.assertRaises(Article.DoesNotExist, - lambda: Article.objects.get(title="Django Rules!")) - self.assertRaises(ValueError, - lambda: list(Article.objects.get(pk__in=Article.objects.using('default'))))