=== django/test/utils.py
==================================================================
--- django/test/utils.py	(/django/trunk)	(revision 10934)
+++ django/test/utils.py	(/local/django/multi-db)	(revision 10934)
@@ -1,6 +1,7 @@
 import sys, time
 from django.conf import settings
-from django.db import connection, backend, get_creation_module
+from django.db import connection, transaction, backend, \
+		connection_info, connections, get_creation_module
 from django.core import management, mail
 from django.core import management, mail
 from django.dispatch import dispatcher
@@ -12,12 +13,21 @@
 TEST_DATABASE_PREFIX = 'test_'
 
 def instrumented_test_render(self, context):
-    """An instrumented Template render method, providing a signal 
-    that can be intercepted by the test system Client
-    
     """
+    An instrumented Template render method, providing a signal that can be
+    intercepted by the test system Client.
+    """
     dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
     return self.nodelist.render(context)
+
+def instrumented_test_iter_render(self, context):
+    """
+    An instrumented Template iter_render method, providing a signal that can be
+    intercepted by the test system Client.
+    """
+    for chunk in self.nodelist.iter_render(context):
+        yield chunk
+    dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
     
 class TestSMTPConnection(object):
     """A substitute SMTP connection for use during test sessions.
@@ -45,7 +55,9 @@
         
     """
     Template.original_render = Template.render
+    Template.original_iter_render = Template.iter_render
     Template.render = instrumented_test_render
+    Template.iter_render = instrumented_test_render
     
     mail.original_SMTPConnection = mail.SMTPConnection
     mail.SMTPConnection = TestSMTPConnection
@@ -60,7 +72,8 @@
         
     """
     Template.render = Template.original_render
-    del Template.original_render
+    Template.iter_render = Template.original_iter_render
+    del Template.original_render, Template.original_iter_render
     
     mail.SMTPConnection = mail.original_SMTPConnection
     del mail.original_SMTPConnection
@@ -73,7 +86,7 @@
         connection.connection.autocommit(True)
     elif hasattr(connection.connection, "set_isolation_level"):
         connection.connection.set_isolation_level(0)
-
+        
 def get_mysql_create_suffix():
     suffix = []
     if settings.TEST_DATABASE_CHARSET:
@@ -97,10 +110,13 @@
     
     if verbosity >= 1:
         print "Creating test database..."
+        
     # If we're using SQLite, it's more convenient to test against an
     # in-memory database.
     if settings.DATABASE_ENGINE == "sqlite3":
-        TEST_DATABASE_NAME = ":memory:"
+        TEST_DATABASE_NAME = "/home/ben/test.db" #":memory:"
+        if verbosity >= 2:
+            print "Using in-memory sqlite database for testing"
     else:
         suffix = {
             'postgresql': get_postgresql_create_suffix,
@@ -112,14 +128,15 @@
             TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
         else:
             TEST_DATABASE_NAME = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
-        
+
+        qn = backend.quote_name
         # Create the test database and connect to it. We need to autocommit
         # if the database supports it because PostgreSQL doesn't allow 
         # CREATE/DROP DATABASE statements within transactions.
         cursor = connection.cursor()
         _set_autocommit(connection)
         try:
-            cursor.execute("CREATE DATABASE %s %s" % (backend.quote_name(TEST_DATABASE_NAME), suffix))
+            cursor.execute("CREATE DATABASE %s %s" % (qn(TEST_DATABASE_NAME), suffix))
         except Exception, e:            
             sys.stderr.write("Got an error creating the test database: %s\n" % e)
             if not autoclobber:
@@ -128,10 +145,10 @@
                 try:
                     if verbosity >= 1:
                         print "Destroying old test database..."                
-                    cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
+                    cursor.execute("DROP DATABASE %s" % qn(TEST_DATABASE_NAME))
                     if verbosity >= 1:
                         print "Creating test database..."
-                    cursor.execute("CREATE DATABASE %s %s" % (backend.quote_name(TEST_DATABASE_NAME), suffix))
+                    cursor.execute("CREATE DATABASE %s %s" % (qn(TEST_DATABASE_NAME), suffix))
                 except Exception, e:
                     sys.stderr.write("Got an error recreating the test database: %s\n" % e)
                     sys.exit(2)
@@ -148,24 +165,48 @@
     # the side effect of initializing the test database.
     cursor = connection.cursor()
 
-def destroy_test_db(old_database_name, verbosity=1):
+    # Fill OTHER_DATABASES with the TEST_DATABASES settings,
+    # and connect each named connection to the test database.
+    test_databases = {}
+    for db_name in settings.TEST_DATABASES:
+        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
+    settings.OTHER_DATABASES = test_databases
+    
+def destroy_test_db(old_database_name, old_databases, verbosity=1):
     # If the database wants to drop the test DB itself, let it
     creation_module = get_creation_module()
     if hasattr(creation_module, "destroy_test_db"):
         creation_module.destroy_test_db(settings, connection, backend, old_database_name, verbosity)
         return
-    
     # Unless we're using SQLite, remove the test database to clean up after
     # ourselves. Connect to the previous database (not the test database)
     # to do so, because it's not allowed to delete a database while being
     # 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():
+            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.
=== django/http/__init__.py
==================================================================
--- django/http/__init__.py	(/django/trunk)	(revision 10934)
+++ django/http/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -224,6 +224,12 @@
         content = ''.join(self._container)
         if isinstance(content, unicode):
             content = content.encode(self._charset)
+
+        # If self._container was an iterator, we have just exhausted it, so we
+        # need to save the results for anything else that needs access
+        if not self._is_string:
+            self._container = [content]
+            self._is_string = True
         return content
 
     def _set_content(self, value):
@@ -233,15 +239,11 @@
     content = property(_get_content, _set_content)
 
     def __iter__(self):
-        self._iterator = self._container.__iter__()
-        return self
+        for chunk in self._container:
+            if isinstance(chunk, unicode):
+                chunk = chunk.encode(self._charset)
+            yield chunk
 
-    def next(self):
-        chunk = self._iterator.next()
-        if isinstance(chunk, unicode):
-            chunk = chunk.encode(self._charset)
-        return chunk
-
     def close(self):
         if hasattr(self._container, 'close'):
             self._container.close()
=== django/oldforms/__init__.py
==================================================================
--- django/oldforms/__init__.py	(/django/trunk)	(revision 10934)
+++ django/oldforms/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -309,6 +309,10 @@
         return data
     html2python = staticmethod(html2python)
 
+    def iter_render(self, data):
+        # this even needed?
+        return (self.render(data),)
+
     def render(self, data):
         raise NotImplementedError
 
=== django/db/models/base.py
==================================================================
--- django/db/models/base.py	(/django/trunk)	(revision 10934)
+++ django/db/models/base.py	(/local/django/multi-db)	(revision 10934)
@@ -6,7 +6,7 @@
 from django.db.models.fields.related import OneToOneRel, ManyToOneRel
 from django.db.models.query import delete_objects
 from django.db.models.options import Options, AdminOptions
-from django.db import connection, backend, transaction
+from django.db import transaction
 from django.db.models import signals
 from django.db.models.loading import register_models, get_model
 from django.dispatch import dispatcher
@@ -22,6 +22,9 @@
     "Metaclass for all models"
     def __new__(cls, name, bases, attrs):
         # If this isn't a subclass of Model, don't do anything special.
+        print "\n\n" + "*"*40
+        print "ModelBase Called with name: %s and attrs:" % name
+        print "\n".join(["%s => %s" % (k,v) for k, v in attrs.items()])
         try:
             if not filter(lambda b: issubclass(b, Model), bases):
                 return super(ModelBase, cls).__new__(cls, name, bases, attrs)
@@ -201,6 +204,10 @@
     def save(self):
         dispatcher.send(signal=signals.pre_save, sender=self.__class__, instance=self)
 
+        db = self.__class__._default_manager.db
+        connection = db.connection
+        backend = db.backend
+        qn = backend.quote_name
         non_pks = [f for f in self._meta.fields if not f.primary_key]
         cursor = connection.cursor()
 
@@ -211,21 +218,21 @@
         if pk_set:
             # Determine whether a record with the primary key already exists.
             cursor.execute("SELECT COUNT(*) FROM %s WHERE %s=%%s" % \
-                (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
+                (qn(self._meta.db_table), qn(self._meta.pk.column)),
                 self._meta.pk.get_db_prep_lookup('exact', pk_val))
             # If it does already exist, do an UPDATE.
             if cursor.fetchone()[0] > 0:
                 db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks]
                 if db_values:
                     cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
-                        (backend.quote_name(self._meta.db_table),
-                        ','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]),
-                        backend.quote_name(self._meta.pk.column)),
+                        (qn(self._meta.db_table),
+                        ','.join(['%s=%%s' % qn(f.column) for f in non_pks]),
+                        qn(self._meta.pk.column)),
                         db_values + self._meta.pk.get_db_prep_lookup('exact', pk_val))
             else:
                 record_exists = False
         if not pk_set or not record_exists:
-            field_names = [backend.quote_name(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
+            field_names = [qn(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
             db_values = [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
             # If the PK has been manually set, respect that.
             if pk_set:
@@ -233,24 +240,24 @@
                 db_values += [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
             placeholders = ['%s'] * len(field_names)
             if self._meta.order_with_respect_to:
-                field_names.append(backend.quote_name('_order'))
+                field_names.append(qn('_order'))
                 # TODO: This assumes the database supports subqueries.
                 placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
-                    (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column)))
+                    (qn(self._meta.db_table), qn(self._meta.order_with_respect_to.column)))
                 db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
             if db_values:
                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
-                    (backend.quote_name(self._meta.db_table), ','.join(field_names),
+                    (qn(self._meta.db_table), ','.join(field_names),
                     ','.join(placeholders)), db_values)
             else:
                 # Create a new record with defaults for everything.
                 cursor.execute("INSERT INTO %s (%s) VALUES (%s)" %
-                    (backend.quote_name(self._meta.db_table),
-                     backend.quote_name(self._meta.pk.column),
+                    (qn(self._meta.db_table),
+                     qn(self._meta.pk.column),
                      backend.get_pk_default_value()))
             if self._meta.has_auto_field and not pk_set:
                 setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
-        transaction.commit_unless_managed()
+        transaction.commit_unless_managed([connection])
 
         # Run any post-save hooks.
         dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
@@ -321,10 +328,11 @@
         return dict(field.choices).get(value, value)
 
     def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
+        qn = self._default_manager.db.backend.quote_name
         op = is_next and '>' or '<'
         where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
-            (backend.quote_name(field.column), op, backend.quote_name(field.column),
-            backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column), op)
+            (qn(field.column), op, qn(field.column),
+            qn(self._meta.db_table), qn(self._meta.pk.column), op)
         param = str(getattr(self, field.attname))
         q = self.__class__._default_manager.filter(**kwargs).order_by((not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name)
         q._where.append(where)
@@ -335,14 +343,16 @@
             raise self.DoesNotExist, "%s matching query does not exist." % self.__class__._meta.object_name
 
     def _get_next_or_previous_in_order(self, is_next):
+        qn = self._default_manager.db.backend.quote_name
+
         cachename = "__%s_order_cache" % is_next
         if not hasattr(self, cachename):
             op = is_next and '>' or '<'
             order_field = self._meta.order_with_respect_to
             where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
-                (backend.quote_name('_order'), op, backend.quote_name('_order'),
-                backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
-                '%s=%%s' % backend.quote_name(order_field.column)]
+                (qn('_order'), op, qn('_order'),
+                qn(self._meta.db_table), qn(self._meta.pk.column)),
+                '%s=%%s' % qn(order_field.column)]
             params = [self._get_pk_val(), getattr(self, order_field.attname)]
             obj = self._default_manager.order_by('_order').extra(where=where, params=params)[:1].get()
             setattr(self, cachename, obj)
@@ -424,24 +434,30 @@
 # ORDERING METHODS #########################
 
 def method_set_order(ordered_obj, self, id_list):
+    db = ordered_obj._default_manager.db
+    connection = db.connection
+    qn = db.backend.quote_name
     cursor = connection.cursor()
     # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
     sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \
-        (backend.quote_name(ordered_obj._meta.db_table), backend.quote_name('_order'),
-        backend.quote_name(ordered_obj._meta.order_with_respect_to.column),
-        backend.quote_name(ordered_obj._meta.pk.column))
+        (qn(ordered_obj._meta.db_table), qn('_order'),
+        qn(ordered_obj._meta.order_with_respect_to.column),
+        qn(ordered_obj._meta.pk.column))
     rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
     cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
-    transaction.commit_unless_managed()
+    transaction.commit_unless_managed([connection])
 
 def method_get_order(ordered_obj, self):
+    db = ordered_obj._default_manager.db
+    connection = db.connection
+    qn = db.backend.quote_name
     cursor = connection.cursor()
     # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
     sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \
-        (backend.quote_name(ordered_obj._meta.pk.column),
-        backend.quote_name(ordered_obj._meta.db_table),
-        backend.quote_name(ordered_obj._meta.order_with_respect_to.column),
-        backend.quote_name('_order'))
+        (qn(ordered_obj._meta.pk.column),
+        qn(ordered_obj._meta.db_table),
+        qn(ordered_obj._meta.order_with_respect_to.column),
+        qn('_order'))
     rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
     cursor.execute(sql, [rel_val])
     return [r[0] for r in cursor.fetchall()]
=== django/db/models/manager.py
==================================================================
--- django/db/models/manager.py	(/django/trunk)	(revision 10934)
+++ django/db/models/manager.py	(/local/django/multi-db)	(revision 10934)
@@ -1,8 +1,16 @@
+from django.db import ConnectionInfoDescriptor
 from django.db.models.query import QuerySet, EmptyQuerySet
 from django.dispatch import dispatcher
-from django.db.models import signals
+from django.db.models import signals, get_apps, get_models
 from django.db.models.fields import FieldDoesNotExist
 
+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
@@ -17,13 +25,18 @@
         except FieldDoesNotExist:
             pass
         cls.add_to_class('objects', Manager())
-
+    elif cls._default_manager.model != cls:
+        # cls is an inherited model; don't want the parent manager
+        cls.add_to_class('objects', Manager())
 dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)
 
+
+
 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.
@@ -35,9 +48,11 @@
         # TODO: Use weakref because of possible memory leak / circular reference.
         self.model = model
         setattr(model, name, ManagerDescriptor(self))
-        if not hasattr(model, '_default_manager') or self.creation_counter < model._default_manager.creation_counter:
+        if not hasattr(model, '_default_manager') \
+            or self.creation_counter < model._default_manager.creation_counter \
+            or model._default_manager.model != model:
             model._default_manager = self
-
+        
     #######################
     # PROXIES TO QUERYSET #
     #######################
@@ -105,6 +120,70 @@
     def values(self, *args, **kwargs):
         return self.get_query_set().values(*args, **kwargs)
 
+    #######################
+    # SCHEMA MANIPULATION #
+    #######################
+
+    def install(self, initial_data=False, pending=None):
+        """Install my model's table, indexes and (if requested) initial data.
+
+        Returns a dict of pending statements, keyed by the model that
+        needs to be created before the statements can be executed.
+        (Pending statements are those that could not yet be executed,
+        such as foreign key constraints for tables that don't exist at
+        install time.)
+        """
+        if pending is None:
+            pending = {}
+        builder = self.db.get_creation_module().builder
+        run, pending = builder.get_create_table(self.model, pending=pending)
+        run += builder.get_create_indexes(self.model)
+        many_many = builder.get_create_many_to_many(self.model)
+
+        for statement in run:
+            statement.execute()
+        for klass, statements in many_many.items():
+            if klass in builder.models_already_seen:
+                for statement in statements:
+                    statement.execute()
+            else:
+                pending.setdefault(klass, []).extend(statements)
+        if initial_data:
+            self.load_initial_data()
+        return pending
+
+    def get_pending(self, rel_class, f):
+        """Get list pending statement relating my model to rel_class via
+        field f
+        """
+        builder = self.db.get_creation_module().builder
+        return builder.get_ref_sql(self.model, rel_class, f)
+
+    def load_initial_data(self):
+        """Install initial data for model in db, Returns statements executed.
+        """
+        builder = self.db.get_creation_module().builder
+        statements = builder.get_initialdata(self.model)
+        for statement in statements:
+            statement.execute()
+        return statements
+        
+    def get_installed_models(self, table_list):
+        """Get list of models installed, given a list of tables.
+        """
+        all_models = []
+        for app in get_apps():
+            for model in get_models(app):
+                all_models.append(model)
+        return set([m for m in all_models
+                    if m._meta.db_table in table_list])
+
+    def get_table_list(self):
+        """Get list of tables accessible via my model's connection.
+        """
+        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.
     # For example, Poll.objects works, but poll_obj.objects raises AttributeError.
@@ -115,3 +194,4 @@
         if instance != None:
             raise AttributeError, "Manager isn't accessible via %s instances" % type.__name__
         return self.manager
+
=== django/db/models/options.py
==================================================================
--- django/db/models/options.py	(/django/trunk)	(revision 10934)
+++ django/db/models/options.py	(/local/django/multi-db)	(revision 10934)
@@ -1,4 +1,5 @@
 from django.conf import settings
+from django.db import connection_info, connections
 from django.db.models.related import RelatedObject
 from django.db.models.fields.related import ManyToManyRel
 from django.db.models.fields import AutoField, FieldDoesNotExist
@@ -7,6 +8,7 @@
 from django.db.models import Manager
 from bisect import bisect
 import re
+import weakref
 
 # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
 get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
@@ -79,6 +81,9 @@
             self.db_table = truncate_name(self.db_table,
                                           backend.get_max_name_length())
 
+        # Keep a weakref to my model, for access to managers and such
+        self._model = weakref.ref(model)
+
     def add_field(self, field):
         # Insert the given field in the order in which it was created, using
         # the "creation_counter" attribute of the field.
@@ -105,6 +110,12 @@
                 return f
         raise FieldDoesNotExist, '%s has no field named %r' % (self.object_name, name)
 
+    def get_default_manager(self):
+        model = self._model()
+        if model is None:
+            raise ReferenceError("Model no longer available in %s" % self)
+        return model._default_manager
+
     def get_order_sql(self, table_prefix=''):
         "Returns the full 'ORDER BY' clause for this object, according to self.ordering."
         if not self.ordering: return ''
=== django/db/models/loading.py
==================================================================
--- django/db/models/loading.py	(/django/trunk)	(revision 10934)
+++ django/db/models/loading.py	(/local/django/multi-db)	(revision 10934)
@@ -12,6 +12,9 @@
 _app_models = {} # Dictionary of models against app label
                  # Each value is a dictionary of model name: model class
                  # Applabel and Model entry exists in cache when individual model is loaded.
+_app_model_order = {} # Dictionary of models against app label
+                      # Each value is a list of model names, in the order in
+                      # which the models were created
 _app_errors = {} # Dictionary of errors that were experienced when loading the INSTALLED_APPS
                  # Key is the app_name of the model, value is the exception that was raised
                  # during model loading.
@@ -61,14 +64,19 @@
     get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
     return _app_errors
 
-def get_models(app_mod=None):
+def get_models(app_mod=None, creation_order=False):
     """
     Given a module containing models, returns a list of the models. Otherwise
     returns a list of all installed models.
     """
     app_list = get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
     if app_mod:
-        return _app_models.get(app_mod.__name__.split('.')[-2], {}).values()
+        app_label = app_mod.__name__.split('.')[-2]
+        app_models = _app_models.get(app_label, {})
+        if creation_order:
+            return [ app_models[name]
+                     for name in _app_model_order.get(app_label, []) ]
+        return app_models.values()
     else:
         model_list = []
         for app_mod in app_list:
=== django/db/models/fields/__init__.py
==================================================================
--- django/db/models/fields/__init__.py	(/django/trunk)	(revision 10934)
+++ django/db/models/fields/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -536,13 +536,19 @@
                 except ValueError:
                     raise validators.ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
 
-    def get_db_prep_save(self, value):
-        # Casts dates into string format for entry into database.
+    def pre_save(self, model_instance, add):
+        value = super(DateField, self).pre_save(model_instance, add)
         if value is not None:
             # MySQL will throw a warning if microseconds are given, because it
             # doesn't support microseconds.
+            settings = model_instance._default_manager.db.connection.settings
             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
                 value = value.replace(microsecond=0)
+        return value
+
+    def get_db_prep_save(self, value):
+        # Casts dates into string format for entry into database.
+        if value is not None:
             value = str(value)
         return Field.get_db_prep_save(self, value)
 
@@ -909,6 +915,7 @@
         if value is not None:
             # MySQL will throw a warning if microseconds are given, because it
             # doesn't support microseconds.
+            settings = model_instance._default_manager.db.connection.settings
             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
                 value = value.replace(microsecond=0)
             if settings.DATABASE_ENGINE == 'oracle':
=== django/db/models/fields/related.py
==================================================================
--- django/db/models/fields/related.py	(/django/trunk)	(revision 10934)
+++ django/db/models/fields/related.py	(/local/django/multi-db)	(revision 10934)
@@ -1,4 +1,4 @@
-from django.db import backend, transaction
+from django.db import transaction
 from django.db.models import signals, get_model
 from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
 from django.db.models.related import RelatedObject
@@ -318,7 +318,7 @@
             # source_col_name: the PK colname in join_table for the source object
             # target_col_name: the PK colname in join_table for the target object
             # *objs - objects to add. Either object instances, or primary keys of object instances.
-            from django.db import connection
+            connection = self.model._default_manager.db.connection
 
             # If there aren't any objects, there is nothing to do.
             if objs:
@@ -343,13 +343,13 @@
                     cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
                         (self.join_table, source_col_name, target_col_name),
                         [self._pk_val, obj_id])
-                transaction.commit_unless_managed()
+            transaction.commit_unless_managed(connection)
 
         def _remove_items(self, source_col_name, target_col_name, *objs):
             # source_col_name: the PK colname in join_table for the source object
             # target_col_name: the PK colname in join_table for the target object
             # *objs - objects to remove
-            from django.db import connection
+            connection = self.model._default_manager.db.connection
 
             # If there aren't any objects, there is nothing to do.
             if objs:
@@ -366,16 +366,16 @@
                     (self.join_table, source_col_name,
                     target_col_name, ",".join(['%s'] * len(old_ids))),
                     [self._pk_val] + list(old_ids))
