diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 450218650c..51eeeaf0ef 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -1,11 +1,20 @@ +from django import core from django.utils.functional import curry -from django.db import backend, connection +from django.core.exceptions import ImproperlyConfigured +from django.db import connections, _default from django.db.models.query import QuerySet from django.dispatch import dispatcher from django.db.models import signals, get_apps, get_models from django.db.models.fields import FieldDoesNotExist from django.utils.datastructures import SortedDict +try: + # Only exists in Python 2.4+ + from threading import local +except ImportError: + # Import copy of _thread_local.py from Python 2.4 + from django.utils._threading_local import local + # Size of each "chunk" for get_iterator calls. # Larger values are slightly faster at the expense of more storage space. GET_ITERATOR_CHUNK_SIZE = 100 @@ -25,10 +34,79 @@ def ensure_default_manager(sender): cls.add_to_class('objects', Manager()) dispatcher.connect(ensure_default_manager, signal=signals.class_prepared) +class ConnectionInfoDescriptor(object): + """Descriptor used to access database connection information from a + manager or other connection holder. Keeps a thread-local cache of + connections per instance, and always returns the same connection for an + instance in particular thread during a particular request. + + Any object that includes an attribute ``model`` that holds a model class + can use this descriptor to manage connections. + """ + + def __init__(self): + self.cnx = local() + self.cnx.cache = {} + + def __get__(self, instance, type=None): + if instance is None: + raise AttributeError, \ + "ConnectionInfo is accessible only through an instance" + instance_connection = self.cnx.cache.get(instance, None) + if instance_connection is None: + instance_connection = self.get_connection(instance) + def reset(): + self.reset(instance) + dispatcher.connect(reset, signal=core.signals.request_finished) + self.cnx.cache[instance] = instance_connection + return instance_connection + + def __set__(self, instance, value): + self.cnx.cache[instance] = instance_connection + + def __delete__(self, instance): + self.reset(instance) + + def get_connection(self, instance): + from django.conf import settings + app = instance.model._meta.app_label + model = instance.model.__name__ + app_model = "%s.%s" % (app, model) + + # Quick exit if no OTHER_DATABASES defined + if (not hasattr(settings, 'OTHER_DATABASES') + or not settings.OTHER_DATABASES): + return connections[_default] + # Look in MODELS for the best match: app_label.Model. If that isn't + # found, take just app_label. If nothing is found, use the default + maybe = None + for name, db_def in settings.OTHER_DATABASES.items(): + if not 'MODELS' in db_def: + continue + mods = db_def['MODELS'] + # Can't get a better match than this + if app_model in mods: + return connections[name] + elif app in mods: + if maybe is not None: + raise ImproperlyConfigured, \ + "App %s appears in more than one OTHER_DATABASES " \ + "setting (%s and %s)" % (maybe, name) + maybe = name + if maybe: + return connections[name] + # No named connection for this model; use the default + return connections[_default] + + def reset(self, instance): + self.cnx.cache[instance] = None + + class Manager(object): # Tracks each time a Manager instance is created. Used to retain order. creation_counter = 0 - + db = ConnectionInfoDescriptor() + def __init__(self): super(Manager, self).__init__() # Increase the creation counter, and save our local copy. @@ -44,7 +122,7 @@ class Manager(object): or self.creation_counter < model._default_manager.creation_counter \ or model._default_manager.model != model: model._default_manager = self - + ####################### # PROXIES TO QUERYSET # ####################### @@ -119,8 +197,7 @@ class Manager(object): such as foreign key constraints for tables that don't exist at install time.) """ - creator = self.model._meta.connection_info.get_creation_module() - builder = creator.builder + builder = self.db.get_creation_module().builder run, pending = builder.get_create_table(self.model) run += builder.get_create_indexes(self.model) if initial_data: @@ -158,9 +235,8 @@ class Manager(object): def get_table_list(self): """Get list of tables accessible via my model's connection. """ - info = self.model._meta.connection_info - builder = info.get_creation_module.builder() - return builder.get_table_list(info) + builder = self.db.get_creation_module().builder + return builder.get_table_list(self.db) class ManagerDescriptor(object): # This class ensures managers aren't accessible via model instances. @@ -172,3 +248,4 @@ class ManagerDescriptor(object): if instance != None: raise AttributeError, "Manager isn't accessible via %s instances" % type.__name__ return self.manager +