mirror of
https://github.com/django/django.git
synced 2025-07-05 02:09:13 +00:00
[multi-db] Added initialization code to runtests.py that creates
two named test databases, 'django_test_db_a' and 'django_test_db_b', along with the default test database. Moved multiple databases test to modeltests and reorganized it to match other model tests. git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3241 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
763e087de1
commit
4190c9e16f
1
tests/modeltests/multiple_databases/__init__.py
Normal file
1
tests/modeltests/multiple_databases/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
pass
|
96
tests/modeltests/multiple_databases/models.py
Normal file
96
tests/modeltests/multiple_databases/models.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
XXX. Using multiple database connections
|
||||||
|
|
||||||
|
Django normally uses only a single database connection. However,
|
||||||
|
support is available for using any number of different, named
|
||||||
|
connections. Multiple database support is entirely optional and has
|
||||||
|
no impact on your application if you don't use it.
|
||||||
|
|
||||||
|
Named connections are defined in your settings module. Create a
|
||||||
|
`DATABASES` variable that is a dict, mapping connection names to their
|
||||||
|
particulars. The particulars are defined in a dict with the same keys
|
||||||
|
as the variable names as are used to define the default connection.
|
||||||
|
|
||||||
|
Access to named connections is through `django.db.connections`, which
|
||||||
|
behaves like a dict: you access connections by name. Connections are
|
||||||
|
established lazily, when accessed. `django.db.connections[database]`
|
||||||
|
holds a `ConnectionInfo` instance, with the attributes:
|
||||||
|
`DatabaseError`, `backend`, `get_introspection_module`,
|
||||||
|
`get_creation_module`, and `runshell`.
|
||||||
|
|
||||||
|
Models can define which connection to use, by name. To use a named
|
||||||
|
connection, set the `db_connection` property in the model's Meta class
|
||||||
|
to the name of the connection. The name used must be a key in
|
||||||
|
settings.DATABASES, of course.
|
||||||
|
|
||||||
|
To access a model's connection, use `model._meta.connection`. To find
|
||||||
|
the backend or other connection metadata, use
|
||||||
|
`model._meta.connection_info`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Artist(models.Model):
|
||||||
|
name = models.CharField(maxlength=100)
|
||||||
|
alive = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_connection = 'django_test_db_a'
|
||||||
|
|
||||||
|
class Widget(models.Model):
|
||||||
|
code = models.CharField(maxlength=10, unique=True)
|
||||||
|
weight = models.IntegerField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.code
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_connection = 'django_test_db_b'
|
||||||
|
|
||||||
|
class Vehicle(models.Model):
|
||||||
|
make = models.CharField(maxlength=20)
|
||||||
|
model = models.CharField(maxlength=20)
|
||||||
|
year = models.IntegerField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%d %s %s" % (self.year, self.make, self.model)
|
||||||
|
|
||||||
|
|
||||||
|
API_TESTS = """
|
||||||
|
|
||||||
|
# See what connections are defined. django.db.connections acts like a dict.
|
||||||
|
>>> from django.db import connection, connections
|
||||||
|
>>> from django.conf import settings
|
||||||
|
>>> connections.keys()
|
||||||
|
['django_test_db_a', 'django_test_db_b']
|
||||||
|
|
||||||
|
# Each connection references its settings
|
||||||
|
>>> connections['django_test_db_a'].settings.DATABASE_NAME == settings.DATABASES['django_test_db_a']['DATABASE_NAME']
|
||||||
|
True
|
||||||
|
>>> connections['django_test_db_b'].settings.DATABASE_NAME == settings.DATABASES['django_test_db_b']['DATABASE_NAME']
|
||||||
|
True
|
||||||
|
>>> connections['django_test_db_b'].settings.DATABASE_NAME == settings.DATABASES['django_test_db_a']['DATABASE_NAME']
|
||||||
|
False
|
||||||
|
|
||||||
|
# Invalid connection names raise ImproperlyConfigured
|
||||||
|
>>> connections['bad']
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ImproperlyConfigured: No database connection 'bad' has been configured
|
||||||
|
|
||||||
|
# Models can access their connections through their _meta properties
|
||||||
|
>>> Artist._meta.connection.settings == connections['django_test_db_a'].settings
|
||||||
|
True
|
||||||
|
>>> Widget._meta.connection.settings == connections['django_test_db_b'].settings
|
||||||
|
True
|
||||||
|
>>> Vehicle._meta.connection.settings == connection.settings
|
||||||
|
True
|
||||||
|
>>> Artist._meta.connection.settings == Widget._meta.connection.settings
|
||||||
|
False
|
||||||
|
>>> Artist._meta.connection.settings == Vehicle._meta.connection.settings
|
||||||
|
False
|
||||||
|
|
||||||
|
"""
|
@ -1,175 +0,0 @@
|
|||||||
import copy
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from django.conf import settings
|
|
||||||
from django import db
|
|
||||||
from runtests import doctest, DjangoDoctestRunner, error_list
|
|
||||||
|
|
||||||
# database files
|
|
||||||
fh, db_a = tempfile.mkstemp()
|
|
||||||
os.close(fh)
|
|
||||||
fh, db_b = tempfile.mkstemp()
|
|
||||||
os.close(fh)
|
|
||||||
|
|
||||||
# patches
|
|
||||||
tmp_settings = None
|
|
||||||
tmp_connections = None
|
|
||||||
|
|
||||||
# tests
|
|
||||||
test = r"""
|
|
||||||
XXX. Using multiple database connections
|
|
||||||
|
|
||||||
Django normally uses only a single database connection. However, support is
|
|
||||||
available for connecting to any number of different, named databases.
|
|
||||||
|
|
||||||
Named connections are defined in your settings module. Create a
|
|
||||||
`DATABASES` variable that is a dict, mapping connection names to their
|
|
||||||
particulars. The particulars are defined in a dict with the same keys
|
|
||||||
as the variable names as are used to define the default connection.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Please note that this uses the sqlite3 backend and writes temporary
|
|
||||||
database files to disk. This test will fail unless sqlite3 is
|
|
||||||
installed and your temp directory is writable.
|
|
||||||
|
|
||||||
>>> from django.conf import settings
|
|
||||||
>>> settings.DATABASES = {
|
|
||||||
... 'a': { 'DATABASE_ENGINE': 'sqlite3',
|
|
||||||
... 'DATABASE_NAME': db_a
|
|
||||||
... },
|
|
||||||
... 'b': { 'DATABASE_ENGINE': 'sqlite3',
|
|
||||||
... 'DATABASE_NAME': db_b
|
|
||||||
... }}
|
|
||||||
|
|
||||||
Connections are established lazily, when requested by name. When
|
|
||||||
accessed, `connections[database]` holds a `ConnectionInfo` instance,
|
|
||||||
with the attributes: `DatabaseError`, `backend`,
|
|
||||||
`get_introspection_module`, `get_creation_module`, and
|
|
||||||
`runshell`. Access connections through the `connections` property of
|
|
||||||
the `django.db` module:
|
|
||||||
|
|
||||||
>>> from django.db import connection, connections
|
|
||||||
>>> connections['a'].settings.DATABASE_NAME == db_a
|
|
||||||
True
|
|
||||||
>>> connections['b'].settings.DATABASE_NAME == db_b
|
|
||||||
True
|
|
||||||
|
|
||||||
Invalid connection names raise ImproperlyConfigured:
|
|
||||||
|
|
||||||
>>> connections['bad']
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ImproperlyConfigured: No database connection 'bad' has been configured
|
|
||||||
|
|
||||||
Models can define which connection to use, by name. To use a named
|
|
||||||
connection, set the `db_connection` property in the model's Meta class
|
|
||||||
to the name of the connection. The name used must be a key in
|
|
||||||
settings.DATABASES, of course.
|
|
||||||
|
|
||||||
>>> from django.db import models
|
|
||||||
>>> class Artist(models.Model):
|
|
||||||
... name = models.CharField(maxlength=100)
|
|
||||||
... alive = models.BooleanField(default=True)
|
|
||||||
...
|
|
||||||
... def __str__(self):
|
|
||||||
... return self.name
|
|
||||||
...
|
|
||||||
... class Meta:
|
|
||||||
... app_label = 'mdb'
|
|
||||||
... db_connection = 'a'
|
|
||||||
...
|
|
||||||
>>> class Widget(models.Model):
|
|
||||||
... code = models.CharField(maxlength=10, unique=True)
|
|
||||||
... weight = models.IntegerField()
|
|
||||||
...
|
|
||||||
... def __str__(self):
|
|
||||||
... return self.code
|
|
||||||
...
|
|
||||||
... class Meta:
|
|
||||||
... app_label = 'mdb'
|
|
||||||
... db_connection = 'b'
|
|
||||||
|
|
||||||
But they don't have to. Multiple database support is entirely optional
|
|
||||||
and has no impact on your application if you don't use it.
|
|
||||||
|
|
||||||
>>> class Vehicle(models.Model):
|
|
||||||
... make = models.CharField(maxlength=20)
|
|
||||||
... model = models.CharField(maxlength=20)
|
|
||||||
... year = models.IntegerField()
|
|
||||||
...
|
|
||||||
... def __str__(self):
|
|
||||||
... return "%d %s %s" % (self.year, self.make, self.model)
|
|
||||||
...
|
|
||||||
... class Meta:
|
|
||||||
... app_label = 'mdb'
|
|
||||||
|
|
||||||
>>> Artist._meta.connection.settings.DATABASE_NAME == \
|
|
||||||
... connections['a'].connection.settings.DATABASE_NAME
|
|
||||||
True
|
|
||||||
>>> Widget._meta.connection.settings.DATABASE_NAME == \
|
|
||||||
... connections['b'].connection.settings.DATABASE_NAME
|
|
||||||
True
|
|
||||||
>>> Vehicle._meta.connection.settings.DATABASE_NAME == \
|
|
||||||
... connection.settings.DATABASE_NAME
|
|
||||||
True
|
|
||||||
>>> Artist._meta.connection.settings.DATABASE_NAME == \
|
|
||||||
... Widget._meta.connection.settings.DATABASE_NAME
|
|
||||||
False
|
|
||||||
>>> Artist._meta.connection.settings.DATABASE_NAME == \
|
|
||||||
... Vehicle._meta.connection.settings.DATABASE_NAME
|
|
||||||
False
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def cleanup():
|
|
||||||
if os.path.exists(db_a):
|
|
||||||
os.unlink(db_a)
|
|
||||||
if os.path.exists(db_b):
|
|
||||||
os.unlink(db_b)
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
global tmp_settings, tmp_connections
|
|
||||||
try:
|
|
||||||
tmp_connections = db.connections
|
|
||||||
db.connections = db.LazyConnectionManager()
|
|
||||||
tmp_settings = copy.copy(settings.DATABASES)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def teardown():
|
|
||||||
try:
|
|
||||||
db.connections = tmp_connections
|
|
||||||
settings.DATABASES = tmp_settings
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
cleanup()
|
|
||||||
|
|
||||||
def run_tests(verbosity_level):
|
|
||||||
setup()
|
|
||||||
try:
|
|
||||||
main(verbosity_level)
|
|
||||||
finally:
|
|
||||||
teardown()
|
|
||||||
|
|
||||||
def main(verbosity_level):
|
|
||||||
mod = sys.modules[__name__]
|
|
||||||
p = doctest.DocTestParser()
|
|
||||||
dtest = p.get_doctest(mod.test, mod.__dict__, __name__, None, None)
|
|
||||||
runner = DjangoDoctestRunner(verbosity_level=verbosity_level,
|
|
||||||
verbose=False)
|
|
||||||
runner.run(dtest, clear_globs=True, out=sys.stdout.write)
|
|
||||||
if error_list:
|
|
||||||
out = []
|
|
||||||
for d in error_list:
|
|
||||||
out.extend([d['title'], "=" * len(d['title']),
|
|
||||||
d['description']])
|
|
||||||
|
|
||||||
raise Exception, "%s multiple_databases test%s failed:\n\n %s" \
|
|
||||||
% (len(error_list),
|
|
||||||
len(error_list) != 1 and 's' or '',
|
|
||||||
'\n'.join(out))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_tests(1)
|
|
@ -85,39 +85,25 @@ class TestRunner:
|
|||||||
self.verbosity_level = verbosity_level
|
self.verbosity_level = verbosity_level
|
||||||
self.which_tests = which_tests
|
self.which_tests = which_tests
|
||||||
self.created_dbs = []
|
self.created_dbs = []
|
||||||
self.sqlite_memory_db_used = False
|
|
||||||
self.cleanup_files = []
|
self.cleanup_files = []
|
||||||
|
self.old_database_name = None
|
||||||
|
self.old_databases = None
|
||||||
|
self.sqlite_memory_db_used = False
|
||||||
|
|
||||||
def output(self, required_level, message):
|
def output(self, required_level, message):
|
||||||
if self.verbosity_level > required_level - 1:
|
if self.verbosity_level > required_level - 1:
|
||||||
print message
|
print message
|
||||||
|
|
||||||
def create_test_db(self, connection):
|
def create_test_db(self, db_name, connection):
|
||||||
"""Create a test db, returning a ConnectionInfo object holding
|
"""Create a test db, returning a ConnectionInfo object holding
|
||||||
a connection to that db.
|
a connection to that db.
|
||||||
"""
|
"""
|
||||||
from django.db import connect
|
from django.db import connect
|
||||||
|
|
||||||
|
# settings may be a dict or settings object
|
||||||
settings = connection.settings
|
settings = connection.settings
|
||||||
|
|
||||||
# If we're using SQLite, it's more convenient to test against an
|
if settings.DATABASE_ENGINE != "sqlite3":
|
||||||
# in-memory database. But we can only do this for the first one; after
|
|
||||||
# that we have to use temp files.
|
|
||||||
if settings.DATABASE_ENGINE == "sqlite3":
|
|
||||||
if self.sqlite_memory_db_used:
|
|
||||||
import tempfile
|
|
||||||
fd, filename = tempfile.mkstemp()
|
|
||||||
os.close(fd)
|
|
||||||
db_name = filename
|
|
||||||
self.cleanup_files.append(filename)
|
|
||||||
else:
|
|
||||||
db_name = ":memory:"
|
|
||||||
self.sqlite_memory_db_used = True
|
|
||||||
else:
|
|
||||||
db_name = TEST_DATABASE_NAME
|
|
||||||
if self.created_dbs:
|
|
||||||
db_name += "_%s" % (len(self.created_dbs))
|
|
||||||
|
|
||||||
# Create the test database and connect to it. We need to autocommit
|
# Create the test database and connect to it. We need to autocommit
|
||||||
# if the database supports it because PostgreSQL doesn't allow
|
# if the database supports it because PostgreSQL doesn't allow
|
||||||
# CREATE/DROP DATABASE statements within transactions.
|
# CREATE/DROP DATABASE statements within transactions.
|
||||||
@ -136,6 +122,7 @@ class TestRunner:
|
|||||||
else:
|
else:
|
||||||
raise Exception("Tests cancelled.")
|
raise Exception("Tests cancelled.")
|
||||||
|
|
||||||
|
|
||||||
settings.DATABASE_NAME = db_name
|
settings.DATABASE_NAME = db_name
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
@ -153,6 +140,7 @@ class TestRunner:
|
|||||||
self.teardown()
|
self.teardown()
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
global TEST_DATABASE_NAME
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection, connections
|
from django.db import connection, connections
|
||||||
from django.core import management
|
from django.core import management
|
||||||
@ -170,17 +158,38 @@ class TestRunner:
|
|||||||
|
|
||||||
self.output(0, "Running tests with database %r" % settings.DATABASE_ENGINE)
|
self.output(0, "Running tests with database %r" % settings.DATABASE_ENGINE)
|
||||||
|
|
||||||
# Create test dbs for the default connection and any named connections
|
# Create test dbs for the default connection and two named connections,
|
||||||
# in settings. Remeber the original default db name so we can connect
|
# replacing any named connections defined in settings. All connections
|
||||||
# there to drop the created test dbs.
|
# will use the default DATABASE_ENGINE
|
||||||
self._old_database_name = settings.DATABASE_NAME
|
self.old_database_name = settings.DATABASE_NAME
|
||||||
self.create_test_db(connection)
|
self.old_databases = settings.DATABASES
|
||||||
|
|
||||||
|
db_a = TEST_DATABASE_NAME + '_a'
|
||||||
|
db_b = TEST_DATABASE_NAME + '_b'
|
||||||
|
if settings.DATABASE_ENGINE == 'sqlite3':
|
||||||
|
# If we're using SQLite, it's more convenient to test against an
|
||||||
|
# in-memory database. But we can only do this for the default;
|
||||||
|
# after that we have to use temp files.
|
||||||
|
TEST_DATABASE_NAME = ':memory:'
|
||||||
|
db_a_name = self._tempfile()
|
||||||
|
db_b_name = self._tempfile()
|
||||||
|
self.cleanup_files.append(db_a_name)
|
||||||
|
self.cleanup_files.append(db_b_name)
|
||||||
|
else:
|
||||||
|
db_a_name = db_a
|
||||||
|
db_b_name = db_b
|
||||||
|
|
||||||
if hasattr(settings, 'DATABASES'):
|
settings.DATABASES = {
|
||||||
for name, info in settings.DATABASES.items():
|
db_a: { 'DATABASE_NAME': db_a_name },
|
||||||
cx = connections[name]
|
db_b: { 'DATABASE_NAME': db_b_name }
|
||||||
test_connection = self.create_test_db(cx.connection)
|
}
|
||||||
connections[name] = test_connection
|
|
||||||
|
self.create_test_db(TEST_DATABASE_NAME, connection)
|
||||||
|
for name, info in settings.DATABASES.items():
|
||||||
|
cx = connections[name]
|
||||||
|
test_connection = self.create_test_db(info['DATABASE_NAME'],
|
||||||
|
cx.connection)
|
||||||
|
connections[name] = test_connection
|
||||||
|
|
||||||
# Install the core always installed apps
|
# Install the core always installed apps
|
||||||
for app in ALWAYS_INSTALLED_APPS:
|
for app in ALWAYS_INSTALLED_APPS:
|
||||||
@ -196,7 +205,8 @@ class TestRunner:
|
|||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
connection.close()
|
connection.close()
|
||||||
settings.DATABASE_NAME = self._old_database_name
|
settings.DATABASE_NAME = self.old_database_name
|
||||||
|
settings.DATABASES = self.old_databases
|
||||||
for db_name, cx in self.created_dbs:
|
for db_name, cx in self.created_dbs:
|
||||||
settings = cx.settings
|
settings = cx.settings
|
||||||
cx.close()
|
cx.close()
|
||||||
@ -334,6 +344,12 @@ class TestRunner:
|
|||||||
elif hasattr(connection.connection, "set_isolation_level"):
|
elif hasattr(connection.connection, "set_isolation_level"):
|
||||||
connection.connection.set_isolation_level(0)
|
connection.connection.set_isolation_level(0)
|
||||||
|
|
||||||
|
def _tempfile(self):
|
||||||
|
import tempfile
|
||||||
|
fd, filename = tempfile.mkstemp()
|
||||||
|
os.close(fd)
|
||||||
|
return filename
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
usage = "%prog [options] [model model model ...]"
|
usage = "%prog [options] [model model model ...]"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user