-                transaction.commit_unless_managed()
+            transaction.commit_unless_managed(connection)
 
         def _clear_items(self, source_col_name):
             # source_col_name: the PK colname in join_table for the source object
-            from django.db import connection
+            connection = self.model._default_manager.db.connection
             cursor = connection.cursor()
             cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
                 (self.join_table, source_col_name),
                 [self._pk_val])
-            transaction.commit_unless_managed()
+            transaction.commit_unless_managed(connection)
 
     return ManyRelatedManager
 
@@ -399,7 +399,7 @@
         superclass = rel_model._default_manager.__class__
         RelatedManager = create_many_related_manager(superclass)
 
-        qn = backend.quote_name
+        qn = rel_model._default_manager.db.backend.quote_name
         manager = RelatedManager(
             model=rel_model,
             core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
@@ -440,7 +440,7 @@
         superclass = rel_model._default_manager.__class__
         RelatedManager = create_many_related_manager(superclass)
 
-        qn = backend.quote_name
+        qn = rel_model._default_manager.db.backend.quote_name
         manager = RelatedManager(
             model=rel_model,
             core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
=== django/db/models/query.py
==================================================================
--- django/db/models/query.py	(/django/trunk)	(revision 10934)
+++ django/db/models/query.py	(/local/django/multi-db)	(revision 10934)
@@ -182,8 +182,7 @@
         # self._select is a dictionary, and dictionaries' key order is
         # undefined, so we convert it to a list of tuples.
         extra_select = self._select.items()
-
-        cursor = connection.cursor()
+        cursor = self.model._default_manager.db.connection.cursor()
         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
 
         fill_cache = self._select_related
@@ -217,7 +216,11 @@
         """
         if self._result_cache is not None:
             return len(self._result_cache)
-
+	#from multi-db
+        db = self.model._default_manager.db
+        backend = db.backend
+        connection = db.connection 
+        
         counter = self._clone()
         counter._order_by = ()
         counter._select_related = False
@@ -304,6 +307,7 @@
         Returns a dictionary mapping each of the given IDs to the object with
         that ID.
         """
+        backend = self.model._default_manager.db.backend
         assert self._limit is None and self._offset is None, \
                 "Cannot use 'limit' or 'offset' with in_bulk"
         assert isinstance(id_list, (tuple,  list)), "in_bulk() must be provided with a list of IDs."
@@ -481,9 +485,11 @@
 
     def _get_sql_clause(self):
         opts = self.model._meta
-
+        backend = self.model._default_manager.db.backend
+        qn = backend.quote_name
         # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
-        select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
+        select = ["%s.%s" % (qn(opts.db_table), qn(f.column))
+                  for f in opts.fields]
         tables = [quote_only_if_word(t) for t in self._tables]
         joins = SortedDict()
         where = self._where[:]
@@ -504,10 +510,11 @@
 
         # Add any additional SELECTs.
         if self._select:
-            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
+            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0]))
+                           for s in self._select.items()])
 
         # Start composing the body of the SQL statement.
-        sql = [" FROM", backend.quote_name(opts.db_table)]
+        sql = [" FROM", qn(opts.db_table)]
 
         # Compose the join dictionary into SQL describing the joins.
         if joins:
@@ -540,15 +547,15 @@
                     order = "ASC"
                 if "." in col_name:
                     table_prefix, col_name = col_name.split('.', 1)
-                    table_prefix = backend.quote_name(table_prefix) + '.'
+                    table_prefix = qn(table_prefix) + '.'
                 else:
                     # Use the database table as a column prefix if it wasn't given,
                     # and if the requested column isn't a custom SELECT.
                     if "." not in col_name and col_name not in (self._select or ()):
-                        table_prefix = backend.quote_name(opts.db_table) + '.'
+                        table_prefix = qn(opts.db_table) + '.'
                     else:
                         table_prefix = ''
-                order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order))
+                order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order))
         if order_by:
             sql.append("ORDER BY " + ", ".join(order_by))
 
@@ -597,11 +604,20 @@
 
         columns = [f.column for f in fields]
         select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
+            
+	#from multi-db
+	db = self.model._default_manager.db
+        backend = db.backend
+        qn = backend.quote_name
+        connection = db.connection
+
         # Add any additional SELECTs.
         if self._select:
             select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()])
 
         cursor = connection.cursor()
+        select = ['%s.%s' % (qn(self.model._meta.db_table), qn(c))
+                  for c in columns]
         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
 
         has_resolve_columns = hasattr(self, 'resolve_columns')
@@ -623,17 +639,23 @@
     def iterator(self):
         from django.db.backends.util import typecast_timestamp
         from django.db.models.fields import DateTimeField
+        db = self.model._default_manager.db
+        backend = db.backend
+        qn = backend.quote_name
+        connection = db.connection
+        
         self._order_by = () # Clear this because it'll mess things up otherwise.
         if self._field.null:
             self._where.append('%s.%s IS NOT NULL' % \
-                (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
+                (qn(self.model._meta.db_table), qn(self._field.column)))
+
         try:
             select, sql, params = self._get_sql_clause()
         except EmptyResultSet:
             raise StopIteration
 
-        table_name = backend.quote_name(self.model._meta.db_table)
-        field_name = backend.quote_name(self._field.column)
+        table_name = qn(self.model._meta.db_table)
+        field_name = qn(self._field.column)
 
         if backend.allows_group_by_ordinal:
             group_by = '1'
@@ -768,10 +790,13 @@
             return SortedDict(), [], []
         return joins, where2, params
 
-def get_where_clause(lookup_type, table_prefix, field_name, value):
+def get_where_clause(opts, lookup_type, table_prefix, field_name, value):
+    backend = opts.get_default_manager().db.backend
+    qn = backend.quote_name
+    
     if table_prefix.endswith('.'):
-        table_prefix = backend.quote_name(table_prefix[:-1])+'.'
-    field_name = backend.quote_name(field_name)
+        table_prefix = qn(table_prefix[:-1])+'.'
+    field_name = qn(field_name)
     if type(value) == datetime.datetime and backend.get_datetime_cast_sql():
         cast_sql = backend.get_datetime_cast_sql()
     else:
@@ -832,12 +857,12 @@
     Helper function that recursively populates the select, tables and where (in
     place) for select_related queries.
     """
+    qn = opts.get_default_manager().db.backend.quote_name
 
     # If we've got a max_depth set and we've exceeded that depth, bail now.
     if max_depth and cur_depth > max_depth:
         return None
 
-    qn = backend.quote_name
     for f in opts.fields:
         if f.rel and not f.null:
             db_table = f.rel.to._meta.db_table
@@ -935,13 +960,16 @@
     return choices
 
 def lookup_inner(path, lookup_type, value, opts, table, column):
-    qn = backend.quote_name
     joins, where, params = SortedDict(), [], []
     current_opts = opts
     current_table = table
     current_column = column
     intermediate_table = None
     join_required = False
+    db = current_opts.get_default_manager().db
+    backend = db.backend
+    connection = db.connection
+    qn = backend.quote_name
 
     name = path.pop(0)
     # Has the primary key been requested? If so, expand it out
@@ -1092,20 +1120,23 @@
             # Last query term was a normal field.
             column = field.column
 
-        where.append(get_where_clause(lookup_type, current_table + '.', column, value))
+        where.append(get_where_clause(current_opts, lookup_type, current_table + '.', column, value))
         params.extend(field.get_db_prep_lookup(lookup_type, value))
 
     return joins, where, params
 
 def delete_objects(seen_objs):
     "Iterate through a list of seen classes, and remove any instances that are referred to"
-    qn = backend.quote_name
     ordered_classes = seen_objs.keys()
     ordered_classes.reverse()
 
-    cursor = connection.cursor()
-
     for cls in ordered_classes:
+        db = cls._default_manager.db
+        backend = db.backend
+        connection = db.connection
+        cursor = connection.cursor()
+        qn = backend.quote_name
+        
         seen_objs[cls] = seen_objs[cls].items()
         seen_objs[cls].sort()
 
@@ -1144,7 +1175,17 @@
                         pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
 
     # Now delete the actual data
+    dirty_conns = []
     for cls in ordered_classes:
+
+        db = cls._default_manager.db
+        backend = db.backend
+        connection = db.connection
+        qn = backend.quote_name
+        cursor = connection.cursor()
+        if connection not in dirty_conns:
+            dirty_conns.append(connection)
+            
         seen_objs[cls].reverse()
         pk_list = [pk for pk,instance in seen_objs[cls]]
         for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
@@ -1163,4 +1204,4 @@
             setattr(instance, cls._meta.pk.attname, None)
             dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
 
-    transaction.commit_unless_managed()
+    transaction.commit_unless_managed(dirty_conns)
=== django/db/__init__.py
==================================================================
--- django/db/__init__.py	(/django/trunk)	(revision 10934)
+++ django/db/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -1,49 +1,376 @@
-from django.conf import settings
+from django.conf import settings, UserSettingsHolder
 from django.core import signals
+from django.core.exceptions import ImproperlyConfigured
 from django.dispatch import dispatcher
 
+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
+
 __all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
 
+
+# singleton to represent the default connection in connections
+class dummy(object):
+    def __repr__(self):
+        return self.__str__()    
+    def __str__(self):
+        return '<default>'
+_default = dummy()
+del dummy
+
+
+# storage for local default connection
+_local = local()
+
+
 if not settings.DATABASE_ENGINE:
     settings.DATABASE_ENGINE = 'dummy'
+    
 
-try:
-    backend = __import__('django.db.backends.%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
-except ImportError, e:
-    # The database backend wasn't found. Display a helpful error message
-    # listing all possible database backends.
-    from django.core.exceptions import ImproperlyConfigured
-    import os
-    backend_dir = os.path.join(__path__[0], 'backends')
-    available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
-    available_backends.sort()
-    if settings.DATABASE_ENGINE not in available_backends:
-        raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
-            (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
-    else:
-        raise # If there's some other error, this must be an error in Django itself.
+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.
+    """
+    return ConnectionInfo(settings, **kw)
 
-get_introspection_module = lambda: __import__('django.db.backends.%s.introspection' % settings.DATABASE_ENGINE, {}, {}, [''])
-get_creation_module = lambda: __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
-runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, {}, {}, ['']).runshell()
+    
+class ConnectionInfo(object):
+    """Encapsulates all information about a connection and the backend
+    to which it belongs. Provides methods for loading backend
+    creation, introspection, and shell modules, closing the
+    connection, and resetting the connection's query log.
+    """
+    def __init__(self, settings=None, **kw):
+        super(ConnectionInfo, self).__init__(**kw)
+        if settings is None:
+            from django.conf import settings
+        if not settings.DATABASE_OPTIONS:
+            settings.DATABASE_OPTIONS = {}
+        self.settings = settings
+        self.backend = self.load_backend()
+        self.connection = self.backend.DatabaseWrapper(settings)
+        self.DatabaseError = self.backend.DatabaseError
 
-connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
-DatabaseError = 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,
+                  self.settings.DATABASE_ENGINE,
+                  self.settings.DATABASE_NAME)
+
+    def close(self):
+        """Close connection"""
+        self.connection.close()
+
+    def get_introspection_module(self):
+        return __import__('django.db.backends.%s.introspection' % 
+                          self.settings.DATABASE_ENGINE, {}, {}, [''])
+
+    def get_creation_module(self):
+        return __import__('django.db.backends.%s.creation' % 
+                          self.settings.DATABASE_ENGINE, {}, {}, [''])
+
+    def load_backend(self):
+        try:
+            backend = __import__('django.db.backends.%s.base' %
+                                 self.settings.DATABASE_ENGINE, {}, {}, [''])
+        except ImportError, e:
+            # The database backend wasn't found. Display a helpful error
+            # message listing all possible database backends.
+            import os
+            backend_dir = os.path.join(__path__[0], 'backends')
+            available_backends = [f for f in os.listdir(backend_dir)
+                                  if not f.startswith('_')  \
+                                  and not f.startswith('.') \
+                                  and not f.endswith('.py') \
+                                  and not f.endswith('.pyc')]
+            available_backends.sort()
+            if self.settings.DATABASE_ENGINE not in available_backends:
+                raise ImproperlyConfigured, \
+                      "%r isn't an available database backend. "\
+                      "Available options are: %s" % \
+                      (self.settings.DATABASE_ENGINE,
+                       ", ".join(map(repr, available_backends)))
+            else:
+                # If there's some other error, this must be an error
+                # in Django itself.
+                raise
+        return backend
+
+    def runshell(self):
+        __import__('django.db.backends.%s.client' %
+                   self.settings.DATABASE_ENGINE, {}, {}, ['']
+                   ).runshell(self.settings)
+
+
+        
+    def reset_queries(self):
+        """Reset log of queries executed by connection"""
+        self.connection.queries = []
+
+
+class LazyConnectionManager(object):
+    """Manages named connections lazily, instantiating them as
+    they are requested.
+    """
+    def __init__(self):
+        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):
+        # 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)
+
+    def __getitem__(self, k):
+        try:
+            return self.local.connections[k]
+        except (AttributeError, KeyError):
+            return self.connect(k)
+
+    def __setitem__(self, k, v):
+        try:
+            self.local.connections[k] = v
+        except AttributeError:
+            # First access in thread
+            self.local.connections = {k: v}
+            
+    def connect(self, name):
+        """Return the connection with this name in
+        settings.OTHER_DATABASES. Creates the connection if it doesn't yet
+        exist. Reconnects if it does. If the name requested is the default
+        connection (a singleton defined in django.db), then the default
+        connection is returned.        
+        """
+        try:
+            cnx = self.local.connections
+        except AttributeError:
+            cnx = self.local.connections = {}
+            
+        if name in cnx:
+            cnx[name].close()
+        if name is _default:
+            cnx[name] = connect(settings)
+            return cnx[name]
+        try:
+            info = settings.OTHER_DATABASES[name]
+        except KeyError:
+            raise ImproperlyConfigured, \
+                  "No database connection '%s' has been configured" % name
+        except AttributeError:
+            raise ImproperlyConfigured, \
+                  "No OTHER_DATABASES in settings."
+
+        # In settings it's a dict, but connect() needs an object:
+        # pass global settings so that the default connection settings
+        # can be defaults for the named connections.
+        database = UserSettingsHolder(settings)
+        for k, v in info.items():
+            setattr(database, k, v)
+        cnx[name] = connect(database)
+        return cnx[name]
+
+    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
+    current settings.
+    """
+    app = klass._meta.app_label
+    model = klass.__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 _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 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 maybe
+    # No named connection for this model; use the default
+    return _default
+
+
+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 a ``model`` attribute that holds a model class
+    can use this descriptor to manage connections.
+    """
+    
+    def __init__(self):
+        self.local = local()
+        self.local.cnx = {}
+        dispatcher.connect(self.reset, signal=signals.request_finished)
+        
+    def __get__(self, instance, type=None):
+        if instance is None:
+            raise AttributeError, \
+                "ConnectionInfo is accessible only through an instance"
+        try:
+            instance_connection = self.local.cnx.get(instance, None)
+        except AttributeError:
+            # First access in this thread
+            self.local.cnx = {}
+            instance_connection = None
+        if instance_connection is None:
+            instance_connection = self.get_connection(instance)
+            self.local.cnx[instance] = instance_connection
+        return instance_connection
+
+    def __set__(self, instance, value):
+        try:
+            self.local.cnx[instance] = value
+        except AttributeError:
+            # First access in thread
+            self.local.cnx = {instance: value}
+
+    def __delete__(self, instance):
+        try:
+            del self.local.cnx[instance]
+        except (AttributeError, KeyError):
+            # Not stored, no need to reset
+            pass
+
+    def get_connection(self, instance):
+        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
+    attach module-level properties to local storage.
+    """
+    def __init__(self, name, storage, func, *arg, **kw):
+        self.__name = name
+        self.__storage = storage
+        self.__func = func
+        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'):
+            return self.__dict__[attr]
+        try:
+            return getattr(getattr(self.__storage, self.__name), attr)
+        except AttributeError:
+            setattr(self.__storage, self.__name, self.__func(*self.__arg,
+                                                             **self.__kw))
+            return getattr(getattr(self.__storage, self.__name), attr)
+
+    def __setattr__(self, attr, val):
+        # Private (__*) attributes are munged
+        if attr.startswith('_LocalizingProxy'):
+            self.__dict__[attr] = val
+            return
+        try:
+            stor = getattr(self.__storage, self.__name)            
+        except AttributeError:
+            stor =  self.__func(*self.__arg)
+            setattr(self.__storage, self.__name, stor)
+        setattr(stor, attr, val)
+
+
+# Create a manager for named connections
+connections = LazyConnectionManager()
+
+# Backwards compatibility: establish the default connection and set the
+# default connection properties at module level, using the lazy proxy so that
+# each thread may have a different default connection, if so configured
+connection_info = LocalizingProxy('connection_info', _local,
+                                  lambda: connections[_default])
+connection = LocalizingProxy('connection', _local,
+                             lambda: connections[_default].connection)
+backend = LocalizingProxy('backend', _local,
+                          lambda: connections[_default].backend)
+DatabaseError = LocalizingProxy('DatabaseError', _local,
+                                lambda: connections[_default].DatabaseError)
+#================================BUG==Might need LocalizingProxy==============
 IntegrityError = backend.IntegrityError
+get_introspection_module = LocalizingProxy(
+    'get_introspection_module', _local,
+    lambda: connections[_default].get_introspection_module)
+get_creation_module = LocalizingProxy(
+    'get_creation_module', _local,
+    lambda: connections[_default].get_creation_module)
+runshell = LocalizingProxy('runshell', _local,
+                           lambda: connections[_default].runshell)
 
-# Register an event that closes the database connection
-# when a Django request is finished.
-dispatcher.connect(connection.close, signal=signals.request_finished)
 
-# Register an event that resets connection.queries
-# when a Django request is started.
-def reset_queries():
-    connection.queries = []
-dispatcher.connect(reset_queries, signal=signals.request_started)
-
-# Register an event that rolls back the connection
+# Register an event that rolls back all connections
 # when a Django request has an exception.
 def _rollback_on_exception():
     from django.db import transaction
     transaction.rollback_unless_managed()
-dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
+dispatcher.connect(_rollback_on_exception,
+                   signal=signals.got_request_exception)
+
+
=== django/db/backends/ado_mssql/base.py
==================================================================
--- django/db/backends/ado_mssql/base.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/ado_mssql/base.py	(/local/django/multi-db)	(revision 10934)
@@ -48,20 +48,15 @@
     return res
 Database.convertVariantToPython = variantToPython
 
-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
-
-class DatabaseWrapper(local):
-    def __init__(self, **kwargs):
+class DatabaseWrapper(object):
+    def __init__(self, settings):
+        self.settings = settings
+        self.options = settings.DATABASE_OPTIONS
         self.connection = None
         self.queries = []
 
     def cursor(self):
-        from django.conf import settings
+        settings = self.settings
         if self.connection is None:
             if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '':
                 from django.core.exceptions import ImproperlyConfigured
@@ -95,6 +90,7 @@
 needs_datetime_string_cast = True
 needs_upper_for_iops = False
 supports_constraints = True
+supports_compound_statements = True
 supports_tablespaces = True
 uses_case_insensitive_names = False
 
=== django/db/backends/ado_mssql/client.py
==================================================================
--- django/db/backends/ado_mssql/client.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/ado_mssql/client.py	(/local/django/multi-db)	(revision 10934)
@@ -1,2 +1,2 @@
-def runshell():
+def runshell(settings):
     raise NotImplementedError
=== django/db/backends/ado_mssql/creation.py
==================================================================
--- django/db/backends/ado_mssql/creation.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/ado_mssql/creation.py	(/local/django/multi-db)	(revision 10934)
@@ -1,3 +1,6 @@
+from django.db.backends.ansi import sql
+builder = sql.SchemaBuilder()
+
 DATA_TYPES = {
     'AutoField':         'int IDENTITY (1, 1)',
     'BooleanField':      'bit',
=== django/db/backends/postgresql/base.py
==================================================================
--- django/db/backends/postgresql/base.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/postgresql/base.py	(/local/django/multi-db)	(revision 10934)
@@ -14,13 +14,9 @@
 DatabaseError = Database.DatabaseError
 IntegrityError = Database.IntegrityError
 
-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
-
+class DatabaseWrapper(object):
+    def __init__(self, settings):
+        self.settings = settings
 def smart_basestring(s, charset):
     if isinstance(s, unicode):
         return s.encode(charset)
@@ -55,14 +51,12 @@
 
 postgres_version = None
 
-class DatabaseWrapper(local):
-    def __init__(self, **kwargs):
         self.connection = None
         self.queries = []
-        self.options = kwargs
+        self.options = settings.DATABASE_OPTIONS
 
     def cursor(self):
-        from django.conf import settings
+        settings = self.settings
         set_tz = False
         if self.connection is None:
             set_tz = True
@@ -111,6 +105,7 @@
 needs_datetime_string_cast = True
 needs_upper_for_iops = False
 supports_constraints = True
+supports_compound_statements = True
 supports_tablespaces = False
 uses_case_insensitive_names = False
 
=== django/db/backends/postgresql/client.py
==================================================================
--- django/db/backends/postgresql/client.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/postgresql/client.py	(/local/django/multi-db)	(revision 10934)
@@ -1,7 +1,6 @@
-from django.conf import settings
 import os
 
-def runshell():
+def runshell(settings):
     args = ['psql']
     if settings.DATABASE_USER:
         args += ["-U", settings.DATABASE_USER]
=== django/db/backends/postgresql/sql.py
==================================================================
--- django/db/backends/postgresql/sql.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/postgresql/sql.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,41 @@
+from django.db import models
+from django.db.backends.ansi.sql import BoundStatement, SchemaBuilder, \
+    default_style
+
+class PgSchemaBuilder(SchemaBuilder):
+    """SchemaBuilder for postgres. Implements an additional method that
+    outputs SQL statements to reset the sequence(s) for a model.
+    """
+    def get_sequence_reset(self, model, style=None):
+        """Get sequence reset sql for a model.
+        """
+        if style is None:
+            style=default_style
+        for f in model._meta.fields:
+            output = []
+            db = model._default_manager.db
+            connection = db.connection
+            qn = db.backend.quote_name
+            if isinstance(f, models.AutoField):
+                output.append(BoundStatement(
+                        "%s setval('%s', (%s max(%s) %s %s));" % \
+                        (style.SQL_KEYWORD('SELECT'),
+                         style.SQL_FIELD('%s_%s_seq' %
+                                         (model._meta.db_table, f.column)),
+                         style.SQL_KEYWORD('SELECT'),
+                         style.SQL_FIELD(qn(f.column)),
+                         style.SQL_KEYWORD('FROM'),
+                         style.SQL_TABLE(qn(model._meta.db_table))),
+                        connection))
+                break # Only one AutoField is allowed per model, so don't bother continuing.
+        for f in model._meta.many_to_many:
+            output.append(
+                BoundStatement("%s setval('%s', (%s max(%s) %s %s));" % \
+                               (style.SQL_KEYWORD('SELECT'),
+                                style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()),
+                                style.SQL_KEYWORD('SELECT'),
+                                style.SQL_FIELD(qn('id')),
+                                style.SQL_KEYWORD('FROM'),
+                                style.SQL_TABLE(f.m2m_db_table())),
+                               connection))
+        return output
=== django/db/backends/postgresql/creation.py
==================================================================
--- django/db/backends/postgresql/creation.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/postgresql/creation.py	(/local/django/multi-db)	(revision 10934)
@@ -1,3 +1,6 @@
+from django.db.backends.postgresql import sql
+builder = sql.PgSchemaBuilder()
+
 # This dictionary maps Field objects to their associated PostgreSQL column
 # types, as strings. Column-type strings can contain format strings; they'll
 # be interpolated against the values of Field.__dict__ before being output.
=== django/db/backends/sqlite3/base.py
==================================================================
--- django/db/backends/sqlite3/base.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/sqlite3/base.py	(/local/django/multi-db)	(revision 10934)
@@ -50,13 +50,14 @@
     from django.utils._threading_local import local
 
 class DatabaseWrapper(local):
-    def __init__(self, **kwargs):
+    def __init__(self, settings):
+        self.settings = settings
         self.connection = None
         self.queries = []
-        self.options = kwargs
+        self.options = settings.DATABASE_OPTIONS
 
     def cursor(self):
-        from django.conf import settings
+        settings = self.settings
         if self.connection is None:
             kwargs = {
                 'database': settings.DATABASE_NAME,
@@ -114,6 +115,7 @@
 needs_datetime_string_cast = True
 needs_upper_for_iops = False
 supports_constraints = False
+supports_compound_statements = False
 supports_tablespaces = False
 uses_case_insensitive_names = False
 
=== django/db/backends/sqlite3/client.py
==================================================================
--- django/db/backends/sqlite3/client.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/sqlite3/client.py	(/local/django/multi-db)	(revision 10934)
@@ -1,6 +1,5 @@
-from django.conf import settings
 import os
 
-def runshell():
+def runshell(settings):
     args = ['', settings.DATABASE_NAME]
     os.execvp('sqlite3', args)
=== django/db/backends/sqlite3/creation.py
==================================================================
--- django/db/backends/sqlite3/creation.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/sqlite3/creation.py	(/local/django/multi-db)	(revision 10934)
@@ -1,3 +1,6 @@
+from django.db.backends.ansi import sql
+builder = sql.SchemaBuilder()
+
 # SQLite doesn't actually support most of these types, but it "does the right
 # thing" given more verbose field definitions, so leave them as is so that
 # schema inspection is more useful.
=== django/db/backends/mysql/base.py
==================================================================
--- django/db/backends/mysql/base.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/mysql/base.py	(/local/django/multi-db)	(revision 10934)
@@ -53,19 +53,14 @@
 # standard util.CursorDebugWrapper can be used. Also, using sql_mode
 # TRADITIONAL will automatically cause most warnings to be treated as errors.
 
-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
 
-class DatabaseWrapper(local):
-    def __init__(self, **kwargs):
+class DatabaseWrapper(object):
+    def __init__(self, settings):
+        self.settings = settings
         self.connection = None
         self.queries = []
         self.server_version = None
-        self.options = kwargs
+        self.options = settings.DATABASE_OPTIONS
 
     def _valid_connection(self):
         if self.connection is not None:
@@ -78,7 +73,7 @@
         return False
 
     def cursor(self):
-        from django.conf import settings
+        settings = self.settings
         from warnings import filterwarnings
         if not self._valid_connection():
             kwargs = {
@@ -140,6 +135,7 @@
 needs_datetime_string_cast = True     # MySQLdb requires a typecast for dates
 needs_upper_for_iops = False
 supports_constraints = True
+supports_compound_statements = True
 supports_tablespaces = False
 uses_case_insensitive_names = False
 
=== django/db/backends/mysql/client.py
==================================================================
--- django/db/backends/mysql/client.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/mysql/client.py	(/local/django/multi-db)	(revision 10934)
@@ -1,7 +1,6 @@
-from django.conf import settings
 import os
 
-def runshell():
+def runshell(settings):
     args = ['']
     db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
     user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
=== django/db/backends/mysql/creation.py
==================================================================
--- django/db/backends/mysql/creation.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/mysql/creation.py	(/local/django/multi-db)	(revision 10934)
@@ -1,3 +1,6 @@
+from django.db.backends.ansi import sql
+builder = sql.SchemaBuilder()
+
 # This dictionary maps Field objects to their associated MySQL column
 # types, as strings. Column-type strings can contain format strings; they'll
 # be interpolated against the values of Field.__dict__ before being output.
=== django/db/backends/oracle/base.py
==================================================================
--- django/db/backends/oracle/base.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/oracle/base.py	(/local/django/multi-db)	(revision 10934)
@@ -18,23 +18,18 @@
 DatabaseError = Database.Error
 IntegrityError = Database.IntegrityError
 
-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
-
-class DatabaseWrapper(local):
-    def __init__(self, **kwargs):
+class DatabaseWrapper(object):
+    def __init__(self, settings):
+        self.settings = settings
         self.connection = None
         self.queries = []
-        self.options = kwargs
+        self.options = settings.DATABASE_OPTIONS
 
     def _valid_connection(self):
         return self.connection is not None
 
     def cursor(self):
+        settings = self.settings
         if not self._valid_connection():
             if len(settings.DATABASE_HOST.strip()) == 0:
                 settings.DATABASE_HOST = 'localhost'
@@ -73,6 +68,7 @@
 needs_datetime_string_cast = False
 needs_upper_for_iops = True
 supports_constraints = True
+supports_compound_statements = True
 supports_tablespaces = True
 uses_case_insensitive_names = True
 
=== django/db/backends/oracle/client.py
==================================================================
--- django/db/backends/oracle/client.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/oracle/client.py	(/local/django/multi-db)	(revision 10934)
@@ -1,7 +1,6 @@
-from django.conf import settings
 import os
 
-def runshell():
+def runshell(settings):
     dsn = settings.DATABASE_USER
     if settings.DATABASE_PASSWORD:
         dsn += "/%s" % settings.DATABASE_PASSWORD
=== django/db/backends/postgresql_psycopg2/base.py
==================================================================
--- django/db/backends/postgresql_psycopg2/base.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/postgresql_psycopg2/base.py	(/local/django/multi-db)	(revision 10934)
@@ -24,13 +24,14 @@
 postgres_version = None
 
 class DatabaseWrapper(local):
-    def __init__(self, **kwargs):
+    def __init__(self, settings):
+        self.settings = settings
         self.connection = None
         self.queries = []
-        self.options = kwargs
+        self.options = settings.DATABASE_OPTIONS
 
     def cursor(self):
-        from django.conf import settings
+        settings = self.settings
         set_tz = False
         if self.connection is None:
             set_tz = True
@@ -79,6 +80,7 @@
 needs_datetime_string_cast = False
 needs_upper_for_iops = False
 supports_constraints = True
+supports_compound_statements = True
 supports_tablespaces = False
 uses_case_insensitive_names = False
 
=== django/db/backends/ansi/__init__.py
==================================================================
--- django/db/backends/ansi/__init__.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/ansi/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1 @@
+pass
=== django/db/backends/ansi/sql.py
==================================================================
--- django/db/backends/ansi/sql.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/ansi/sql.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,363 @@
+"""ANSISQL schema manipulation functions and classes
+"""
+import os
+import re
+from django.db import models
+from django.contrib.contenttypes import generic
+
+# For Python 2.3
+if not hasattr(__builtins__, 'set'):
+    from sets import Set as set
+
+# default dummy style
+class dummy:
+    def __getattr__(self, attr):
+        return lambda x: x
+default_style = dummy()
+del dummy
+
+class BoundStatement(object):
+    """Represents an SQL statement that is to be executed, at some point in
+    the future, using a specific database connection.
+    """
+    def __init__(self, sql, connection):
+        self.sql = sql
+        self.connection = connection
+
+    def execute(self):
+        cursor = self.connection.cursor()
+        cursor.execute(self.sql)
+
+    def __repr__(self):
+        return "BoundStatement(%r)" % self.sql
+
+    def __str__(self):
+        return self.sql
+
+    def __eq__(self, other):
+        return self.sql == other.sql and self.connection == other.connection
+
+class SchemaBuilder(object):
+    """Basic ANSI SQL schema element builder. Instances of this class may be
+    used to construct SQL expressions that create or drop schema elements such
+    as tables, indexes and (for those backends that support them) foreign key
+    or other constraints.
+    """
+    def __init__(self):
+        # models that I have created
+        self.models_already_seen = set()
+        # model references, keyed by the referrent model
+        self.references = {}
+        # table cache; set to short-circuit table lookups
+        self.tables = None
+        
+    def get_create_table(self, model, style=None, pending=None):
+        """Construct and return the SQL expression(s) needed to create the
+        table for the given model, and any constraints on that
+        table. The return value is a 2-tuple. The first element of the tuple
+        is a list of BoundStatements that may be executed immediately. The
+        second is a dict of BoundStatements representing constraints that
+        can't be executed immediately because (for instance) the referent
+        table does not exist, keyed by the model class they reference.
+        """
+        if style is None:
+            style = default_style
+        if pending is None:
+            pending = {}
+        self.models_already_seen.add(model)
+        
+        opts = model._meta
+        db = model._default_manager.db
+        backend = db.backend
+        quote_name = backend.quote_name
+        data_types = db.get_creation_module().DATA_TYPES
+        table_output = []
+
+        for f in opts.fields:
+            if isinstance(f, (models.ForeignKey, models.OneToOneField)):
+                rel_field = f.rel.get_related_field()
+                data_type = self.get_rel_data_type(rel_field)
+            else:
+                rel_field = f
+                data_type = f.get_internal_type()
+            col_type = data_types[data_type]
+            if col_type is not None:
+                # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
+                field_output = [style.SQL_FIELD(quote_name(f.column)),
+                    style.SQL_COLTYPE(col_type % rel_field.__dict__)]
+                field_output.append(style.SQL_KEYWORD(
+                        '%sNULL' % (not f.null and 'NOT ' or '')))
+                if f.unique:
+                    field_output.append(style.SQL_KEYWORD('UNIQUE'))
+                if f.primary_key:
+                    field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
+                if f.rel:
+                    if f.rel.to in self.models_already_seen:
+                        field_output.append(
+                            style.SQL_KEYWORD('REFERENCES') + ' ' + 
+                            style.SQL_TABLE(
+                                quote_name(f.rel.to._meta.db_table)) + ' (' + 
+                            style.SQL_FIELD(
+                                quote_name(f.rel.to._meta.get_field(
+                                        f.rel.field_name).column)) + ')'
+                        )
+                    else:
+                        # We haven't yet created the table to which this field
+                        # is related, so save it for later.
+                        if backend.supports_constraints:
+                            pending.setdefault(f.rel.to, []).append((model, f))
+                table_output.append(' '.join(field_output))
+        if opts.order_with_respect_to:
+            table_output.append(style.SQL_FIELD(quote_name('_order')) + ' ' + \
+                style.SQL_COLTYPE(data_types['IntegerField']) + ' ' + \
+                style.SQL_KEYWORD('NULL'))
+        for field_constraints in opts.unique_together:
+            table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
+                ", ".join([quote_name(style.SQL_FIELD(
+                                opts.get_field(f).column))
+                           for f in field_constraints]))
+
+        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + 
+                          style.SQL_TABLE(quote_name(opts.db_table)) + ' (']
+        for i, line in enumerate(table_output): # Combine and add commas.
+            full_statement.append('    %s%s' %
+                                  (line, i < len(table_output)-1 and ',' or ''))
+        full_statement.append(');')
+        create = [BoundStatement('\n'.join(full_statement), db.connection)]
+
+        # Pull out any pending statements for me
+        if pending:
+            if model in pending:
+                for rel_class, f in pending[model]:
+                    create.append(self.get_ref_sql(model, rel_class, f,
+                                                   style=style))
+                # What was pending for me is now no longer pending
+                pending.pop(model)
+        return (create, pending)    
+
+    def get_create_indexes(self, model, style=None):
+        """Construct and return SQL statements needed to create the indexes for
+        a model. Returns a list of BoundStatements.
+        """
+        if style is None:
+            style = default_style
+        db = model._default_manager.db
+        backend = db.backend
+        connection = db.connection
+        output = []
+        for f in model._meta.fields:
+            if f.db_index:
+                unique = f.unique and 'UNIQUE ' or ''
+                output.append(
+                    BoundStatement(
+                        ' '.join(
+                            [style.SQL_KEYWORD('CREATE %sINDEX' % unique), 
+                             style.SQL_TABLE('%s_%s' %
+                                             (model._meta.db_table, f.column)),
+                             style.SQL_KEYWORD('ON'), 
+                             style.SQL_TABLE(
+                                    backend.quote_name(model._meta.db_table)),
+                             "(%s);" % style.SQL_FIELD(
+                                    backend.quote_name(f.column))]),
+                        connection)
+                    )
+        return output
+
+    def get_create_many_to_many(self, model, style=None):
+        """Construct and return SQL statements needed to create the
+        tables and relationships for all many-to-many relations
+        defined in the model. Returns a list of bound statments. Note
+        that these statements should only be executed after all models
+        for an app have been created.
+        """
+        if style is None:
+            style = default_style
+        db = model._default_manager.db
+        quote_name = db.backend.quote_name
+        connection = db.connection
+        data_types = db.get_creation_module().DATA_TYPES
+        opts = model._meta
+
+        # statements to execute, keyed by the other model
+        output = {}       
+        for f in opts.many_to_many:
+            if not isinstance(f.rel, generic.GenericRel):
+                table_output = [
+                    style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
+                    style.SQL_TABLE(quote_name(f.m2m_db_table())) + ' (']
+                table_output.append('    %s %s %s,' % \
+                    (style.SQL_FIELD(quote_name('id')),
+                    style.SQL_COLTYPE(data_types['AutoField']),
+                    style.SQL_KEYWORD('NOT NULL PRIMARY KEY')))
+                table_output.append('    %s %s %s %s (%s),' % \
+                    (style.SQL_FIELD(quote_name(f.m2m_column_name())),
+                    style.SQL_COLTYPE(data_types[self.get_rel_data_type(opts.pk)] % opts.pk.__dict__),
+                    style.SQL_KEYWORD('NOT NULL REFERENCES'),
+                    style.SQL_TABLE(quote_name(opts.db_table)),
+                    style.SQL_FIELD(quote_name(opts.pk.column))))
+                table_output.append('    %s %s %s %s (%s),' % \
+                    (style.SQL_FIELD(quote_name(f.m2m_reverse_name())),
+                    style.SQL_COLTYPE(data_types[self.get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__),
+                    style.SQL_KEYWORD('NOT NULL REFERENCES'),
+                    style.SQL_TABLE(quote_name(f.rel.to._meta.db_table)),
+                    style.SQL_FIELD(quote_name(f.rel.to._meta.pk.column))))
+                table_output.append('    %s (%s, %s)' % \
+                    (style.SQL_KEYWORD('UNIQUE'),
+                    style.SQL_FIELD(quote_name(f.m2m_column_name())),
+                    style.SQL_FIELD(quote_name(f.m2m_reverse_name()))))
+                table_output.append(');')
+                output.setdefault(f.rel.to, []).append(
+                    BoundStatement('\n'.join(table_output), connection))
+        return output
+
+    def get_drop_table(self, model, cascade=False, style=None):
+        """Construct and return the SQL statment(s) needed to drop a model's
+        table. If cascade is true, then output additional statments to drop any
+        many-to-many tables that this table created and any foreign keys that
+        reference this table.
+        """
+        if style is None:
+            style = default_style
+        opts = model._meta
+        db = model._default_manager.db
+        db_table = opts.db_table
+        backend = db.backend
+        qn = backend.quote_name
+        output = []
+        output.append(BoundStatement(
+                '%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
+                            style.SQL_TABLE(qn(db_table))),
+                db.connection))
+
+        if cascade:
+            # deal with others that might have a foreign key TO me: alter
+            # their tables to drop the constraint
+            if backend.supports_constraints:
+                references_to_delete = self.get_references()
+                if model in references_to_delete:
+                    for rel_class, f in references_to_delete[model]:
+                        table = rel_class._meta.db_table
+                        if not self.table_exists(db, table):
+                            print "NO TABLE %s" % table
+                            continue
+                        col = f.column
+                        r_table = opts.db_table
+                        r_col = opts.get_field(f.rel.field_name).column
+                        output.append(BoundStatement(
+                            '%s %s %s %s;' % 
+                            (style.SQL_KEYWORD('ALTER TABLE'),
+                             style.SQL_TABLE(qn(table)),
+                             style.SQL_KEYWORD(
+                                        backend.get_drop_foreignkey_sql()),
+                             style.SQL_FIELD(qn("%s_refs_%s_%x" %
+                                                (col, r_col,
+                                                 abs(hash((table, r_table)))))
+                                             )),                            
+                            db.connection))
+                    del references_to_delete[model]
+            # many to many: drop any many-many tables that are my
+            # responsiblity
+            for f in opts.many_to_many:
+                if not isinstance(f.rel, generic.GenericRel):
+                    output.append(BoundStatement(
+                            '%s %s;' %
+                            (style.SQL_KEYWORD('DROP TABLE'),
+                             style.SQL_TABLE(qn(f.m2m_db_table()))),
+                            db.connection))
+        return output
+        
+    def get_initialdata(self, model):
+        opts = model._meta
+        db = model._default_manager.db
+        settings = db.connection.settings
+        backend = db.backend
+        app_dir = self.get_initialdata_path(model)
+        output = []
+
+        # Some backends can't execute more than one SQL statement at a time.
+        # We'll split the initial data into individual statements unless
+        # backend.supports_compound_statements.
+        statements = re.compile(r";[ \t]*$", re.M)
+
+        # Find custom SQL, if it's available.
+        sql_files = [os.path.join(app_dir, "%s.%s.sql" %
+                                  (opts.object_name.lower(),
+                                   settings.DATABASE_ENGINE)),
+                     os.path.join(app_dir, "%s.sql" %
+                                  opts.object_name.lower())]
+        for sql_file in sql_files:
+            if os.path.exists(sql_file):
+                fp = open(sql_file, 'U')
+                if backend.supports_compound_statements:
+                    output.append(BoundStatement(fp.read(), db.connection))
+                else:                                 
+                    for statement in statements.split(fp.read()):
+                        # Remove any comments from the file
+                        statement = re.sub(r"--.*[\n\Z]", "", statement)
+                        if statement.strip():
+                            output.append(BoundStatement(statement + ";",
+                                                         db.connection))
+                fp.close()
+        return output
+
+    def get_initialdata_path(self, model):
+        """Get the path from which to load sql initial data files for a model.
+        """
+        return os.path.normpath(os.path.join(os.path.dirname(
+                    models.get_app(model._meta.app_label).__file__), 'sql'))
+            
+    def get_rel_data_type(self, f):
+        return (f.get_internal_type() in ('AutoField', 'PositiveIntegerField',
+                                          'PositiveSmallIntegerField')) \
+                                          and 'IntegerField' \
+                                          or f.get_internal_type()
+
+    def get_ref_sql(self, model, rel_class, f, style=None):
+        """Get sql statement for a reference between model and rel_class on
+        field f.
+        """
+        if style is None:
+            style = default_style
+        
+        db = model._default_manager.db
+        qn = db.backend.quote_name
+        opts = model._meta
+        rel_opts = rel_class._meta
+        table = rel_opts.db_table
+        r_col = f.column
+        r_table = opts.db_table
+        col = opts.get_field(f.rel.field_name).column
+        # For MySQL, r_name must be unique in the first 64 
+        # characters. So we are careful with character usage here.
+        r_name = '%s_refs_%s_%x' % (col, r_col,
+                                    abs(hash((r_table, table))))
+        sql = style.SQL_KEYWORD('ALTER TABLE') + \
+              ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \
+              (qn(table), qn(r_name),
+               qn(r_col), qn(r_table), qn(col))
+        return BoundStatement(sql, db.connection)
+        
+    def get_references(self):
+        """Fill (if needed) and return the reference cache.
+        """
+        if self.references:
+            return self.references
+        for klass in models.get_models():
+            for f in klass._meta.fields:
+                if f.rel:
+                    self.references.setdefault(f.rel.to,
+                                               []).append((klass, f))
+        return self.references
+
+    def get_table_list(self, db):
+        """Get list of tables accessible via db.
+        """
+        if self.tables is not None:
+            return self.tables
+        cursor = db.connection.cursor()
+        introspection = db.get_introspection_module()
+        return introspection.get_table_list(cursor)        
+    
+    def table_exists(self, db, table):
+        tables = self.get_table_list(db)
+        return table in tables
=== django/db/backends/dummy/base.py
==================================================================
--- django/db/backends/dummy/base.py	(/django/trunk)	(revision 10934)
+++ django/db/backends/dummy/base.py	(/local/django/multi-db)	(revision 10934)
@@ -22,12 +22,13 @@
     pass
 
 class DatabaseWrapper:
+    
     cursor = complain
     _commit = complain
     _rollback = ignore
 
-    def __init__(self, **kwargs):
-        pass
+    def __init__(self, settings):
+        self.settings = settings
 
     def close(self):
         pass # close()
=== django/db/transaction.py
==================================================================
--- django/db/transaction.py	(/django/trunk)	(revision 10934)
+++ django/db/transaction.py	(/local/django/multi-db)	(revision 10934)
@@ -16,7 +16,7 @@
     import thread
 except ImportError:
     import dummy_thread as thread
-from django.db import connection
+from django import db
 from django.conf import settings
 
 class TransactionManagementError(Exception):
@@ -116,48 +116,67 @@
     Puts the transaction manager into a manual state: managed transactions have
     to be committed explicitly by the user. If you switch off transaction
     management and there is a pending commit/rollback, the data will be
-    commited.
+    commited. Note that managed state applies across all connections.
     """
     thread_ident = thread.get_ident()
     top = state.get(thread_ident, None)
     if top:
         top[-1] = flag
         if not flag and is_dirty():
