Ticket #4747: multidb_6433.diff

File multidb_6433.diff, 116.0 KB (added by Koen Biermans <koen.biermans@…>, 8 years ago)

patch for trunk r6433 (beware for management commands)

Line 
1=== django/test/utils.py
2==================================================================
3--- django/test/utils.py        (/mirror/django/trunk)  (revision 4191)
4
5+++ django/test/utils.py        (/local/django/multidb) (revision 4191)
6
7@@ -1,6 +1,6 @@
8
9 import sys, time
10 from django.conf import settings
11-from django.db import connection, get_creation_module
12+from django.db import connection, connections, get_creation_module
13 from django.core import mail
14 from django.core.management import call_command
15 from django.dispatch import dispatcher
16@@ -106,6 +106,7 @@
17
18     # in-memory database.
19     if settings.DATABASE_ENGINE == "sqlite3":
20         TEST_DATABASE_NAME = ":memory:"
21+##        TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
22     else:
23         suffix = {
24             'postgresql': get_postgresql_create_suffix,
25@@ -148,6 +149,7 @@
26
27 
28     connection.close()
29     settings.DATABASE_NAME = TEST_DATABASE_NAME
30+    settings.OTHER_DATABASES = settings.TEST_OTHER_DATABASES
31 
32     call_command('syncdb', verbosity=verbosity, interactive=False)
33 
34@@ -161,7 +163,8 @@
35
36 
37     return TEST_DATABASE_NAME
38 
39-def destroy_test_db(old_database_name, verbosity=1):
40+def destroy_test_db(old_database_name, old_databases, verbosity=1):
41+
42     # If the database wants to drop the test DB itself, let it
43     creation_module = get_creation_module()
44     if hasattr(creation_module, "destroy_test_db"):
45@@ -175,7 +178,12 @@
46
47     if verbosity >= 1:
48         print "Destroying test database..."
49     connection.close()
50+##    for cnx in connections.keys():
51+##        connections[cnx].close()
52+##    connections.reset()
53     TEST_DATABASE_NAME = settings.DATABASE_NAME
54+    if verbosity >= 2:
55+        print "Closed connections to %s" % TEST_DATABASE_NAME
56     settings.DATABASE_NAME = old_database_name
57 
58     if settings.DATABASE_ENGINE != "sqlite3":
59@@ -184,3 +192,11 @@
60
61         time.sleep(1) # To avoid "database is being accessed by other users" errors.
62         cursor.execute("DROP DATABASE %s" % connection.ops.quote_name(TEST_DATABASE_NAME))
63         connection.close()
64+##    settings.OTHER_DATABASES = old_databases
65+##    for cnx in connections.keys():
66+##        try:
67+##            cursor = connections[cnx].connection.cursor()
68+##        except (KeyboardInterrupt, SystemExit):
69+##            raise
70+##        except:
71+##            pass
72=== django/db/models/base.py
73==================================================================
74--- django/db/models/base.py    (/mirror/django/trunk)  (revision 4191)
75
76+++ django/db/models/base.py    (/local/django/multidb) (revision 4191)
77
78@@ -6,7 +6,7 @@
79
80 from django.db.models.fields.related import OneToOneRel, ManyToOneRel
81 from django.db.models.query import delete_objects
82 from django.db.models.options import Options, AdminOptions
83-from django.db import connection, transaction
84+from django.db import transaction
85 from django.db.models import signals
86 from django.db.models.loading import register_models, get_model
87 from django.dispatch import dispatcher
88@@ -209,6 +209,8 @@
89
90     def save(self, raw=False):
91         dispatcher.send(signal=signals.pre_save, sender=self.__class__, instance=self)
92 
93+        db = self.__class__._default_manager.db
94+        connection = db.connection
95         non_pks = [f for f in self._meta.fields if not f.primary_key]
96         cursor = connection.cursor()
97 
98@@ -227,7 +229,7 @@
99
100                 self._meta.pk.get_db_prep_lookup('exact', pk_val))
101             # If it does already exist, do an UPDATE.
102             if cursor.fetchone():
103-                db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False)) for f in non_pks]
104+                db_values = [f.get_db_prep_save(self, raw and getattr(self, f.attname) or f.pre_save(self, False)) for f in non_pks]
105                 if db_values:
106                     cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
107                         (qn(self._meta.db_table),
108@@ -238,11 +240,11 @@
109
110                 record_exists = False
111         if not pk_set or not record_exists:
112             field_names = [qn(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
113-            db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
114+            db_values = [f.get_db_prep_save(self, raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
115             # If the PK has been manually set, respect that.
116             if pk_set:
117                 field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
118-                db_values += [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
119+                db_values += [f.get_db_prep_save(self, raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
120             placeholders = ['%s'] * len(field_names)
121             if self._meta.order_with_respect_to:
122                 field_names.append(qn('_order'))
123@@ -264,7 +266,7 @@
124
125                      connection.ops.pk_default_value()))
126             if self._meta.has_auto_field and not pk_set:
127                 setattr(self, self._meta.pk.attname, connection.ops.last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
128-        transaction.commit_unless_managed()
129+        transaction.commit_unless_managed([connection])
130 
131         # Run any post-save hooks.
132         dispatcher.send(signal=signals.post_save, sender=self.__class__,
133@@ -336,6 +338,8 @@
134
135         return force_unicode(dict(field.choices).get(value, value), strings_only=True)
136 
137     def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
138+        db = self.__class__._default_manager.db
139+        connection = db.connection
140         qn = connection.ops.quote_name
141         op = is_next and '>' or '<'
142         where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
143@@ -351,6 +355,8 @@
144
145             raise self.DoesNotExist, "%s matching query does not exist." % self.__class__._meta.object_name
146 
147     def _get_next_or_previous_in_order(self, is_next):
148+        db = self.__class__._default_manager.db
149+        connection = db.connection
150         qn = connection.ops.quote_name
151         cachename = "__%s_order_cache" % is_next
152         if not hasattr(self, cachename):
153@@ -441,6 +447,8 @@
154
155 # ORDERING METHODS #########################
156 
157 def method_set_order(ordered_obj, self, id_list):
158+    db = ordered_obj._default_manager.db
159+    connection = db.connection
160     qn = connection.ops.quote_name
161     cursor = connection.cursor()
162     # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
163@@ -450,9 +458,11 @@
164
165         qn(ordered_obj._meta.pk.column))
166     rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name)
167     cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
168-    transaction.commit_unless_managed()
169+    transaction.commit_unless_managed([connection])
170 
171 def method_get_order(ordered_obj, self):
172+    db = ordered_obj._default_manager.db
173+    connection = db.connection
174     qn = connection.ops.quote_name
175     cursor = connection.cursor()
176     # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
177=== django/db/models/manager.py
178==================================================================
179--- django/db/models/manager.py (/mirror/django/trunk)  (revision 4191)
180
181+++ django/db/models/manager.py (/local/django/multidb) (revision 4191)
182
183@@ -1,6 +1,7 @@
184
185-from django.db.models.query import QuerySet, EmptyQuerySet
186+from django.db import ConnectionInfoDescriptor
187+from django.db.models.query import _QuerySet, EmptyQuerySet
188 from django.dispatch import dispatcher
189-from django.db.models import signals
190+from django.db.models import signals, get_apps, get_models
191 from django.db.models.fields import FieldDoesNotExist
192 
193 def ensure_default_manager(sender):
194@@ -13,13 +14,17 @@
195
196         except FieldDoesNotExist:
197             pass
198         cls.add_to_class('objects', Manager())
199+    elif cls._default_manager.model != cls:
200+        # cls is an inherited model; don't want the parent manager
201+        cls.add_to_class('objects', Manager())
202 
203 dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)
204 
205 class Manager(object):
206     # Tracks each time a Manager instance is created. Used to retain order.
207     creation_counter = 0
208-
209+    db = ConnectionInfoDescriptor()
210+   
211     def __init__(self):
212         super(Manager, self).__init__()
213         # Increase the creation counter, and save our local copy.
214@@ -31,9 +36,11 @@
215
216         # TODO: Use weakref because of possible memory leak / circular reference.
217         self.model = model
218         setattr(model, name, ManagerDescriptor(self))
219-        if not hasattr(model, '_default_manager') or self.creation_counter < model._default_manager.creation_counter:
220+        if not hasattr(model, '_default_manager') \
221+            or self.creation_counter < model._default_manager.creation_counter \
222+            or model._default_manager.model != model:
223             model._default_manager = self
224-
225+       
226     #######################
227     # PROXIES TO QUERYSET #
228     #######################
229@@ -45,7 +52,11 @@
230
231         """Returns a new QuerySet object.  Subclasses can override this method
232         to easily customize the behavior of the Manager.
233         """
234-        return QuerySet(self.model)
235+        if self.db.connection.features.uses_custom_queryset:
236+            QS = self.db.connection.ops.query_set_class(_QuerySet)
237+            return QS(self.model)
238+        else:
239+            return _QuerySet(self.model)
240     
241     def none(self):
242         return self.get_empty_query_set()
243=== django/db/models/options.py
244==================================================================
245--- django/db/models/options.py (/mirror/django/trunk)  (revision 4191)
246
247+++ django/db/models/options.py (/local/django/multidb) (revision 4191)
248
249@@ -1,4 +1,5 @@
250
251 from django.conf import settings
252+from django.db import connection_info, connections
253 from django.db.models.related import RelatedObject
254 from django.db.models.fields.related import ManyToManyRel
255 from django.db.models.fields import AutoField, FieldDoesNotExist
256@@ -9,6 +10,7 @@
257
258 from django.utils.encoding import force_unicode, smart_str
259 from bisect import bisect
260 import re
261+import weakref
262 
263 # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
264 get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
265@@ -91,6 +93,9 @@
266
267             self.db_table = "%s_%s" % (self.app_label, self.module_name)
268             self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
269 
270+        # Keep a weakref to my model, for access to managers and such
271+        self._model = weakref.ref(model)
272+
273     def add_field(self, field):
274         # Insert the given field in the order in which it was created, using
275         # the "creation_counter" attribute of the field.
276@@ -130,6 +135,12 @@
277
278                 return f
279         raise FieldDoesNotExist, '%s has no field named %r' % (self.object_name, name)
280 
281+    def get_default_manager(self):
282+        model = self._model()
283+        if model is None:
284+            raise ReferenceError("Model no longer available in %s" % self)
285+        return model._default_manager
286+
287     def get_order_sql(self, table_prefix=''):
288         "Returns the full 'ORDER BY' clause for this object, according to self.ordering."
289         if not self.ordering: return ''
290=== django/db/models/fields/__init__.py
291==================================================================
292--- django/db/models/fields/__init__.py (/mirror/django/trunk)  (revision 4191)
293
294+++ django/db/models/fields/__init__.py (/local/django/multidb) (revision 4191)
295
296@@ -201,7 +201,7 @@
297
298         "Returns field's value just before saving."
299         return getattr(model_instance, self.attname)
300 
301-    def get_db_prep_save(self, value):
302+    def get_db_prep_save(self, model_instance, value):
303         "Returns field's value prepared for saving into a database."
304         return value
305 
306@@ -536,7 +536,7 @@
307
308         else:
309             return self.editable or self.auto_now or self.auto_now_add
310 
311-    def get_db_prep_save(self, value):
312+    def get_db_prep_save(self, model_instance, value):
313         # Casts dates into string format for entry into database.
314         if value is not None:
315             try:
316@@ -545,7 +545,7 @@
317
318                 # If value is already a string it won't have a strftime method,
319                 # so we'll just let it pass through.
320                 pass
321-        return Field.get_db_prep_save(self, value)
322+        return Field.get_db_prep_save(self, model_instance, value)
323 
324     def get_manipulator_field_objs(self):
325         return [oldforms.DateField]
326@@ -578,7 +578,17 @@
327
328                 except ValueError:
329                     raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
330 
331-    def get_db_prep_save(self, value):
332+    def pre_save(self, model_instance, add):
333+        value = super(DateTimeField, self).pre_save(model_instance, add)
334+        if value is not None:
335+            # MySQL will throw a warning if microseconds are given, because it
336+            # doesn't support microseconds.
337+            settings = model_instance._default_manager.db.connection.settings
338+            if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
339+                value = value.replace(microsecond=0)
340+        return value
341+
342+    def get_db_prep_save(self, model_instance, value):
343         # Casts dates into string format for entry into database.
344         if value is not None:
345             # MySQL will throw a warning if microseconds are given, because it
346@@ -586,7 +596,7 @@
347
348             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
349                 value = value.replace(microsecond=0)
350             value = smart_unicode(value)
351-        return Field.get_db_prep_save(self, value)
352+        return Field.get_db_prep_save(self, model_instance, value)
353 
354     def get_db_prep_lookup(self, lookup_type, value):
355         if lookup_type == 'range':
356@@ -660,10 +670,10 @@
357
358 
359         return u"%.*f" % (self.decimal_places, value)
360 
361-    def get_db_prep_save(self, value):
362+    def get_db_prep_save(self, model_instance, value):
363         if value is not None:
364             value = self._format(value)
365-        return super(DecimalField, self).get_db_prep_save(value)
366+        return super(DecimalField, self).get_db_prep_save(model_instance, value)
367 
368     def get_db_prep_lookup(self, lookup_type, value):
369         if lookup_type == 'range':
370@@ -986,11 +996,12 @@
371
372         else:
373             return super(TimeField, self).pre_save(model_instance, add)
374 
375-    def get_db_prep_save(self, value):
376+    def get_db_prep_save(self, model_instance, value):
377         # Casts dates into string format for entry into database.
378         if value is not None:
379             # MySQL will throw a warning if microseconds are given, because it
380             # doesn't support microseconds.
381+            settings = model_instance._default_manager.db.connection.settings
382             if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
383                 value = value.replace(microsecond=0)
384             if settings.DATABASE_ENGINE == 'oracle':
385@@ -1002,7 +1013,7 @@
386
387                     value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
388             else:
389                 value = smart_unicode(value)
390-        return Field.get_db_prep_save(self, value)
391+        return Field.get_db_prep_save(self, model_instance, value)
392 
393     def get_manipulator_field_objs(self):
394         return [oldforms.TimeField]
395=== django/db/models/fields/related.py
396==================================================================
397--- django/db/models/fields/related.py  (/mirror/django/trunk)  (revision 4191)
398
399+++ django/db/models/fields/related.py  (/local/django/multidb) (revision 4191)
400
401@@ -1,4 +1,4 @@
402
403-from django.db import connection, transaction
404+from django.db import transaction
405 from django.db.models import signals, get_model
406 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class
407 from django.db.models.related import RelatedObject
408@@ -319,6 +319,7 @@
409
410             # source_col_name: the PK colname in join_table for the source object
411             # target_col_name: the PK colname in join_table for the target object
412             # *objs - objects to add. Either object instances, or primary keys of object instances.
413+            connection = self.model._default_manager.db.connection
414 
415             # If there aren't any objects, there is nothing to do.
416             if objs:
417@@ -343,12 +344,13 @@
418
419                     cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
420                         (self.join_table, source_col_name, target_col_name),
421                         [self._pk_val, obj_id])
422-                transaction.commit_unless_managed()
423+            transaction.commit_unless_managed(connection)
424 
425         def _remove_items(self, source_col_name, target_col_name, *objs):
426             # source_col_name: the PK colname in join_table for the source object
427             # target_col_name: the PK colname in join_table for the target object
428             # *objs - objects to remove
429+            connection = self.model._default_manager.db.connection
430 
431             # If there aren't any objects, there is nothing to do.
432             if objs:
433@@ -365,15 +367,16 @@
434
435                     (self.join_table, source_col_name,
436                     target_col_name, ",".join(['%s'] * len(old_ids))),
437                     [self._pk_val] + list(old_ids))
438-                transaction.commit_unless_managed()
439+            transaction.commit_unless_managed(connection)
440 
441         def _clear_items(self, source_col_name):
442             # source_col_name: the PK colname in join_table for the source object
443+            connection = self.model._default_manager.db.connection
444             cursor = connection.cursor()
445             cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
446                 (self.join_table, source_col_name),
447                 [self._pk_val])
448-            transaction.commit_unless_managed()
449+            transaction.commit_unless_managed(connection)
450 
451     return ManyRelatedManager
452 
453@@ -397,7 +400,7 @@
454
455         superclass = rel_model._default_manager.__class__
456         RelatedManager = create_many_related_manager(superclass)
457 
458-        qn = connection.ops.quote_name
459+        qn = rel_model._default_manager.db.connection.ops.quote_name
460         manager = RelatedManager(
461             model=rel_model,
462             core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
463@@ -438,7 +441,7 @@
464
465         superclass = rel_model._default_manager.__class__
466         RelatedManager = create_many_related_manager(superclass)
467 
468-        qn = connection.ops.quote_name
469+        qn = rel_model._default_manager.db.connection.ops.quote_name
470         manager = RelatedManager(
471             model=rel_model,
472             core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
473@@ -519,11 +522,11 @@
474
475         else:
476             return [oldforms.IntegerField]
477 
478-    def get_db_prep_save(self, value):
479+    def get_db_prep_save(self, model_instance, value):
480         if value == '' or value == None:
481             return None
482         else:
483-            return self.rel.get_related_field().get_db_prep_save(value)
484+            return self.rel.get_related_field().get_db_prep_save(model_instance, value)
485 
486     def flatten_data(self, follow, obj=None):
487         if not obj:
488=== django/db/models/query.py
489==================================================================
490--- django/db/models/query.py   (/mirror/django/trunk)  (revision 4191)
491
492+++ django/db/models/query.py   (/local/django/multidb) (revision 4191)
493
494@@ -77,11 +77,11 @@
495
496             output.append('%s%s ASC' % (prefix, qn(orderfield2column(f, opts))))
497     return ', '.join(output)
498 
499-def quote_only_if_word(word):
500+def quote_only_if_word(word, qn):
501     if re.search('\W', word): # Don't quote if there are spaces or non-word chars.
502         return word
503     else:
504-        return connection.ops.quote_name(word)
505+        return qn(word)
506 
507 class _QuerySet(object):
508     "Represents a lazy database lookup for a set of objects"
509@@ -184,8 +184,7 @@
510
511         # self._select is a dictionary, and dictionaries' key order is
512         # undefined, so we convert it to a list of tuples.
513         extra_select = self._select.items()
514-
515-        cursor = connection.cursor()
516+        cursor = self.model._default_manager.db.connection.cursor()
517         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
518 
519         fill_cache = self._select_related
520@@ -219,7 +218,9 @@
521
522         """
523         if self._result_cache is not None:
524             return len(self._result_cache)
525-
526+        #from multi-db
527+        connection = self.model._default_manager.db.connection
528+       
529         counter = self._clone()
530         counter._order_by = ()
531         counter._select_related = False
532@@ -306,6 +307,7 @@
533
534         Returns a dictionary mapping each of the given IDs to the object with
535         that ID.
536         """
537+        connection = self.model._default_manager.db.connection
538         assert self._limit is None and self._offset is None, \
539                 "Cannot use 'limit' or 'offset' with in_bulk"
540         assert isinstance(id_list, (tuple,  list)), "in_bulk() must be provided with a list of IDs."
541@@ -483,12 +485,12 @@
542
543         return self._result_cache
544 
545     def _get_sql_clause(self):
546+        connection = self.model._default_manager.db.connection
547         qn = connection.ops.quote_name
548         opts = self.model._meta
549-
550         # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
551         select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
552-        tables = [quote_only_if_word(t) for t in self._tables]
553+        tables = [quote_only_if_word(t, qn) for t in self._tables]
554         joins = SortedDict()
555         where = self._where[:]
556         params = self._params[:]
557@@ -508,7 +510,7 @@
558
559 
560         # Add any additional SELECTs.
561         if self._select:
562-            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()])
563+            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1], qn), qn(s[0])) for s in self._select.items()])
564 
565         # Start composing the body of the SQL statement.
566         sql = [" FROM", qn(opts.db_table)]
567@@ -565,6 +567,7 @@
568
569         return select, " ".join(sql), params
570 
571 # Use the backend's QuerySet class if it defines one. Otherwise, use _QuerySet.
572+# This is now in manager.py
573 if connection.features.uses_custom_queryset:
574     QuerySet = connection.ops.query_set_class(_QuerySet)
575 else:
576@@ -582,6 +585,9 @@
577
578         except EmptyResultSet:
579             raise StopIteration
580 
581+        #from multi-db
582+        db = self.model._default_manager.db
583+        connection = db.connection
584         qn = connection.ops.quote_name
585 
586         # self._select is a dictionary, and dictionaries' key order is
587@@ -612,7 +618,7 @@
588
589         columns = [f.column for f in fields]
590         select = ['%s.%s' % (qn(self.model._meta.db_table), qn(c)) for c in columns]
591         if extra_select:
592-            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in extra_select])
593+            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1], qn), qn(s[0])) for s in extra_select])
594             field_names.extend([f[0] for f in extra_select])
595 
596         cursor = connection.cursor()
597@@ -638,6 +644,8 @@
598
599         from django.db.backends.util import typecast_timestamp
600         from django.db.models.fields import DateTimeField
601 
602+        db = self.model._default_manager.db
603+        connection = db.connection
604         qn = connection.ops.quote_name
605         self._order_by = () # Clear this because it'll mess things up otherwise.
606         if self._field.null:
607@@ -783,7 +791,8 @@
608
609             return SortedDict(), [], []
610         return joins, where2, params
611 
612-def get_where_clause(lookup_type, table_prefix, field_name, value, db_type):
613+def get_where_clause(opts, lookup_type, table_prefix, field_name, value, db_type):
614+    connection = opts.get_default_manager().db.connection
615     if table_prefix.endswith('.'):
616         table_prefix = connection.ops.quote_name(table_prefix[:-1])+'.'
617     field_name = connection.ops.quote_name(field_name)
618@@ -850,6 +859,7 @@
619
620     Helper function that recursively populates the select, tables and where (in
621     place) for select_related queries.
622     """
623+    connection = opts.get_default_manager().db.connection
624 
625     # If we've got a max_depth set and we've exceeded that depth, bail now.
626     if max_depth and cur_depth > max_depth:
627@@ -953,13 +963,20 @@
628
629     return choices
630 
631 def lookup_inner(path, lookup_type, value, opts, table, column):
632-    qn = connection.ops.quote_name
633     joins, where, params = SortedDict(), [], []
634     current_opts = opts
635     current_table = table
636     current_column = column
637     intermediate_table = None
638     join_required = False
639+    db = current_opts.get_default_manager().db
640+    backend = db.backend
641+    connection = db.connection
642+    qn = connection.ops.quote_name
643+    if hasattr(connection.ops, 'alias'):
644+        al = connection.ops.alias
645+    else:
646+        al = lambda x,y: "%s__%s" % (x,y)
647 
648     name = path.pop(0)
649     # Has the primary key been requested? If so, expand it out
650@@ -972,7 +989,7 @@
651
652         # Does the name belong to a defined many-to-many field?
653         field = find_field(name, current_opts.many_to_many, False)
654         if field:
655-            new_table = current_table + '__' + name
656+            new_table = al(current_table, name)
657             new_opts = field.rel.to._meta
658             new_column = new_opts.pk.column
659 
660@@ -989,7 +1006,7 @@
661
662         # Does the name belong to a reverse defined many-to-many field?
663         field = find_field(name, current_opts.get_all_related_many_to_many_objects(), True)
664         if field:
665-            new_table = current_table + '__' + name
666+            new_table = al(current_table, name)
667             new_opts = field.opts
668             new_column = new_opts.pk.column
669 
670@@ -1006,7 +1023,7 @@
671
672         # Does the name belong to a one-to-many field?
673         field = find_field(name, current_opts.get_all_related_objects(), True)
674         if field:
675-            new_table = table + '__' + name
676+            new_table = al(current_table, name)
677             new_opts = field.opts
678             new_column = field.field.column
679             join_column = opts.pk.column
680@@ -1020,7 +1037,7 @@
681
682         field = find_field(name, current_opts.fields, False)
683         if field:
684             if field.rel: # One-to-One/Many-to-one field
685-                new_table = current_table + '__' + name
686+                new_table = al(current_table, name)
687                 new_opts = field.rel.to._meta
688                 new_column = new_opts.pk.column
689                 join_column = field.column
690@@ -1112,20 +1129,23 @@
691
692             column = field.column
693             db_type = field.db_type()
694 
695-        where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type))
696+        where.append(get_where_clause(current_opts, lookup_type, current_table + '.', column, value, db_type))
697         params.extend(field.get_db_prep_lookup(lookup_type, value))
698 
699     return joins, where, params
700 
701 def delete_objects(seen_objs):
702     "Iterate through a list of seen classes, and remove any instances that are referred to"
703-    qn = connection.ops.quote_name
704     ordered_classes = seen_objs.keys()
705     ordered_classes.reverse()
706 
707-    cursor = connection.cursor()
708-
709     for cls in ordered_classes:
710+        db = cls._default_manager.db
711+        backend = db.backend
712+        connection = db.connection
713+        cursor = connection.cursor()
714+       qn = connection.ops.quote_name
715+       
716         seen_objs[cls] = seen_objs[cls].items()
717         seen_objs[cls].sort()
718 
719@@ -1164,7 +1184,17 @@
720
721                         pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
722 
723     # Now delete the actual data
724+    dirty_conns = []
725     for cls in ordered_classes:
726+
727+        db = cls._default_manager.db
728+        backend = db.backend
729+        connection = db.connection
730+       qn = connection.ops.quote_name
731+        cursor = connection.cursor()
732+        if connection not in dirty_conns:
733+            dirty_conns.append(connection)
734+           
735         seen_objs[cls].reverse()
736         pk_list = [pk for pk,instance in seen_objs[cls]]
737         for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
738@@ -1183,4 +1213,4 @@
739
740             dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
741             setattr(instance, cls._meta.pk.attname, None)
742 
743-    transaction.commit_unless_managed()
744+    transaction.commit_unless_managed(dirty_conns)
745=== django/db/__init__.py
746==================================================================
747--- django/db/__init__.py       (/mirror/django/trunk)  (revision 4191)
748
749+++ django/db/__init__.py       (/local/django/multidb) (revision 4191)
750
751@@ -1,70 +1,380 @@
752
753 import os
754-from django.conf import settings
755+from django.conf import settings, UserSettingsHolder
756 from django.core import signals
757 from django.core.exceptions import ImproperlyConfigured
758 from django.dispatch import dispatcher
759 from django.utils.functional import curry
760 
761+try:
762+    # Only exists in Python 2.4+
763+    from threading import local
764+except ImportError:
765+    # Import copy of _thread_local.py from Python 2.4
766+    from django.utils._threading_local import local
767+
768 __all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
769 
770+
771+# singleton to represent the default connection in connections
772+class dummy(object):
773+    def __repr__(self):
774+        return self.__str__()   
775+    def __str__(self):
776+        return '<default>'
777+_default = dummy()
778+del dummy
779+
780+
781+# storage for local default connection
782+_local = local()
783+
784 if not settings.DATABASE_ENGINE:
785     settings.DATABASE_ENGINE = 'dummy'
786+   
787 
788-try:
789-    # Most of the time, the database backend will be one of the official
790-    # backends that ships with Django, so look there first.
791-    _import_path = 'django.db.backends.'
792-    backend = __import__('%s%s.base' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
793-except ImportError, e:
794-    # If the import failed, we might be looking for a database backend
795-    # distributed external to Django. So we'll try that next.
796-    try:
797-        _import_path = ''
798-        backend = __import__('%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
799-    except ImportError, e_user:
800-        # The database backend wasn't found. Display a helpful error message
801-        # listing all possible (built-in) database backends.
802-        backend_dir = os.path.join(__path__[0], 'backends')
803-        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')]
804-        available_backends.sort()
805-        if settings.DATABASE_ENGINE not in available_backends:
806-            raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
807-                (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
808-        else:
809-            raise # If there's some other error, this must be an error in Django itself.
810+def connect(settings, **kw):
811+    """Connect to the database specified in settings. Returns a
812+    ConnectionInfo on success, raises ImproperlyConfigured if the
813+    settings don't specify a valid database connection.
814+    """
815+    return ConnectionInfo(settings, **kw)
816 
817-def _import_database_module(import_path='', module_name=''):
818-    """Lazyily import a database module when requested."""
819-    return __import__('%s%s.%s' % (_import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
820+   
821+class ConnectionInfo(object):
822+    """Encapsulates all information about a connection and the backend
823+    to which it belongs. Provides methods for loading backend
824+    creation, introspection, and shell modules, closing the
825+    connection, and resetting the connection's query log.
826+    """
827+    def __init__(self, settings=None, **kw):
828+        super(ConnectionInfo, self).__init__(**kw)
829+        if settings is None:
830+            from django.conf import settings
831+        if not settings.DATABASE_OPTIONS:
832+            settings.DATABASE_OPTIONS = {}
833+        self.settings = settings
834+        self.backend = self.load_backend()
835+        self.connection = self.backend.DatabaseWrapper(settings)
836+        self.DatabaseError = self.backend.DatabaseError
837 
838-# We don't want to import the introspect/creation modules unless
839-# someone asks for 'em, so lazily load them on demmand.
840-get_introspection_module = curry(_import_database_module, _import_path, 'introspection')
841-get_creation_module = curry(_import_database_module, _import_path, 'creation')
842+        # Register an event that closes the database connection
843+        # when a Django request is finished.
844+        dispatcher.connect(self.close, signal=signals.request_finished)
845+   
846+        # Register an event that resets connection.queries
847+        # when a Django request is started.
848+        dispatcher.connect(self.reset_queries, signal=signals.request_started)
849 
850-# We want runshell() to work the same way, but we have to treat it a
851-# little differently (since it just runs instead of returning a module like
852-# the above) and wrap the lazily-loaded runshell() method.
853-runshell = lambda: _import_database_module(_import_path, "client").runshell()
854+    def __repr__(self):
855+        return "Connection: %r (ENGINE=%s NAME=%s)" \
856+               % (self.connection,
857+                  self.settings.DATABASE_ENGINE,
858+                  self.settings.DATABASE_NAME)
859 
860-# Convenient aliases for backend bits.
861-connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
862-DatabaseError = backend.DatabaseError
863+    def close(self):
864+        """Close connection"""
865+        self.connection.close()
866+
867+    # We don't want to import the introspect/creation modules unless
868+    # someone asks for 'em, so lazily load them on demmand.
869+    def get_introspection_module(self):
870+        return __import__('%s%s.%s' % (self._import_path, self.settings.DATABASE_ENGINE, 'introspection'), {}, {}, [''])
871+   
872+    def get_creation_module(self):
873+        return __import__('%s%s.%s' % (self._import_path, self.settings.DATABASE_ENGINE, 'creation'), {}, {}, [''])
874+
875+    def load_backend(self):
876+        try:
877+            # Most of the time, the database backend will be one of the official
878+            # backends that ships with Django, so look there first.
879+            self._import_path = 'django.db.backends.'
880+            backend = __import__('%s%s.base' % (self._import_path,
881+                            self.settings.DATABASE_ENGINE), {}, {}, [''])
882+        except ImportError, e:
883+            # If the import failed, we might be looking for a database backend
884+            # distributed external to Django. So we'll try that next.
885+            try:
886+                self._import_path = ''
887+                backend = __import__('%s.base' % self.settings.DATABASE_ENGINE, {}, {}, [''])
888+            except ImportError, e_user:
889+                # The database backend wasn't found. Display a helpful error message
890+                # listing all possible (built-in) database backends.
891+                backend_dir = os.path.join(__path__[0], 'backends')
892+                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')]
893+                available_backends.sort()
894+                if self.settings.DATABASE_ENGINE not in available_backends:
895+                    raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
896+                        (self.settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
897+                else:
898+                    raise # If there's some other error, this must be an error in Django itself.
899+        return backend
900+
901+    # We want runshell() to work the same way, but we have to treat it a
902+    # little differently (since it just runs instead of returning a module like
903+    # the above) and wrap the lazily-loaded runshell() method.
904+    def runshell(self):
905+        return lambda: __import__('%s%s.%s' % (self._import_path, self.settings.DATABASE_ENGINE, 'client'), {}, {}, ['']).runshell()
906+
907+    def reset_queries(self):
908+        """Reset log of queries executed by connection"""
909+        self.connection.queries = []
910+
911+class LazyConnectionManager(object):
912+    """Manages named connections lazily, instantiating them as
913+    they are requested.
914+    """
915+    def __init__(self):
916+        self.local = local()
917+        self.local.connections = {}
918+
919+        # Reset connections on request finish, to make sure each request can
920+        # load the correct connections for its settings
921+        dispatcher.connect(self.reset, signal=signals.request_finished)
922+       
923+    def __iter__(self):
924+        # Iterates only over *active* connections, not all possible
925+        # connections
926+        return iter(self.local.connections.keys())
927+
928+    def __getattr__(self, attr):
929+        return getattr(self.local.connections, attr)
930+
931+    def __getitem__(self, k):
932+        try:
933+            return self.local.connections[k]
934+        except (AttributeError, KeyError):
935+            return self.connect(k)
936+
937+    def __setitem__(self, k, v):
938+        try:
939+            self.local.connections[k] = v
940+        except AttributeError:
941+            # First access in thread
942+            self.local.connections = {k: v}
943+           
944+    def connect(self, name):
945+        """Return the connection with this name in
946+        settings.OTHER_DATABASES. Creates the connection if it doesn't yet
947+        exist. Reconnects if it does. If the name requested is the default
948+        connection (a singleton defined in django.db), then the default
949+        connection is returned.       
950+        """
951+        try:
952+            cnx = self.local.connections
953+        except AttributeError:
954+            cnx = self.local.connections = {}
955+        if name in cnx:
956+            cnx[name].close()
957+        if name is _default:
958+            cnx[name] = connect(settings)
959+            return cnx[name]
960+        try:
961+            info = settings.OTHER_DATABASES[name]
962+        except KeyError:
963+            raise ImproperlyConfigured, \
964+                  "No database connection '%s' has been configured" % name
965+        except AttributeError:
966+            raise ImproperlyConfigured, \
967+                  "No OTHER_DATABASES in settings."
968+
969+        # In settings it's a dict, but connect() needs an object:
970+        # pass global settings so that the default connection settings
971+        # can be defaults for the named connections.
972+        database = UserSettingsHolder(settings)
973+        for k, v in info.items():
974+            setattr(database, k, v)
975+        cnx[name] = connect(database)
976+        return cnx[name]
977+
978+    def items(self):
979+        # Iterates over *all possible* connections
980+        items = []
981+        for key in self.keys():
982+            items.append((key, self[key]))
983+        return items
984+   
985+    def keys(self):
986+        # Iterates over *all possible* connections
987+        keys = [_default]
988+        try:
989+            keys.extend(settings.OTHER_DATABASES.keys())
990+        except AttributeError:
991+            pass
992+        return keys
993+   
994+    def reset(self):
995+        if not hasattr(self.local, 'connections'):
996+            return
997+        self.local.connections = {}
998+       
999+def model_connection_name(klass):
1000+    """Get the connection name that a model is configured to use, with the
1001+    current settings.
1002+    """
1003+    app = klass._meta.app_label
1004+    model = klass.__name__
1005+    app_model = "%s.%s" % (app, model)
1006+
1007+    # Quick exit if no OTHER_DATABASES defined
1008+    if (not hasattr(settings, 'OTHER_DATABASES')
1009+        or not settings.OTHER_DATABASES):
1010+        return _default
1011+    # Look in MODELS for the best match: app_label.Model. If that isn't
1012+    # found, take just app_label. If nothing is found, use the default
1013+    maybe = None
1014+    for name, db_def in settings.OTHER_DATABASES.items():
1015+        if not 'MODELS' in db_def:
1016+            continue
1017+        mods = db_def['MODELS']
1018+        # Can't get a better match than this
1019+        if app_model in mods:
1020+            return name
1021+        elif app in mods:
1022+            if maybe is not None:
1023+                raise ImproperlyConfigured, \
1024+                    "App %s appears in more than one OTHER_DATABASES " \
1025+                    "setting (%s and %s)" % (maybe, name)
1026+            maybe = name
1027+    if maybe:
1028+        return maybe
1029+    # No named connection for this model; use the default
1030+    return _default
1031+
1032+
1033+class ConnectionInfoDescriptor(object):
1034+    """Descriptor used to access database connection information from a
1035+    manager or other connection holder. Keeps a thread-local cache of
1036+    connections per instance, and always returns the same connection for an
1037+    instance in particular thread during a particular request.
1038+
1039+    Any object that includes a ``model`` attribute that holds a model class
1040+    can use this descriptor to manage connections.
1041+    """
1042+   
1043+    def __init__(self):
1044+        self.local = local()
1045+        self.local.cnx = {}
1046+        dispatcher.connect(self.reset, signal=signals.request_finished)
1047+       
1048+    def __get__(self, instance, type=None):
1049+        if instance is None:
1050+            raise AttributeError, \
1051+                "ConnectionInfo is accessible only through an instance"
1052+        try:
1053+            instance_connection = self.local.cnx.get(instance, None)
1054+        except AttributeError:
1055+            # First access in this thread
1056+            self.local.cnx = {}
1057+            instance_connection = None
1058+        if instance_connection is None:
1059+            instance_connection = self.get_connection(instance)
1060+            self.local.cnx[instance] = instance_connection
1061+        return instance_connection
1062+
1063+    def __set__(self, instance, value):
1064+        try:
1065+            self.local.cnx[instance] = value
1066+        except AttributeError:
1067+            # First access in thread
1068+            self.local.cnx = {instance: value}
1069+
1070+    def __delete__(self, instance):
1071+        try:
1072+            del self.local.cnx[instance]
1073+        except (AttributeError, KeyError):
1074+            # Not stored, no need to reset
1075+            pass
1076+
1077+    def get_connection(self, instance):
1078+        return connections[model_connection_name(instance.model)]
1079+
1080+    def reset(self):
1081+        if not hasattr(self.local, 'cnx'):
1082+            return
1083+        self.local.cnx = {}
1084+
1085+class LocalizingProxy:
1086+    """A lazy-initializing proxy. The proxied object is not
1087+    initialized until the first attempt to access it. This is used to
1088+    attach module-level properties to local storage.
1089+    """
1090+    def __init__(self, name, storage, func, *arg, **kw):
1091+        self.__name = name
1092+        self.__storage = storage
1093+        self.__func = func
1094+        self.__arg = arg
1095+        self.__kw = kw
1096+
1097+        # We need to clear out this thread's storage at the end of each
1098+        # request, in case new settings are loaded with the next
1099+        def reset(stor=storage, name=name):
1100+            if hasattr(stor, name):
1101+                delattr(stor, name)
1102+        dispatcher.connect(reset, signal=signals.request_finished)
1103+       
1104+    def __getattr__(self, attr):
1105+        # Private (__*) attributes are munged
1106+        if attr.startswith('_LocalizingProxy'):
1107+            return self.__dict__[attr]
1108+        try:
1109+            return getattr(getattr(self.__storage, self.__name), attr)
1110+        except AttributeError:
1111+            setattr(self.__storage, self.__name, self.__func(*self.__arg,
1112+                                                             **self.__kw))
1113+            return getattr(getattr(self.__storage, self.__name), attr)
1114+
1115+    def __setattr__(self, attr, val):
1116+        # Private (__*) attributes are munged
1117+        if attr.startswith('_LocalizingProxy'):
1118+            self.__dict__[attr] = val
1119+            return
1120+        try:
1121+            stor = getattr(self.__storage, self.__name)           
1122+        except AttributeError:
1123+            stor =  self.__func(*self.__arg)
1124+            setattr(self.__storage, self.__name, stor)
1125+        setattr(stor, attr, val)
1126+
1127+
1128+# Create a manager for named connections
1129+connections = LazyConnectionManager()
1130+
1131+# Backwards compatibility: establish the default connection and set the
1132+# default connection properties at module level, using the lazy proxy so that
1133+# each thread may have a different default connection, if so configured
1134+connection_info = LocalizingProxy('connection_info', _local,
1135+                                  lambda: connections[_default])
1136+connection = LocalizingProxy('connection', _local,
1137+                             lambda: connections[_default].connection)
1138+backend = LocalizingProxy('backend', _local,
1139+                          lambda: connections[_default].backend)
1140+DatabaseError = LocalizingProxy('DatabaseError', _local,
1141+                                lambda: connections[_default].DatabaseError)
1142+#================================BUG==Might need LocalizingProxy==============
1143 IntegrityError = backend.IntegrityError
1144+get_introspection_module = LocalizingProxy(
1145+    'get_introspection_module', _local,
1146+    lambda: connections[_default].get_introspection_module)
1147+get_creation_module = LocalizingProxy(
1148+    'get_creation_module', _local,
1149+    lambda: connections[_default].get_creation_module)
1150+runshell = LocalizingProxy('runshell', _local,
1151+                           lambda: connections[_default].runshell)
1152 
1153-# Register an event that closes the database connection
1154-# when a Django request is finished.
1155-dispatcher.connect(connection.close, signal=signals.request_finished)
1156 
1157-# Register an event that resets connection.queries
1158-# when a Django request is started.
1159-def reset_queries():
1160-    connection.queries = []
1161-dispatcher.connect(reset_queries, signal=signals.request_started)
1162-
1163-# Register an event that rolls back the connection
1164+# Register an event that rolls back all connections
1165 # when a Django request has an exception.
1166 def _rollback_on_exception():
1167     from django.db import transaction
1168     transaction.rollback_unless_managed()
1169-dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
1170+dispatcher.connect(_rollback_on_exception,
1171+                   signal=signals.got_request_exception)
1172+
1173+def reset_queries():
1174+    connections[_default].reset_queries()
1175+    for c in connections:
1176+        try:
1177+            c.reset_queries()
1178+        except:
1179+            pass
1180+
1181=== django/db/backends/ado_mssql/client.py
1182==================================================================
1183--- django/db/backends/ado_mssql/client.py      (/mirror/django/trunk)  (revision 4191)
1184
1185+++ django/db/backends/ado_mssql/client.py      (/local/django/multidb) (revision 4191)
1186
1187@@ -1,2 +1,2 @@
1188
1189-def runshell():
1190+def runshell(settings):
1191     raise NotImplementedError
1192=== django/db/backends/mysql_old/base.py
1193==================================================================
1194--- django/db/backends/mysql_old/base.py        (/mirror/django/trunk)  (revision 4191)
1195
1196+++ django/db/backends/mysql_old/base.py        (/local/django/multidb) (revision 4191)
1197
1198@@ -148,8 +148,8 @@
1199
1200         'iendswith': 'LIKE %s',
1201     }
1202 
1203-    def __init__(self, **kwargs):
1204-        super(DatabaseWrapper, self).__init__(**kwargs)
1205+    def __init__(self, settings):
1206+        super(DatabaseWrapper, self).__init__(settings)
1207         self.server_version = None
1208 
1209     def _valid_connection(self):
1210=== django/db/backends/postgresql/client.py
1211==================================================================
1212--- django/db/backends/postgresql/client.py     (/mirror/django/trunk)  (revision 4191)
1213
1214+++ django/db/backends/postgresql/client.py     (/local/django/multidb) (revision 4191)
1215
1216@@ -1,7 +1,6 @@
1217
1218-from django.conf import settings
1219 import os
1220 
1221-def runshell():
1222+def runshell(settings):
1223     args = ['psql']
1224     if settings.DATABASE_USER:
1225         args += ["-U", settings.DATABASE_USER]
1226=== django/db/backends/sqlite3/base.py
1227==================================================================
1228--- django/db/backends/sqlite3/base.py  (/mirror/django/trunk)  (revision 4191)
1229
1230+++ django/db/backends/sqlite3/base.py  (/local/django/multidb) (revision 4191)
1231
1232@@ -115,7 +115,7 @@
1233
1234         return self.connection.cursor(factory=SQLiteCursorWrapper)
1235 
1236     def close(self):
1237-        from django.conf import settings
1238+        settings = self.settings
1239         # If database is in memory, closing the connection destroys the
1240         # database. To prevent accidental data loss, ignore close requests on
1241         # an in-memory db.
1242=== django/db/backends/sqlite3/client.py
1243==================================================================
1244--- django/db/backends/sqlite3/client.py        (/mirror/django/trunk)  (revision 4191)
1245
1246+++ django/db/backends/sqlite3/client.py        (/local/django/multidb) (revision 4191)
1247
1248@@ -1,6 +1,5 @@
1249
1250-from django.conf import settings
1251 import os
1252 
1253-def runshell():
1254+def runshell(settings):
1255     args = ['', settings.DATABASE_NAME]
1256     os.execvp('sqlite3', args)
1257=== django/db/backends/mysql/base.py
1258==================================================================
1259--- django/db/backends/mysql/base.py    (/mirror/django/trunk)  (revision 4191)
1260
1261+++ django/db/backends/mysql/base.py    (/local/django/multidb) (revision 4191)
1262
1263@@ -144,8 +144,8 @@
1264
1265         'iendswith': 'LIKE %s',
1266     }
1267 
1268-    def __init__(self, **kwargs):
1269-        super(DatabaseWrapper, self).__init__(**kwargs)
1270+    def __init__(self, settings):
1271+        super(DatabaseWrapper, self).__init__(settings)
1272         self.server_version = None
1273 
1274     def _valid_connection(self):
1275=== django/db/backends/mysql/client.py
1276==================================================================
1277--- django/db/backends/mysql/client.py  (/mirror/django/trunk)  (revision 4191)
1278
1279+++ django/db/backends/mysql/client.py  (/local/django/multidb) (revision 4191)
1280
1281@@ -1,7 +1,6 @@
1282
1283-from django.conf import settings
1284 import os
1285 
1286-def runshell():
1287+def runshell(settings):
1288     args = ['']
1289     db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
1290     user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
1291=== django/db/backends/oracle/base.py
1292==================================================================
1293--- django/db/backends/oracle/base.py   (/mirror/django/trunk)  (revision 4191)
1294
1295+++ django/db/backends/oracle/base.py   (/local/django/multidb) (revision 4191)
1296
1297@@ -412,6 +412,7 @@
1298
1299         return self.connection is not None
1300 
1301     def _cursor(self, settings):
1302+        settings = self.settings
1303         if not self._valid_connection():
1304             if len(settings.DATABASE_HOST.strip()) == 0:
1305                 settings.DATABASE_HOST = 'localhost'
1306=== django/db/backends/oracle/client.py
1307==================================================================
1308--- django/db/backends/oracle/client.py (/mirror/django/trunk)  (revision 4191)
1309
1310+++ django/db/backends/oracle/client.py (/local/django/multidb) (revision 4191)
1311
1312@@ -1,7 +1,6 @@
1313
1314-from django.conf import settings
1315 import os
1316 
1317-def runshell():
1318+def runshell(settings):
1319     dsn = settings.DATABASE_USER
1320     if settings.DATABASE_PASSWORD:
1321         dsn += "/%s" % settings.DATABASE_PASSWORD
1322=== django/db/backends/__init__.py
1323==================================================================
1324--- django/db/backends/__init__.py      (/mirror/django/trunk)  (revision 4191)
1325
1326+++ django/db/backends/__init__.py      (/local/django/multidb) (revision 4191)
1327
1328@@ -5,15 +5,16 @@
1329
1330     # Import copy of _thread_local.py from Python 2.4
1331     from django.utils._threading_local import local
1332 
1333-class BaseDatabaseWrapper(local):
1334+class BaseDatabaseWrapper(object):
1335     """
1336     Represents a database connection.
1337     """
1338     ops = None
1339-    def __init__(self, **kwargs):
1340+    def __init__(self, settings):
1341+        self.settings = settings
1342+        self.options = settings.DATABASE_OPTIONS
1343         self.connection = None
1344         self.queries = []
1345-        self.options = kwargs
1346 
1347     def _commit(self):
1348         if self.connection is not None:
1349@@ -29,7 +30,7 @@
1350
1351             self.connection = None
1352 
1353     def cursor(self):
1354-        from django.conf import settings
1355+        settings = self.settings
1356         cursor = self._cursor(settings)
1357         if settings.DEBUG:
1358             return self.make_debug_cursor(cursor)
1359=== django/db/backends/dummy/base.py
1360==================================================================
1361--- django/db/backends/dummy/base.py    (/mirror/django/trunk)  (revision 4191)
1362
1363+++ django/db/backends/dummy/base.py    (/local/django/multidb) (revision 4191)
1364
1365@@ -33,8 +33,5 @@
1366
1367     _commit = complain
1368     _rollback = ignore
1369 
1370-    def __init__(self, **kwargs):
1371-        pass
1372-
1373     def close(self):
1374         pass
1375=== django/db/transaction.py
1376==================================================================
1377--- django/db/transaction.py    (/mirror/django/trunk)  (revision 4191)
1378
1379+++ django/db/transaction.py    (/local/django/multidb) (revision 4191)
1380
1381@@ -16,7 +16,6 @@
1382
1383     import thread
1384 except ImportError:
1385     import dummy_thread as thread
1386-from django.db import connection
1387 from django.conf import settings
1388 
1389 class TransactionManagementError(Exception):
1390@@ -116,48 +115,67 @@
1391
1392     Puts the transaction manager into a manual state: managed transactions have
1393     to be committed explicitly by the user. If you switch off transaction
1394     management and there is a pending commit/rollback, the data will be
1395-    commited.
1396+    commited. Note that managed state applies across all connections.
1397     """
1398     thread_ident = thread.get_ident()
1399     top = state.get(thread_ident, None)
1400     if top:
1401         top[-1] = flag
1402         if not flag and is_dirty():
1403-            connection._commit()
1404+            for cx in all_connections():
1405+                cx._commit()
1406             set_clean()
1407     else:
1408         raise TransactionManagementError("This code isn't under transaction management")
1409 
1410-def commit_unless_managed():
1411+def commit_unless_managed(connections=None):
1412     """
1413     Commits changes if the system is not in managed transaction mode.
1414     """
1415     if not is_managed():
1416-        connection._commit()
1417+        if connections is None:
1418+            connections = all_connections()
1419+        else:
1420+            connections = ensure_connections(connections)
1421+        for cx in connections:
1422+            cx._commit()
1423     else:
1424         set_dirty()
1425 
1426-def rollback_unless_managed():
1427+def rollback_unless_managed(connections=None):
1428     """
1429     Rolls back changes if the system is not in managed transaction mode.
1430     """
1431     if not is_managed():
1432-        connection._rollback()
1433+        if connections is None:
1434+            connections = all_connections()
1435+        for cx in connections:
1436+            cx._rollback()
1437     else:
1438         set_dirty()
1439 
1440-def commit():
1441+def commit(connections=None):
1442     """
1443     Does the commit itself and resets the dirty flag.
1444     """
1445-    connection._commit()
1446+    if connections is None:
1447+        connections = all_connections()
1448+    else:
1449+        connections = ensure_connections(connections)
1450+    for cx in connections:
1451+        cx._commit()
1452     set_clean()
1453 
1454-def rollback():
1455+def rollback(connections=None):
1456     """
1457     This function does the rollback itself and resets the dirty flag.
1458     """
1459-    connection._rollback()
1460+    if connections is None:
1461+        connections = all_connections()
1462+    else:
1463+        connections = ensure_connections(connections)
1464+    for cx in connections:
1465+        cx._rollback()
1466     set_clean()
1467 
1468 ##############
1469@@ -179,7 +197,7 @@
1470
1471             leave_transaction_management()
1472     return _autocommit
1473 
1474-def commit_on_success(func):
1475+def commit_on_success(func, connections=None):
1476     """
1477     This decorator activates commit on response. This way, if the view function
1478     runs successfully, a commit is made; if the viewfunc produces an exception,
1479@@ -198,7 +216,7 @@
1480
1481                 raise
1482             else:
1483                 if is_dirty():
1484-                    commit()
1485+                    commit(connections)
1486             return res
1487         finally:
1488             leave_transaction_management()
1489@@ -220,3 +238,31 @@
1490
1491             leave_transaction_management()
1492 
1493     return _commit_manually
1494+
1495+###########
1496+# HELPERS #
1497+###########
1498+
1499+def all_connections():
1500+    from django.db import connection, connections
1501+    return [connection] + [ c.connection
1502+                               for c in connections.values() ]
1503+
1504+def ensure_connections(val):
1505+    from django.db import connections
1506+    conns = []
1507+    if isinstance(val, basestring):
1508+        val = [val]
1509+    try:
1510+        iter(val)
1511+    except:
1512+        val = [val]
1513+    for cx in val:
1514+        if hasattr(cx, 'cursor'):
1515+            conns.append(cx)
1516+        elif hasattr(cx, 'connection'):
1517+            conns.append(cx.connection)
1518+        elif isinstance(cx, basestring):
1519+            conns.append(connections[cx].connection)
1520+    return conns
1521+       
1522=== django/conf/global_settings.py
1523==================================================================
1524--- django/conf/global_settings.py      (/mirror/django/trunk)  (revision 4191)
1525
1526+++ django/conf/global_settings.py      (/local/django/multidb) (revision 4191)
1527
1528@@ -118,6 +118,9 @@
1529
1530 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
1531 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
1532 
1533+# Optional named database connections in addition to the default.
1534+OTHER_DATABASES = {}
1535+
1536 # Host for sending e-mail.
1537 EMAIL_HOST = 'localhost'
1538 
1539@@ -343,6 +346,16 @@
1540
1541 # If None, a name of 'test_' + DATABASE_NAME will be assumed
1542 TEST_DATABASE_NAME = None
1543 
1544+# Tuple of other test databases to create. Names in this tuple
1545+# are suffixes that will be appended to TEST_DATABASE_NAME
1546+TEST_DATABASES = []
1547+
1548+# Models to assign to each test database. This must be a list of
1549+# dicts, with each dict key being a name from TEST_DATABASES and value
1550+# a list of models or app_labels that will use that database.
1551+TEST_DATABASE_MODELS = []
1552+
1553+############### FROM TRUNK #############################
1554 # Strings used to set the character set and collation order for the test
1555 # database. These values are passed literally to the server, so they are
1556 # backend-dependent. If None, no special settings are sent (system defaults are
1557=== django/core/management/commands/sqlsequencereset.py
1558==================================================================
1559--- django/core/management/commands/sqlsequencereset.py (/mirror/django/trunk)  (revision 4191)
1560
1561+++ django/core/management/commands/sqlsequencereset.py (/local/django/multidb) (revision 4191)
1562
1563@@ -5,5 +5,8 @@
1564
1565     output_transaction = True
1566 
1567     def handle_app(self, app, **options):
1568-        from django.db import connection, models
1569-        return '\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app)))
1570+        from django.db import connection, models, model_connection_name
1571+        connection_output = []
1572+        for model in models.get_models(app):
1573+            connection_output.extend(model._default_manager.db.connection.ops.sequence_reset_sql(self.style,[model]))
1574+        return '\n'.join(connection_output)
1575=== django/core/management/commands/flush.py
1576==================================================================
1577--- django/core/management/commands/flush.py    (/mirror/django/trunk)  (revision 4191)
1578
1579+++ django/core/management/commands/flush.py    (/local/django/multidb) (revision 4191)
1580
1581@@ -14,7 +14,7 @@
1582
1583 
1584     def handle_noargs(self, **options):
1585         from django.conf import settings
1586-        from django.db import connection, transaction, models
1587+        from django.db import connections, connection, transaction, models
1588         from django.dispatch import dispatcher
1589         from django.core.management.sql import sql_flush, emit_post_sync_signal
1590 
1591@@ -32,7 +32,6 @@
1592
1593                 pass
1594 
1595         sql_list = sql_flush(self.style, only_django=True)
1596-
1597         if interactive:
1598             confirm = raw_input("""You have requested a flush of the database.
1599 This will IRREVERSIBLY DESTROY all data currently in the %r database,
1600@@ -45,8 +44,8 @@
1601
1602 
1603         if confirm == 'yes':
1604             try:
1605-                cursor = connection.cursor()
1606-                for sql in sql_list:
1607+                for conn, sql in sql_list:
1608+                    cursor = connections[conn].connection.cursor()
1609                     cursor.execute(sql)
1610             except Exception, e:
1611                 transaction.rollback_unless_managed()
1612=== django/core/management/commands/syncdb.py
1613==================================================================
1614--- django/core/management/commands/syncdb.py   (/mirror/django/trunk)  (revision 4191)
1615
1616+++ django/core/management/commands/syncdb.py   (/local/django/multidb) (revision 4191)
1617
1618@@ -19,7 +19,8 @@
1619
1620     help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
1621 
1622     def handle_noargs(self, **options):
1623-        from django.db import connection, transaction, models
1624+        from django.db import model_connection_name
1625+        from django.db import transaction, models
1626         from django.conf import settings
1627         from django.core.management.sql import table_list, installed_models, sql_model_create, sql_for_pending_references, many_to_many_sql_for_model, custom_sql_for_model, sql_indexes_for_model, emit_post_sync_signal
1628 
1629@@ -36,15 +37,9 @@
1630
1631             except ImportError:
1632                 pass
1633 
1634-        cursor = connection.cursor()
1635-
1636-        if connection.features.uses_case_insensitive_names:
1637-            table_name_converter = lambda x: x.upper()
1638-        else:
1639-            table_name_converter = lambda x: x
1640         # Get a list of all existing database tables, so we know what needs to
1641         # be added.
1642-        tables = [table_name_converter(name) for name in table_list()]
1643+        tables = table_list()
1644 
1645         # Get a list of already installed *models* so that references work right.
1646         seen_models = installed_models(tables)
1647@@ -58,8 +53,15 @@
1648
1649             for model in model_list:
1650                 # Create the model's database table, if it doesn't already exist.
1651                 if verbosity >= 2:
1652-                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
1653-                if table_name_converter(model._meta.db_table) in tables:
1654+                    print "Processing %s.%s model (%s))" % (app_name, model._meta.object_name, model_connection_name(model))
1655+                connection = model._default_manager.db.connection
1656+                cursor = connection.cursor()
1657+                if connection.features.uses_case_insensitive_names:
1658+                    table_name_converter = lambda x: x.upper()
1659+                else:
1660+                    table_name_converter = lambda x: x
1661+                ctables = [table_name_converter(name) for name in tables]
1662+                if table_name_converter(model._meta.db_table) in ctables:
1663                     continue
1664                 sql, references = sql_model_create(model, self.style, seen_models)
1665                 seen_models.add(model)
1666@@ -80,6 +82,8 @@
1667
1668             model_list = models.get_models(app)
1669             for model in model_list:
1670                 if model in created_models:
1671+                    connection = model._default_manager.db.connection
1672+                    cursor = connection.cursor()
1673                     sql = many_to_many_sql_for_model(model, self.style)
1674                     if sql:
1675                         if verbosity >= 2:
1676@@ -99,6 +103,8 @@
1677
1678             app_name = app.__name__.split('.')[-2]
1679             for model in models.get_models(app):
1680                 if model in created_models:
1681+                    connection = model._default_manager.db.connection
1682+                    cursor = connection.cursor()
1683                     custom_sql = custom_sql_for_model(model)
1684                     if custom_sql:
1685                         if verbosity >= 1:
1686@@ -118,6 +124,8 @@
1687
1688             app_name = app.__name__.split('.')[-2]
1689             for model in models.get_models(app):
1690                 if model in created_models:
1691+                    connection = model._default_manager.db.connection
1692+                    cursor = connection.cursor()
1693                     index_sql = sql_indexes_for_model(model, self.style)
1694                     if index_sql:
1695                         if verbosity >= 1:
1696@@ -135,3 +143,4 @@
1697
1698         # Install the 'initial_data' fixture, using format discovery
1699         from django.core.management import call_command
1700         call_command('loaddata', 'initial_data', verbosity=verbosity)
1701+
1702=== django/core/management/validation.py
1703==================================================================
1704--- django/core/management/validation.py        (/mirror/django/trunk)  (revision 4191)
1705
1706+++ django/core/management/validation.py        (/local/django/multidb) (revision 4191)
1707
1708@@ -19,7 +19,7 @@
1709
1710     Returns number of errors.
1711     """
1712     from django.conf import settings
1713-    from django.db import models, connection
1714+    from django.db import models, connections, model_connection_name
1715     from django.db.models.loading import get_app_errors
1716     from django.db.models.fields.related import RelatedObject
1717 
1718@@ -30,6 +30,8 @@
1719
1720 
1721     for cls in models.get_models(app):
1722         opts = cls._meta
1723+        connection_name = model_connection_name(cls)
1724+        connection = connections[connection_name]
1725 
1726         # Do field-specific validation.
1727         for f in opts.fields:
1728@@ -63,7 +65,7 @@
1729
1730 
1731             # Check that max_length <= 255 if using older MySQL versions.
1732             if settings.DATABASE_ENGINE == 'mysql':
1733-                db_version = connection.get_server_version()
1734+                db_version = connection.connection.get_server_version()
1735                 if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.max_length > 255:
1736                     e.add(opts, '"%s": %s cannot have a "max_length" 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]])))
1737 
1738@@ -74,6 +76,11 @@
1739
1740                 if f.rel.to not in models.get_models():
1741                     e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))
1742 
1743+                #TODO: Fix this to allow relations that span databases by splitting querys up
1744+                rel_connection = model_connection_name(f.rel.to)
1745+                if rel_connection != connection_name:
1746+                    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))
1747+
1748                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
1749                 rel_query_name = f.related_query_name()
1750                 for r in rel_opts.fields:
1751=== django/core/management/sql.py
1752==================================================================
1753--- django/core/management/sql.py       (/mirror/django/trunk)  (revision 4191)
1754
1755+++ django/core/management/sql.py       (/local/django/multidb) (revision 4191)
1756
1757@@ -8,11 +8,21 @@
1758
1759     from sets import Set as set   # Python 2.3 fallback
1760 
1761 def table_list():
1762-    "Returns a list of all table names that exist in the database."
1763-    from django.db import connection, get_introspection_module
1764-    cursor = connection.cursor()
1765-    return get_introspection_module().get_table_list(cursor)
1766+    """Returns a list of all table names that exist in the database."""
1767+    from django.db import connections
1768+    table_list = []
1769+    for conn in connections:
1770+        table_list.extend(table_list_conn(conn))
1771+    return table_list
1772 
1773+def table_list_conn(conn):
1774+    from django.db import connections
1775+    try:
1776+        cursor = connections[conn].connection.cursor()
1777+        return connections[conn].get_introspection_module().get_table_list(cursor)
1778+    except:
1779+        return []
1780+
1781 def django_table_list(only_existing=False):
1782     """
1783     Returns a list of all table names that have associated Django models and
1784@@ -32,18 +42,34 @@
1785
1786         tables = [t for t in tables if t in existing]
1787     return tables
1788 
1789+def django_table_list_conn(conn, only_existing=False):
1790+    from django.db import models
1791+    from django.db import model_connection_name
1792+    tables = []
1793+    for app in models.get_apps():
1794+        for model in models.get_models(app):
1795+            if model_connection_name(model)==conn:
1796+                tables.append(model._meta.db_table)
1797+                tables.extend([f.m2m_db_table() for f in model._meta.many_to_many])
1798+    if only_existing:
1799+        existing = table_list_conn(conn)
1800+        tables = [t for t in tables if t in existing]
1801+    return tables
1802+
1803 def installed_models(table_list):
1804     "Returns a set of all models that are installed, given a list of existing table names."
1805     from django.db import connection, models
1806     all_models = []
1807     for app in models.get_apps():
1808         for model in models.get_models(app):
1809-            all_models.append(model)
1810-    if connection.features.uses_case_insensitive_names:
1811-        converter = lambda x: x.upper()
1812-    else:
1813-        converter = lambda x: x
1814-    return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)])
1815+            connection = model._default_manager.db.connection
1816+            if connection.features.uses_case_insensitive_names:
1817+                converter = lambda x: x.upper()
1818+            else:
1819+                converter = lambda x: x
1820+            if converter(model._meta.db_table) in map(converter, table_list):
1821+                all_models.append(converter(model))
1822+    return set(all_models)
1823 
1824 def sequence_list():
1825     "Returns a list of information about all DB sequences for all models in all apps."
1826@@ -66,7 +92,7 @@
1827
1828 
1829 def sql_create(app, style):
1830     "Returns a list of the CREATE TABLE SQL statements for the given app."
1831-    from django.db import models
1832+    from django.db import models, model_connection_name
1833     from django.conf import settings
1834 
1835     if settings.DATABASE_ENGINE == 'dummy':
1836@@ -81,22 +107,30 @@
1837
1838     # generate invalid SQL (leaving models out of known_models is harmless, so
1839     # we can be conservative).
1840     app_models = models.get_models(app)
1841-    final_output = []
1842+    # final_output = []
1843     known_models = set([model for model in installed_models(table_list()) if model not in app_models])
1844     pending_references = {}
1845 
1846+    connection_output = {}
1847+
1848     for model in app_models:
1849+        connection_name = model_connection_name(model)
1850+        f_output = connection_output.setdefault(connection_name, [])
1851         output, references = sql_model_create(model, style, known_models)
1852-        final_output.extend(output)
1853+        f_output.extend(output)
1854         for refto, refs in references.items():
1855             pending_references.setdefault(refto, []).extend(refs)
1856-        final_output.extend(sql_for_pending_references(model, style, pending_references))
1857+        f_output.extend(sql_for_pending_references(model, style, pending_references))
1858         # Keep track of the fact that we've created the table for this model.
1859         known_models.add(model)
1860 
1861+    final_output = _collate(connection_output)
1862+
1863     # Create the many-to-many join tables.
1864     for model in app_models:
1865-        final_output.extend(many_to_many_sql_for_model(model, style))
1866+        connection_name = model_connection_name(model)
1867+        f_output = connection_output.setdefault(connection_name, [])
1868+        f_output.extend(many_to_many_sql_for_model(model,style))
1869 
1870     # Handle references to tables that are from other apps
1871     # but don't exist physically.
1872@@ -110,39 +144,45 @@
1873
1874             final_output.append('-- The following references should be added but depend on non-existent tables:')
1875             final_output.extend(alter_sql)
1876 
1877+    # convert Boundstatements into strings
1878+    final_output = map(str, final_output)
1879+
1880     return final_output
1881 
1882 def sql_delete(app, style):
1883     "Returns a list of the DROP TABLE SQL statements for the given app."
1884+    from django.db import model_connection_name
1885     from django.db import connection, models, get_introspection_module
1886     from django.db.backends.util import truncate_name
1887-    introspection = get_introspection_module()
1888 
1889-    # This should work even if a connection isn't available
1890-    try:
1891-        cursor = connection.cursor()
1892-    except:
1893-        cursor = None
1894-
1895-    # Figure out which tables already exist
1896-    if cursor:
1897-        table_names = introspection.get_table_list(cursor)
1898-    else:
1899-        table_names = []
1900-    if connection.features.uses_case_insensitive_names:
1901-        table_name_converter = lambda x: x.upper()
1902-    else:
1903-        table_name_converter = lambda x: x
1904-
1905     output = []
1906-    qn = connection.ops.quote_name
1907-
1908+   
1909     # Output DROP TABLE statements for standard application tables.
1910     to_delete = set()
1911 
1912     references_to_delete = {}
1913+
1914     app_models = models.get_models(app)
1915     for model in app_models:
1916+        connection = model._default_manager.db.connection
1917+        introspection = model._default_manager.db.get_introspection_module()
1918+        # This should work even if a connection isn't available
1919+        try:
1920+            cursor = connection.cursor()
1921+        except:
1922+            cursor = None
1923+        # Figure out which tables already exist
1924+        if cursor:
1925+            table_names = introspection.get_table_list(cursor)
1926+        else:
1927+            table_names = []
1928+        if connection.features.uses_case_insensitive_names:
1929+            table_name_converter = lambda x: x.upper()
1930+        else:
1931+            table_name_converter = lambda x: x
1932+
1933+        qn = connection.ops.quote_name
1934+
1935         if cursor and table_name_converter(model._meta.db_table) in table_names:
1936             # The table exists, so it needs to be dropped
1937             opts = model._meta
1938@@ -153,6 +193,25 @@
1939
1940             to_delete.add(model)
1941 
1942     for model in app_models:
1943+        connection = model._default_manager.db.connection
1944+        introspection = model._default_manager.db.get_introspection_module()
1945+        # This should work even if a connection isn't available
1946+        try:
1947+            cursor = connection.cursor()
1948+        except:
1949+            cursor = None
1950+        # Figure out which tables already exist
1951+        if cursor:
1952+            table_names = introspection.get_table_list(cursor)
1953+        else:
1954+            table_names = []
1955+        if connection.features.uses_case_insensitive_names:
1956+            table_name_converter = str.upper
1957+        else:
1958+            table_name_converter = lambda x: x
1959+
1960+        qn = connection.ops.quote_name
1961+
1962         if cursor and table_name_converter(model._meta.db_table) in table_names:
1963             # Drop the table now
1964             output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
1965@@ -177,6 +236,25 @@
1966
1967 
1968     # Output DROP TABLE statements for many-to-many tables.
1969     for model in app_models:
1970+        connection = model._default_manager.db.connection
1971+        introspection = model._default_manager.db.get_introspection_module()
1972+        # This should work even if a connection isn't available
1973+        try:
1974+            cursor = connection.cursor()
1975+        except:
1976+            cursor = None
1977+        # Figure out which tables already exist
1978+        if cursor:
1979+            table_names = introspection.get_table_list(cursor)
1980+        else:
1981+            table_names = []
1982+        if connection.features.uses_case_insensitive_names:
1983+            table_name_converter = str.upper
1984+        else:
1985+            table_name_converter = lambda x: x
1986+
1987+        qn = connection.ops.quote_name
1988+
1989         opts = model._meta
1990         for f in opts.many_to_many:
1991             if cursor and table_name_converter(f.m2m_db_table()) in table_names:
1992@@ -190,9 +268,15 @@
1993
1994 
1995     # Close database connection explicitly, in case this output is being piped
1996     # directly into a database client, to avoid locking issues.
1997-    if cursor:
1998-        cursor.close()
1999-        connection.close()
2000+    for model in app_models:
2001+        connection = model._default_manager.db.connection
2002+        try:
2003+            cursor = connection.cursor()
2004+        except:
2005+            cursor = None
2006+        if cursor:
2007+            cursor.close()
2008+            connection.close()
2009 
2010     return output[::-1] # Reverse it, to deal with table dependencies.
2011 
2012@@ -207,34 +291,42 @@
2013
2014     If only_django is True, then only table names that have associated Django
2015     models and are in INSTALLED_APPS will be included.
2016     """
2017-    from django.db import connection
2018-    if only_django:
2019-        tables = django_table_list()
2020-    else:
2021-        tables = table_list()
2022-    statements = connection.ops.sql_flush(style, tables, sequence_list())
2023+    from django.db import connections
2024+    statements = []
2025+    for conn in connections:
2026+        if only_django:
2027+            tables = django_table_list_conn(conn)
2028+        else:
2029+            tables = table_list_conn(conn)
2030+        statements.extend((conn,f) for f in connections[conn].connection.ops.sql_flush(style, tables, sequence_list()))
2031     return statements
2032 
2033 def sql_custom(app):
2034     "Returns a list of the custom table modifying SQL statements for the given app."
2035     from django.db.models import get_models
2036-    output = []
2037+    from django.db import model_connection_name
2038+    connection_output = {}
2039 
2040     app_models = get_models(app)
2041     app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql'))
2042 
2043     for model in app_models:
2044-        output.extend(custom_sql_for_model(model))
2045+        connection_name = model_connection_name(model)
2046+        output = connection_output.setdefault(connection_name, [])
2047+        output.extend(map(str, custom_sql_for_model(model)))
2048+    return _collate(connection_output)
2049 
2050-    return output
2051-
2052 def sql_indexes(app, style):
2053     "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
2054-    from django.db import models
2055-    output = []
2056-    for model in models.get_models(app):
2057-        output.extend(sql_indexes_for_model(model, style))
2058-    return output
2059+    from django.db import model_connection_name
2060+    from django.db.models import get_models
2061+    connection_output = {}
2062+    for model in get_models(app):
2063+        opts = model._meta
2064+        connection_name = model_connection_name(model)
2065+        output = connection_output.setdefault(connection_name, [])
2066+        output.extend(map(str, sql_indexes_for_model(model, style)))
2067+    return _collate(connection_output)
2068 
2069 def sql_all(app, style):
2070     "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
2071@@ -245,12 +337,13 @@
2072
2073     Returns the SQL required to create a single model, as a tuple of:
2074         (list_of_sql, pending_references_dict)
2075     """
2076-    from django.db import connection, models
2077+    from django.db import models
2078 
2079     opts = model._meta
2080     final_output = []
2081     table_output = []
2082     pending_references = {}
2083+    connection = model._default_manager.db.connection
2084     qn = connection.ops.quote_name
2085     for f in opts.fields:
2086         col_type = f.db_type()
2087@@ -314,10 +407,14 @@
2088
2089     """
2090     Returns any ALTER TABLE statements to add constraints after the fact.
2091     """
2092-    from django.db import connection
2093     from django.db.backends.util import truncate_name
2094 
2095+    connection = model._default_manager.db.connection
2096     qn = connection.ops.quote_name
2097+    if hasattr(connection.ops, 'max_constraint_length'):
2098+        mnl = connection.ops.max_constraint_length
2099+    else:
2100+        mnl = connection.ops.max_name_length
2101     final_output = []
2102     if connection.features.supports_constraints:
2103         opts = model._meta
2104@@ -332,18 +429,19 @@
2105
2106                 # So we are careful with character usage here.
2107                 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
2108                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
2109-                    (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
2110+                    (qn(r_table), truncate_name(r_name, mnl()),
2111                     qn(r_col), qn(table), qn(col),
2112                     connection.ops.deferrable_sql()))
2113             del pending_references[model]
2114     return final_output
2115 
2116 def many_to_many_sql_for_model(model, style):
2117-    from django.db import connection, models
2118+    from django.db import models
2119     from django.contrib.contenttypes import generic
2120 
2121     opts = model._meta
2122     final_output = []
2123+    connection = model._default_manager.db.connection
2124     qn = connection.ops.quote_name
2125     for f in opts.many_to_many:
2126         if not isinstance(f.rel, generic.GenericRel):
2127@@ -455,3 +553,25 @@
2128
2129         dispatcher.send(signal=models.signals.post_syncdb, sender=app,
2130             app=app, created_models=created_models,
2131             verbosity=verbosity, interactive=interactive)
2132+
2133+def _collate(connection_output, reverse=False):
2134+    from django.db import _default
2135+    final_output = []
2136+    if len(connection_output.keys()) == 1:
2137+        # all for the default connection
2138+        for statements in connection_output.values():
2139+            final_output.extend(statements)
2140+            if reverse:
2141+                final_output.reverse()
2142+    else:
2143+        for connection_name, statements in connection_output.items():
2144+            if not statements:
2145+                continue
2146+            final_output.append(' -- The following statements are for connection: %s' % connection_name)
2147+            if reverse:
2148+                statements.reverse()
2149+            final_output.extend(statements)
2150+            final_output.append(' -- END statements for %s\n' %
2151+                                connection_name)
2152+    return map(str, final_output)
2153+
2154=== django/core/context_processors.py
2155==================================================================
2156--- django/core/context_processors.py   (/mirror/django/trunk)  (revision 4191)
2157
2158+++ django/core/context_processors.py   (/local/django/multidb) (revision 4191)
2159
2160@@ -33,8 +33,11 @@
2161
2162     context_extras = {}
2163     if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
2164         context_extras['debug'] = True
2165-        from django.db import connection
2166-        context_extras['sql_queries'] = connection.queries
2167+        from django.db import connections
2168+        sqlq = []
2169+        for c in connections:
2170+            sqlq.extend(connections[c].connection.queries)
2171+        context_extras['sql_queries'] = sqlq
2172     return context_extras
2173 
2174 def i18n(request):
2175=== tests/modeltests/invalid_models/__init__.py
2176==================================================================
2177--- tests/modeltests/invalid_models/__init__.py (/mirror/django/trunk)  (revision 4191)
2178
2179+++ tests/modeltests/invalid_models/__init__.py (/local/django/multidb) (revision 4191)
2180
2181@@ -1 +0,0 @@
2182
2183-
2184=== tests/modeltests/multiple_databases (new directory)
2185==================================================================
2186=== tests/modeltests/multiple_databases/__init__.py
2187==================================================================
2188--- tests/modeltests/multiple_databases/__init__.py     (/mirror/django/trunk)  (revision 4191)
2189
2190+++ tests/modeltests/multiple_databases/__init__.py     (/local/django/multidb) (revision 4191)
2191
2192@@ -0,0 +1 @@
2193
2194+pass
2195
2196=== tests/modeltests/multiple_databases/models.py
2197==================================================================
2198--- tests/modeltests/multiple_databases/models.py       (/mirror/django/trunk)  (revision 4191)
2199
2200+++ tests/modeltests/multiple_databases/models.py       (/local/django/multidb) (revision 4191)
2201
2202@@ -0,0 +1,221 @@
2203
2204+"""
2205+XXX. Using multiple database connections
2206+
2207+Django normally uses only a single database connection. However,
2208+support is available for using any number of different, named
2209+connections. Multiple database support is entirely optional and has
2210+no impact on your application if you don't use it.
2211+
2212+Named connections are defined in your settings module. Create a
2213+`OTHER_DATABASES` variable that is a dict, mapping connection names to their
2214+particulars. The particulars are defined in a dict with the same keys
2215+as the variable names as are used to define the default connection, with one
2216+addition: MODELS.
2217+
2218+The MODELS item in an OTHER_DATABASES entry is a list of the apps and models
2219+that will use that connection.
2220+
2221+Access to named connections is through `django.db.connections`, which
2222+behaves like a dict: you access connections by name. Connections are
2223+established lazily, when accessed.  `django.db.connections[database]`
2224+holds a `ConnectionInfo` instance, with the attributes:
2225+`DatabaseError`, `backend`, `get_introspection_module`,
2226+`get_creation_module`, and `runshell`.
2227+
2228+To access a model's connection, use its manager. The connection is available
2229+at `model._default_manager.db.connection`. To find the backend or other
2230+connection metadata, use `model._meta.db` to access the full ConnectionInfo
2231+with connection metadata.
2232+"""
2233+
2234+from django.db import models
2235+
2236+class Artist(models.Model):
2237+    name = models.CharField(maxlength=100)
2238+    alive = models.BooleanField(default=True)
2239+   
2240+    def __str__(self):
2241+        return self.name
2242+
2243+   
2244+class Opus(models.Model):
2245+    artist = models.ForeignKey(Artist)
2246+    name = models.CharField(maxlength=100)
2247+    year = models.IntegerField()
2248+   
2249+    def __str__(self):
2250+        return "%s (%s)" % (self.name, self.year)
2251+
2252+
2253+class Widget(models.Model):
2254+    code = models.CharField(maxlength=10, unique=True)
2255+    weight = models.IntegerField()
2256+
2257+    def __str__(self):
2258+        return self.code
2259+
2260+
2261+class DooHickey(models.Model):
2262+    name = models.CharField(maxlength=50)
2263+    widgets = models.ManyToManyField(Widget, related_name='doohickeys')
2264+   
2265+    def __str__(self):
2266+        return self.name
2267+
2268+
2269+class Vehicle(models.Model):
2270+    make = models.CharField(maxlength=20)
2271+    model = models.CharField(maxlength=20)
2272+    year = models.IntegerField()
2273+
2274+    def __str__(self):
2275+        return "%d %s %s" % (self.year, self.make, self.model)
2276+
2277+
2278+__test__ = {'API_TESTS': """
2279+
2280+# See what connections are defined. django.db.connections acts like a dict.
2281+>>> from django.db import connection, connections, _default, model_connection_name
2282+>>> from django.conf import settings
2283+
2284+# Connections are referenced by name
2285+>>> connections['_a']
2286+Connection: ...
2287+>>> connections['_b']
2288+Connection: ...
2289+
2290+# Let's see what connections are available. The default connection is always
2291+# included in connections as well, and may be accessed as connections[_default].
2292+
2293+>>> connection_names = connections.keys()
2294+>>> connection_names.sort()
2295+>>> connection_names
2296+[<default>, '_a', '_b']
2297+   
2298+# Invalid connection names raise ImproperlyConfigured
2299+
2300+>>> connections['bad']
2301+Traceback (most recent call last):
2302+ ...
2303+ImproperlyConfigured: No database connection 'bad' has been configured
2304+
2305+# The model_connection_name() function will tell you the name of the
2306+# connection that a model is configured to use.
2307+
2308+>>> model_connection_name(Artist)
2309+'_a'
2310+>>> model_connection_name(Widget)
2311+'_b'
2312+>>> model_connection_name(Vehicle) is _default
2313+True
2314+>>> a = Artist(name="Paul Klee", alive=False)
2315+>>> a.save()
2316+>>> w = Widget(code='100x2r', weight=1000)
2317+>>> w.save()
2318+>>> v = Vehicle(make='Chevy', model='Camaro', year='1966')
2319+>>> v.save()
2320+>>> artists = Artist.objects.all()
2321+>>> list(artists)
2322+[<Artist: Paul Klee>]
2323+
2324+# Models can access their connections through the db property of their
2325+# default manager.
2326+
2327+>>> paul = _[0]
2328+>>> Artist.objects.db
2329+Connection: ... (ENGINE=... NAME=...)
2330+>>> paul._default_manager.db
2331+Connection: ... (ENGINE=... NAME=...)
2332+
2333+# When transactions are not managed, model save will commit only
2334+# for the model's connection.
2335+
2336+>>> from django.db import transaction
2337+>>> transaction.enter_transaction_management()
2338+>>> transaction.managed(False)
2339+>>> a = Artist(name="Joan Miro", alive=False)
2340+>>> w = Widget(code="99rbln", weight=1)
2341+>>> a.save()
2342+
2343+# Only connection '_a' is committed, so if we rollback
2344+# all connections we'll forget the new Widget.
2345+
2346+>>> transaction.rollback()
2347+>>> list(Artist.objects.all())
2348+[<Artist: Paul Klee>, <Artist: Joan Miro>]
2349+>>> list(Widget.objects.all())
2350+[<Widget: 100x2r>]
2351+
2352+# Managed transaction state applies across all connections.
2353+
2354+>>> transaction.managed(True)
2355+
2356+# When managed, just as when using a single connection, updates are
2357+# not committed until a commit is issued.
2358+
2359+>>> a = Artist(name="Pablo Picasso", alive=False)
2360+>>> a.save()
2361+>>> w = Widget(code="99rbln", weight=1)
2362+>>> w.save()
2363+>>> v = Vehicle(make='Pontiac', model='Fiero', year='1987')
2364+>>> v.save()
2365+
2366+# The connections argument may be passed to commit, rollback, and the
2367+# commit_on_success decorator as a keyword argument, as the first (for
2368+# commit and rollback) or second (for the decorator) positional
2369+# argument. It may be passed as a ConnectionInfo object, a connection
2370+# (DatabaseWrapper) object, a connection name, or a list or dict of
2371+# ConnectionInfo objects, connection objects, or connection names. If a
2372+# dict is passed, the keys are ignored and the values used as the list
2373+# of connections to commit, rollback, etc.
2374+
2375+>>> transaction.commit(connections['_b'])
2376+>>> transaction.commit('_b')
2377+>>> transaction.commit(connections='_b')
2378+>>> transaction.commit(connections=['_b'])
2379+>>> transaction.commit(['_a', '_b'])
2380+>>> transaction.commit(connections)
2381+
2382+# When the connections argument is omitted entirely, the transaction
2383+# command applies to all connections. Here we have committed
2384+# connections 'django_test_db_a' and 'django_test_db_b', but not the
2385+# default connection, so the new vehicle is lost on rollback.
2386+
2387+>>> transaction.rollback()
2388+>>> list(Artist.objects.all())
2389+[<Artist: Paul Klee>, <Artist: Joan Miro>, <Artist: Pablo Picasso>]
2390+>>> list(Widget.objects.all())
2391+[<Widget: 100x2r>, <Widget: 99rbln>]
2392+>>> list(Vehicle.objects.all())
2393+[<Vehicle: 1966 Chevy Camaro>]
2394+>>> transaction.rollback()
2395+>>> transaction.managed(False)
2396+>>> transaction.leave_transaction_management()
2397+
2398+# Of course, relations and all other normal database operations work
2399+# with models that use named connections just the same as with models
2400+# that use the default connection. The only caveat is that you can't
2401+# use a relation between two models that are stored in different
2402+# databases. Note that that doesn't mean that two models using
2403+# different connection *names* can't be related; only that in the the
2404+# context in which they are used, if you use the relation, the
2405+# connections named by the two models must resolve to the same
2406+# database.
2407+
2408+>>> a = Artist.objects.get(name="Paul Klee")
2409+>>> list(a.opus_set.all())
2410+[]
2411+>>> a.opus_set.create(name="Magic Garden", year="1926")
2412+<Opus: Magic Garden (1926)>
2413+>>> list(a.opus_set.all())
2414+[<Opus: Magic Garden (1926)>]
2415+>>> d = DooHickey(name='Thing')
2416+>>> d.save()
2417+>>> d.widgets.create(code='d101', weight=92)
2418+<Widget: d101>
2419+>>> list(d.widgets.all())
2420+[<Widget: d101>]
2421+>>> w = Widget.objects.get(code='d101')
2422+>>> list(w.doohickeys.all())
2423+[<DooHickey: Thing>]
2424+"""}
2425
2426Property changes on: tests/modeltests/multiple_databases
2427___________________________________________________________________
2428Name: svn:ignore
2429 +*.pyc
2430 +
2431
2432=== tests/regressiontests/manager_db    (new directory)
2433==================================================================
2434=== tests/regressiontests/manager_db/__init__.py
2435==================================================================
2436=== tests/regressiontests/manager_db/tests.py
2437==================================================================
2438--- tests/regressiontests/manager_db/tests.py   (/mirror/django/trunk)  (revision 4191)
2439
2440+++ tests/regressiontests/manager_db/tests.py   (/local/django/multidb) (revision 4191)
2441
2442@@ -0,0 +1,17 @@
2443
2444+import unittest
2445+from regressiontests.manager_db.models import Insect
2446+
2447+class TestManagerDBAccess(unittest.TestCase):
2448+
2449+    def test_db_property(self):
2450+        m = Insect.objects
2451+        db = Insect.objects.db
2452+        assert db
2453+        assert db.connection
2454+        assert db.connection.cursor
2455+        assert db.backend
2456+        assert db.connection.ops.quote_name
2457+        assert db.get_creation_module
2458+
2459+if __name__ == '__main__':
2460+    unittest.main()
2461=== tests/regressiontests/manager_db/models.py
2462==================================================================
2463--- tests/regressiontests/manager_db/models.py  (/mirror/django/trunk)  (revision 4191)
2464
2465+++ tests/regressiontests/manager_db/models.py  (/local/django/multidb) (revision 4191)
2466
2467@@ -0,0 +1,5 @@
2468
2469+from django.db import models
2470+
2471+class Insect(models.Model):
2472+    common_name = models.CharField(maxlength=64)
2473+    latin_name = models.CharField(maxlength=128)
2474
2475Property changes on: tests/regressiontests/manager_db
2476___________________________________________________________________
2477Name: svn:ignore
2478 +*.pyc
2479 +
2480
2481=== tests/regressiontests/thread_isolation      (new directory)
2482==================================================================
2483=== tests/regressiontests/thread_isolation/__init__.py
2484==================================================================
2485=== tests/regressiontests/thread_isolation/tests.py
2486==================================================================
2487--- tests/regressiontests/thread_isolation/tests.py     (/mirror/django/trunk)  (revision 4191)
2488
2489+++ tests/regressiontests/thread_isolation/tests.py     (/local/django/multidb) (revision 4191)
2490
2491@@ -0,0 +1,251 @@
2492
2493+# tests that db settings can be different in different threads
2494+#
2495+#
2496+#    What's going on here:
2497+#
2498+#    Simulating multiple web requests in a threaded environment, one in
2499+#    which settings are different for each request. So we replace
2500+#    django.conf.settings with a thread local, with different
2501+#    configurations in each thread, and then fire off three
2502+#    simultaneous requests (using a condition to sync them up), and
2503+#    test that each thread sees its own settings and the models in each
2504+#    thread attempt to connect to the correct database as per their
2505+#    settings.
2506+#
2507+
2508+
2509+import copy
2510+import os
2511+import sys
2512+import threading
2513+import unittest
2514+from thread import get_ident
2515+
2516+from django.conf import settings, UserSettingsHolder
2517+from django.core.handlers.wsgi import WSGIHandler
2518+from django.db import model_connection_name, _default, connection, connections
2519+from regressiontests.request_isolation.tests import MockHandler
2520+from regressiontests.thread_isolation.models import *
2521+
2522+try:
2523+    # Only exists in Python 2.4+
2524+    from threading import local
2525+except ImportError:
2526+    # Import copy of _thread_local.py from Python 2.4
2527+    from django.utils._threading_local import local
2528+
2529+# helpers
2530+EV = threading.Event()
2531+
2532+class LocalSettings:
2533+    """Settings holder that allows thread-local overrides of defaults.
2534+    """
2535+    def __init__(self, defaults):
2536+        self._defaults = defaults
2537+        self._local = local()
2538+
2539+    def __getattr__(self, attr):
2540+        if attr in ('_defaults', '_local'):
2541+            return self.__dict__[attr]
2542+        _local = self.__dict__['_local']
2543+        _defaults = self.__dict__['_defaults']
2544+        debug("LS get %s (%s)", attr, hasattr(_local, attr))
2545+        if not hasattr(_local, attr):
2546+            # Make sure everything we return is the local version; this
2547+            # avoids sets to deep datastructures overwriting the defaults
2548+            setattr(_local, attr, copy.deepcopy(getattr(_defaults, attr)))
2549+        return getattr(_local, attr)
2550+
2551+    def __setattr__(self, attr, val):
2552+        if attr in ('_defaults', '_local'):
2553+            self.__dict__[attr] = val
2554+        else:
2555+            debug("LS set local %s = %s", attr, val)
2556+            setattr(self.__dict__['_local'], attr, val)
2557+
2558+def thread_two(func, *arg):
2559+    def start():
2560+        # from django.conf import settings
2561+        settings.OTHER_DATABASES['_b']['MODELS'] = []
2562+
2563+        debug("t2 ODB: %s", settings.OTHER_DATABASES)
2564+        debug("t2 waiting")
2565+        EV.wait(2.0)
2566+        func(*arg)
2567+        debug("t2 complete")
2568+    t2 = threading.Thread(target=start)
2569+    t2.start()
2570+    return t2
2571+
2572+def thread_three(func, *arg):
2573+    def start():
2574+        # from django.conf import settings           
2575+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ti.MY']
2576+        settings.OTHER_DATABASES['_b'], \
2577+            settings.OTHER_DATABASES['_a'] = \
2578+            settings.OTHER_DATABASES['_a'], \
2579+            settings.OTHER_DATABASES['_b']
2580+
2581+        settings.DATABASE_NAME = \
2582+            settings.OTHER_DATABASES['_a']['DATABASE_NAME']
2583+
2584+        debug("t3 ODB: %s", settings.OTHER_DATABASES)
2585+        debug("3 %s: start: default: %s", get_ident(), settings.DATABASE_NAME)
2586+        debug("3 %s: start: conn: %s", get_ident(),
2587+              connection.settings.DATABASE_NAME)
2588+       
2589+        debug("t3 waiting")
2590+        EV.wait(2.0)
2591+        func(*arg)
2592+        debug("t3 complete")
2593+    t3 = threading.Thread(target=start)
2594+    t3.start()
2595+    return t3
2596+
2597+def debug(*arg):
2598+    pass
2599+#    msg, arg = arg[0], arg[1:]
2600+#    print msg % arg
2601+
2602+def start_response(code, headers):
2603+    debug("start response: %s %s", code, headers)
2604+    pass
2605+   
2606+class TestThreadIsolation(unittest.TestCase):
2607+    # event used to synchronize threads so we can be sure they are running
2608+    # together
2609+    lock = threading.RLock()
2610+    errors = []
2611+   
2612+    def setUp(self):
2613+        debug("setup")
2614+        self.settings = settings._target
2615+        settings._target = UserSettingsHolder(copy.deepcopy(settings._target))
2616+        settings.OTHER_DATABASES['_a']['MODELS'] =  ['ti.MX']
2617+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ti.MY']
2618+
2619+        # normal settings holders aren't thread-safe, so we need to substitute
2620+        # one that is (and so allows per-thread settings)
2621+        holder = settings._target
2622+        settings._target = LocalSettings(holder)
2623+
2624+    def teardown(self):
2625+        debug("teardown")
2626+        settings._target = self.settings
2627+
2628+    def add_thread_error(self, err):
2629+        self.lock.acquire()
2630+        try:
2631+            self.errors.append(err)
2632+        finally:
2633+            self.lock.release()
2634+
2635+    def thread_errors(self):
2636+        self.lock.acquire()
2637+        try:
2638+            return self.errors[:]
2639+        finally:
2640+            self.lock.release()
2641+           
2642+    def request_one(self, request):
2643+        """Start out with settings as originally configured"""
2644+        from django.conf import settings
2645+        debug("request_one: %s", settings.OTHER_DATABASES)
2646+
2647+        self.assertEqual(model_connection_name(MQ), _default)
2648+        self.assertEqual(model_connection_name(MX), '_a')
2649+        self.assertEqual(
2650+            MX._default_manager.db.connection.settings.DATABASE_NAME,
2651+            settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
2652+        self.assertEqual(model_connection_name(MY), '_b')
2653+        self.assertEqual(
2654+            MY._default_manager.db.connection.settings.DATABASE_NAME,
2655+            settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
2656+        self.assert_(MQ._default_manager.db.connection is
2657+                     connections[_default].connection)
2658+        self.assertEqual(
2659+            MQ._default_manager.db.connection.settings.DATABASE_NAME,
2660+            settings.DATABASE_NAME)
2661+        self.assertEqual(connection.settings.DATABASE_NAME,
2662+                         settings.DATABASE_NAME)
2663+
2664+    def request_two(self, request):
2665+        """Between the first and second requests, settings change to assign
2666+        model MY to a different connection
2667+        """
2668+        # from django.conf import settings
2669+        debug("request_two: %s", settings.OTHER_DATABASES)
2670+
2671+        try:
2672+            self.assertEqual(model_connection_name(MQ), _default)
2673+            self.assertEqual(model_connection_name(MX), '_a')
2674+            self.assertEqual(
2675+                MX._default_manager.db.connection.settings.DATABASE_NAME,
2676+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
2677+            self.assertEqual(model_connection_name(MY), _default)
2678+            self.assertEqual(
2679+                MY._default_manager.db.connection.settings.DATABASE_NAME,
2680+                settings.DATABASE_NAME)
2681+            self.assert_(MQ._default_manager.db.connection is
2682+                         connections[_default].connection)
2683+            self.assertEqual(
2684+                MQ._default_manager.db.connection.settings.DATABASE_NAME,
2685+                settings.DATABASE_NAME)
2686+            self.assertEqual(connection.settings.DATABASE_NAME,
2687+                             settings.DATABASE_NAME)
2688+        except:
2689+            self.add_thread_error(sys.exc_info())
2690+
2691+    def request_three(self, request):
2692+        """Between the 2nd and 3rd requests, the settings at the names in
2693+        OTHER_DATABASES have changed.
2694+        """
2695+        # from django.conf import settings
2696+        debug("3 %s: %s", get_ident(), settings.OTHER_DATABASES)
2697+        debug("3 %s: default: %s", get_ident(), settings.DATABASE_NAME)
2698+        debug("3 %s: conn: %s", get_ident(),
2699+              connection.settings.DATABASE_NAME)
2700+        try:
2701+            self.assertEqual(model_connection_name(MQ), _default)
2702+            self.assertEqual(model_connection_name(MX), '_b')
2703+            self.assertEqual(
2704+                MX._default_manager.db.connection.settings.DATABASE_NAME,
2705+                settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
2706+            self.assertEqual(model_connection_name(MY), '_a')
2707+            self.assertEqual(
2708+                MY._default_manager.db.connection.settings.DATABASE_NAME,
2709+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
2710+            self.assert_(MQ._default_manager.db.connection is
2711+                         connections[_default].connection)
2712+            self.assertEqual(
2713+                connection.settings.DATABASE_NAME,
2714+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
2715+        except:
2716+            self.add_thread_error(sys.exc_info())
2717+       
2718+    def test_thread_isolation(self):
2719+       
2720+        debug("running tests")
2721+
2722+        env = os.environ.copy()
2723+        env['PATH_INFO'] = '/'
2724+        env['REQUEST_METHOD'] = 'GET'
2725+
2726+        t2 = thread_two(MockHandler(self.request_two), env, start_response)
2727+        t3 = thread_three(MockHandler(self.request_three), env, start_response)
2728+
2729+        try:
2730+            EV.set()
2731+            MockHandler(self.request_one)(env, start_response)
2732+        finally:
2733+            t2.join()
2734+            t3.join()
2735+            err = self.thread_errors()
2736+            if err:
2737+                import traceback
2738+                for e in err:
2739+                    traceback.print_exception(*e)
2740+                    raise AssertionError("%s thread%s failed" %
2741+                                         (len(err), len(err) > 1 and 's' or
2742+                                          ''))
2743+               
2744=== tests/regressiontests/thread_isolation/models.py
2745==================================================================
2746--- tests/regressiontests/thread_isolation/models.py    (/mirror/django/trunk)  (revision 4191)
2747
2748+++ tests/regressiontests/thread_isolation/models.py    (/local/django/multidb) (revision 4191)
2749
2750@@ -0,0 +1,19 @@
2751
2752+from django.db import models
2753+
2754+# models
2755+class MQ(models.Model):
2756+    val = models.CharField(maxlength=10)
2757+    class Meta:
2758+        app_label = 'ti'
2759+
2760+
2761+class MX(models.Model):
2762+    val = models.CharField(maxlength=10)
2763+    class Meta:
2764+        app_label = 'ti'
2765+
2766+       
2767+class MY(models.Model):
2768+    val = models.CharField(maxlength=10)
2769+    class Meta:
2770+        app_label = 'ti'
2771
2772Property changes on: tests/regressiontests/thread_isolation
2773___________________________________________________________________
2774Name: svn:ignore
2775 +*.pyc
2776 +
2777
2778
2779Property changes on: tests/regressiontests/initial_sql_regress/sql
2780___________________________________________________________________
2781Name: svn:ignore
2782 +*.pyc
2783 +
2784
2785=== tests/regressiontests/request_isolation     (new directory)
2786==================================================================
2787=== tests/regressiontests/request_isolation/__init__.py
2788==================================================================
2789=== tests/regressiontests/request_isolation/tests.py
2790==================================================================
2791--- tests/regressiontests/request_isolation/tests.py    (/mirror/django/trunk)  (revision 4191)
2792
2793+++ tests/regressiontests/request_isolation/tests.py    (/local/django/multidb) (revision 4191)
2794
2795@@ -0,0 +1,100 @@
2796
2797+# tests that db settings can change between requests
2798+import copy
2799+import os
2800+import unittest
2801+from django.conf import settings, UserSettingsHolder
2802+from django.core.handlers.wsgi import WSGIHandler
2803+from django.db import models, model_connection_name, _default, connection
2804+from django.http import HttpResponse
2805+from regressiontests.request_isolation.models import *
2806+
2807+
2808+# helpers
2809+class MockHandler(WSGIHandler):
2810+
2811+    def __init__(self, test):
2812+        self.test = test
2813+        super(MockHandler, self).__init__()
2814+       
2815+    def get_response(self, request):
2816+        # debug("mock handler answering %s, %s", path, request)
2817+        return HttpResponse(self.test(request))
2818+
2819+
2820+def debug(*arg):
2821+    pass
2822+    # msg, arg = arg[0], arg[1:]
2823+    # print msg % arg
2824+
2825+
2826+def start_response(code, headers):
2827+    debug("start response: %s %s", code, headers)
2828+    pass
2829+
2830+# tests
2831+class TestRequestIsolation(unittest.TestCase):
2832+
2833+    def setUp(self):
2834+        debug("setup")
2835+        self.settings = settings._target
2836+        settings._target = UserSettingsHolder(copy.deepcopy(settings._target))
2837+        settings.OTHER_DATABASES['_a']['MODELS'] = ['ri.MX']
2838+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ri.MY']
2839+
2840+    def tearDown(self):
2841+        debug("teardown")
2842+        settings._target = self.settings
2843+
2844+    def testRequestIsolation(self):
2845+        env = os.environ.copy()
2846+        env['PATH_INFO'] = '/'
2847+        env['REQUEST_METHOD'] = 'GET'
2848+
2849+        def request_one(request):
2850+            """Start out with settings as originally configured"""
2851+            self.assertEqual(model_connection_name(MX), '_a')
2852+            self.assertEqual(
2853+                MX._default_manager.db.connection.settings.DATABASE_NAME,
2854+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
2855+            self.assertEqual(model_connection_name(MY), '_b')
2856+            self.assertEqual(
2857+                MY._default_manager.db.connection.settings.DATABASE_NAME,
2858+                settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
2859+
2860+        def request_two(request):
2861+            """Between the first and second requests, settings change to assign
2862+            model MY to a different connection
2863+            """
2864+            self.assertEqual(model_connection_name(MX), '_a')
2865+            self.assertEqual(
2866+                MX._default_manager.db.connection.settings.DATABASE_NAME,
2867+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
2868+            self.assertEqual(model_connection_name(MY), _default)
2869+            self.assertEqual(
2870+                MY._default_manager.db.connection.settings.DATABASE_NAME,
2871+                settings.DATABASE_NAME)
2872+
2873+        def request_three(request):
2874+            """Between the 2nd and 3rd requests, the settings at the names in
2875+            OTHER_DATABASES have changed.
2876+            """
2877+            self.assertEqual(model_connection_name(MX), '_b')
2878+            self.assertEqual(
2879+                MX._default_manager.db.connection.settings.DATABASE_NAME,
2880+                settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
2881+            self.assertEqual(model_connection_name(MY), '_a')
2882+            self.assertEqual(
2883+                MY._default_manager.db.connection.settings.DATABASE_NAME,
2884+                settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
2885+   
2886+        MockHandler(request_one)(env, start_response)
2887+
2888+        settings.OTHER_DATABASES['_b']['MODELS'] = []
2889+        MockHandler(request_two)(env, start_response)
2890+
2891+        settings.OTHER_DATABASES['_b']['MODELS'] = ['ri.MY']
2892+        settings.OTHER_DATABASES['_b'], \
2893+            settings.OTHER_DATABASES['_a'] = \
2894+            settings.OTHER_DATABASES['_a'], \
2895+            settings.OTHER_DATABASES['_b']
2896+        MockHandler(request_three)(env, start_response)
2897=== tests/regressiontests/request_isolation/models.py
2898==================================================================
2899--- tests/regressiontests/request_isolation/models.py   (/mirror/django/trunk)  (revision 4191)
2900
2901+++ tests/regressiontests/request_isolation/models.py   (/local/django/multidb) (revision 4191)
2902
2903@@ -0,0 +1,13 @@
2904
2905+from django.db import models
2906+
2907+# models
2908+class MX(models.Model):
2909+    val = models.CharField(maxlength=10)
2910+    class Meta:
2911+        app_label = 'ri'
2912+
2913+       
2914+class MY(models.Model):
2915+    val = models.CharField(maxlength=10)
2916+    class Meta:
2917+        app_label = 'ri'
2918
2919Property changes on: tests/regressiontests/request_isolation
2920___________________________________________________________________
2921Name: svn:ignore
2922 +*.pyc
2923 +
2924
2925=== tests/runtests.py
2926==================================================================
2927--- tests/runtests.py   (/mirror/django/trunk)  (revision 4191)
2928
2929+++ tests/runtests.py   (/local/django/multidb) (revision 4191)
2930
2931@@ -18,6 +18,14 @@
2932
2933 TEST_TEMPLATE_DIR = 'templates'
2934 
2935 CONTRIB_DIR = os.path.dirname(contrib.__file__)
2936+TEST_OTHER_DATABASES = {
2937+    '_a': { 'DATABASE_NAME': 'django_test_a',
2938+            'MODELS': [ 'multiple_databases.Artist',
2939+            'multiple_databases.Opus' ]},
2940+    '_b': { 'DATABASE_NAME': 'django_test_b',
2941+            'MODELS': [ 'multiple_databases.Widget',
2942+            'multiple_databases.DooHickey' ]}
2943+}
2944 MODEL_TEST_DIR = os.path.join(os.path.dirname(__file__), MODEL_TESTS_DIR_NAME)
2945 REGRESSION_TEST_DIR = os.path.join(os.path.dirname(__file__), REGRESSION_TESTS_DIR_NAME)
2946 
2947@@ -97,6 +105,7 @@
2948
2949 
2950     # Redirect some settings for the duration of these tests.
2951     settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
2952+    settings.TEST_OTHER_DATABASES = TEST_OTHER_DATABASES
2953     settings.ROOT_URLCONF = 'urls'
2954     settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
2955     settings.USE_I18N = True
2956
2957Property changes on: tests/templates
2958___________________________________________________________________
2959Name: svn:ignore
2960 +*.pyc
2961 +
2962
2963=== docs/settings.txt
2964==================================================================
2965--- docs/settings.txt   (/mirror/django/trunk)  (revision 4191)
2966
2967+++ docs/settings.txt   (/local/django/multidb) (revision 4191)
2968
2969@@ -667,6 +667,13 @@
2970
2971 See `allowed date format strings`_. See also ``DATE_FORMAT``,
2972 ``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``YEAR_MONTH_FORMAT``.
2973 
2974+OTHER_DATABASES
2975+---------------
2976+
2977+Default: ``{}``
2978+
2979+Other database connections to use in addition to the default connection. See the `multiple database support docs`_.
2980+
2981 PREPEND_WWW
2982 -----------
2983 
2984=== docs/multiple_database_support.txt
2985==================================================================
2986--- docs/multiple_database_support.txt  (/mirror/django/trunk)  (revision 4191)
2987
2988+++ docs/multiple_database_support.txt  (/local/django/multidb) (revision 4191)
2989
2990@@ -0,0 +1,163 @@
2991
2992+========================
2993+Using Multiple Databases
2994+========================
2995+
2996+Standard Django practice is to use a single database connection for
2997+all models in all applications. However, Django supports configuring
2998+and using multiple database connections on a per-application, per-model
2999+or an ad-hoc basis. Using multiple database connections is optional.
3000+
3001+Configuring other database connections
3002+======================================
3003+
3004+Django's default database connection is configured via the settings
3005+DATABASE_ENGINE, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD,
3006+DATABASE_HOST, and DATABASE_PORT. Other connections are configured via
3007+the OTHER_DATABASES setting. Define OTHER_DATABASES as a dict, with a
3008+name for each connection as the key and a dict of settings as the
3009+value. In each OTHER_DATABASES entry (called a "named connection"),
3010+the keys are the same as the DATABASE_ENGINE, etc, settings used to
3011+configure the default connection. All keys are optional; any that are
3012+missing in a named connection's settings will inherit their values
3013+from the default connection.
3014+
3015+Here's an example::
3016+
3017+    DATABASE_ENGINE = 'postgresql'
3018+    DATABASE_NAME = 'django_apps'
3019+    DATABASE_USER = 'default_user'
3020+    DATABASE_PASSWORD = 'xxx'
3021+       
3022+    OTHER_DATABASES = {
3023+        'local': { 'DATABASE_ENGINE': 'sqlite3',
3024+                   'DATABASE_NAME': '/tmp/cache.db' },
3025+        'public': { 'DATABASE_HOST': 'public',
3026+                    'DATABASE_USER': 'public_user',
3027+                    'DATABASE_PASSWORD': 'xxx' }
3028+        'private': { 'DATABASE_HOST': 'private',
3029+                     'DATABASE_USER': 'private_user',
3030+                     'DATABASE_PASSWORD': 'xxx' }
3031+    }
3032+
3033+In addition to the DATABASE_* settings, each named connection in
3034+OTHER_DATABASES may optionally include a MODELS setting. This should
3035+be a list of app or app.model names, and is used to configure which
3036+models should use this connection instead of the default connection.
3037+
3038+Here's the example above, with ``MODELS``::
3039+
3040+    OTHER_DATABASES = {
3041+        'local': { 'DATABASE_ENGINE': 'sqlite3',
3042+                   'DATABASE_NAME': '/tmp/cache.db',
3043+                   # A model name: only the model ContentItem
3044+                   # with the app_label myapp will use this connection
3045+                   'MODELS': ['myapp.ContentItem'] },
3046+        'public': { 'DATABASE_HOST': 'public',
3047+                    'DATABASE_USER': 'public_user',
3048+                    'DATABASE_PASSWORD': 'xxx',
3049+                   # Two models in myapp will use the connection
3050+                    # named 'public', as will ALL models in
3051+                    # django.contribe.comments
3052+                   'MODELS': ['myapp.Blog','myapp.Article',
3053+                               'django.contrib.comments' ] }
3054+        # No models or apps are configured to use the private db
3055+        'private': { 'DATABASE_HOST': 'private',
3056+                     'DATABASE_USER': 'private_user',
3057+                     'DATABASE_PASSWORD': 'xxx' }
3058+    }
3059+
3060+Accessing a model's connection
3061+==============================
3062+
3063+Each manager has a ``db`` attribute that can be used to access the model's
3064+connection. Access the ``db`` attribute of a model's manager to obtain the
3065+model's currently configured connection.
3066+
3067+Example::
3068+
3069+    from django.db import models
3070+
3071+    class Blog(models.Model)
3072+        name = models.CharField(maxlength=50)
3073+
3074+    class Article(models.Model)
3075+       blog = models.ForeignKey(Blog)
3076+        title = models.CharField(maxlength=100)
3077+        slug = models.SlugField()
3078+        summary = models.CharField(maxlength=500)
3079+        body = models.TextField()
3080+
3081+    class ContentItem(models.Model)
3082+        slug = models.SlugField()
3083+        mimetype = models.CharField(maxlength=50)
3084+       file = models.FileField()
3085+       
3086+    # Get a ConnectionInfo instance that describes the connection
3087+    article_db = Article.objects.db
3088+   
3089+    # Get a connection and a cursor
3090+    connection = article_db.connection
3091+    cursor = connection.cursor()
3092+
3093+    # Get the ``quote_name`` function from the backend
3094+    qn = article_db.backend.quote_name
3095+
3096+Ordinarily you won't have to access a model's connection directly;
3097+just use the model and manager normally and they will use the
3098+connection configured for the model.
3099+
3100+ConnectionInfo objects
3101+======================
3102+
3103+FIXME Describe the ConnectionInfo object and each of its attributes.
3104+
3105+
3106+Accessing connections by name
3107+=============================
3108+
3109+Access named connections directly through
3110+``django.db.connections``. Each entry in ``django.db.connections`` is
3111+a ``ConnectionInfo`` instance bound to the settings configured in the
3112+OTHER_DATABASES entry under the same key.
3113+
3114+Example::
3115+
3116+    from django.db import connections
3117+
3118+    private_db = connections['private']
3119+    cursor = private_db.connection.cursor()
3120+
3121+
3122+Using transactions with other database connections
3123+==================================================
3124+
3125+Transaction managed state applies across all connections
3126+commit/rollback apply to all connections by default
3127+but you can specify individual connections or lists or dicts of connections
3128+
3129+
3130+Changing model connections on the fly
3131+=====================================
3132+
3133+Here's an example of primitive mirroring::
3134+
3135+    # Read all articles from the private db
3136+    # Note that we pull the articles into a list; this is necessary
3137+    # because query sets are lazy. If we were to change the model's
3138+    # connection without copying the articles into a local list, we'd
3139+    # wind up reading from public instead of private.
3140+
3141+    Article.objects.db = connections['private']
3142+    all_articles = list(Article.objects.all())
3143+   
3144+    # Save each article in the public db
3145+    Article.objects.db = connections['public']
3146+    for article in all_articles:
3147+        article.save()
3148+
3149+Thread and request isolation
3150+============================
3151+
3152+connections close after each request
3153+connection settings are thread-local
3154+
3155
3156Property changes on:
3157___________________________________________________________________
3158Name: svk:merge
3159 -bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:1054
3160 +bbbf0183-8649-de40-ae96-8c00a8d06f91:/local/django/db2_9:3912
3161 +bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:6433
3162
Back to Top