mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
[multi-db] Fixed bugs in connection handling and test database setup. All tests now pass for postgres backend. Still failures for mysql and sqlite3, others unknown.
git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3768 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
ecb5b81e0d
commit
27bcc64ac5
@ -14,6 +14,8 @@ __all__ = ('backend', 'connection', 'DatabaseError')
|
||||
|
||||
# singleton to represent the default connection in connections
|
||||
class dummy(object):
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
def __str__(self):
|
||||
return '<default>'
|
||||
_default = dummy()
|
||||
@ -28,22 +30,12 @@ if not settings.DATABASE_ENGINE:
|
||||
settings.DATABASE_ENGINE = 'dummy'
|
||||
|
||||
|
||||
def connect(settings):
|
||||
def connect(settings, **kw):
|
||||
"""Connect to the database specified in settings. Returns a
|
||||
ConnectionInfo on success, raises ImproperlyConfigured if the
|
||||
settings don't specify a valid database connection.
|
||||
"""
|
||||
info = ConnectionInfo(settings)
|
||||
|
||||
# Register an event that closes the database connection
|
||||
# when a Django request is finished.
|
||||
dispatcher.connect(info.close, signal=signals.request_finished)
|
||||
|
||||
# Register an event that resets connection.queries
|
||||
# when a Django request is started.
|
||||
dispatcher.connect(info.reset_queries, signal=signals.request_started)
|
||||
|
||||
return info
|
||||
return ConnectionInfo(settings, **kw)
|
||||
|
||||
|
||||
class ConnectionInfo(object):
|
||||
@ -52,7 +44,8 @@ class ConnectionInfo(object):
|
||||
creation, introspection, and shell modules, closing the
|
||||
connection, and resetting the connection's query log.
|
||||
"""
|
||||
def __init__(self, settings=None):
|
||||
def __init__(self, settings=None, **kw):
|
||||
super(ConnectionInfo, self).__init__(**kw)
|
||||
if settings is None:
|
||||
from django.conf import settings
|
||||
self.settings = settings
|
||||
@ -60,6 +53,14 @@ class ConnectionInfo(object):
|
||||
self.connection = self.backend.DatabaseWrapper(settings)
|
||||
self.DatabaseError = self.backend.DatabaseError
|
||||
|
||||
# Register an event that closes the database connection
|
||||
# when a Django request is finished.
|
||||
dispatcher.connect(self.close, signal=signals.request_finished)
|
||||
|
||||
# Register an event that resets connection.queries
|
||||
# when a Django request is started.
|
||||
dispatcher.connect(self.reset_queries, signal=signals.request_started)
|
||||
|
||||
def __repr__(self):
|
||||
return "Connection: %r (ENGINE=%s NAME=%s)" \
|
||||
% (self.connection,
|
||||
@ -123,8 +124,14 @@ class LazyConnectionManager(object):
|
||||
self.local = local()
|
||||
self.local.connections = {}
|
||||
|
||||
# Reset connections on request finish, to make sure each request can
|
||||
# load the correct connections for its settings
|
||||
dispatcher.connect(self.reset, signal=signals.request_finished)
|
||||
|
||||
def __iter__(self):
|
||||
return self.local.connections.keys()
|
||||
# Iterates only over *active* connections, not all possible
|
||||
# connections
|
||||
return iter(self.local.connections.keys())
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.local.connections, attr)
|
||||
@ -177,9 +184,26 @@ class LazyConnectionManager(object):
|
||||
cnx[name] = connect(database)
|
||||
return cnx[name]
|
||||
|
||||
def reset(self):
|
||||
self.local.connections = {}
|
||||
def items(self):
|
||||
# Iterates over *all possible* connections
|
||||
items = []
|
||||
for key in self.keys():
|
||||
items.append((key, self[key]))
|
||||
return items
|
||||
|
||||
def keys(self):
|
||||
# Iterates over *all possible* connections
|
||||
keys = [_default]
|
||||
try:
|
||||
keys.extend(settings.OTHER_DATABASES.keys())
|
||||
except AttributeError:
|
||||
pass
|
||||
return keys
|
||||
|
||||
def reset(self):
|
||||
if not hasattr(self.local, 'connections'):
|
||||
return
|
||||
self.local.connections = {}
|
||||
|
||||
def model_connection_name(klass):
|
||||
"""Get the connection name that a model is configured to use, with the
|
||||
@ -263,9 +287,10 @@ class ConnectionInfoDescriptor(object):
|
||||
return connections[model_connection_name(instance.model)]
|
||||
|
||||
def reset(self):
|
||||
if not hasattr(self.local, 'cnx'):
|
||||
return
|
||||
self.local.cnx = {}
|
||||
|
||||
|
||||
class LocalizingProxy:
|
||||
"""A lazy-initializing proxy. The proxied object is not
|
||||
initialized until the first attempt to access it. This is used to
|
||||
@ -278,6 +303,13 @@ class LocalizingProxy:
|
||||
self.__arg = arg
|
||||
self.__kw = kw
|
||||
|
||||
# We need to clear out this thread's storage at the end of each
|
||||
# request, in case new settings are loaded with the next
|
||||
def reset(stor=storage, name=name):
|
||||
if hasattr(stor, name):
|
||||
delattr(stor, name)
|
||||
dispatcher.connect(reset, signal=signals.request_finished)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# Private (__*) attributes are munged
|
||||
if attr.startswith('_LocalizingProxy'):
|
||||
@ -295,7 +327,6 @@ class LocalizingProxy:
|
||||
self.__dict__[attr] = val
|
||||
return
|
||||
try:
|
||||
print self.__storage, self.__name
|
||||
stor = getattr(self.__storage, self.__name)
|
||||
except AttributeError:
|
||||
stor = self.__func(*self.__arg)
|
||||
@ -327,11 +358,6 @@ runshell = LocalizingProxy('runshell', _local,
|
||||
lambda: connections[_default].runshell)
|
||||
|
||||
|
||||
# Reset connections on request finish, to make sure each request can
|
||||
# load the correct connections for its settings
|
||||
dispatcher.connect(connections.reset, signal=signals.request_finished)
|
||||
|
||||
|
||||
# Register an event that rolls back all connections
|
||||
# when a Django request has an exception.
|
||||
def _rollback_on_exception():
|
||||
|
@ -1,6 +1,5 @@
|
||||
import sys, time
|
||||
from django.conf import settings
|
||||
|
||||
from django.db import backend, connect, connection, connection_info, connections
|
||||
from django.dispatch import dispatcher
|
||||
from django.test import signals
|
||||
@ -94,21 +93,13 @@ def create_test_db(verbosity=1, autoclobber=False):
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Fill OTHER_DATABASES with the TEST_DATABASES settings,
|
||||
# and connect each named connection to the test database, using
|
||||
# a separate connection instance for each (so, eg, transactions don't
|
||||
# collide)
|
||||
# and connect each named connection to the test database.
|
||||
test_databases = {}
|
||||
for db_name in settings.TEST_DATABASES:
|
||||
if settings.DATABASE_ENGINE == 'sqlite3':
|
||||
full_name = TEST_DATABASE_NAME
|
||||
else:
|
||||
full_name = TEST_DATABASE_NAME + db_name
|
||||
db_st = {'DATABASE_NAME': full_name}
|
||||
db_st = {'DATABASE_NAME': TEST_DATABASE_NAME}
|
||||
if db_name in settings.TEST_DATABASE_MODELS:
|
||||
db_st['MODELS'] = settings.TEST_DATABASE_MODELS.get(db_name, [])
|
||||
test_databases[db_name] = db_st
|
||||
connections[db_name] = connect(connection_info.settings)
|
||||
connections[db_name].connection.cursor() # Initialize it
|
||||
settings.OTHER_DATABASES = test_databases
|
||||
|
||||
def destroy_test_db(old_database_name, old_databases, verbosity=1):
|
||||
@ -118,15 +109,26 @@ def destroy_test_db(old_database_name, old_databases, verbosity=1):
|
||||
# connected to it.
|
||||
if verbosity >= 1:
|
||||
print "Destroying test database..."
|
||||
|
||||
connection.close()
|
||||
for cnx in connections.keys():
|
||||
connections[cnx].close()
|
||||
connections.reset()
|
||||
|
||||
TEST_DATABASE_NAME = settings.DATABASE_NAME
|
||||
if verbosity >= 2:
|
||||
print "Closed connections to %s" % TEST_DATABASE_NAME
|
||||
settings.DATABASE_NAME = old_database_name
|
||||
|
||||
if settings.DATABASE_ENGINE != "sqlite3":
|
||||
settings.OTHER_DATABASES = old_databases
|
||||
for cnx in connections.keys():
|
||||
connections[cnx].connection.cursor()
|
||||
try:
|
||||
connections[cnx].connection.cursor()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pass
|
||||
cursor = connection.cursor()
|
||||
_set_autocommit(connection)
|
||||
time.sleep(1) # To avoid "database is being accessed by other users" errors.
|
||||
|
@ -84,22 +84,24 @@ Connection: ...
|
||||
>>> connections['_b']
|
||||
Connection: ...
|
||||
|
||||
# Let's see what connections are available.The default connection is
|
||||
# in there, but let's ignore it
|
||||
# Let's see what connections are available. The default connection is always
|
||||
# included in connections as well, and may be accessed as connections[_default].
|
||||
|
||||
>>> non_default = connections.keys()
|
||||
>>> non_default.remove(_default)
|
||||
>>> non_default.sort()
|
||||
>>> non_default
|
||||
['_a', '_b']
|
||||
>>> connection_names = connections.keys()
|
||||
>>> connection_names.sort()
|
||||
>>> connection_names
|
||||
[<default>, '_a', '_b']
|
||||
|
||||
# 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 managers
|
||||
# The model_connection_name() function will tell you the name of the
|
||||
# connection that a model is configured to use.
|
||||
|
||||
>>> model_connection_name(Artist)
|
||||
'_a'
|
||||
>>> model_connection_name(Widget)
|
||||
@ -116,6 +118,15 @@ True
|
||||
>>> list(artists)
|
||||
[<Artist: Paul Klee>]
|
||||
|
||||
# Models can access their connections through the db property of their
|
||||
# default manager.
|
||||
|
||||
>>> paul = _[0]
|
||||
>>> Artist.objects.db
|
||||
Connection: ... (ENGINE=... NAME=...)
|
||||
>>> paul._default_manager.db
|
||||
Connection: ... (ENGINE=... NAME=...)
|
||||
|
||||
# When transactions are not managed, model save will commit only
|
||||
# for the model's connection.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user