-            connection._commit()
+            for cx in all_connections():
+                cx._commit()
             set_clean()
     else:
         raise TransactionManagementError("This code isn't under transaction management")
 
-def commit_unless_managed():
+def commit_unless_managed(connections=None):
     """
     Commits changes if the system is not in managed transaction mode.
     """
     if not is_managed():
-        connection._commit()
+        if connections is None:
+            connections = all_connections()
+        else:
+            connections = ensure_connections(connections)
+        for cx in connections:
+            cx._commit()
     else:
         set_dirty()
 
-def rollback_unless_managed():
+def rollback_unless_managed(connections=None):
     """
     Rolls back changes if the system is not in managed transaction mode.
     """
     if not is_managed():
-        connection._rollback()
+        if connections is None:
+            connections = all_connections()
+        for cx in connections:
+            cx._rollback()
     else:
         set_dirty()
 
-def commit():
+def commit(connections=None):
     """
     Does the commit itself and resets the dirty flag.
     """
-    connection._commit()
+    if connections is None:
+        connections = all_connections()
+    else:
+        connections = ensure_connections(connections)
+    for cx in connections:
+        cx._commit()
     set_clean()
 
-def rollback():
+def rollback(connections=None):
     """
     This function does the rollback itself and resets the dirty flag.
     """
-    connection._rollback()
+    if connections is None:
+        connections = all_connections()
+    else:
+        connections = ensure_connections(connections)
+    for cx in connections:
+        cx._rollback()
     set_clean()
 
 ##############
@@ -179,7 +198,7 @@
             leave_transaction_management()
     return _autocommit
 
-def commit_on_success(func):
+def commit_on_success(func, connections=None):
     """
     This decorator activates commit on response. This way, if the view function
     runs successfully, a commit is made; if the viewfunc produces an exception,
@@ -198,7 +217,7 @@
                 raise
             else:
                 if is_dirty():
-                    commit()
+                    commit(connections)
             return res
         finally:
             leave_transaction_management()
@@ -220,3 +239,29 @@
             leave_transaction_management()
 
     return _commit_manually
+
+###########
+# HELPERS #
+###########
+
+def all_connections():
+    return [db.connection] + [ c.connection
+                               for c in db.connections.values() ]
+
+def ensure_connections(val):
+    connections = []
+    if isinstance(val, basestring):
+        val = [val]
+    try:
+        iter(val)
+    except:
+        val = [val]
+    for cx in val:
+        if hasattr(cx, 'cursor'):
+            connections.append(cx)
+        elif hasattr(cx, 'connection'):
+            connections.append(cx.connection)
+        elif isinstance(cx, basestring):
+            connections.append(db.connections[cx].connection)
+    return connections
+        
=== django/conf/global_settings.py
==================================================================
--- django/conf/global_settings.py	(/django/trunk)	(revision 10934)
+++ django/conf/global_settings.py	(/local/django/multi-db)	(revision 10934)
@@ -112,6 +112,9 @@
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
 
+# Optional named database connections in addition to the default.
+OTHER_DATABASES = {}
+
 # Host for sending e-mail.
 EMAIL_HOST = 'localhost'
 
@@ -334,6 +337,16 @@
 # If None, a name of 'test_' + DATABASE_NAME will be assumed
 TEST_DATABASE_NAME = None
 
+# Tuple of other test databases to create. Names in this tuple
+# are suffixes that will be appended to TEST_DATABASE_NAME
+TEST_DATABASES = []
+
+# Models to assign to each test database. This must be a list of
+# dicts, with each dict key being a name from TEST_DATABASES and value
+# a list of models or app_labels that will use that database.
+TEST_DATABASE_MODELS = []
+
+############### FROM TRUNK #############################
 # Strings used to set the character set and collation order for the test
 # database. These values are passed literally to the server, so they are
 # backend-dependent. If None, no special settings are sent (system defaults are
=== django/core/servers/basehttp.py
==================================================================
--- django/core/servers/basehttp.py	(/django/trunk)	(revision 10934)
+++ django/core/servers/basehttp.py	(/local/django/multi-db)	(revision 10934)
@@ -309,7 +309,7 @@
         """
         if not self.result_is_file() and not self.sendfile():
             for data in self.result:
-                self.write(data)
+                self.write(data, False)
             self.finish_content()
         self.close()
 
@@ -377,7 +377,7 @@
         else:
             self._write('Status: %s\r\n' % self.status)
 
-    def write(self, data):
+    def write(self, data, flush=True):
         """'write()' callable as specified by PEP 333"""
 
         assert type(data) is StringType,"write() argument must be string"
@@ -394,7 +394,8 @@
 
         # XXX check Content-Length and truncate if too many bytes written?
         self._write(data)
-        self._flush()
+        if flush:
+            self._flush()
 
     def sendfile(self):
         """Platform-specific file transmission
@@ -421,8 +422,6 @@
         if not self.headers_sent:
             self.headers['Content-Length'] = "0"
             self.send_headers()
-        else:
-            pass # XXX check if content-length was too short?
 
     def close(self):
         try:
=== django/core/management.py
==================================================================
--- django/core/management.py	(/django/trunk)	(revision 10934)
+++ django/core/management.py	(/local/django/multi-db)	(revision 10934)
@@ -5,13 +5,14 @@
 from django.core.exceptions import ImproperlyConfigured
 from optparse import OptionParser
 from django.utils import termcolors
+from django.db import model_connection_name
 import os, re, shutil, sys, textwrap
 
-try:
-    set
-except NameError:
-    from sets import Set as set   # Python 2.3 fallback
+# For Python 2.3
+if not hasattr(__builtins__, 'set'):
+    from sets import Set as set
 
+
 # For backwards compatibility: get_version() used to be in this module.
 get_version = django.get_version
 
@@ -103,52 +104,80 @@
 
 def get_sql_create(app):
     "Returns a list of the CREATE TABLE SQL statements for the given app."
-    from django.db import get_creation_module, models
+    from django.db import get_creation_module, models, model_connection_name
 
-    data_types = get_creation_module().DATA_TYPES
+    # final output will be divided by comments into sections for each
+    # named connection, if there are any named connections
+    connection_output = {}
+    pending = {}
+    final_output = []
+    
+    app_models = models.get_models(app)#, creation_order=True)
+    for model in app_models:
+        opts = model._meta
+        connection_name = model_connection_name(model)
+        output = connection_output.setdefault(connection_name, [])
+        db = model._default_manager.db
+        creation = db.get_creation_module()
+        data_types = creation.DATA_TYPES
+        if not data_types:
+            # This must be the "dummy" database backend, which means the user
+            # hasn't set DATABASE_ENGINE.
 
-    if not data_types:
-        # This must be the "dummy" database backend, which means the user
-        # hasn't set DATABASE_ENGINE.
-        sys.stderr.write(style.ERROR("Error: Django doesn't know which syntax to use for your SQL statements,\n" +
-            "because you haven't specified the DATABASE_ENGINE setting.\n" +
-            "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.\n"))
-        sys.exit(1)
+            # FIXME diff error message for bad default v bad named
+            sys.stderr.write(style.ERROR("Error: Django doesn't know which syntax to use for your SQL statements,\n" +
+                                         "because you haven't specified the DATABASE_ENGINE setting.\n" +
+                                         "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.\n"))
+            sys.exit(1)
 
-    # Get installed models, so we generate REFERENCES right.
-    # We trim models from the current app so that the sqlreset command does not
-    # generate invalid SQL (leaving models out of known_models is harmless, so
-    # we can be conservative).
-    app_models = models.get_models(app)
-    final_output = []
-    known_models = set([model for model in _get_installed_models(_get_table_list()) if model not in app_models])
-    pending_references = {}
+        # Get installed models, so we generate REFERENCES right
+        # We trim models from the current app so that the sqlreset command does
+        # not generate invalid SQL (leaving models out of known_models is 
+        # harmless, so we can be conservative).
+        manager = model._default_manager
+        try:
+            tables = manager.get_table_list()
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            # Something else went wrong -- maybe the database isn't
+            # running. But we can still generate sql, so use an empty
+            # table list.
+            tables = []
 
-    for model in app_models:
-        output, references = _get_sql_model_create(model, known_models)
-        final_output.extend(output)
-        for refto, refs in references.items():
-            pending_references.setdefault(refto,[]).extend(refs)
-        final_output.extend(_get_sql_for_pending_references(model, pending_references))
-        # Keep track of the fact that we've created the table for this model.
-        known_models.add(model)
+        installed_models = [ m for m in
+                             manager.get_installed_models(tables)
+                             if m not in app_models ]
+        models_output = set(installed_models) 
+        builder = creation.builder
+        builder.models_already_seen.update(models_output)
+        model_output, pending = builder.get_create_table(model, style, pending)
+        output.extend(model_output)
 
-    # Create the many-to-many join tables.
-    for model in app_models:
-        final_output.extend(_get_many_to_many_sql_for_model(model))
+        # Create the many-to-many join tables.
+        many_many = builder.get_create_many_to_many(model, style)
+        for refmodel, statements in many_many.items():
+            output.extend(statements)
 
+    final_output = _collate(connection_output)
+    
     # Handle references to tables that are from other apps
     # but don't exist physically
-    not_installed_models = set(pending_references.keys())
+    not_installed_models = set(pending.keys())
     if not_installed_models:
         alter_sql = []
         for model in not_installed_models:
-            alter_sql.extend(['-- ' + sql for sql in
-                _get_sql_for_pending_references(model, pending_references)])
+            builder = model._default_manager.db.get_creation_module().builder
+            
+            for rel_class, f in pending[model]:
+                sql = builder.get_ref_sql(model, rel_class, f, style)
+                alter_sql.append('-- '+ str(sql))
         if alter_sql:
-            final_output.append('-- The following references should be added but depend on non-existent tables:')
+            final_output.append('-- The following references should be added '
+                                'but depend on non-existent tables:')
             final_output.extend(alter_sql)
-
+    # convert BoundStatements into strings
+    final_output = map(str, final_output)
     return final_output
 get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given app name(s)."
 get_sql_create.args = APP_ARGS
@@ -314,84 +343,38 @@
 
 def get_sql_delete(app):
     "Returns a list of the DROP TABLE SQL statements for the given app."
+    from django.db import model_connection_name
     from django.db import backend, connection, models, get_introspection_module
     from django.db.backends.util import truncate_name
     introspection = get_introspection_module()
-
-    # This should work even if a connection isn't available
-    try:
-        cursor = connection.cursor()
-    except:
-        cursor = None
-
-    # Figure out which tables already exist
-    if cursor:
-        table_names = introspection.get_table_list(cursor)
-    else:
-        table_names = []
-    if backend.uses_case_insensitive_names:
-        table_name_converter = str.upper
-    else:
-        table_name_converter = lambda x: x
-
-    output = []
-
-    # Output DROP TABLE statements for standard application tables.
-    to_delete = set()
-
-    references_to_delete = {}
+    
+    connection_output = {}
+    final_output = []
     app_models = models.get_models(app)
     for model in app_models:
-        if cursor and table_name_converter(model._meta.db_table) in table_names:
-            # The table exists, so it needs to be dropped
-            opts = model._meta
-            for f in opts.fields:
-                if f.rel and f.rel.to not in to_delete:
-                    references_to_delete.setdefault(f.rel.to, []).append( (model, f) )
-
-            to_delete.add(model)
-
-    for model in app_models:
-        if cursor and table_name_converter(model._meta.db_table) in table_names:
-            # Drop the table now
-            output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
-                style.SQL_TABLE(backend.quote_name(model._meta.db_table))))
-            if backend.supports_constraints and model in references_to_delete:
-                for rel_class, f in references_to_delete[model]:
-                    table = rel_class._meta.db_table
-                    col = f.column
-                    r_table = model._meta.db_table
-                    r_col = model._meta.get_field(f.rel.field_name).column
-                    r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
-                    output.append('%s %s %s %s;' % \
-                        (style.SQL_KEYWORD('ALTER TABLE'),
-                        style.SQL_TABLE(backend.quote_name(table)),
-                        style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()),
-                        style.SQL_FIELD(truncate_name(r_name, backend.get_max_name_length()))))
-                del references_to_delete[model]
-            if model._meta.has_auto_field and hasattr(backend, 'get_drop_sequence'):
-                output.append(backend.get_drop_sequence(model._meta.db_table))
-
-    # Output DROP TABLE statements for many-to-many tables.
-    for model in app_models:
-        opts = model._meta
-        for f in opts.many_to_many:
-            if cursor and table_name_converter(f.m2m_db_table()) in table_names:
-                output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
-                    style.SQL_TABLE(backend.quote_name(f.m2m_db_table()))))
-                if hasattr(backend, 'get_drop_sequence'):
-                    output.append(backend.get_drop_sequence("%s_%s" % (model._meta.db_table, f.column)))
-
-
-    app_label = app_models[0]._meta.app_label
-
-    # Close database connection explicitly, in case this output is being piped
-    # directly into a database client, to avoid locking issues.
-    if cursor:
-        cursor.close()
-        connection.close()
-
-    return output[::-1] # Reverse it, to deal with table dependencies.
+        db = model._default_manager.db
+        connection = db.connection
+        try:
+            cursor = connection.cursor()
+        except:
+            cursor = None
+        #******** Start of conflict section ######
+        ###** From multi-db
+        builder = db.get_creation_module().builder
+        connection_name = model_connection_name(model)
+        output = connection_output.setdefault(connection_name, [])
+        output.extend(map(str,
+                          builder.get_drop_table(model,
+                                                 cascade=True, style=style)))
+        if cursor:
+            # Close database connection explicitly, in case this
+            # output is being piped directly into a database client,
+            # to avoid locking issues.
+            cursor.close()
+            connection.close()
+    
+    final_output = _collate(connection_output, reverse=True)
+    return final_output
 get_sql_delete.help_doc = "Prints the DROP TABLE SQL statements for the given app name(s)."
 get_sql_delete.args = APP_ARGS
 
@@ -439,15 +422,19 @@
 def get_custom_sql(app):
     "Returns a list of the custom table modifying SQL statements for the given app."
     from django.db.models import get_models
-    output = []
+    connection_output = {}
 
     app_models = get_models(app)
     app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql'))
 
     for model in app_models:
-        output.extend(get_custom_sql_for_model(model))
+        opts = model._meta
+        connection_name = model_connection_name(model)
+        output = connection_output.setdefault(connection_name, [])
+        builder = model._default_manager.db.get_creation_module().builder
+        output.extend(builder.get_initialdata(model))
 
-    return output
+    return _collate(connection_output)
 get_custom_sql.help_doc = "Prints the custom table modifying SQL statements for the given app name(s)."
 get_custom_sql.args = APP_ARGS
 
@@ -458,19 +445,39 @@
 get_sql_initial_data.args = ''
 
 def get_sql_sequence_reset(app):
-    "Returns a list of the SQL statements to reset sequences for the given app."
-    from django.db import backend, models
-    return backend.get_sql_sequence_reset(style, models.get_models(app))
-get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting sequences for the given app name(s)."
+    "Returns a list of the SQL statements to reset PostgreSQL sequences for the given app."
+    from django.db import model_connection_name
+    from django.db.models import get_models
+    connection_output = {}
+    for model in get_models(app):
+        connection_name = model_connection_name(model)
+        output = connection_output.setdefault(connection_name, [])
+        builder = model._default_manager.db.get_creation_module().builder
+        try:
+            output.extend(builder.get_sequence_reset(model, style))
+        except AttributeError:
+            sys.stderr.write(
+                "%s is configured to use database engine %s, which does " 
+                "not support sequence reset.\n" % 
+                (model.__name__,
+                 model._default_manager.db.connection.settings.DATABASE_ENGINE))
+    
+    return _collate(connection_output)
+get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app name(s)."
 get_sql_sequence_reset.args = APP_ARGS
 
 def get_sql_indexes(app):
     "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
-    from django.db import models
-    output = []
-    for model in models.get_models(app):
-        output.extend(get_sql_indexes_for_model(model))
-    return output
+    from django.db import model_connection_name
+    from django.db.models import get_models
+    connection_output = {}
+    for model in get_models(app):
+        opts = model._meta
+        connection_name = model_connection_name(model)
+        output = connection_output.setdefault(connection_name, [])
+        output.extend(map(str, get_sql_indexes_for_model(model)))
+    return _collate(connection_output)
+
 get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given model module name(s)."
 get_sql_indexes.args = APP_ARGS
 
@@ -503,6 +510,27 @@
 get_sql_all.help_doc = "Prints the CREATE TABLE, initial-data and CREATE INDEX SQL statements for the given model module name(s)."
 get_sql_all.args = APP_ARGS
 
+def _collate(connection_output, reverse=False):
+    from django.db import _default
+    final_output = []
+    if len(connection_output.keys()) == 1:
+        # all for the default connection
+        for statements in connection_output.values():
+            final_output.extend(statements)
+            if reverse:
+                final_output.reverse()
+    else:
+        for connection_name, statements in connection_output.items():
+            if not statements:
+                continue
+            final_output.append(' -- The following statements are for connection: %s' % connection_name)
+            if reverse:
+                statements.reverse()
+            final_output.extend(statements)
+            final_output.append(' -- END statements for %s\n' %
+                                connection_name)
+    return map(str, final_output)
+
 def _emit_post_sync_signal(created_models, verbosity, interactive):
     from django.db import models
     from django.dispatch import dispatcher
@@ -687,6 +715,86 @@
     print '\n'.join(output)
 diffsettings.args = ""
 
+def install(app):
+    "Executes the equivalent of 'get_sql_all' in the current database."
+    # Wrap _install to hide the return value so ./manage.py install
+    # doesn't complain about unprintable output.    
+    _install(app)
+
+def _install(app, commit=True, initial_data=True, pending_allowed=False,
+             pending=None, verbosity=1, signal=True, interactive=True):
+    from django.db import connection, models, transaction
+    import sys
+    
+    app_name = app.__name__.split('.')[-2]
+
+    disable_termcolors()
+
+    # First, try validating the models.
+    _check_for_validation_errors(app)
+
+    created_models = []
+    try:
+        if pending is None:
+            pending = {}
+        for model in models.get_models(app, creation_order=True):
+            if verbosity >= 2:
+                print "Processing %s.%s model" % (app_name,
+                                                  model._meta.object_name)
+            manager = model._default_manager
+            tables = manager.get_table_list()
+            models_installed = manager.get_installed_models(tables)
+            # Don't re-install already-installed models
+            if not model in models_installed:
+                pending = manager.install(initial_data=initial_data,
+                                          pending=pending)
+                created_models.append(model)
+                
+        if pending:            
+            models_installed = manager.get_installed_models(tables)
+
+            for model in pending.keys():
+                manager = model._default_manager
+                if model in models_installed:
+                    for rel_class, f in pending[model]:
+                        manager.get_pending(rel_class, f).execute()
+                    pending.pop(model)
+                elif not pending_allowed:
+                    raise Exception("%s is not installed, but it has pending "
+                                    "references" % model)
+    except Exception, e:
+        import traceback
+        print traceback.format_exception(*sys.exc_info())
+        sys.stderr.write(style.ERROR("""Error: %s couldn't be installed. Possible reasons:
+  * The database isn't running or isn't configured correctly.
+  * At least one of the database tables already exists.
+  * The SQL was invalid.
+Hint: Look at the output of 'django-admin.py sqlall %s'. That's the SQL this command wasn't able to run.
+The full error: """ % (app_name, app_name)) + style.ERROR_OUTPUT(str(e)) + '\n')
+        transaction.rollback_unless_managed()
+        sys.exit(1)
+    if commit:
+        transaction.commit_unless_managed()
+
+    if signal:
+        _post_syncdb(app, created_models=created_models,
+                     verbosity=verbosity, interactive=interactive)
+        
+    return created_models, pending
+install.help_doc = "Executes ``sqlall`` for the given app(s) in the current database."
+install.args = APP_ARGS
+
+def _post_syncdb(app, created_models, verbosity=1, interactive=True):
+    """Send the post_syncdb signal for an application."""
+    from django.dispatch import dispatcher
+    from django.db.models import signals
+    
+    if verbosity >= 2:
+        print "Sending post-syncdb signal for application", app.__name__.split('.')[-2]
+    dispatcher.send(signal=signals.post_syncdb, sender=app,
+                    app=app, created_models=created_models,
+                    verbosity=verbosity, interactive=interactive)
+
 def reset(app, interactive=True):
     "Executes the equivalent of 'get_sql_reset' in the current database."
     from django.db import connection, transaction
@@ -983,7 +1091,7 @@
     Returns number of errors.
     """
     from django.conf import settings
-    from django.db import models, connection
+    from django.db import connections, models, connection, model_connection_name
     from django.db.models.loading import get_app_errors
     from django.db.models.fields.related import RelatedObject
 
@@ -994,7 +1102,9 @@
 
     for cls in models.get_models(app):
         opts = cls._meta
-
+        connection_name = model_connection_name(cls)
+        connection = connections[connection_name]
+        
         # Do field-specific validation.
         for f in opts.fields:
             if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
@@ -1027,7 +1137,7 @@
 
             # Check that maxlength <= 255 if using older MySQL versions.
             if settings.DATABASE_ENGINE == 'mysql':
-                db_version = connection.get_server_version()
+                db_version = connection.connection.get_server_version()
                 if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.maxlength > 255:
                     e.add(opts, '"%s": %s cannot have a "maxlength" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]])))
 
@@ -1038,6 +1148,11 @@
                 if f.rel.to not in models.get_models():
                     e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))
 
+                #TODO: Fix this to allow relations that span databases by splitting querys up
+                rel_connection = model_connection_name(f.rel.to)
+                if rel_connection != connection_name:
+                    e.add(opts, "'%s' is configured to use connection '%s' but has relation with '%s', which is configured to use connection '%s'" % (cls.__name__, connection_name, f.rel.to.__name__, rel_connection))
+
                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
                 rel_query_name = f.related_query_name()
                 for r in rel_opts.fields:
@@ -1546,6 +1661,7 @@
     'createcachetable',
     'dbshell',
     'diffsettings',
+    'install',
     'reset',
     'sqlindexes',
     'syncdb',

Property changes on: django/core/management.py
___________________________________________________________________
Name: svn:eol-style
 -native

=== django/views/generic/simple.py
==================================================================
--- django/views/generic/simple.py	(/django/trunk)	(revision 10934)
+++ django/views/generic/simple.py	(/local/django/multi-db)	(revision 10934)
@@ -15,7 +15,7 @@
             dictionary[key] = value
     c = RequestContext(request, dictionary)
     t = loader.get_template(template)
-    return HttpResponse(t.render(c), mimetype=mimetype)
+    return HttpResponse(t.iter_render(c), mimetype=mimetype)
 
 def redirect_to(request, url, **kwargs):
     """
=== django/views/generic/date_based.py
==================================================================
--- django/views/generic/date_based.py	(/django/trunk)	(revision 10934)
+++ django/views/generic/date_based.py	(/local/django/multi-db)	(revision 10934)
@@ -44,7 +44,7 @@
             c[key] = value()
         else:
             c[key] = value
-    return HttpResponse(t.render(c), mimetype=mimetype)
+    return HttpResponse(t.iter_render(c), mimetype=mimetype)
 
 def archive_year(request, year, queryset, date_field, template_name=None,
         template_loader=loader, extra_context=None, allow_empty=False,
@@ -92,7 +92,7 @@
             c[key] = value()
         else:
             c[key] = value
-    return HttpResponse(t.render(c), mimetype=mimetype)
+    return HttpResponse(t.iter_render(c), mimetype=mimetype)
 
 def archive_month(request, year, month, queryset, date_field,
         month_format='%b', template_name=None, template_loader=loader,
@@ -158,7 +158,7 @@
             c[key] = value()
         else:
             c[key] = value
-    return HttpResponse(t.render(c), mimetype=mimetype)
+    return HttpResponse(t.iter_render(c), mimetype=mimetype)
 
 def archive_week(request, year, week, queryset, date_field,
         template_name=None, template_loader=loader,
@@ -206,7 +206,7 @@
             c[key] = value()
         else:
             c[key] = value
-    return HttpResponse(t.render(c), mimetype=mimetype)
+    return HttpResponse(t.iter_render(c), mimetype=mimetype)
 
 def archive_day(request, year, month, day, queryset, date_field,
         month_format='%b', day_format='%d', template_name=None,
@@ -270,7 +270,7 @@
             c[key] = value()
         else:
             c[key] = value
-    return HttpResponse(t.render(c), mimetype=mimetype)
+    return HttpResponse(t.iter_render(c), mimetype=mimetype)
 
 def archive_today(request, **kwargs):
     """
@@ -339,6 +339,6 @@
             c[key] = value()
         else:
             c[key] = value
-    response = HttpResponse(t.render(c), mimetype=mimetype)
+    response = HttpResponse(t.iter_render(c), mimetype=mimetype)
     populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
     return response
=== django/views/generic/list_detail.py
==================================================================
--- django/views/generic/list_detail.py	(/django/trunk)	(revision 10934)
+++ django/views/generic/list_detail.py	(/local/django/multi-db)	(revision 10934)
@@ -84,7 +84,7 @@
         model = queryset.model
         template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
     t = template_loader.get_template(template_name)
-    return HttpResponse(t.render(c), mimetype=mimetype)
+    return HttpResponse(t.iter_render(c), mimetype=mimetype)
 
 def object_detail(request, queryset, object_id=None, slug=None,
         slug_field=None, template_name=None, template_name_field=None,
@@ -126,6 +126,6 @@
             c[key] = value()
         else:
             c[key] = value
-    response = HttpResponse(t.render(c), mimetype=mimetype)
+    response = HttpResponse(t.iter_render(c), mimetype=mimetype)
     populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
     return response
=== django/views/generic/create_update.py
==================================================================
--- django/views/generic/create_update.py	(/django/trunk)	(revision 10934)
+++ django/views/generic/create_update.py	(/local/django/multi-db)	(revision 10934)
@@ -68,7 +68,7 @@
             c[key] = value()
         else:
             c[key] = value
-    return HttpResponse(t.render(c))
+    return HttpResponse(t.iter_render(c))
 
 def update_object(request, model, object_id=None, slug=None,
         slug_field=None, template_name=None, template_loader=loader,
@@ -141,7 +141,7 @@
             c[key] = value()
         else:
             c[key] = value
-    response = HttpResponse(t.render(c))
+    response = HttpResponse(t.iter_render(c))
     populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
     return response
 
@@ -195,6 +195,6 @@
                 c[key] = value()
             else:
                 c[key] = value
-        response = HttpResponse(t.render(c))
+        response = HttpResponse(t.iter_render(c))
         populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
         return response
=== django/views/debug.py
==================================================================
--- django/views/debug.py	(/django/trunk)	(revision 10934)
+++ django/views/debug.py	(/local/django/multi-db)	(revision 10934)
@@ -137,7 +137,7 @@
         'template_does_not_exist': template_does_not_exist,
         'loader_debug_info': loader_debug_info,
     })
-    return HttpResponseServerError(t.render(c), mimetype='text/html')
+    return HttpResponseServerError(t.iter_render(c), mimetype='text/html')
 
 def technical_404_response(request, exception):
     "Create a technical 404 error response. The exception should be the Http404."
@@ -160,7 +160,7 @@
         'request_protocol': request.is_secure() and "https" or "http",
         'settings': get_safe_settings(),
     })
-    return HttpResponseNotFound(t.render(c), mimetype='text/html')
+    return HttpResponseNotFound(t.iter_render(c), mimetype='text/html')
 
 def empty_urlconf(request):
     "Create an empty URLconf 404 error response."
@@ -168,7 +168,7 @@
     c = Context({
         'project_name': settings.SETTINGS_MODULE.split('.')[0]
     })
-    return HttpResponseNotFound(t.render(c), mimetype='text/html')
+    return HttpResponseNotFound(t.iter_render(c), mimetype='text/html')
 
 def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
     """
=== django/views/defaults.py
==================================================================
--- django/views/defaults.py	(/django/trunk)	(revision 10934)
+++ django/views/defaults.py	(/local/django/multi-db)	(revision 10934)
@@ -76,7 +76,7 @@
             The path of the requested URL (e.g., '/app/pages/bad_page/')
     """
     t = loader.get_template(template_name) # You need to create a 404.html template.
-    return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path})))
+    return http.HttpResponseNotFound(t.iter_render(RequestContext(request, {'request_path': request.path})))
 
 def server_error(request, template_name='500.html'):
     """
@@ -86,4 +86,4 @@
     Context: None
     """
     t = loader.get_template(template_name) # You need to create a 500.html template.
-    return http.HttpResponseServerError(t.render(Context({})))
+    return http.HttpResponseServerError(t.iter_render(Context({})))
=== django/views/static.py
==================================================================
--- django/views/static.py	(/django/trunk)	(revision 10934)
+++ django/views/static.py	(/local/django/multi-db)	(revision 10934)
@@ -92,7 +92,7 @@
         'directory' : path + '/',
         'file_list' : files,
     })
-    return HttpResponse(t.render(c))
+    return HttpResponse(t.iter_render(c))
 
 def was_modified_since(header=None, mtime=0, size=0):
     """
=== django/shortcuts/__init__.py
==================================================================
--- django/shortcuts/__init__.py	(/django/trunk)	(revision 10934)
+++ django/shortcuts/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -7,7 +7,7 @@
 from django.db.models.manager import Manager
 
 def render_to_response(*args, **kwargs):
-    return HttpResponse(loader.render_to_string(*args, **kwargs))
+    return HttpResponse(loader.render_to_iter(*args, **kwargs))
 load_and_render = render_to_response # For backwards compatibility.
 
 def get_object_or_404(klass, *args, **kwargs):
=== django/contrib/comments/templatetags/comments.py
==================================================================
--- django/contrib/comments/templatetags/comments.py	(/django/trunk)	(revision 10934)
+++ django/contrib/comments/templatetags/comments.py	(/local/django/multi-db)	(revision 10934)
@@ -24,7 +24,7 @@
         self.photo_options, self.rating_options = photo_options, rating_options
         self.is_public = is_public
 
-    def render(self, context):
+    def iter_render(self, context):
         from django.conf import settings
         from django.utils.text import normalize_newlines
         import base64
@@ -33,7 +33,7 @@
             try:
                 self.obj_id = template.resolve_variable(self.obj_id_lookup_var, context)
             except template.VariableDoesNotExist:
-                return ''
+                return
             # Validate that this object ID is valid for this content-type.
             # We only have to do this validation if obj_id_lookup_var is provided,
             # because do_comment_form() validates hard-coded object IDs.
@@ -67,9 +67,9 @@
             context['hash'] = Comment.objects.get_security_hash(context['options'], context['photo_options'], context['rating_options'], context['target'])
             context['logout_url'] = settings.LOGOUT_URL
             default_form = loader.get_template(COMMENT_FORM)
-        output = default_form.render(context)
+        for chunk in default_form.iter_render(context):
+            yield chunk
         context.pop()
-        return output
 
 class CommentCountNode(template.Node):
     def __init__(self, package, module, context_var_name, obj_id, var_name, free):
@@ -77,7 +77,7 @@
         self.context_var_name, self.obj_id = context_var_name, obj_id
         self.var_name, self.free = var_name, free
 
-    def render(self, context):
+    def iter_render(self, context):
         from django.conf import settings
         manager = self.free and FreeComment.objects or Comment.objects
         if self.context_var_name is not None:
@@ -86,7 +86,7 @@
             content_type__app_label__exact=self.package,
             content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count()
         context[self.var_name] = comment_count
-        return ''
+        return ()
 
 class CommentListNode(template.Node):
     def __init__(self, package, module, context_var_name, obj_id, var_name, free, ordering, extra_kwargs=None):
@@ -96,14 +96,14 @@
         self.ordering = ordering
         self.extra_kwargs = extra_kwargs or {}
 
-    def render(self, context):
+    def iter_render(self, context):
         from django.conf import settings
         get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma
         if self.context_var_name is not None:
             try:
                 self.obj_id = template.resolve_variable(self.context_var_name, context)
             except template.VariableDoesNotExist:
-                return ''
+                return ()
         kwargs = {
             'object_id__exact': self.obj_id,
             'content_type__app_label__exact': self.package,
@@ -127,7 +127,7 @@
                 comment_list = [c for c in comment_list if not c.is_hidden or (user_id == c.user_id)]
 
         context[self.var_name] = comment_list
-        return ''
+        return ()
 
 class DoCommentForm:
     """
=== django/contrib/admin/templatetags/admin_modify.py
==================================================================
--- django/contrib/admin/templatetags/admin_modify.py	(/django/trunk)	(revision 10934)
+++ django/contrib/admin/templatetags/admin_modify.py	(/local/django/multi-db)	(revision 10934)
@@ -94,15 +94,15 @@
             return cls.nodelists[klass]
     get_nodelist = classmethod(get_nodelist)
 
-    def render(self, context):
+    def iter_render(self, context):
         bound_field = template.resolve_variable(self.bound_field_var, context)
 
         context.push()
         context['bound_field'] = bound_field
 
-        output = self.get_nodelist(bound_field.field.__class__).render(context)
+        for chunk in self.get_nodelist(bound_field.field.__class__).iter_render(context):
+            yield chunk
         context.pop()
-        return output
 
 class FieldWrapper(object):
     def __init__(self, field ):
@@ -157,7 +157,7 @@
     def __init__(self, rel_var):
         self.rel_var = rel_var
 
-    def render(self, context):
+    def iter_render(self, context):
         relation = template.resolve_variable(self.rel_var, context)
         context.push()
         if relation.field.rel.edit_inline == models.TABULAR:
@@ -169,10 +169,9 @@
         original = context.get('original', None)
         bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
         context['bound_related_object'] = bound_related_object
-        t = loader.get_template(bound_related_object.template_name())
-        output = t.render(context)
+        for chunk in loader.get_template(bound_related_object.template_name()).iter_render(context):
+            yield chunk
         context.pop()
-        return output
 
 def output_all(form_fields):
     return ''.join([str(f) for f in form_fields])
=== django/contrib/admin/templatetags/log.py
==================================================================
--- django/contrib/admin/templatetags/log.py	(/django/trunk)	(revision 10934)
+++ django/contrib/admin/templatetags/log.py	(/local/django/multi-db)	(revision 10934)
@@ -10,14 +10,14 @@
     def __repr__(self):
         return "<GetAdminLog Node>"
 
-    def render(self, context):
+    def iter_render(self, context):
         if self.user is None:
             context[self.varname] = LogEntry.objects.all().select_related()[:self.limit]
         else:
             if not self.user.isdigit():
                 self.user = context[self.user].id
             context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
-        return ''
+        return ()
 
 class DoGetAdminLog:
     """
=== django/contrib/admin/templatetags/adminapplist.py
==================================================================
--- django/contrib/admin/templatetags/adminapplist.py	(/django/trunk)	(revision 10934)
+++ django/contrib/admin/templatetags/adminapplist.py	(/local/django/multi-db)	(revision 10934)
@@ -7,7 +7,7 @@
     def __init__(self, varname):
         self.varname = varname
 
-    def render(self, context):
+    def iter_render(self, context):
         from django.db import models
         from django.utils.text import capfirst
         app_list = []
@@ -54,7 +54,7 @@
                         'models': model_list,
                     })
         context[self.varname] = app_list
-        return ''
+        return ()
 
 def get_admin_app_list(parser, token):
     """
=== django/template/__init__.py
==================================================================
--- django/template/__init__.py	(/django/trunk)	(revision 10934)
+++ django/template/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -55,6 +55,7 @@
 '\n<html>\n\n</html>\n'
 """
 import re
+import types
 from inspect import getargspec
 from django.conf import settings
 from django.template.context import Context, RequestContext, ContextPopException
@@ -167,10 +168,13 @@
             for subnode in node:
                 yield subnode
 
-    def render(self, context):
+    def iter_render(self, context):
         "Display stage -- can be called many times"
-        return self.nodelist.render(context)
+        return self.nodelist.iter_render(context)
 
+    def render(self, context):
+        return ''.join(self.iter_render(context))
+
 def compile_string(template_string, origin):
     "Compiles template_string into NodeList ready for rendering"
     lexer = lexer_factory(template_string, origin)
@@ -695,10 +699,26 @@
             del bits[0]
     return current
 
+class NodeBase(type):
+    def __new__(cls, name, bases, attrs):
+        """
+        Ensures that either a 'render' or 'render_iter' method is defined on
+        any Node sub-class. This avoids potential infinite loops at runtime.
+        """
+        if not (isinstance(attrs.get('render'), types.FunctionType) or
+                isinstance(attrs.get('iter_render'), types.FunctionType)):
+            raise TypeError('Unable to create Node subclass without either "render" or "iter_render" method.')
+        return type.__new__(cls, name, bases, attrs)
+
 class Node(object):
+    __metaclass__ = NodeBase
+
+    def iter_render(self, context):
+        return (self.render(context),)
+
     def render(self, context):
         "Return the node rendered as a string"
-        pass
+        return ''.join(self.iter_render(context))
 
     def __iter__(self):
         yield self
@@ -714,13 +734,12 @@
 
 class NodeList(list):
     def render(self, context):
-        bits = []
+        return ''.join(self.iter_render(context))
+
+    def iter_render(self, context):
         for node in self:
-            if isinstance(node, Node):
-                bits.append(self.render_node(node, context))
-            else:
-                bits.append(node)
-        return ''.join(bits)
+            for chunk in node.iter_render(context):
+                yield chunk
 
     def get_nodes_by_type(self, nodetype):
         "Return a list of all nodes of the given type"
@@ -729,24 +748,25 @@
             nodes.extend(node.get_nodes_by_type(nodetype))
         return nodes
 
-    def render_node(self, node, context):
-        return(node.render(context))
-
 class DebugNodeList(NodeList):
-    def render_node(self, node, context):
-        try:
-            result = node.render(context)
-        except TemplateSyntaxError, e:
-            if not hasattr(e, 'source'):
-                e.source = node.source
-            raise
-        except Exception, e:
-            from sys import exc_info
-            wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
-            wrapped.source = node.source
-            wrapped.exc_info = exc_info()
-            raise wrapped
-        return result
+    def iter_render(self, context):
+        for node in self:
+            if not isinstance(node, Node):
+                yield node
+                continue
+            try:
+                for chunk in node.iter_render(context):
+                    yield chunk
+            except TemplateSyntaxError, e:
+                if not hasattr(e, 'source'):
+                    e.source = node.source
+                raise
+            except Exception, e:
+                from sys import exc_info
+                wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
+                wrapped.source = node.source
+                wrapped.exc_info = exc_info()
+                raise wrapped
 
 class TextNode(Node):
     def __init__(self, s):
@@ -755,6 +775,9 @@
     def __repr__(self):
         return "<Text Node: '%s'>" % self.s[:25]
 
+    def iter_render(self, context):
+        return (self.s,)
+
     def render(self, context):
         return self.s
 
@@ -778,6 +801,9 @@
         else:
             return output
 
+    def iter_render(self, context):
+        return (self.render(context),)
+
     def render(self, context):
         output = self.filter_expression.resolve(context)
         return self.encode_output(output)
@@ -866,6 +892,9 @@
             def __init__(self, vars_to_resolve):
                 self.vars_to_resolve = vars_to_resolve
 
+            #def iter_render(self, context):
+            #    return (self.render(context),)
+
             def render(self, context):
                 resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
                 return func(*resolved_vars)
@@ -888,7 +917,7 @@
                 def __init__(self, vars_to_resolve):
                     self.vars_to_resolve = vars_to_resolve
 
-                def render(self, context):
+                def iter_render(self, context):
                     resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
                     if takes_context:
                         args = [context] + resolved_vars
@@ -904,7 +933,7 @@
                         else:
                             t = get_template(file_name)
                         self.nodelist = t.nodelist
-                    return self.nodelist.render(context_class(dict))
+                    return self.nodelist.iter_render(context_class(dict))
 
             compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
             compile_func.__doc__ = func.__doc__
=== django/template/defaulttags.py
==================================================================
--- django/template/defaulttags.py	(/django/trunk)	(revision 10934)
+++ django/template/defaulttags.py	(/local/django/multi-db)	(revision 10934)
@@ -16,8 +16,8 @@
 register = Library()
 
 class CommentNode(Node):
-    def render(self, context):
-        return ''
+    def iter_render(self, context):
+        return ()
 
 class CycleNode(Node):
     def __init__(self, cyclevars, variable_name=None):
@@ -26,6 +26,9 @@
         self.counter = -1
         self.variable_name = variable_name
 
+    def iter_render(self, context):
+        return (self.render(context),)
+
     def render(self, context):
         self.counter += 1
         value = self.cyclevars[self.counter % self.cyclevars_len]
@@ -34,29 +37,32 @@
         return value
 
 class DebugNode(Node):
-    def render(self, context):
+    def iter_render(self, context):
         from pprint import pformat
-        output = [pformat(val) for val in context]
-        output.append('\n\n')
-        output.append(pformat(sys.modules))
-        return ''.join(output)
+        for val in context:
+            yield pformat(val)
+        yield "\n\n"
+        yield pformat(sys.modules)
 
 class FilterNode(Node):
     def __init__(self, filter_expr, nodelist):
         self.filter_expr, self.nodelist = filter_expr, nodelist
 
-    def render(self, context):
+    def iter_render(self, context):
         output = self.nodelist.render(context)
         # apply filters
         context.update({'var': output})
         filtered = self.filter_expr.resolve(context)
         context.pop()
-        return filtered
+        return (filtered,)
 
 class FirstOfNode(Node):
     def __init__(self, vars):
         self.vars = vars
 
+    def iter_render(self, context):
+        return (self.render(context),)
+
     def render(self, context):
         for var in self.vars:
             try:
@@ -92,8 +98,7 @@
         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
         return nodes
 
-    def render(self, context):
-        nodelist = NodeList()
+    def iter_render(self, context):
         if 'forloop' in context:
             parentloop = context['forloop']
         else:
@@ -101,12 +106,12 @@
         context.push()
         try:
             values = self.sequence.resolve(context, True)
+            if values is None:
+                values = ()
+            elif not hasattr(values, '__len__'):
+                values = list(values)
         except VariableDoesNotExist:
-            values = []
-        if values is None:
-            values = []
-        if not hasattr(values, '__len__'):
-            values = list(values)
+            values = ()
         len_values = len(values)
         if self.reversed:
             values = reversed(values)
@@ -125,12 +130,17 @@
                 'parentloop': parentloop,
             }
             if unpack:
-                # If there are multiple loop variables, unpack the item into them.
+                # If there are multiple loop variables, unpack the item into
+                # them.
                 context.update(dict(zip(self.loopvars, item)))
             else:
                 context[self.loopvars[0]] = item
+
+            # We inline this to avoid the overhead since ForNode is pretty
+            # common.
             for node in self.nodelist_loop:
-                nodelist.append(node.render(context))
+                for chunk in node.iter_render(context):
+                    yield chunk
             if unpack:
                 # The loop variables were pushed on to the context so pop them
                 # off again. This is necessary because the tag lets the length
@@ -139,7 +149,6 @@
                 # context.
                 context.pop()
         context.pop()
-        return nodelist.render(context)
 
 class IfChangedNode(Node):
     def __init__(self, nodelist, *varlist):
@@ -147,7 +156,7 @@
         self._last_seen = None
         self._varlist = varlist
 
-    def render(self, context):
+    def iter_render(self, context):
         if 'forloop' in context and context['forloop']['first']:
             self._last_seen = None
         try:
@@ -165,11 +174,9 @@
             self._last_seen = compare_to
             context.push()
             context['ifchanged'] = {'firstloop': firstloop}
-            content = self.nodelist.render(context)
+            for chunk in self.nodelist.iter_render(context):
+                yield chunk
             context.pop()
-            return content
-        else:
-            return ''
 
 class IfEqualNode(Node):
     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
@@ -180,7 +187,7 @@
     def __repr__(self):
         return "<IfEqualNode>"
 
-    def render(self, context):
+    def iter_render(self, context):
         try:
             val1 = resolve_variable(self.var1, context)
         except VariableDoesNotExist:
@@ -190,8 +197,8 @@
         except VariableDoesNotExist:
             val2 = None
         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
-            return self.nodelist_true.render(context)
-        return self.nodelist_false.render(context)
+            return self.nodelist_true.iter_render(context)
+        return self.nodelist_false.iter_render(context)
 
 class IfNode(Node):
     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
@@ -216,7 +223,7 @@
         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
         return nodes
 
-    def render(self, context):
+    def iter_render(self, context):
         if self.link_type == IfNode.LinkTypes.or_:
             for ifnot, bool_expr in self.bool_exprs:
                 try:
@@ -224,8 +231,8 @@
                 except VariableDoesNotExist:
                     value = None
                 if (value and not ifnot) or (ifnot and not value):
-                    return self.nodelist_true.render(context)
-            return self.nodelist_false.render(context)
+                    return self.nodelist_true.iter_render(context)
+            return self.nodelist_false.iter_render(context)
         else:
             for ifnot, bool_expr in self.bool_exprs:
                 try:
@@ -233,8 +240,8 @@
                 except VariableDoesNotExist:
                     value = None
                 if not ((value and not ifnot) or (ifnot and not value)):
-                    return self.nodelist_false.render(context)
-            return self.nodelist_true.render(context)
+                    return self.nodelist_false.iter_render(context)
+            return self.nodelist_true.iter_render(context)
 
     class LinkTypes:
         and_ = 0,
@@ -245,16 +252,16 @@
         self.target, self.expression = target, expression
         self.var_name = var_name
 
-    def render(self, context):
+    def iter_render(self, context):
         obj_list = self.target.resolve(context, True)
         if obj_list == None: # target_var wasn't found in context; fail silently
             context[self.var_name] = []
-            return ''
+            return ()
         # List of dictionaries in the format
         # {'grouper': 'key', 'list': [list of contents]}.
         context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in
             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))]
-        return ''
+        return ()
 
 def include_is_allowed(filepath):
     for root in settings.ALLOWED_INCLUDE_ROOTS:
@@ -266,10 +273,10 @@
     def __init__(self, filepath, parsed):
         self.filepath, self.parsed = filepath, parsed
 
-    def render(self, context):
+    def iter_render(self, context):
         if not include_is_allowed(self.filepath):
             if settings.DEBUG:
-                return "[Didn't have permission to include file]"
+                return ("[Didn't have permission to include file]",)
             else:
                 return '' # Fail silently for invalid includes.
         try:
@@ -280,23 +287,25 @@
             output = ''
         if self.parsed:
             try:
-                t = Template(output, name=self.filepath)
-                return t.render(context)
+                return Template(output, name=self.filepath).iter_render(context)
             except TemplateSyntaxError, e:
                 if settings.DEBUG:
                     return "[Included template had syntax error: %s]" % e
                 else:
                     return '' # Fail silently for invalid included templates.
-        return output
+        return (output,)
 
 class LoadNode(Node):
-    def render(self, context):
-        return ''
+    def iter_render(self, context):
+        return ()
 
 class NowNode(Node):
     def __init__(self, format_string):
         self.format_string = format_string
 
+    def iter_render(self, context):
+        return (self.render(context),)
+
     def render(self, context):
         from datetime import datetime
         from django.utils.dateformat import DateFormat
@@ -325,6 +334,9 @@
     def __init__(self, tagtype):
         self.tagtype = tagtype
 
+    def iter_render(self, context):
+        return (self.render(context),)
+
     def render(self, context):
         return self.mapping.get(self.tagtype, '')
 
@@ -334,18 +346,18 @@
         self.args = args
         self.kwargs = kwargs
 
-    def render(self, context):
+    def iter_render(self, context):
         from django.core.urlresolvers import reverse, NoReverseMatch
         args = [arg.resolve(context) for arg in self.args]
         kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
         try:
-            return reverse(self.view_name, args=args, kwargs=kwargs)
+            return (reverse(self.view_name, args=args, kwargs=kwargs),)
         except NoReverseMatch:
             try:
                 project_name = settings.SETTINGS_MODULE.split('.')[0]
                 return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs)
             except NoReverseMatch:
-                return ''
+                return ()
 
 class WidthRatioNode(Node):
     def __init__(self, val_expr, max_expr, max_width):
@@ -353,6 +365,9 @@
         self.max_expr = max_expr
         self.max_width = max_width
 
+    def iter_render(self, context):
+        return (self.render(context),)
+
     def render(self, context):
         try:
             value = self.val_expr.resolve(context)
@@ -376,13 +391,13 @@
     def __repr__(self):
         return "<WithNode>"
 
-    def render(self, context):
+    def iter_render(self, context):
         val = self.var.resolve(context)
         context.push()
         context[self.name] = val
-        output = self.nodelist.render(context)
+        for chunk in self.nodelist.iter_render(context):
+            yield chunk
         context.pop()
-        return output
 
 #@register.tag
 def comment(parser, token):
=== django/template/loader_tags.py
==================================================================
--- django/template/loader_tags.py	(/django/trunk)	(revision 10934)
+++ django/template/loader_tags.py	(/local/django/multi-db)	(revision 10934)
@@ -15,14 +15,14 @@
     def __repr__(self):
         return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
 
-    def render(self, context):
+    def iter_render(self, context):
         context.push()
         # Save context in case of block.super().
         self.context = context
         context['block'] = self
-        result = self.nodelist.render(context)
+        for chunk in self.nodelist.iter_render(context):
+            yield chunk
         context.pop()
-        return result
 
     def super(self):
         if self.parent:
@@ -57,9 +57,9 @@
         except TemplateDoesNotExist:
             raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
         else:
-            return get_template_from_string(source, origin, parent)
+            return get_template_from_string(source, origin)
 
-    def render(self, context):
+    def iter_render(self, context):
         compiled_parent = self.get_parent(context)
         parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
         parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
@@ -79,7 +79,7 @@
                 parent_block.parent = block_node.parent
                 parent_block.add_parent(parent_block.nodelist)
                 parent_block.nodelist = block_node.nodelist
-        return compiled_parent.render(context)
+        return compiled_parent.iter_render(context)
 
 class ConstantIncludeNode(Node):
     def __init__(self, template_path):
@@ -91,27 +91,26 @@
                 raise
             self.template = None
 
-    def render(self, context):
+    def iter_render(self, context):
         if self.template:
-            return self.template.render(context)
-        else:
-            return ''
+            return self.template.iter_render(context)
+        return ()
 
 class IncludeNode(Node):
     def __init__(self, template_name):
         self.template_name = template_name
 
-    def render(self, context):
+    def iter_render(self, context):
         try:
             template_name = resolve_variable(self.template_name, context)
             t = get_template(template_name)
-            return t.render(context)
+            return t.iter_render(context)
         except TemplateSyntaxError, e:
             if settings.TEMPLATE_DEBUG:
                 raise
-            return ''
+            return ()
         except:
-            return '' # Fail silently for invalid included templates.
+            return () # Fail silently for invalid included templates.
 
 def do_block(parser, token):
     """
=== django/template/loader.py
==================================================================
--- django/template/loader.py	(/django/trunk)	(revision 10934)
+++ django/template/loader.py	(/local/django/multi-db)	(revision 10934)
@@ -76,25 +76,21 @@
     Returns a compiled Template object for the given template name,
     handling template inheritance recursively.
     """
-    source, origin = find_template_source(template_name)
-    template = get_template_from_string(source, origin, template_name)
-    return template
+    return get_template_from_string(*find_template_source(template_name))
 
-def get_template_from_string(source, origin=None, name=None):
+def get_template_from_string(source, origin=None):
     """
     Returns a compiled Template object for the given template code,
     handling template inheritance recursively.
     """
-    return Template(source, origin, name)
+    return Template(source, origin)
 
-def render_to_string(template_name, dictionary=None, context_instance=None):
+def _render_setup(template_name, dictionary=None, context_instance=None):
     """
-    Loads the given template_name and renders it with the given dictionary as
-    context. The template_name may be a string to load a single template using
-    get_template, or it may be a tuple to use select_template to find one of
-    the templates in the list. Returns a string.
+    Common setup code for render_to_string and render_to_iter.
     """
-    dictionary = dictionary or {}
+    if dictionary is None:
+        dictionary = {}
     if isinstance(template_name, (list, tuple)):
         t = select_template(template_name)
     else:
@@ -103,8 +99,29 @@
         context_instance.update(dictionary)
     else:
         context_instance = Context(dictionary)
-    return t.render(context_instance)
+    return t, context_instance
 
+def render_to_string(template_name, dictionary=None, context_instance=None):
+    """
+    Loads the given template_name and renders it with the given dictionary as
+    context. The template_name may be a string to load a single template using
+    get_template, or it may be a tuple to use select_template to find one of
+    the templates in the list. Returns a string.
+    """
+    t, c = _render_setup(template_name, dictionary=dictionary, context_instance=context_instance)
+    return t.render(c)
+
+def render_to_iter(template_name, dictionary=None, context_instance=None):
+    """
+    Loads the given template_name and renders it with the given dictionary as
+    context. The template_name may be a string to load a single template using
+    get_template, or it may be a tuple to use select_template to find one of
+    the templates in the list. Returns a string.
+    """
+    t, c = _render_setup(template_name, dictionary=dictionary, context_instance=context_instance)
+    return t.iter_render(c)
+
+
 def select_template(template_name_list):
     "Given a list of template names, returns the first that can be loaded."
     for template_name in template_name_list:
=== tests/modeltests/multiple_databases/__init__.py
==================================================================
--- tests/modeltests/multiple_databases/__init__.py	(/django/trunk)	(revision 10934)
+++ tests/modeltests/multiple_databases/__init__.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1 @@
+pass
=== tests/modeltests/multiple_databases/models.py
==================================================================
--- tests/modeltests/multiple_databases/models.py	(/django/trunk)	(revision 10934)
+++ tests/modeltests/multiple_databases/models.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,221 @@
+"""
+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
+`OTHER_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, with one
+addition: MODELS.
+
+The MODELS item in an OTHER_DATABASES entry is a list of the apps and models
+that will use that 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`.
+
+To access a model's connection, use its manager. The connection is available
+at `model._default_manager.db.connection`. To find the backend or other
+connection metadata, use `model._meta.db` to access the full ConnectionInfo
+with connection metadata.
+"""
+
+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 Opus(models.Model):
+    artist = models.ForeignKey(Artist)
+    name = models.CharField(maxlength=100)
+    year = models.IntegerField()
+    
+    def __str__(self):
+        return "%s (%s)" % (self.name, self.year)
+
+
+class Widget(models.Model):
+    code = models.CharField(maxlength=10, unique=True)
+    weight = models.IntegerField()
+
+    def __str__(self):
+        return self.code
+
+
+class DooHickey(models.Model):
+    name = models.CharField(maxlength=50)
+    widgets = models.ManyToManyField(Widget, related_name='doohickeys')
+    
+    def __str__(self):
+        return self.name
+
+
+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)
+
+
+__test__ = {'API_TESTS': """
+
+# See what connections are defined. django.db.connections acts like a dict.
+>>> from django.db import connection, connections, _default, model_connection_name
+>>> from django.conf import settings
+
+# Connections are referenced by name
+>>> connections['_a']
+Connection: ...
+>>> connections['_b']
+Connection: ...
+
+# Let's see what connections are available. The default connection is always
+# included in connections as well, and may be accessed as connections[_default].
+
+>>> 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
+
+# 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)
+'_b'
+>>> model_connection_name(Vehicle) is _default
+True
+>>> a = Artist(name="Paul Klee", alive=False)
+>>> a.save()
+>>> w = Widget(code='100x2r', weight=1000)
+>>> w.save()
+>>> v = Vehicle(make='Chevy', model='Camaro', year='1966')
+>>> v.save()
+>>> artists = Artist.objects.all()
+>>> 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.
+
+>>> from django.db import transaction
+>>> transaction.enter_transaction_management()
+>>> transaction.managed(False)
+>>> a = Artist(name="Joan Miro", alive=False)
+>>> w = Widget(code="99rbln", weight=1)
+>>> a.save()
+
+# Only connection '_a' is committed, so if we rollback
+# all connections we'll forget the new Widget.
+
+>>> transaction.rollback()
+>>> list(Artist.objects.all())
+[<Artist: Paul Klee>, <Artist: Joan Miro>]
+>>> list(Widget.objects.all())
+[<Widget: 100x2r>]
+
+# Managed transaction state applies across all connections.
+
+>>> transaction.managed(True)
+
+# When managed, just as when using a single connection, updates are
+# not committed until a commit is issued.
+
+>>> a = Artist(name="Pablo Picasso", alive=False)
+>>> a.save()
+>>> w = Widget(code="99rbln", weight=1)
+>>> w.save()
+>>> v = Vehicle(make='Pontiac', model='Fiero', year='1987')
+>>> v.save()
+
+# The connections argument may be passed to commit, rollback, and the
+# commit_on_success decorator as a keyword argument, as the first (for
+# commit and rollback) or second (for the decorator) positional
+# argument. It may be passed as a ConnectionInfo object, a connection
+# (DatabaseWrapper) object, a connection name, or a list or dict of
+# ConnectionInfo objects, connection objects, or connection names. If a
+# dict is passed, the keys are ignored and the values used as the list
+# of connections to commit, rollback, etc.
+
+>>> transaction.commit(connections['_b'])
+>>> transaction.commit('_b')
+>>> transaction.commit(connections='_b')
+>>> transaction.commit(connections=['_b'])
+>>> transaction.commit(['_a', '_b'])
+>>> transaction.commit(connections)
+
+# When the connections argument is omitted entirely, the transaction
+# command applies to all connections. Here we have committed
+# connections 'django_test_db_a' and 'django_test_db_b', but not the
+# default connection, so the new vehicle is lost on rollback.
+
+>>> transaction.rollback()
+>>> list(Artist.objects.all())
+[<Artist: Paul Klee>, <Artist: Joan Miro>, <Artist: Pablo Picasso>]
+>>> list(Widget.objects.all())
+[<Widget: 100x2r>, <Widget: 99rbln>]
+>>> list(Vehicle.objects.all())
+[<Vehicle: 1966 Chevy Camaro>]
+>>> transaction.rollback()
+>>> transaction.managed(False)
+>>> transaction.leave_transaction_management()
+
+# Of course, relations and all other normal database operations work
+# with models that use named connections just the same as with models
+# that use the default connection. The only caveat is that you can't
+# use a relation between two models that are stored in different
+# databases. Note that that doesn't mean that two models using
+# different connection *names* can't be related; only that in the the
+# context in which they are used, if you use the relation, the
+# connections named by the two models must resolve to the same
+# database.
+
+>>> a = Artist.objects.get(name="Paul Klee")
+>>> list(a.opus_set.all())
+[]
+>>> a.opus_set.create(name="Magic Garden", year="1926")
+<Opus: Magic Garden (1926)>
+>>> list(a.opus_set.all())
+[<Opus: Magic Garden (1926)>]
+>>> d = DooHickey(name='Thing')
+>>> d.save()
+>>> d.widgets.create(code='d101', weight=92)
+<Widget: d101>
+>>> list(d.widgets.all())
+[<Widget: d101>]
+>>> w = Widget.objects.get(code='d101')
+>>> list(w.doohickeys.all())
+[<DooHickey: Thing>]
+"""}

Property changes on: tests/modeltests/multiple_databases
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== tests/regressiontests/manager_db/__init__.py
==================================================================
=== tests/regressiontests/manager_db/tests.py
==================================================================
--- tests/regressiontests/manager_db/tests.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/manager_db/tests.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,17 @@
+import unittest
+from regressiontests.manager_db.models import Insect
+
+class TestManagerDBAccess(unittest.TestCase):
+
+    def test_db_property(self):
+        m = Insect.objects
+        db = Insect.objects.db
+        assert db
+        assert db.connection
+        assert db.connection.cursor
+        assert db.backend
+        assert db.backend.quote_name
+        assert db.get_creation_module
+
+if __name__ == '__main__':
+    unittest.main()
=== tests/regressiontests/manager_db/models.py
==================================================================
--- tests/regressiontests/manager_db/models.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/manager_db/models.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,5 @@
+from django.db import models
+
+class Insect(models.Model):
+    common_name = models.CharField(maxlength=64)
+    latin_name = models.CharField(maxlength=128)

Property changes on: tests/regressiontests/manager_db
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== tests/regressiontests/thread_isolation/__init__.py
==================================================================
=== tests/regressiontests/thread_isolation/tests.py
==================================================================
--- tests/regressiontests/thread_isolation/tests.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/thread_isolation/tests.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,251 @@
+# tests that db settings can be different in different threads
+#
+#
+#    What's going on here:
+#
+#    Simulating multiple web requests in a threaded environment, one in
+#    which settings are different for each request. So we replace
+#    django.conf.settings with a thread local, with different
+#    configurations in each thread, and then fire off three
+#    simultaneous requests (using a condition to sync them up), and
+#    test that each thread sees its own settings and the models in each
+#    thread attempt to connect to the correct database as per their
+#    settings.
+#
+
+
+import copy
+import os
+import sys
+import threading
+import unittest
+from thread import get_ident
+
+from django.conf import settings, UserSettingsHolder
+from django.core.handlers.wsgi import WSGIHandler
+from django.db import model_connection_name, _default, connection, connections
+from regressiontests.request_isolation.tests import MockHandler
+from regressiontests.thread_isolation.models import *
+
+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
+
+# helpers
+EV = threading.Event()
+
+class LocalSettings:
+    """Settings holder that allows thread-local overrides of defaults.
+    """
+    def __init__(self, defaults):
+        self._defaults = defaults
+        self._local = local()
+
+    def __getattr__(self, attr):
+        if attr in ('_defaults', '_local'):
+            return self.__dict__[attr]
+        _local = self.__dict__['_local']
+        _defaults = self.__dict__['_defaults']
+        debug("LS get %s (%s)", attr, hasattr(_local, attr))
+        if not hasattr(_local, attr):
+            # Make sure everything we return is the local version; this
+            # avoids sets to deep datastructures overwriting the defaults
+            setattr(_local, attr, copy.deepcopy(getattr(_defaults, attr)))
+        return getattr(_local, attr)
+
+    def __setattr__(self, attr, val):
+        if attr in ('_defaults', '_local'):
+            self.__dict__[attr] = val
+        else:
+            debug("LS set local %s = %s", attr, val)
+            setattr(self.__dict__['_local'], attr, val)
+
+def thread_two(func, *arg):
+    def start():
+        # from django.conf import settings
+        settings.OTHER_DATABASES['_b']['MODELS'] = []
+
+        debug("t2 ODB: %s", settings.OTHER_DATABASES)
+        debug("t2 waiting")
+        EV.wait(2.0)
+        func(*arg)
+        debug("t2 complete")
+    t2 = threading.Thread(target=start)
+    t2.start()
+    return t2
+
+def thread_three(func, *arg):
+    def start():
+        # from django.conf import settings            
+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ti.MY']
+        settings.OTHER_DATABASES['_b'], \
+            settings.OTHER_DATABASES['_a'] = \
+            settings.OTHER_DATABASES['_a'], \
+            settings.OTHER_DATABASES['_b']
+
+        settings.DATABASE_NAME = \
+            settings.OTHER_DATABASES['_a']['DATABASE_NAME']
+
+        debug("t3 ODB: %s", settings.OTHER_DATABASES)
+        debug("3 %s: start: default: %s", get_ident(), settings.DATABASE_NAME)
+        debug("3 %s: start: conn: %s", get_ident(),
+              connection.settings.DATABASE_NAME)
+        
+        debug("t3 waiting")
+        EV.wait(2.0)
+        func(*arg)
+        debug("t3 complete")
+    t3 = threading.Thread(target=start)
+    t3.start()
+    return t3
+
+def debug(*arg):
+    pass
+#    msg, arg = arg[0], arg[1:]
+#    print msg % arg
+
+def start_response(code, headers):
+    debug("start response: %s %s", code, headers)
+    pass
+    
+class TestThreadIsolation(unittest.TestCase):
+    # event used to synchronize threads so we can be sure they are running
+    # together
+    lock = threading.RLock()
+    errors = []
+    
+    def setUp(self):
+        debug("setup")
+        self.settings = settings._target
+        settings._target = UserSettingsHolder(copy.deepcopy(settings._target))
+        settings.OTHER_DATABASES['_a']['MODELS'] =  ['ti.MX']
+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ti.MY']
+
+        # normal settings holders aren't thread-safe, so we need to substitute
+        # one that is (and so allows per-thread settings)
+        holder = settings._target
+        settings._target = LocalSettings(holder)
+
+    def teardown(self):
+        debug("teardown")
+        settings._target = self.settings
+
+    def add_thread_error(self, err):
+        self.lock.acquire()
+        try:
+            self.errors.append(err)
+        finally:
+            self.lock.release()
+
+    def thread_errors(self):
+        self.lock.acquire()
+        try:
+            return self.errors[:]
+        finally:
+            self.lock.release()
+            
+    def request_one(self, request):
+        """Start out with settings as originally configured"""
+        from django.conf import settings
+        debug("request_one: %s", settings.OTHER_DATABASES)
+
+        self.assertEqual(model_connection_name(MQ), _default)
+        self.assertEqual(model_connection_name(MX), '_a')
+        self.assertEqual(
+            MX._default_manager.db.connection.settings.DATABASE_NAME, 
+            settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
+        self.assertEqual(model_connection_name(MY), '_b')
+        self.assertEqual(
+            MY._default_manager.db.connection.settings.DATABASE_NAME,
+            settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
+        self.assert_(MQ._default_manager.db.connection is
+                     connections[_default].connection)
+        self.assertEqual(
+            MQ._default_manager.db.connection.settings.DATABASE_NAME,
+            settings.DATABASE_NAME)
+        self.assertEqual(connection.settings.DATABASE_NAME,
+                         settings.DATABASE_NAME)
+
+    def request_two(self, request):
+        """Between the first and second requests, settings change to assign
+        model MY to a different connection
+        """
+        # from django.conf import settings
+        debug("request_two: %s", settings.OTHER_DATABASES)
+
+        try:
+            self.assertEqual(model_connection_name(MQ), _default)
+            self.assertEqual(model_connection_name(MX), '_a')
+            self.assertEqual(
+                MX._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
+            self.assertEqual(model_connection_name(MY), _default)
+            self.assertEqual(
+                MY._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.DATABASE_NAME)
+            self.assert_(MQ._default_manager.db.connection is
+                         connections[_default].connection)
+            self.assertEqual(
+                MQ._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.DATABASE_NAME)
+            self.assertEqual(connection.settings.DATABASE_NAME,
+                             settings.DATABASE_NAME)
+        except:
+            self.add_thread_error(sys.exc_info())
+
+    def request_three(self, request):
+        """Between the 2nd and 3rd requests, the settings at the names in
+        OTHER_DATABASES have changed.
+        """
+        # from django.conf import settings
+        debug("3 %s: %s", get_ident(), settings.OTHER_DATABASES)
+        debug("3 %s: default: %s", get_ident(), settings.DATABASE_NAME)
+        debug("3 %s: conn: %s", get_ident(),
+              connection.settings.DATABASE_NAME)
+        try:
+            self.assertEqual(model_connection_name(MQ), _default)
+            self.assertEqual(model_connection_name(MX), '_b')
+            self.assertEqual(
+                MX._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
+            self.assertEqual(model_connection_name(MY), '_a')
+            self.assertEqual(
+                MY._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
+            self.assert_(MQ._default_manager.db.connection is
+                         connections[_default].connection)
+            self.assertEqual(
+                connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
+        except:
+            self.add_thread_error(sys.exc_info())
+        
+    def test_thread_isolation(self):
+        
+        debug("running tests")
+
+        env = os.environ.copy()
+        env['PATH_INFO'] = '/'
+        env['REQUEST_METHOD'] = 'GET'
+
+        t2 = thread_two(MockHandler(self.request_two), env, start_response)
+        t3 = thread_three(MockHandler(self.request_three), env, start_response)
+
+        try:
+            EV.set()
+            MockHandler(self.request_one)(env, start_response)
+        finally:
+            t2.join()
+            t3.join()
+            err = self.thread_errors()
+            if err:
+                import traceback 
+                for e in err:
+                    traceback.print_exception(*e)
+                    raise AssertionError("%s thread%s failed" %
+                                         (len(err), len(err) > 1 and 's' or
+                                          ''))
+                
=== tests/regressiontests/thread_isolation/models.py
==================================================================
--- tests/regressiontests/thread_isolation/models.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/thread_isolation/models.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,19 @@
+from django.db import models
+
+# models
+class MQ(models.Model):
+    val = models.CharField(maxlength=10)
+    class Meta:
+        app_label = 'ti'
+
+
+class MX(models.Model):
+    val = models.CharField(maxlength=10)
+    class Meta:
+        app_label = 'ti'
+
+        
+class MY(models.Model):
+    val = models.CharField(maxlength=10)
+    class Meta:
+        app_label = 'ti'

Property changes on: tests/regressiontests/thread_isolation
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +


Property changes on: tests/regressiontests/initial_sql_regress/sql
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== tests/regressiontests/ansi_sql/sql/car.sql
==================================================================
--- tests/regressiontests/ansi_sql/sql/car.sql	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/ansi_sql/sql/car.sql	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,2 @@
+insert into ansi_sql_car (make, model, year, condition)
+       values ('Chevy', 'Impala', 1966, 'mint');

Property changes on: tests/regressiontests/ansi_sql/sql
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== tests/regressiontests/ansi_sql/__init__.py
==================================================================
=== tests/regressiontests/ansi_sql/models.py
==================================================================
--- tests/regressiontests/ansi_sql/models.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/ansi_sql/models.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,138 @@
+"""
+>>> from django.db.backends.ansi import sql
+
+# so we can test with a predicatable constraint setting
+>>> real_cnst = Mod._default_manager.db.backend.supports_constraints
+>>> Mod._default_manager.db.backend.supports_constraints = True
+    
+# generate create sql
+>>> builder = sql.SchemaBuilder()
+>>> builder.get_create_table(Car)
+([BoundStatement('CREATE TABLE "ansi_sql_car" (...);')], {})
+>>> builder.models_already_seen
+Set([<class 'regressiontests.ansi_sql.models.Car'>])
+>>> builder.models_already_seen = set()
+
+# test that styles are used
+>>> builder.get_create_table(Car, style=mockstyle())
+([BoundStatement('SQL_KEYWORD(CREATE TABLE) SQL_TABLE("ansi_sql_car") (...SQL_FIELD("id")...);')], {})
+
+# test pending relationships
+>>> builder.models_already_seen = set()
+>>> builder.get_create_table(Mod)
+([BoundStatement('CREATE TABLE "ansi_sql_mod" (..."car_id" integer NOT NULL,...);')], {<class 'regressiontests.ansi_sql.models.Car'>: [(<class 'regressiontests.ansi_sql.models.Mod'>, <django.db.models.fields.related.ForeignKey...>)]})
+>>> builder.models_already_seen = set()
+>>> builder.get_create_table(Car)
+([BoundStatement('CREATE TABLE "ansi_sql_car" (...);')], {})
+>>> builder.get_create_table(Mod)
+([BoundStatement('CREATE TABLE "ansi_sql_mod" (..."car_id" integer NOT NULL REFERENCES "ansi_sql_car" ("id"),...);')], {})
+
+# test many-many
+>>> builder.get_create_table(Collector)
+([BoundStatement('CREATE TABLE "ansi_sql_collector" (...);')], {})
+>>> builder.get_create_many_to_many(Collector)
+{<class 'regressiontests.ansi_sql.models.Car'>: [BoundStatement('CREATE TABLE "ansi_sql_collector_cars" (...);')]}
+
+# test indexes
+>>> builder.get_create_indexes(Car)
+[]
+>>> builder.get_create_indexes(Mod)
+[BoundStatement('CREATE INDEX ... ON "ansi_sql_mod" ("car_id");'), BoundStatement('CREATE INDEX ... ON "ansi_sql_mod" ("part");')]
+>>> builder.get_create_indexes(Collector)
+[]
+
+# test initial data
+# patch builder so that it looks for initial data where we want it to
+# >>> builder.get_initialdata_path = othertests_sql
+>>> builder.get_initialdata(Car)
+[BoundStatement("insert into ansi_sql_car (...)...values (...);...")]
+
+# test drop
+>>> builder.get_drop_table(Mod)
+[BoundStatement('DROP TABLE "ansi_sql_mod";')]
+>>> builder.get_drop_table(Mod, cascade=True)
+[BoundStatement('DROP TABLE "ansi_sql_mod";')]
+>>> builder.get_drop_table(Car)
+[BoundStatement('DROP TABLE "ansi_sql_car";')]
+
+# drop with cascade
+>>> builder.tables = ['ansi_sql_car', 'ansi_sql_mod', 'ansi_sql_collector']
+>>> Mod._default_manager.db.backend.supports_constraints = False
+>>> Mod._default_manager.db.backend.supports_constraints
+False
+>>> Car._default_manager.db.backend.supports_constraints
+False
+>>> builder.get_drop_table(Car, cascade=True)
+[BoundStatement('DROP TABLE "ansi_sql_car";')]
+>>> Mod._default_manager.db.backend.supports_constraints = True
+>>> Mod._default_manager.db.backend.supports_constraints
+True
+>>> Car._default_manager.db.backend.supports_constraints
+True
+>>> builder.get_drop_table(Car, cascade=True)
+[BoundStatement('DROP TABLE "ansi_sql_car";'), BoundStatement('ALTER TABLE "ansi_sql_mod" ...')]
+>>> builder.get_drop_table(Collector)
+[BoundStatement('DROP TABLE "ansi_sql_collector";')]
+>>> builder.get_drop_table(Collector, cascade=True)
+[BoundStatement('DROP TABLE "ansi_sql_collector";'), BoundStatement('DROP TABLE "ansi_sql_collector_cars";')]
+>>> Mod._default_manager.db.backend.supports_constraints = real_cnst
+
+"""
+#import os
+#import sys
+#from django.conf import settings
+#from django.core.management import install
+from django.db import models
+#from django.db.models import loading
+
+
+# For Python 2.3
+if not hasattr(__builtins__, 'set'):
+    from sets import Set as set
+
+
+# test models
+class Car(models.Model):
+    make = models.CharField(maxlength=32)
+    model = models.CharField(maxlength=32)
+    year = models.IntegerField()
+    condition = models.CharField(maxlength=32)
+
+        
+class Collector(models.Model):
+    name = models.CharField(maxlength=32)
+    cars = models.ManyToManyField(Car)
+
+        
+class Mod(models.Model):
+    car = models.ForeignKey(Car)
+    part = models.CharField(maxlength=32, db_index=True)
+    description = models.TextField()
+
+
+class mockstyle:
+    """mock style that wraps text in STYLE(text), for testing"""
+    def __getattr__(self, attr):
+        if attr in ('ERROR', 'ERROR_OUTPUT', 'SQL_FIELD', 'SQL_COLTYPE',
+                    'SQL_KEYWORD', 'SQL_TABLE'):
+            return lambda text: "%s(%s)" % (attr, text)
+
+        
+# def othertests_sql(mod):
+#     """Look in othertests/sql for sql initialdata"""
+#     return os.path.normpath(os.path.join(os.path.dirname(__file__), 'sql'))
+
+
+# # install my models and force myself into the registry of apps and models
+# # (without this, references and such to/from the models installed here
+# # won't be picked up in get_models(), since get_models() looks only at
+# # models from apps in INSTALLED_APPS, and the model and app lookup rules
+# # enforce a module structure that this test file can't follow)
+# Car.objects.install()
+# Collector.objects.install()
+# Mod.objects.install()
+# loading.get_apps()
+# loading._app_list.append(sys.modules[__name__])
+
+# # model lookup only works for pk.models, so fake up my module name
+# __name__ = 'othertests.ansi_sql.models'

Property changes on: tests/regressiontests/ansi_sql
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== tests/regressiontests/request_isolation/__init__.py
==================================================================
=== tests/regressiontests/request_isolation/tests.py
==================================================================
--- tests/regressiontests/request_isolation/tests.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/request_isolation/tests.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,100 @@
+# tests that db settings can change between requests
+import copy
+import os
+import unittest
+from django.conf import settings, UserSettingsHolder
+from django.core.handlers.wsgi import WSGIHandler
+from django.db import models, model_connection_name, _default, connection
+from django.http import HttpResponse
+from regressiontests.request_isolation.models import *
+
+
+# helpers
+class MockHandler(WSGIHandler):
+
+    def __init__(self, test):
+        self.test = test
+        super(MockHandler, self).__init__()
+        
+    def get_response(self, request):
+        # debug("mock handler answering %s, %s", path, request)
+        return HttpResponse(self.test(request))
+
+
+def debug(*arg):
+    pass
+    # msg, arg = arg[0], arg[1:]
+    # print msg % arg
+
+
+def start_response(code, headers):
+    debug("start response: %s %s", code, headers)
+    pass
+
+# tests
+class TestRequestIsolation(unittest.TestCase):
+
+    def setUp(self):
+        debug("setup")
+        self.settings = settings._target
+        settings._target = UserSettingsHolder(copy.deepcopy(settings._target))
+        settings.OTHER_DATABASES['_a']['MODELS'] = ['ri.MX']
+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ri.MY']
+
+    def tearDown(self):
+        debug("teardown")
+        settings._target = self.settings
+
+    def testRequestIsolation(self):
+        env = os.environ.copy()
+        env['PATH_INFO'] = '/'
+        env['REQUEST_METHOD'] = 'GET'
+
+        def request_one(request):
+            """Start out with settings as originally configured"""
+            self.assertEqual(model_connection_name(MX), '_a')
+            self.assertEqual(
+                MX._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
+            self.assertEqual(model_connection_name(MY), '_b')
+            self.assertEqual(
+                MY._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
+
+        def request_two(request):
+            """Between the first and second requests, settings change to assign
+            model MY to a different connection
+            """
+            self.assertEqual(model_connection_name(MX), '_a')
+            self.assertEqual(
+                MX._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
+            self.assertEqual(model_connection_name(MY), _default)
+            self.assertEqual(
+                MY._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.DATABASE_NAME)
+
+        def request_three(request):
+            """Between the 2nd and 3rd requests, the settings at the names in
+            OTHER_DATABASES have changed.
+            """
+            self.assertEqual(model_connection_name(MX), '_b')
+            self.assertEqual(
+                MX._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
+            self.assertEqual(model_connection_name(MY), '_a')
+            self.assertEqual(
+                MY._default_manager.db.connection.settings.DATABASE_NAME,
+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
+    
+        MockHandler(request_one)(env, start_response)
+
+        settings.OTHER_DATABASES['_b']['MODELS'] = []
+        MockHandler(request_two)(env, start_response)
+
+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ri.MY']
+        settings.OTHER_DATABASES['_b'], \
+            settings.OTHER_DATABASES['_a'] = \
+            settings.OTHER_DATABASES['_a'], \
+            settings.OTHER_DATABASES['_b']
+        MockHandler(request_three)(env, start_response)
=== tests/regressiontests/request_isolation/models.py
==================================================================
--- tests/regressiontests/request_isolation/models.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/request_isolation/models.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,13 @@
+from django.db import models
+
+# models
+class MX(models.Model):
+    val = models.CharField(maxlength=10)
+    class Meta:
+        app_label = 'ri'
+
+        
+class MY(models.Model):
+    val = models.CharField(maxlength=10)
+    class Meta:
+        app_label = 'ri'

Property changes on: tests/regressiontests/request_isolation
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== tests/regressiontests/manager_schema_manipulation/__init__.py
==================================================================
=== tests/regressiontests/manager_schema_manipulation/tests.py
==================================================================
--- tests/regressiontests/manager_schema_manipulation/tests.py	(/django/trunk)	(revision 10934)
+++ tests/regressiontests/manager_schema_manipulation/tests.py	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,143 @@
+"""
+# Django uses a model's default manager to perform schema
+# manipulations such as creating or dropping the model's table.
+
+>>> from django.db import models
+>>> from django.conf import settings
+>>> import copy
+
+# save copy of settings so we can restore it later
+>>> odb = copy.deepcopy(settings.OTHER_DATABASES)
+>>> settings.OTHER_DATABASES['django_msm_test_db_a'] = { 'MODELS': ['msm.PA', 'msm.P', 'msm.PC']}
+>>> settings.OTHER_DATABASES['django_msm_test_db_b'] = {'MODELS': ['msm.QA', 'msm.QB', 'msm.QC', 'msm.QD']}
+
+# default connection
+>>> class DA(models.Model):
+...     name = models.CharField(maxlength=20)
+...     
+...     def __str__(self):
+...         return self.name
+    
+# connection a
+>>> class PA(models.Model):
+...     name = models.CharField(maxlength=20)
+...     # This creates a cycle in the dependency graph
+...     c = models.ForeignKey('PC', null=True)
+...     
+...     def __str__(self):
+...         return self.name
+...     
+...     class Meta:
+...         app_label = 'msm'
+
+>>> class PB(models.Model):
+...     name = models.CharField(maxlength=20)
+...     a = models.ForeignKey(PA)
+...     
+...     def __str__(self):
+...         return self.name
+...     
+...     class Meta:
+...         app_label = 'msm'
+
+>>> class PC(models.Model):
+...     name = models.CharField(maxlength=20)
+...     b = models.ForeignKey(PB)
+...     
+...     def __str__(self):
+...         return self.name
+...     
+...     class Meta:
+...         app_label = 'msm'
+    
+# connection b
+>>> class QA(models.Model):
+...     name = models.CharField(maxlength=20)
+...     
+...     def __str__(self):
+...         return self.name
+...     
+...     class Meta:
+...         app_label = 'msm'
+
+>>> class QB(models.Model):
+...     name = models.CharField(maxlength=20)
+...     a = models.ForeignKey(QA)
+...     
+...     def __str__(self):
+...         return self.name
+...     
+...     class Meta:
+...         app_label = 'msm'
+
+# many-many
+>>> class QC(models.Model):
+...     name = models.CharField(maxlength=20)
+...     
+...     def __str__(self):
+...         return self.name
+...     
+...     class Meta:
+...         app_label = 'msm'
+
+>>> class QD(models.Model):
+...     name = models.CharField(maxlength=20)
+...     qcs = models.ManyToManyField(QC)
+...     
+...     def __str__(self):
+...         return self.name
+...     
+...     class Meta:
+...         app_label = 'msm'
+
+# Using the manager, models can be installed individually, whether they
+# use the default connection or a named connection.
+
+>>> DA.objects.install()
+{}
+>>> QA.objects.install()
+{}
+>>> QB.objects.install()
+{}
+>>> DA.objects.all()
+[]
+>>> list(QA.objects.all())
+[]
+>>> list(QB.objects.all())
+[]
+>>> QA(name="something").save()
+>>> QA.objects.all()
+[<QA: something>]
+
+# The `install()` method returns a tuple, the first element of which is a
+# list of statements that were executed, and the second, pending
+# statements that could not be executed because (for instance) they are
+# meant to establish foreign key relationships to tables that don't
+# exist. These are bound to the model's connection and should
+# be executed after all models in the app have been installed. The pending
+# statments are returned as a dict keyed by the model which must be installed
+# before the pending statements can be installed.
+
+# NOTE: pretend db supports constraints for this test
+>>> real_cnst = PA._default_manager.db.backend.supports_constraints
+>>> PA._default_manager.db.backend.supports_constraints = True
+>>> result = PA.objects.install()
+>>> result
+{<class 'regressiontests.manager_schema_manipulation.tests.PC'>: [(<class 'regressiontests.manager_schema_manipulation.tests.PA'>, <django.db.models.fields.related.ForeignKey ...>)]}
+
+# NOTE: restore real constraint flag
+>>> PA._default_manager.db.backend.supports_constraints = real_cnst
+
+# Models with many-many relationships may also have pending statement
+# lists. Like other pending statements, these should be executed after
+# all models in the app have been installed. If the related table's model
+# has already been created, then there will be no pending list.
+
+>>> QC.objects.install()
+{}
+>>> QD.objects.install()
+{}
+
+# Finally, restore the original settings
+>>> settings.OTHER_DATABASES = odb
+"""
=== tests/regressiontests/manager_schema_manipulation/models.py
==================================================================

Property changes on: tests/regressiontests/manager_schema_manipulation
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== tests/runtests.py
==================================================================
--- tests/runtests.py	(/django/trunk)	(revision 10934)
+++ tests/runtests.py	(/local/django/multi-db)	(revision 10934)
@@ -12,6 +12,17 @@
 TEST_TEMPLATE_DIR = 'templates'
 
 CONTRIB_DIR = os.path.dirname(contrib.__file__)
+
+TEST_DATABASES = ('_a', '_b')
+
+TEST_DATABASE_MODELS = {
+    '_a': [ 'multiple_databases.Artist',
+            'multiple_databases.Opus' ],
+    '_b': [ 'multiple_databases.Widget',
+            'multiple_databases.DooHickey' ]
+}
+
+
 MODEL_TEST_DIR = os.path.join(os.path.dirname(__file__), MODEL_TESTS_DIR_NAME)
 REGRESSION_TEST_DIR = os.path.join(os.path.dirname(__file__), REGRESSION_TESTS_DIR_NAME)
 
@@ -86,6 +97,8 @@
     # Redirect some settings for the duration of these tests.
     settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
     settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
+    settings.TEST_DATABASES = TEST_DATABASES
+    settings.TEST_DATABASE_MODELS = TEST_DATABASE_MODELS
     settings.ROOT_URLCONF = 'urls'
     settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
     settings.USE_I18N = True

Property changes on: tests/templates
___________________________________________________________________
Name: svn:ignore
 +*.pyc
 +

=== docs/settings.txt
==================================================================
--- docs/settings.txt	(/django/trunk)	(revision 10934)
+++ docs/settings.txt	(/local/django/multi-db)	(revision 10934)
@@ -640,6 +640,13 @@
 See `allowed date format strings`_. See also DATE_FORMAT, DATETIME_FORMAT,
 TIME_FORMAT and YEAR_MONTH_FORMAT.
 
+OTHER_DATABASES
+---------------
+
+Default: ``{}``
+
+Other database connections to use in addition to the default connection. See the `multiple database support docs`_.
+
 PREPEND_WWW
 -----------
 
@@ -966,6 +973,7 @@
 
 .. _cache docs: ../cache/
 .. _middleware docs: ../middleware/
+.. _multiple database support docs: ../multiple_database_support/
 .. _session docs: ../sessions/
 .. _See available choices: http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
 .. _template documentation: ../templates_python/
=== docs/multiple_database_support.txt
==================================================================
--- docs/multiple_database_support.txt	(/django/trunk)	(revision 10934)
+++ docs/multiple_database_support.txt	(/local/django/multi-db)	(revision 10934)
@@ -0,0 +1,163 @@
+========================
+Using Multiple Databases
+========================
+
+Standard Django practice is to use a single database connection for
+all models in all applications. However, Django supports configuring
+and using multiple database connections on a per-application, per-model
+or an ad-hoc basis. Using multiple database connections is optional.
+
+Configuring other database connections
+======================================
+
+Django's default database connection is configured via the settings
+DATABASE_ENGINE, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD,
+DATABASE_HOST, and DATABASE_PORT. Other connections are configured via
+the OTHER_DATABASES setting. Define OTHER_DATABASES as a dict, with a
+name for each connection as the key and a dict of settings as the
+value. In each OTHER_DATABASES entry (called a "named connection"),
+the keys are the same as the DATABASE_ENGINE, etc, settings used to
+configure the default connection. All keys are optional; any that are
+missing in a named connection's settings will inherit their values
+from the default connection.
+
+Here's an example::
+
+    DATABASE_ENGINE = 'postgresql'
+    DATABASE_NAME = 'django_apps'
+    DATABASE_USER = 'default_user'
+    DATABASE_PASSWORD = 'xxx'
+	
+    OTHER_DATABASES = {
+        'local': { 'DATABASE_ENGINE': 'sqlite3',
+                   'DATABASE_NAME': '/tmp/cache.db' },
+        'public': { 'DATABASE_HOST': 'public',
+                    'DATABASE_USER': 'public_user',
+                    'DATABASE_PASSWORD': 'xxx' }
+        'private': { 'DATABASE_HOST': 'private',
+                     'DATABASE_USER': 'private_user',
+                     'DATABASE_PASSWORD': 'xxx' }
+    }
+
+In addition to the DATABASE_* settings, each named connection in
+OTHER_DATABASES may optionally include a MODELS setting. This should
+be a list of app or app.model names, and is used to configure which
+models should use this connection instead of the default connection.
+
+Here's the example above, with ``MODELS``::
+
+    OTHER_DATABASES = {
+        'local': { 'DATABASE_ENGINE': 'sqlite3',
+                   'DATABASE_NAME': '/tmp/cache.db',
+                   # A model name: only the model ContentItem
+                   # with the app_label myapp will use this connection
+                   'MODELS': ['myapp.ContentItem'] },
+        'public': { 'DATABASE_HOST': 'public',
+                    'DATABASE_USER': 'public_user',
+                    'DATABASE_PASSWORD': 'xxx',
+	            # Two models in myapp will use the connection
+                    # named 'public', as will ALL models in 
+                    # django.contribe.comments
+		    'MODELS': ['myapp.Blog','myapp.Article',
+                               'django.contrib.comments' ] }
+        # No models or apps are configured to use the private db
+        'private': { 'DATABASE_HOST': 'private',
+                     'DATABASE_USER': 'private_user',
+                     'DATABASE_PASSWORD': 'xxx' }
+    }
+
+Accessing a model's connection
+==============================
+
+Each manager has a ``db`` attribute that can be used to access the model's
+connection. Access the ``db`` attribute of a model's manager to obtain the
+model's currently configured connection. 
+
+Example::
+
+    from django.db import models
+
+    class Blog(models.Model)
+        name = models.CharField(maxlength=50)
+
+    class Article(models.Model)
+	blog = models.ForeignKey(Blog)
+        title = models.CharField(maxlength=100)
+        slug = models.SlugField()
+        summary = models.CharField(maxlength=500)
+        body = models.TextField()
+
+    class ContentItem(models.Model)
+        slug = models.SlugField()
+        mimetype = models.CharField(maxlength=50)
+	file = models.FileField()
+	
+    # Get a ConnectionInfo instance that describes the connection
+    article_db = Article.objects.db
+    
+    # Get a connection and a cursor
+    connection = article_db.connection
+    cursor = connection.cursor()
+
+    # Get the ``quote_name`` function from the backend
+    qn = article_db.backend.quote_name
+
+Ordinarily you won't have to access a model's connection directly;
+just use the model and manager normally and they will use the
+connection configured for the model.
+
+ConnectionInfo objects
+======================
+
+FIXME Describe the ConnectionInfo object and each of its attributes.
+
+
+Accessing connections by name
+=============================
+
+Access named connections directly through
+``django.db.connections``. Each entry in ``django.db.connections`` is
+a ``ConnectionInfo`` instance bound to the settings configured in the
+OTHER_DATABASES entry under the same key. 
+
+Example::
+
+    from django.db import connections
+
+    private_db = connections['private']
+    cursor = private_db.connection.cursor()
+
+
+Using transactions with other database connections
+==================================================
+
+Transaction managed state applies across all connections
+commit/rollback apply to all connections by default
+but you can specify individual connections or lists or dicts of connections
+
+
+Changing model connections on the fly
+=====================================
+
+Here's an example of primitive mirroring::
+ 
+    # Read all articles from the private db
+    # Note that we pull the articles into a list; this is necessary
+    # because query sets are lazy. If we were to change the model's
+    # connection without copying the articles into a local list, we'd
+    # wind up reading from public instead of private.
+
+    Article.objects.db = connections['private']
+    all_articles = list(Article.objects.all())
+    
+    # Save each article in the public db
+    Article.objects.db = connections['public']
+    for article in all_articles:
+        article.save()
+
+Thread and request isolation
+============================
+
+connections close after each request
+connection settings are thread-local
+
=== docs/templates_python.txt
==================================================================
--- docs/templates_python.txt	(/django/trunk)	(revision 10934)
+++ docs/templates_python.txt	(/local/django/multi-db)	(revision 10934)
@@ -219,13 +219,13 @@
 
     While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
     it is a bad idea to turn it on as a 'development default'.
-    
+
     Many templates, including those in the Admin site, rely upon the
     silence of the template system when a non-existent variable is
     encountered. If you assign a value other than ``''`` to
     ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
     problems with these templates and sites.
-    
+
     Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
     in order to debug a specific template problem, then cleared
     once debugging is complete.
@@ -693,14 +693,15 @@
 
 When Django compiles a template, it splits the raw template text into
 ''nodes''. Each node is an instance of ``django.template.Node`` and has
-a ``render()`` method. A compiled template is, simply, a list of ``Node``
-objects. When you call ``render()`` on a compiled template object, the template
-calls ``render()`` on each ``Node`` in its node list, with the given context.
-The results are all concatenated together to form the output of the template.
+either a ``render()`` or ``iter_render()`` method. A compiled template is,
+simply, a list of ``Node`` objects. When you call ``render()`` on a compiled
+template object, the template calls ``render()`` on each ``Node`` in its node
+list, with the given context. The results are all concatenated together to
+form the output of the template.
 
 Thus, to define a custom template tag, you specify how the raw template tag is
 converted into a ``Node`` (the compilation function), and what the node's
-``render()`` method does.
+``render()`` or ``iter_render()`` method does.
 
 Writing the compilation function
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -770,7 +771,8 @@
 ~~~~~~~~~~~~~~~~~~~~
 
 The second step in writing custom tags is to define a ``Node`` subclass that
-has a ``render()`` method.
+has a ``render()`` method (we will discuss the ``iter_render()`` alternative
+in `Improving rendering speed`_, below).
 
 Continuing the above example, we need to define ``CurrentTimeNode``::
 
@@ -874,7 +876,7 @@
         def __init__(self, date_to_be_formatted, format_string):
             self.date_to_be_formatted = date_to_be_formatted
             self.format_string = format_string
-        
+
         def render(self, context):
             try:
                 actual_date = resolve_variable(self.date_to_be_formatted, context)
@@ -1175,6 +1177,48 @@
 
 .. _configuration:
 
+Improving rendering speed
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For most practical purposes, the ``render()`` method on a ``Node`` will be
+sufficient and the simplest way to implement a new tag. However, if your
+template tag is expected to produce large strings via ``render()``, you can
+speed up the rendering process (and reduce memory usage) using iterative
+rendering via the ``iter_render()`` method.
+
+The ``iter_render()`` method should either be an iterator that yields string
+chunks, one at a time, or a method that returns a sequence of string chunks.
+The template renderer will join the successive chunks together when creating
+the final output. The improvement over the ``render()`` method here is that
+you do not need to create one large string containing all the output of the
+``Node``, instead you can produce the output in smaller chunks.
+
+By way of example, here's a trivial ``Node`` subclass that simply returns the
+contents of a file it is given::
+
+    class FileNode(Node):
+        def __init__(self, filename):
+            self.filename = filename
+
+        def iter_render(self):
+            for line in file(self.filename):
+                yield line
+
+For very large files, the full file contents will never be read entirely into
+memory when this tag is used, which is a useful optimisation.
+
+If you define an ``iter_render()`` method on your ``Node`` subclass, you do
+not need to define a ``render()`` method. The reverse is true as well: the
+default ``Node.iter_render()`` method will call your ``render()`` method if
+necessary. A useful side-effect of this is that you can develop a new tag
+using ``render()`` and producing all the output at once, which is easy to
+debug. Then you can rewrite the method as an iterator, rename it to
+``iter_render()`` and everything will still work.
+
+It is compulsory, however, to define *either* ``render()`` or ``iter_render()``
+in your subclass. If you omit them both, a ``TypeError`` will be raised when
+the code is imported.
+
 Configuring the template system in standalone mode
 ==================================================
 
@@ -1206,3 +1250,4 @@
 
 .. _settings file: ../settings/#using-settings-without-the-django-settings-module-environment-variable
 .. _settings documentation: ../settings/
+
