Ticket #4747: mymultidb_trunk7534_20080520.diff

File mymultidb_trunk7534_20080520.diff, 143.1 KB (added by Koen Biermans <koen.biermans@…>, 16 years ago)

effort to get multidb into trunk r7534

Line 
1=== django/test/simple.py
2==================================================================
3--- django/test/simple.py (/mirror/django/trunk) (revision 5420)
4
5+++ django/test/simple.py (/local/django/mymultidb) (revision 5420)
6
7@@ -138,10 +138,11 @@
8
9 for test in extra_tests:
10 suite.addTest(test)
11
12- old_name = settings.DATABASE_NAME
13+ old_database_name = settings.DATABASE_NAME
14+ old_other_databases = settings.OTHER_DATABASES
15 create_test_db(verbosity, autoclobber=not interactive)
16 result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
17- destroy_test_db(old_name, verbosity)
18+ destroy_test_db(old_database_name, old_other_databases, verbosity)
19
20 teardown_test_environment()
21
22=== django/test/utils.py
23==================================================================
24--- django/test/utils.py (/mirror/django/trunk) (revision 5420)
25
26+++ django/test/utils.py (/local/django/mymultidb) (revision 5420)
27
28@@ -1,6 +1,6 @@
29
30 import sys, time, os
31-from django.conf import settings
32-from django.db import connection, get_creation_module
33+from django.conf import settings, UserSettingsHolder
34+from django.db import connections, get_creation_module, _default
35 from django.core import mail
36 from django.core.management import call_command
37 from django.dispatch import dispatcher
38@@ -78,27 +78,74 @@
39
40 elif hasattr(connection.connection, "set_isolation_level"):
41 connection.connection.set_isolation_level(0)
42
43-def get_mysql_create_suffix():
44+def get_mysql_create_suffix(settings):
45 suffix = []
46- if settings.TEST_DATABASE_CHARSET:
47- suffix.append('CHARACTER SET %s' % settings.TEST_DATABASE_CHARSET)
48- if settings.TEST_DATABASE_COLLATION:
49- suffix.append('COLLATE %s' % settings.TEST_DATABASE_COLLATION)
50+ if settings.DATABASE_CHARSET:
51+ suffix.append('CHARACTER SET %s' % settings.DATABASE_CHARSET)
52+ if settings.DATABASE_COLLATION:
53+ suffix.append('COLLATE %s' % settings.DATABASE_COLLATION)
54 return ' '.join(suffix)
55
56 def get_postgresql_create_suffix():
57- assert settings.TEST_DATABASE_COLLATION is None, "PostgreSQL does not support collation setting at database creation time."
58- if settings.TEST_DATABASE_CHARSET:
59- return "WITH ENCODING '%s'" % settings.TEST_DATABASE_CHARSET
60+ assert settings.DATABASE_COLLATION is None, "PostgreSQL does not support collation setting at database creation time."
61+ if settings.DATABASE_CHARSET:
62+ return "WITH ENCODING '%s'" % settings.DATABASE_CHARSET
63 return ''
64
65 def create_test_db(verbosity=1, autoclobber=False):
66+
67+ if settings.DATABASE_ENGINE == "sqlite3":
68+ if settings.TEST_DATABASE_NAME and settings.TEST_DATABASE_NAME != ":memory:":
69+ TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
70+ else:
71+ TEST_DATABASE_NAME = ":memory:"
72+ else:
73+ if settings.TEST_DATABASE_NAME:
74+ TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
75+ else:
76+ TEST_DATABASE_NAME = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
77+
78+ settings.DATABASE_NAME = TEST_DATABASE_NAME
79+ settings.DATABASE_CHARSET = settings.TEST_DATABASE_CHARSET
80+ settings.DATABASE_COLLATION = settings.TEST_DATABASE_COLLATION
81+
82+ # default database
83+ _create_test_db(settings, verbosity=verbosity, autoclobber=autoclobber)
84+
85+ # other databases
86+ settings.OTHER_DATABASES = settings.TEST_OTHER_DATABASES
87+ for db in settings.OTHER_DATABASES:
88+ info = settings.OTHER_DATABASES[db]
89+ database = UserSettingsHolder(settings)
90+ for k, v in info.items():
91+ setattr(database, k, v)
92+ _create_test_db(database, verbosity=verbosity, autoclobber=autoclobber, connection_name=db)
93+
94+## for cnx in connections:
95+## connections[cnx].connection.close()
96+
97+ call_command('syncdb', verbosity=verbosity, interactive=False)
98+
99+ if settings.CACHE_BACKEND.startswith('db://'):
100+ cache_name = settings.CACHE_BACKEND[len('db://'):]
101+ call_command('createcachetable', cache_name)
102+
103+ # Get a cursor (even though we don't need one yet). This has
104+ # the side effect of initializing the test database.
105+## for cnx in connections:
106+## cursor = connections[cnx].connection.cursor()
107+## cursor = connection.cursor()
108+
109+ return TEST_DATABASE_NAME
110+
111+def _create_test_db(settings, verbosity=1, autoclobber=False, connection_name=_default):
112 """
113 Creates a test database, prompting the user for confirmation if the
114 database already exists. Returns the name of the test database created.
115 """
116+ connection = connections[connection_name].connection
117 # If the database backend wants to create the test DB itself, let it
118- creation_module = get_creation_module()
119+ creation_module = connections[connection_name].get_creation_module()
120 if hasattr(creation_module, "create_test_db"):
121 creation_module.create_test_db(settings, connection, verbosity, autoclobber)
122 return
123@@ -109,19 +156,18 @@
124
125 # in-memory database. Using the TEST_DATABASE_NAME setting you can still choose
126 # to run on a physical database.
127 if settings.DATABASE_ENGINE == "sqlite3":
128- if settings.TEST_DATABASE_NAME and settings.TEST_DATABASE_NAME != ":memory:":
129- TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
130+ if settings.DATABASE_NAME != ":memory:":
131 # Erase the old test database
132 if verbosity >= 1:
133 print "Destroying old test database..."
134- if os.access(TEST_DATABASE_NAME, os.F_OK):
135+ if os.access(settings.DATABASE_NAME, os.F_OK):
136 if not autoclobber:
137- confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % TEST_DATABASE_NAME)
138+ confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % settings.DATABASE_NAME)
139 if autoclobber or confirm == 'yes':
140 try:
141 if verbosity >= 1:
142 print "Destroying old test database..."
143- os.remove(TEST_DATABASE_NAME)
144+ os.remove(settings.DATABASE_NAME)
145 except Exception, e:
146 sys.stderr.write("Got an error deleting the old test database: %s\n" % e)
147 sys.exit(2)
148@@ -130,20 +176,15 @@
149
150 sys.exit(1)
151 if verbosity >= 1:
152 print "Creating test database..."
153- else:
154- TEST_DATABASE_NAME = ":memory:"
155 else:
156 suffix = {
157 'postgresql': get_postgresql_create_suffix,
158 'postgresql_psycopg2': get_postgresql_create_suffix,
159 'mysql': get_mysql_create_suffix,
160 'mysql_old': get_mysql_create_suffix,
161- }.get(settings.DATABASE_ENGINE, lambda: '')()
162- if settings.TEST_DATABASE_NAME:
163- TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME
164- else:
165- TEST_DATABASE_NAME = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
166+ }.get(settings.DATABASE_ENGINE, lambda: '')(settings)
167
168+ connection = connections[connection_name].connection
169 qn = connection.ops.quote_name
170
171 # Create the test database and connect to it. We need to autocommit
172@@ -172,37 +213,40 @@
173
174 print "Tests cancelled."
175 sys.exit(1)
176
177- connection.close()
178- settings.DATABASE_NAME = TEST_DATABASE_NAME
179
180- call_command('syncdb', verbosity=verbosity, interactive=False)
181+def destroy_test_db(old_database_name, old_databases, verbosity=1):
182+ if verbosity >= 1:
183+ print "Destroying test database(s)..."
184+## connection.close()
185+ for cnx in connections.keys():
186+ connections[cnx].close()
187+## connections.reset()
188+ TEST_DATABASE_NAME = settings.DATABASE_NAME
189+ settings.DATABASE_NAME = old_database_name
190+ _destroy_test_db(settings, TEST_DATABASE_NAME, verbosity=verbosity)
191+ TEST_OTHER_DATABASES = settings.OTHER_DATABASES
192+ settings.OTHER_DATABASES = old_databases
193+ for db in TEST_OTHER_DATABASES:
194+ info = TEST_OTHER_DATABASES[db]
195+ database = UserSettingsHolder(settings)
196+ for k, v in info.items():
197+ setattr(database, k, v)
198+ if db in old_databases:
199+ _destroy_test_db(database, info['DATABASE_NAME'], verbosity=verbosity, connection_name=db)
200
201- if settings.CACHE_BACKEND.startswith('db://'):
202- cache_name = settings.CACHE_BACKEND[len('db://'):]
203- call_command('createcachetable', cache_name)
204-
205- # Get a cursor (even though we don't need one yet). This has
206- # the side effect of initializing the test database.
207- cursor = connection.cursor()
208-
209- return TEST_DATABASE_NAME
210-
211-def destroy_test_db(old_database_name, verbosity=1):
212+def _destroy_test_db(settings, TEST_DATABASE_NAME, verbosity=1, connection_name=_default):
213+ connection = connections[connection_name].connection
214 # If the database wants to drop the test DB itself, let it
215- creation_module = get_creation_module()
216+ creation_module = connections[connection_name].get_creation_module()
217 if hasattr(creation_module, "destroy_test_db"):
218 creation_module.destroy_test_db(settings, connection, old_database_name, verbosity)
219 return
220
221- if verbosity >= 1:
222- print "Destroying test database..."
223- connection.close()
224- TEST_DATABASE_NAME = settings.DATABASE_NAME
225- settings.DATABASE_NAME = old_database_name
226 if settings.DATABASE_ENGINE == "sqlite3":
227 if TEST_DATABASE_NAME and TEST_DATABASE_NAME != ":memory:":
228 # Remove the SQLite database file
229- os.remove(TEST_DATABASE_NAME)
230+## os.remove(TEST_DATABASE_NAME)
231+ pass
232 else:
233 # Remove the test database to clean up after
234 # ourselves. Connect to the previous database (not the test database)
235=== django/db/models/sql/where.py
236==================================================================
237--- django/db/models/sql/where.py (/mirror/django/trunk) (revision 5420)
238
239+++ django/db/models/sql/where.py (/local/django/mymultidb) (revision 5420)
240
241@@ -4,11 +4,12 @@
242
243 import datetime
244
245 from django.utils import tree
246-from django.db import connection
247 from django.db.models.fields import Field
248 from django.db.models.query_utils import QueryWrapper
249 from datastructures import EmptyResultSet, FullResultSet
250
251+from copy import deepcopy
252+
253 # Connection types
254 AND = 'AND'
255 OR = 'OR'
256@@ -26,6 +27,20 @@
257
258 """
259 default = AND
260
261+ def __init__(self, connection, *args, **kwargs):
262+ self.connection = connection
263+ super(WhereNode, self).__init__(*args, **kwargs)
264+
265+ def __deepcopy__(self, memo):
266+ x = WhereNode.__new__(WhereNode)
267+ memo[id(self)] = x
268+ for n, v in self.__dict__.iteritems():
269+ if n != 'connection':
270+ setattr(x, n, deepcopy(v, memo))
271+ else:
272+ setattr(x, n, self.connection)
273+ return x
274+
275 def as_sql(self, node=None, qn=None):
276 """
277 Returns the SQL version of the where clause and the value to be
278@@ -38,7 +53,7 @@
279
280 if node is None:
281 node = self
282 if not qn:
283- qn = connection.ops.quote_name
284+ qn = self.connection.ops.quote_name
285 if not node.children:
286 return None, []
287 result = []
288@@ -99,11 +114,12 @@
289
290 lhs = '%s.%s' % (qn(table_alias), qn(name))
291 else:
292 lhs = qn(name)
293- db_type = field and field.db_type() or None
294- field_sql = connection.ops.field_cast_sql(db_type) % lhs
295
296+ db_type = field and field.db_type(self.connection.connection_name) or None
297+ field_sql = self.connection.ops.field_cast_sql(db_type) % lhs
298+
299 if isinstance(value, datetime.datetime):
300- cast_sql = connection.ops.datetime_cast_sql()
301+ cast_sql = self.connection.ops.datetime_cast_sql()
302 else:
303 cast_sql = '%s'
304
305@@ -116,11 +132,11 @@
306
307 else:
308 extra = ''
309
310- if lookup_type in connection.operators:
311- format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type),
312+ if lookup_type in self.connection.operators:
313+ format = "%s %%s %s" % (self.connection.ops.lookup_cast(lookup_type),
314 extra)
315 return (format % (field_sql,
316- connection.operators[lookup_type] % cast_sql), params)
317+ self.connection.operators[lookup_type] % cast_sql), params)
318
319 if lookup_type == 'in':
320 if not value:
321@@ -132,15 +148,15 @@
322
323 elif lookup_type in ('range', 'year'):
324 return ('%s BETWEEN %%s and %%s' % field_sql, params)
325 elif lookup_type in ('month', 'day'):
326- return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type,
327+ return ('%s = %%s' % self.connection.ops.date_extract_sql(lookup_type,
328 field_sql), params)
329 elif lookup_type == 'isnull':
330 return ('%s IS %sNULL' % (field_sql, (not value and 'NOT ' or '')),
331 params)
332 elif lookup_type == 'search':
333- return (connection.ops.fulltext_search_sql(field_sql), params)
334+ return (self.connection.ops.fulltext_search_sql(field_sql), params)
335 elif lookup_type in ('regex', 'iregex'):
336- return connection.ops.regex_lookup(lookup_type) % (field_sql, cast_sql), params
337+ return self.connection.ops.regex_lookup(lookup_type) % (field_sql, cast_sql), params
338
339 raise TypeError('Invalid lookup_type: %r' % lookup_type)
340
341=== django/db/models/sql/query.py
342==================================================================
343--- django/db/models/sql/query.py (/mirror/django/trunk) (revision 5420)
344
345+++ django/db/models/sql/query.py (/local/django/mymultidb) (revision 5420)
346
347@@ -60,7 +60,7 @@
348
349 # SQL-related attributes
350 self.select = []
351 self.tables = [] # Aliases in the order they are created.
352- self.where = where()
353+ self.where = where(self.connection)
354 self.where_class = where
355 self.group_by = []
356 self.having = []
357@@ -215,7 +215,7 @@
358
359 obj.related_select_cols = []
360 obj.related_select_fields = []
361 if obj.distinct and len(obj.select) > 1:
362- obj = self.clone(CountQuery, _query=obj, where=self.where_class(),
363+ obj = self.clone(CountQuery, _query=obj, where=self.where_class(self.connection),
364 distinct=False)
365 obj.select = []
366 obj.extra_select = {}
367@@ -346,10 +346,10 @@
368
369 self.where.add(EverythingNode(), AND)
370 elif self.where:
371 # rhs has an empty where clause.
372- w = self.where_class()
373+ w = self.where_class(self.connection)
374 w.add(EverythingNode(), AND)
375 else:
376- w = self.where_class()
377+ w = self.where_class(self.connection)
378 self.where.add(w, connector)
379
380 # Selection columns and extra extensions are those provided by 'rhs'.
381=== django/db/models/sql/subqueries.py
382==================================================================
383--- django/db/models/sql/subqueries.py (/mirror/django/trunk) (revision 5420)
384
385+++ django/db/models/sql/subqueries.py (/local/django/mymultidb) (revision 5420)
386
387@@ -47,7 +47,7 @@
388
389 for related in cls._meta.get_all_related_many_to_many_objects():
390 if not isinstance(related.field, generic.GenericRelation):
391 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
392- where = self.where_class()
393+ where = self.where_class(self.connection)
394 where.add((None, related.field.m2m_reverse_name(),
395 related.field, 'in',
396 pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
397@@ -55,14 +55,14 @@
398
399 self.do_query(related.field.m2m_db_table(), where)
400
401 for f in cls._meta.many_to_many:
402- w1 = self.where_class()
403+ w1 = self.where_class(self.connection)
404 if isinstance(f, generic.GenericRelation):
405 from django.contrib.contenttypes.models import ContentType
406 field = f.rel.to._meta.get_field(f.content_type_field_name)
407 w1.add((None, field.column, field, 'exact',
408 ContentType.objects.get_for_model(cls).id), AND)
409 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
410- where = self.where_class()
411+ where = self.where_class(self.connection)
412 where.add((None, f.m2m_column_name(), f, 'in',
413 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
414 AND)
415@@ -79,7 +79,7 @@
416
417 lot of values in pk_list.
418 """
419 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
420- where = self.where_class()
421+ where = self.where_class(self.connection)
422 field = self.model._meta.pk
423 where.add((None, field.column, field, 'in',
424 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
425@@ -178,7 +178,7 @@
426
427
428 # Now we adjust the current query: reset the where clause and get rid
429 # of all the tables we don't need (since they're in the sub-select).
430- self.where = self.where_class()
431+ self.where = self.where_class(self.connection)
432 if self.related_updates or must_pre_select:
433 # Either we're using the idents in multiple update queries (so
434 # don't want them to change), or the db backend doesn't support
435@@ -202,7 +202,7 @@
436
437 This is used by the QuerySet.delete_objects() method.
438 """
439 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
440- self.where = self.where_class()
441+ self.where = self.where_class(self.connection)
442 f = self.model._meta.pk
443 self.where.add((None, f.column, f, 'in',
444 pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
445=== django/db/models/base.py
446==================================================================
447--- django/db/models/base.py (/mirror/django/trunk) (revision 5420)
448
449+++ django/db/models/base.py (/local/django/mymultidb) (revision 5420)
450
451@@ -12,7 +12,7 @@
452
453 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
454 from django.db.models.query import delete_objects, Q
455 from django.db.models.options import Options, AdminOptions
456-from django.db import connection, transaction
457+from django.db import transaction
458 from django.db.models import signals
459 from django.db.models.loading import register_models, get_model
460 from django.dispatch import dispatcher
461@@ -329,7 +329,7 @@
462
463 result = manager._insert(values, return_id=update_pk)
464 else:
465 # Create a new record with defaults for everything.
466- result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
467+ result = manager._insert([(meta.pk, manager.db.connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
468
469 if update_pk:
470 setattr(self, meta.pk.attname, result)
471@@ -419,7 +419,7 @@
472
473 def _get_next_or_previous_in_order(self, is_next):
474 cachename = "__%s_order_cache" % is_next
475 if not hasattr(self, cachename):
476- qn = connection.ops.quote_name
477+ qn = self._default_manager.db.connection.ops.quote_name
478 op = is_next and '>' or '<'
479 order = not is_next and '-_order' or '_order'
480 order_field = self._meta.order_with_respect_to
481=== django/db/models/manager.py
482==================================================================
483--- django/db/models/manager.py (/mirror/django/trunk) (revision 5420)
484
485+++ django/db/models/manager.py (/local/django/mymultidb) (revision 5420)
486
487@@ -1,8 +1,9 @@
488
489 import copy
490
491 from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
492+from django.db import ConnectionInfoDescriptor
493 from django.dispatch import dispatcher
494-from django.db.models import signals
495+from django.db.models import signals, get_apps, get_models
496 from django.db.models.fields import FieldDoesNotExist
497
498 def ensure_default_manager(sender):
499@@ -22,6 +23,8 @@
500
501 # Tracks each time a Manager instance is created. Used to retain order.
502 creation_counter = 0
503
504+ db = ConnectionInfoDescriptor()
505+
506 def __init__(self):
507 super(Manager, self).__init__()
508 # Increase the creation counter, and save our local copy.
509=== django/db/models/options.py
510==================================================================
511--- django/db/models/options.py (/mirror/django/trunk) (revision 5420)
512
513+++ django/db/models/options.py (/local/django/mymultidb) (revision 5420)
514
515@@ -53,6 +53,8 @@
516
517 self.module_name = self.object_name.lower()
518 self.verbose_name = get_verbose_name(self.object_name)
519
520+ self.model = cls
521+
522 # Next, apply any overridden values from 'class Meta'.
523 if self.meta:
524 meta_attrs = self.meta.__dict__.copy()
525@@ -84,8 +86,8 @@
526
527 del self.meta
528
529 def _prepare(self, model):
530- from django.db import connection
531 from django.db.backends.util import truncate_name
532+ from django.db import connections, connection_name_app_model
533 if self.order_with_respect_to:
534 self.order_with_respect_to = self.get_field(self.order_with_respect_to)
535 self.ordering = ('_order',)
536@@ -107,7 +109,8 @@
537
538 # If the db_table wasn't provided, use the app_label + module_name.
539 if not self.db_table:
540 self.db_table = "%s_%s" % (self.app_label, self.module_name)
541- self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
542+ connection_name = connection_name_app_model(self.app_label, self.object_name)
543+ self.db_table = truncate_name(self.db_table, connections[connection_name].connection.ops.max_name_length())
544
545 def add_field(self, field):
546 # Insert the given field in the order in which it was created, using
547=== django/db/models/__init__.py
548==================================================================
549--- django/db/models/__init__.py (/mirror/django/trunk) (revision 5420)
550
551+++ django/db/models/__init__.py (/local/django/mymultidb) (revision 5420)
552
553@@ -1,7 +1,6 @@
554
555 from django.conf import settings
556 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
557 from django.core import validators
558-from django.db import connection
559 from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
560 from django.db.models.query import Q
561 from django.db.models.manager import Manager
562=== django/db/models/fields/__init__.py
563==================================================================
564--- django/db/models/fields/__init__.py (/mirror/django/trunk) (revision 5420)
565
566+++ django/db/models/fields/__init__.py (/local/django/mymultidb) (revision 5420)
567
568@@ -7,7 +7,7 @@
569
570 except ImportError:
571 from django.utils import _decimal as decimal # for Python 2.3
572
573-from django.db import get_creation_module
574+from django.db import connections
575 from django.db.models import signals
576 from django.db.models.query_utils import QueryWrapper
577 from django.dispatch import dispatcher
578@@ -144,7 +144,7 @@
579
580 """
581 return value
582
583- def db_type(self):
584+ def db_type(self, connection):
585 """
586 Returns the database column data type for this field, taking into
587 account the DATABASE_ENGINE setting.
588@@ -165,7 +165,7 @@
589
590 # can implement db_type() instead of get_internal_type() to specify
591 # exactly which wacky database column type you want to use.
592 try:
593- return get_creation_module().DATA_TYPES[self.get_internal_type()] % self.__dict__
594+ return connections[connection].get_creation_module().DATA_TYPES[self.get_internal_type()] % self.__dict__
595 except KeyError:
596 return None
597
598=== django/db/models/fields/related.py
599==================================================================
600--- django/db/models/fields/related.py (/mirror/django/trunk) (revision 5420)
601
602+++ django/db/models/fields/related.py (/local/django/mymultidb) (revision 5420)
603
604@@ -1,4 +1,4 @@
605
606-from django.db import connection, transaction
607+from django.db import transaction
608 from django.db.models import signals, get_model
609 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class
610 from django.db.models.related import RelatedObject
611@@ -368,6 +368,8 @@
612
613 # target_col_name: the PK colname in join_table for the target object
614 # *objs - objects to add. Either object instances, or primary keys of object instances.
615
616+ connection = self.model._default_manager.db.connection
617+
618 # If there aren't any objects, there is nothing to do.
619 if objs:
620 # Check that all the objects are of the right type
621@@ -391,13 +393,15 @@
622
623 cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
624 (self.join_table, source_col_name, target_col_name),
625 [self._pk_val, obj_id])
626- transaction.commit_unless_managed()
627+ transaction.commit_unless_managed(connection)
628
629 def _remove_items(self, source_col_name, target_col_name, *objs):
630 # source_col_name: the PK colname in join_table for the source object
631 # target_col_name: the PK colname in join_table for the target object
632 # *objs - objects to remove
633
634+ connection = self.model._default_manager.db.connection
635+
636 # If there aren't any objects, there is nothing to do.
637 if objs:
638 # Check that all the objects are of the right type
639@@ -413,15 +417,16 @@
640
641 (self.join_table, source_col_name,
642 target_col_name, ",".join(['%s'] * len(old_ids))),
643 [self._pk_val] + list(old_ids))
644- transaction.commit_unless_managed()
645+ transaction.commit_unless_managed(connection)
646
647 def _clear_items(self, source_col_name):
648 # source_col_name: the PK colname in join_table for the source object
649+ connection = self.model._default_manager.db.connection
650 cursor = connection.cursor()
651 cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
652 (self.join_table, source_col_name),
653 [self._pk_val])
654- transaction.commit_unless_managed()
655+ transaction.commit_unless_managed(connection)
656
657 return ManyRelatedManager
658
659@@ -445,7 +450,7 @@
660
661 superclass = rel_model._default_manager.__class__
662 RelatedManager = create_many_related_manager(superclass)
663
664- qn = connection.ops.quote_name
665+ qn = rel_model._default_manager.db.connection.ops.quote_name
666 manager = RelatedManager(
667 model=rel_model,
668 core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
669@@ -486,7 +491,7 @@
670
671 superclass = rel_model._default_manager.__class__
672 RelatedManager = create_many_related_manager(superclass)
673
674- qn = connection.ops.quote_name
675+ qn = rel_model._default_manager.db.connection.ops.quote_name
676 manager = RelatedManager(
677 model=rel_model,
678 core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
679@@ -673,15 +678,15 @@
680
681 defaults.update(kwargs)
682 return super(ForeignKey, self).formfield(**defaults)
683
684- def db_type(self):
685+ def db_type(self, connection):
686 # The database column type of a ForeignKey is the column type
687 # of the field to which it points. An exception is if the ForeignKey
688 # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField,
689 # in which case the column type is simply that of an IntegerField.
690 rel_field = self.rel.get_related_field()
691 if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)):
692- return IntegerField().db_type()
693- return rel_field.db_type()
694+ return IntegerField().db_type(connection)
695+ return rel_field.db_type(connection)
696
697 class OneToOneField(ForeignKey):
698 """
699@@ -828,7 +833,7 @@
700
701 defaults['initial'] = [i._get_pk_val() for i in defaults['initial']]
702 return super(ManyToManyField, self).formfield(**defaults)
703
704- def db_type(self):
705+ def db_type(self, connection):
706 # A ManyToManyField is not represented by a single column,
707 # so return None.
708 return None
709=== django/db/models/query.py
710==================================================================
711--- django/db/models/query.py (/mirror/django/trunk) (revision 5420)
712
713+++ django/db/models/query.py (/local/django/mymultidb) (revision 5420)
714
715@@ -1,7 +1,7 @@
716
717 import warnings
718
719 from django.conf import settings
720-from django.db import connection, transaction, IntegrityError
721+from django.db import transaction, IntegrityError
722 from django.db.models.fields import DateField, FieldDoesNotExist
723 from django.db.models.query_utils import Q
724 from django.db.models import signals, sql
725@@ -20,7 +20,7 @@
726
727 "Represents a lazy database lookup for a set of objects"
728 def __init__(self, model=None, query=None):
729 self.model = model
730- self.query = query or sql.Query(self.model, connection)
731+ self.query = query or sql.Query(self.model, self.model._default_manager.db.connection)
732 self._result_cache = None
733 self._iter = None
734
735@@ -689,10 +689,10 @@
736
737 instance=instance)
738
739 pk_list = [pk for pk,instance in seen_objs[cls]]
740- del_query = sql.DeleteQuery(cls, connection)
741+ del_query = sql.DeleteQuery(cls, cls._default_manager.db.connection)
742 del_query.delete_batch_related(pk_list)
743
744- update_query = sql.UpdateQuery(cls, connection)
745+ update_query = sql.UpdateQuery(cls, cls._default_manager.db.connection)
746 for field in cls._meta.fields:
747 if field.rel and field.null and field.rel.to in seen_objs:
748 update_query.clear_related(field, pk_list)
749@@ -701,7 +701,7 @@
750
751 for cls in ordered_classes:
752 seen_objs[cls].reverse()
753 pk_list = [pk for pk,instance in seen_objs[cls]]
754- del_query = sql.DeleteQuery(cls, connection)
755+ del_query = sql.DeleteQuery(cls, cls._default_manager.db.connection)
756 del_query.delete_batch(pk_list)
757
758 # Last cleanup; set NULLs where there once was a reference to the
759@@ -724,7 +724,7 @@
760
761 the InsertQuery class and is how Model.save() is implemented. It is not
762 part of the public API.
763 """
764- query = sql.InsertQuery(model, connection)
765+ query = sql.InsertQuery(model, model._default_manager.db.connection)
766 query.insert_values(values, raw_values)
767 return query.execute_sql(return_id)
768
769=== django/db/__init__.py
770==================================================================
771--- django/db/__init__.py (/mirror/django/trunk) (revision 5420)
772
773+++ django/db/__init__.py (/local/django/mymultidb) (revision 5420)
774
775@@ -1,74 +1,428 @@
776
777 import os
778-from django.conf import settings
779+from django.conf import settings, UserSettingsHolder
780 from django.core import signals
781 from django.core.exceptions import ImproperlyConfigured
782 from django.dispatch import dispatcher
783 from django.utils.functional import curry
784
785+try:
786+ # Only exists in Python 2.4+
787+ from threading import local
788+except ImportError:
789+ # Import copy of _thread_local.py from Python 2.4
790+ from django.utils._threading_local import local
791+
792 __all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
793
794+
795+# singleton to represent the default connection in connections
796+class dummy(object):
797+ def __repr__(self):
798+ return self.__str__()
799+ def __str__(self):
800+ return '<default>'
801+_default = dummy()
802+del dummy
803+
804+
805+# storage for local default connection
806+_local = local()
807+
808 if not settings.DATABASE_ENGINE:
809 settings.DATABASE_ENGINE = 'dummy'
810
811-try:
812- # Most of the time, the database backend will be one of the official
813- # backends that ships with Django, so look there first.
814- _import_path = 'django.db.backends.'
815- backend = __import__('%s%s.base' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
816- creation = __import__('%s%s.creation' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
817-except ImportError, e:
818- # If the import failed, we might be looking for a database backend
819- # distributed external to Django. So we'll try that next.
820- try:
821- _import_path = ''
822- backend = __import__('%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
823- creation = __import__('%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
824- except ImportError, e_user:
825- # The database backend wasn't found. Display a helpful error message
826- # listing all possible (built-in) database backends.
827- backend_dir = os.path.join(__path__[0], 'backends')
828- 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')]
829- available_backends.sort()
830- if settings.DATABASE_ENGINE not in available_backends:
831- raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
832- (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
833+
834+def connect(settings, name, **kw):
835+ """Connect to the database specified in settings. Returns a
836+ ConnectionInfo on success, raises ImproperlyConfigured if the
837+ settings don't specify a valid database connection.
838+ """
839+ return ConnectionInfo(settings, name, **kw)
840+
841+
842+class ConnectionInfo(object):
843+ """Encapsulates all information about a connection and the backend
844+ to which it belongs. Provides methods for loading backend
845+ creation, introspection, and shell modules, closing the
846+ connection, and resetting the connection's query log.
847+ """
848+ def __init__(self, settings=None, name=_default, **kw):
849+ super(ConnectionInfo, self).__init__(**kw)
850+ if settings is None:
851+ from django.conf import settings
852+ if not settings.DATABASE_OPTIONS:
853+ settings.DATABASE_OPTIONS = {}
854+ self.settings = settings
855+ self.name = name
856+ self.backend = self.load_backend()
857+ self.connection = self.backend.DatabaseWrapper(settings, name)
858+ self.DatabaseError = self.backend.DatabaseError
859+
860+ # Register an event that closes the database connection
861+ # when a Django request is finished.
862+ dispatcher.connect(self.close, signal=signals.request_finished)
863+
864+ # Register an event that resets connection.queries
865+ # when a Django request is started.
866+ dispatcher.connect(self.reset_queries, signal=signals.request_started)
867+
868+ def __repr__(self):
869+ return "Connection: %s, %r (ENGINE=%s NAME=%s)" \
870+ % (self.name, self.connection,
871+ self.settings.DATABASE_ENGINE,
872+ self.settings.DATABASE_NAME)
873+
874+ def name(self):
875+ return self.name
876+
877+ def close(self):
878+ """Close connection"""
879+ self.connection.close()
880+
881+ def _import_database_module(self, import_path='', module_name=''):
882+ """Lazily import a database module when requested."""
883+ return __import__('%s%s.%s' % (import_path, self.settings.DATABASE_ENGINE, module_name), {}, {}, [''])
884+
885+ # We don't want to import the introspect module unless someone asks for it, so
886+ # lazily load it on demmand.
887+ def get_introspection_module(self):
888+ return self._import_database_module(self._import_path, 'introspection')
889+
890+ def get_creation_module(self):
891+ return self._import_database_module(self._import_path, 'creation')
892+
893+ def load_backend(self):
894+ try:
895+ # Most of the time, the database backend will be one of the official
896+ # backends that ships with Django, so look there first.
897+ self._import_path = 'django.db.backends.'
898+ backend = __import__('%s%s.base' % (self._import_path,
899+ self.settings.DATABASE_ENGINE), {}, {}, [''])
900+ except ImportError, e:
901+ # If the import failed, we might be looking for a database backend
902+ # distributed external to Django. So we'll try that next.
903+ try:
904+ _import_path = ''
905+ backend = __import__('%s.base' % self.settings.DATABASE_ENGINE, {}, {}, [''])
906+ except ImportError, e_user:
907+ # The database backend wasn't found. Display a helpful error message
908+ # listing all possible (built-in) database backends.
909+ backend_dir = os.path.join(__path__[0], 'backends')
910+ 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')]
911+ available_backends.sort()
912+ if self.settings.DATABASE_ENGINE not in available_backends:
913+ raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
914+ (self.settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
915+ else:
916+ raise # If there's some other error, this must be an error in Django itself.
917+ return backend
918+
919+ # We want runshell() to work the same way, but we have to treat it a
920+ # little differently (since it just runs instead of returning a module like
921+ # the above) and wrap the lazily-loaded runshell() method.
922+ def runshell(self):
923+ return lambda: _import_database_module(_import_path, "client").runshell()
924+
925+ def reset_queries(self):
926+ """Reset log of queries executed by connection"""
927+ self.connection.queries = []
928+
929+class LazyConnectionManager(object):
930+ """Manages named connections lazily, instantiating them as
931+ they are requested.
932+ """
933+ def __init__(self):
934+ self.local = local()
935+ self.local.connections = {}
936+
937+ # Reset connections on request finish, to make sure each request can
938+ # load the correct connections for its settings
939+ dispatcher.connect(self.reset, signal=signals.request_finished)
940+
941+ def __iter__(self):
942+ # Iterates only over *active* connections, not all possible
943+ # connections
944+ return iter(self.local.connections.keys())
945+
946+ def __getattr__(self, attr):
947+ return getattr(self.local.connections, attr)
948+
949+ def __getitem__(self, k):
950+ try:
951+ return self.local.connections[k]
952+ except (AttributeError, KeyError):
953+ return self.connect(k)
954+
955+ def __setitem__(self, k, v):
956+ try:
957+ self.local.connections[k] = v
958+ except AttributeError:
959+ # First access in thread
960+ self.local.connections = {k: v}
961+
962+ def connect(self, name):
963+ """Return the connection with this name in
964+ settings.OTHER_DATABASES. Creates the connection if it doesn't yet
965+ exist. Reconnects if it does. If the name requested is the default
966+ connection (a singleton defined in django.db), then the default
967+ connection is returned.
968+ """
969+ try:
970+ cnx = self.local.connections
971+ except AttributeError:
972+ cnx = self.local.connections = {}
973+ if name in cnx:
974+ cnx[name].close()
975+ if name is _default:
976+ cnx[name] = connect(settings, _default)
977+ return cnx[name]
978+ try:
979+ info = settings.OTHER_DATABASES[name]
980+ except KeyError:
981+ raise ImproperlyConfigured, \
982+ "No database connection '%s' has been configured" % name
983+ except AttributeError:
984+ raise ImproperlyConfigured, \
985+ "No OTHER_DATABASES in settings."
986+
987+ # In settings it's a dict, but connect() needs an object:
988+ # pass global settings so that the default connection settings
989+ # can be defaults for the named connections.
990+ database = UserSettingsHolder(settings)
991+ for k, v in info.items():
992+ setattr(database, k, v)
993+ cnx[name] = connect(database, name)
994+ return cnx[name]
995+
996+ def items(self):
997+ # Iterates over *all possible* connections
998+ items = []
999+ for key in self.keys():
1000+ items.append((key, self[key]))
1001+ return items
1002+
1003+ def keys(self):
1004+ # Iterates over *all possible* connections
1005+ keys = [_default]
1006+ try:
1007+ keys.extend(settings.OTHER_DATABASES.keys())
1008+ except AttributeError:
1009+ pass
1010+ return keys
1011+
1012+ def reset(self):
1013+ if not hasattr(self.local, 'connections'):
1014+ return
1015+ if settings.DATABASE_NAME == ':memory:':
1016+## try:
1017+## cnx = self.local.connections
1018+## except AttributeError:
1019+## cnx = self.local.connections = {}
1020+ self.local.connections = {_default: self.local.connections[_default]}
1021+## for c in cnx:
1022+## if c != _default:
1023+## del self.local.connections[c]
1024 else:
1025- raise # If there's some other error, this must be an error in Django itself.
1026+ self.local.connections = {}
1027
1028-def _import_database_module(import_path='', module_name=''):
1029- """Lazily import a database module when requested."""
1030- return __import__('%s%s.%s' % (import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
1031+def model_connection_name(klass):
1032+ """Get the connection name that a model is configured to use, with the
1033+ current settings.
1034+ """
1035+ app = klass._meta.app_label
1036+ model = klass.__name__
1037+ app_model = "%s.%s" % (app, model)
1038
1039-# We don't want to import the introspect module unless someone asks for it, so
1040-# lazily load it on demmand.
1041-get_introspection_module = curry(_import_database_module, _import_path, 'introspection')
1042+ # Quick exit if no OTHER_DATABASES defined
1043+ if (not hasattr(settings, 'OTHER_DATABASES')
1044+ or not settings.OTHER_DATABASES):
1045+ return _default
1046+ # Look in MODELS for the best match: app_label.Model. If that isn't
1047+ # found, take just app_label. If nothing is found, use the default
1048+ maybe = None
1049+ for name, db_def in settings.OTHER_DATABASES.items():
1050+ if not 'MODELS' in db_def:
1051+ continue
1052+ mods = db_def['MODELS']
1053+ # Can't get a better match than this
1054+ if app_model in mods:
1055+ return name
1056+ elif app in mods:
1057+ if maybe is not None:
1058+ raise ImproperlyConfigured, \
1059+ "App %s appears in more than one OTHER_DATABASES " \
1060+ "setting (%s and %s)" % (maybe, name)
1061+ maybe = name
1062+ if maybe:
1063+ return maybe
1064+ # No named connection for this model; use the default
1065+ return _default
1066
1067-def get_creation_module():
1068- return creation
1069+def connection_name_app_model(app_label, model_name):
1070+ """Get the connection name that a model is configured to use, with the
1071+ current settings.
1072+ """
1073+ app_model = "%s.%s" % (app_label, model_name)
1074
1075-# We want runshell() to work the same way, but we have to treat it a
1076-# little differently (since it just runs instead of returning a module like
1077-# the above) and wrap the lazily-loaded runshell() method.
1078-runshell = lambda: _import_database_module(_import_path, "client").runshell()
1079+ # Quick exit if no OTHER_DATABASES defined
1080+ if (not hasattr(settings, 'OTHER_DATABASES')
1081+ or not settings.OTHER_DATABASES):
1082+ return _default
1083+ # Look in MODELS for the best match: app_label.Model. If that isn't
1084+ # found, take just app_label. If nothing is found, use the default
1085+ maybe = None
1086+ for name, db_def in settings.OTHER_DATABASES.items():
1087+ if not 'MODELS' in db_def:
1088+ continue
1089+ mods = db_def['MODELS']
1090+ # Can't get a better match than this
1091+ if app_model in mods:
1092+ return name
1093+ elif app_label in mods:
1094+ if maybe is not None:
1095+ raise ImproperlyConfigured, \
1096+ "App %s appears in more than one OTHER_DATABASES " \
1097+ "setting (%s and %s)" % (maybe, name)
1098+ maybe = name
1099+ if maybe:
1100+ return maybe
1101+ # No named connection for this model; use the default
1102+ return _default
1103
1104-# Convenient aliases for backend bits.
1105-connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
1106-DatabaseError = backend.DatabaseError
1107-IntegrityError = backend.IntegrityError
1108+class ConnectionInfoDescriptor(object):
1109+ """Descriptor used to access database connection information from a
1110+ manager or other connection holder. Keeps a thread-local cache of
1111+ connections per instance, and always returns the same connection for an
1112+ instance in particular thread during a particular request.
1113
1114-# Register an event that closes the database connection
1115-# when a Django request is finished.
1116-dispatcher.connect(connection.close, signal=signals.request_finished)
1117+ Any object that includes a ``model`` attribute that holds a model class
1118+ can use this descriptor to manage connections.
1119+ """
1120
1121-# Register an event that resets connection.queries
1122-# when a Django request is started.
1123+ def __init__(self):
1124+ self.local = local()
1125+ self.local.cnx = {}
1126+ dispatcher.connect(self.reset, signal=signals.request_finished)
1127+
1128+ def __get__(self, instance, type=None):
1129+ if instance is None:
1130+ raise AttributeError, \
1131+ "ConnectionInfo is accessible only through an instance"
1132+ try:
1133+ instance_connection = self.local.cnx.get(instance, None)
1134+ except AttributeError:
1135+ # First access in this thread
1136+ self.local.cnx = {}
1137+ instance_connection = None
1138+ if instance_connection is None:
1139+ instance_connection = self.get_connection(instance)
1140+ self.local.cnx[instance] = instance_connection
1141+ return instance_connection
1142+
1143+ def __set__(self, instance, value):
1144+ try:
1145+ self.local.cnx[instance] = value
1146+ except AttributeError:
1147+ # First access in thread
1148+ self.local.cnx = {instance: value}
1149+
1150+ def __delete__(self, instance):
1151+ try:
1152+ del self.local.cnx[instance]
1153+ except (AttributeError, KeyError):
1154+ # Not stored, no need to reset
1155+ pass
1156+
1157+ def get_connection(self, instance):
1158+ return connections[model_connection_name(instance.model)]
1159+
1160+ def reset(self):
1161+ if not hasattr(self.local, 'cnx'):
1162+ return
1163+ self.local.cnx = {}
1164+
1165+class LocalizingProxy:
1166+ """A lazy-initializing proxy. The proxied object is not
1167+ initialized until the first attempt to access it. This is used to
1168+ attach module-level properties to local storage.
1169+ """
1170+ def __init__(self, name, storage, func, *arg, **kw):
1171+ self.__name = name
1172+ self.__storage = storage
1173+ self.__func = func
1174+ self.__arg = arg
1175+ self.__kw = kw
1176+
1177+ # We need to clear out this thread's storage at the end of each
1178+ # request, in case new settings are loaded with the next
1179+ def reset(stor=storage, name=name):
1180+ if hasattr(stor, name):
1181+ delattr(stor, name)
1182+ dispatcher.connect(reset, signal=signals.request_finished)
1183+
1184+ def __getattr__(self, attr):
1185+ # Private (__*) attributes are munged
1186+ if attr.startswith('_LocalizingProxy'):
1187+ return self.__dict__[attr]
1188+ try:
1189+ return getattr(getattr(self.__storage, self.__name), attr)
1190+ except AttributeError:
1191+ setattr(self.__storage, self.__name, self.__func(*self.__arg,
1192+ **self.__kw))
1193+ return getattr(getattr(self.__storage, self.__name), attr)
1194+
1195+ def __setattr__(self, attr, val):
1196+ # Private (__*) attributes are munged
1197+ if attr.startswith('_LocalizingProxy'):
1198+ self.__dict__[attr] = val
1199+ return
1200+ try:
1201+ stor = getattr(self.__storage, self.__name)
1202+ except AttributeError:
1203+ stor = self.__func(*self.__arg)
1204+ setattr(self.__storage, self.__name, stor)
1205+ setattr(stor, attr, val)
1206+
1207+
1208+# Create a manager for named connections
1209+connections = LazyConnectionManager()
1210+
1211+# Backwards compatibility: establish the default connection and set the
1212+# default connection properties at module level, using the lazy proxy so that
1213+# each thread may have a different default connection, if so configured
1214+connection_info = LocalizingProxy('connection_info', _local,
1215+ lambda: connections[_default])
1216+connection = LocalizingProxy('connection', _local,
1217+ lambda: connections[_default].connection)
1218+backend = LocalizingProxy('backend', _local,
1219+ lambda: connections[_default].backend)
1220+DatabaseError = LocalizingProxy('DatabaseError', _local,
1221+ lambda: connections[_default].DatabaseError)
1222+IntegrityError = LocalizingProxy('IntegrityError', _local,
1223+ lambda: connections[_default].IntegrityError)
1224+get_introspection_module = LocalizingProxy(
1225+ 'get_introspection_module', _local,
1226+ lambda: connections[_default].get_introspection_module)
1227+get_creation_module = LocalizingProxy(
1228+ 'get_creation_module', _local,
1229+ lambda: connections[_default].get_creation_module)
1230+runshell = LocalizingProxy('runshell', _local,
1231+ lambda: connections[_default].runshell)
1232+
1233+
1234 def reset_queries():
1235- connection.queries = []
1236-dispatcher.connect(reset_queries, signal=signals.request_started)
1237+ connections[_default].reset_queries()
1238+ for c in connections:
1239+ try:
1240+ c.reset_queries()
1241+ except:
1242+ pass
1243
1244-# Register an event that rolls back the connection
1245+# Register an event that rolls back all connections
1246 # when a Django request has an exception.
1247 def _rollback_on_exception():
1248 from django.db import transaction
1249 transaction.rollback_unless_managed()
1250-dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
1251+dispatcher.connect(_rollback_on_exception,
1252+ signal=signals.got_request_exception)
1253+
1254=== django/db/backends/sqlite3/base.py
1255==================================================================
1256--- django/db/backends/sqlite3/base.py (/mirror/django/trunk) (revision 5420)
1257
1258+++ django/db/backends/sqlite3/base.py (/local/django/mymultidb) (revision 5420)
1259
1260@@ -118,7 +118,7 @@
1261
1262 return self.connection.cursor(factory=SQLiteCursorWrapper)
1263
1264 def close(self):
1265- from django.conf import settings
1266+ settings = self.settings
1267 # If database is in memory, closing the connection destroys the
1268 # database. To prevent accidental data loss, ignore close requests on
1269 # an in-memory db.
1270=== django/db/backends/__init__.py
1271==================================================================
1272--- django/db/backends/__init__.py (/mirror/django/trunk) (revision 5420)
1273
1274+++ django/db/backends/__init__.py (/local/django/mymultidb) (revision 5420)
1275
1276@@ -5,15 +5,17 @@
1277
1278 # Import copy of _thread_local.py from Python 2.4
1279 from django.utils._threading_local import local
1280
1281-class BaseDatabaseWrapper(local):
1282+class BaseDatabaseWrapper(object):
1283 """
1284 Represents a database connection.
1285 """
1286 ops = None
1287- def __init__(self, **kwargs):
1288+ def __init__(self, settings, connection_name):
1289+ self.settings = settings
1290+ self.connection_name = connection_name
1291+ self.options = settings.DATABASE_OPTIONS
1292 self.connection = None
1293 self.queries = []
1294- self.options = kwargs
1295
1296 def _commit(self):
1297 if self.connection is not None:
1298@@ -29,7 +31,7 @@
1299
1300 self.connection = None
1301
1302 def cursor(self):
1303- from django.conf import settings
1304+ settings = self.settings
1305 cursor = self._cursor(settings)
1306 if settings.DEBUG:
1307 return self.make_debug_cursor(cursor)
1308=== django/db/transaction.py
1309==================================================================
1310--- django/db/transaction.py (/mirror/django/trunk) (revision 5420)
1311
1312+++ django/db/transaction.py (/local/django/mymultidb) (revision 5420)
1313
1314@@ -16,7 +16,6 @@
1315
1316 import thread
1317 except ImportError:
1318 import dummy_thread as thread
1319-from django.db import connection
1320 from django.conf import settings
1321
1322 class TransactionManagementError(Exception):
1323@@ -116,48 +115,69 @@
1324
1325 Puts the transaction manager into a manual state: managed transactions have
1326 to be committed explicitly by the user. If you switch off transaction
1327 management and there is a pending commit/rollback, the data will be
1328- commited.
1329+ commited. Note that managed state applies across all connections.
1330 """
1331 thread_ident = thread.get_ident()
1332 top = state.get(thread_ident, None)
1333 if top:
1334 top[-1] = flag
1335 if not flag and is_dirty():
1336- connection._commit()
1337+ for cx in all_connections():
1338+ cx._commit()
1339 set_clean()
1340 else:
1341 raise TransactionManagementError("This code isn't under transaction management")
1342
1343-def commit_unless_managed():
1344+def commit_unless_managed(conns=None):
1345 """
1346 Commits changes if the system is not in managed transaction mode.
1347 """
1348 if not is_managed():
1349- connection._commit()
1350+ if conns is None:
1351+ conns = all_connections()
1352+ else:
1353+ conns = ensure_connections(conns)
1354+ for cx in conns:
1355+ cx._commit()
1356 else:
1357 set_dirty()
1358
1359-def rollback_unless_managed():
1360+def rollback_unless_managed(conns=None):
1361 """
1362 Rolls back changes if the system is not in managed transaction mode.
1363 """
1364 if not is_managed():
1365- connection._rollback()
1366+ if conns is None:
1367+ conns = all_connections()
1368+ else:
1369+ conns = ensure_connections(conns)
1370+ for cx in conns:
1371+ cx._rollback()
1372 else:
1373 set_dirty()
1374
1375-def commit():
1376+def commit(conns=None):
1377 """
1378 Does the commit itself and resets the dirty flag.
1379 """
1380- connection._commit()
1381+ if conns is None:
1382+ conns = all_connections()
1383+ else:
1384+ conns = ensure_connections(conns)
1385+ for cx in conns:
1386+ cx._commit()
1387 set_clean()
1388
1389-def rollback():
1390+def rollback(conns=None):
1391 """
1392 This function does the rollback itself and resets the dirty flag.
1393 """
1394- connection._rollback()
1395+ if conns is None:
1396+ conns = all_connections()
1397+ else:
1398+ conns = ensure_connections(conns)
1399+ for cx in conns:
1400+ cx._rollback()
1401 set_clean()
1402
1403 ##############
1404@@ -179,7 +199,7 @@
1405
1406 leave_transaction_management()
1407 return _autocommit
1408
1409-def commit_on_success(func):
1410+def commit_on_success(func, conns = None):
1411 """
1412 This decorator activates commit on response. This way, if the view function
1413 runs successfully, a commit is made; if the viewfunc produces an exception,
1414@@ -198,7 +218,7 @@
1415
1416 raise
1417 else:
1418 if is_dirty():
1419- commit()
1420+ commit(conns)
1421 return res
1422 finally:
1423 leave_transaction_management()
1424@@ -220,3 +240,31 @@
1425
1426 leave_transaction_management()
1427
1428 return _commit_manually
1429+
1430+###########
1431+# HELPERS #
1432+###########
1433+
1434+def all_connections():
1435+ from django.db import connection, connections
1436+ return [connection] + [ c.connection
1437+ for c in connections.values() ]
1438+
1439+def ensure_connections(val):
1440+ from django.db import connections
1441+ conns = []
1442+ if isinstance(val, basestring):
1443+ val = [val]
1444+ try:
1445+ iter(val)
1446+ except:
1447+ val = [val]
1448+ for cx in val:
1449+ if hasattr(cx, 'cursor'):
1450+ conns.append(cx)
1451+ elif hasattr(cx, 'connection'):
1452+ conns.append(cx.connection)
1453+ elif isinstance(cx, basestring):
1454+ conns.append(connections[cx].connection)
1455+ return conns
1456+
1457=== django/conf/global_settings.py
1458==================================================================
1459--- django/conf/global_settings.py (/mirror/django/trunk) (revision 5420)
1460
1461+++ django/conf/global_settings.py (/local/django/mymultidb) (revision 5420)
1462
1463@@ -122,6 +122,9 @@
1464
1465 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
1466 DATABASE_OPTIONS = {} # Set to empty dictionary for default.
1467
1468+# Optional named database connections in addition to the default.
1469+OTHER_DATABASES = {}
1470+
1471 # Host for sending e-mail.
1472 EMAIL_HOST = 'localhost'
1473
1474@@ -352,6 +355,16 @@
1475
1476 # If None, a name of 'test_' + DATABASE_NAME will be assumed
1477 TEST_DATABASE_NAME = None
1478
1479+# Tuple of other test databases to create. Names in this tuple
1480+# are suffixes that will be appended to TEST_DATABASE_NAME
1481+TEST_DATABASES = []
1482+
1483+# Models to assign to each test database. This must be a list of
1484+# dicts, with each dict key being a name from TEST_DATABASES and value
1485+# a list of models or app_labels that will use that database.
1486+TEST_DATABASE_MODELS = []
1487+
1488+
1489 # Strings used to set the character set and collation order for the test
1490 # database. These values are passed literally to the server, so they are
1491 # backend-dependent. If None, no special settings are sent (system defaults are
1492=== django/core/cache/backends/db.py
1493==================================================================
1494--- django/core/cache/backends/db.py (/mirror/django/trunk) (revision 5420)
1495
1496+++ django/core/cache/backends/db.py (/local/django/mymultidb) (revision 5420)
1497
1498@@ -1,7 +1,7 @@
1499
1500 "Database cache backend."
1501
1502 from django.core.cache.backends.base import BaseCache
1503-from django.db import connection, transaction, DatabaseError
1504+from django.db import connections, _default, transaction, DatabaseError
1505 import base64, time
1506 from datetime import datetime
1507 try:
1508@@ -25,7 +25,7 @@
1509
1510 self._cull_frequency = 3
1511
1512 def get(self, key, default=None):
1513- cursor = connection.cursor()
1514+ cursor = connections[_default].connection.cursor()
1515 cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
1516 row = cursor.fetchone()
1517 if row is None:
1518@@ -46,7 +46,7 @@
1519
1520 def _base_set(self, mode, key, value, timeout=None):
1521 if timeout is None:
1522 timeout = self.default_timeout
1523- cursor = connection.cursor()
1524+ cursor = connections[_default].connection.cursor()
1525 cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
1526 num = cursor.fetchone()[0]
1527 now = datetime.now().replace(microsecond=0)
1528@@ -67,12 +67,12 @@
1529
1530 transaction.commit_unless_managed()
1531
1532 def delete(self, key):
1533- cursor = connection.cursor()
1534+ cursor = connections[_default].connection.cursor()
1535 cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
1536 transaction.commit_unless_managed()
1537
1538 def has_key(self, key):
1539- cursor = connection.cursor()
1540+ cursor = connections[_default].connection.cursor()
1541 cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
1542 return cursor.fetchone() is not None
1543
1544=== django/core/management/base.py
1545==================================================================
1546--- django/core/management/base.py (/mirror/django/trunk) (revision 5420)
1547
1548+++ django/core/management/base.py (/local/django/mymultidb) (revision 5420)
1549
1550@@ -85,14 +85,7 @@
1551
1552 self.validate()
1553 output = self.handle(*args, **options)
1554 if output:
1555- if self.output_transaction:
1556- # This needs to be imported here, because it relies on settings.
1557- from django.db import connection
1558- if connection.ops.start_transaction_sql():
1559- print self.style.SQL_KEYWORD(connection.ops.start_transaction_sql())
1560 print output
1561- if self.output_transaction:
1562- print self.style.SQL_KEYWORD("COMMIT;")
1563 except CommandError, e:
1564 sys.stderr.write(self.style.ERROR(str('Error: %s\n' % e)))
1565 sys.exit(1)
1566@@ -124,23 +117,70 @@
1567
1568 args = '<appname appname ...>'
1569
1570 def handle(self, *app_labels, **options):
1571- from django.db import models
1572+ from django.db import models, _default, connections
1573+ from django.core.management.sql import sql_collate
1574 if not app_labels:
1575 raise CommandError('Enter at least one appname.')
1576 try:
1577 app_list = [models.get_app(app_label) for app_label in app_labels]
1578 except (ImproperlyConfigured, ImportError), e:
1579 raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
1580- output = []
1581+ output = {}
1582 for app in app_list:
1583 app_output = self.handle_app(app, **options)
1584- if app_output:
1585- output.append(app_output)
1586- return '\n'.join(output)
1587+ if isinstance(app_output, dict):
1588+ for connection_name, statements in app_output.items():
1589+ f_output = output.setdefault(connection_name, [])
1590+ if self.output_transaction:
1591+ if connections[connection_name].connection.ops.start_transaction_sql():
1592+ f_output.append(self.style.SQL_KEYWORD(connections[connection_name].connection.ops.start_transaction_sql()))
1593+ f_output.extend(statements)
1594+ if self.output_transaction:
1595+ f_output.append(self.style.SQL_KEYWORD("COMMIT;"))
1596+ else:
1597+ f_output = output.setdefault(_default, [])
1598+ f_output.append(app_output)
1599
1600+ return sql_collate(output)
1601+## return '\n'.join(output)
1602+
1603 def handle_app(self, app, **options):
1604 raise NotImplementedError()
1605
1606+class ConnectionCommand(BaseCommand):
1607+ args = '<connectionname connectionname ...>'
1608+
1609+ def handle(self, *connection_labels, **options):
1610+ from django.conf import settings
1611+ from django.db import _default, connections
1612+ from django.core.management.sql import sql_collate
1613+ if not connection_labels:
1614+ connection_list = [_default]
1615+ connection_list.extend(settings.OTHER_DATABASES.keys())
1616+ else:
1617+ # add check connection exists
1618+ connection_list = []
1619+ for name in connection_labels:
1620+ if name == '_default':
1621+ connection_list.append(_default)
1622+ elif name in settings.OTHER_DATABASES:
1623+ connection_list.append(name)
1624+ output = {}
1625+ for connection_name in connection_list:
1626+ f_output = output.setdefault(connection_name, [])
1627+ connection_output = self.handle_connection(connection_name, **options)
1628+ if connection_output:
1629+ if self.output_transaction:
1630+ if connections[connection_name].connection.ops.start_transaction_sql():
1631+ f_output.append(self.style.SQL_KEYWORD(connections[connection_name].connection.ops.start_transaction_sql()))
1632+ f_output.extend(connection_output)
1633+ if self.output_transaction:
1634+ f_output.append(self.style.SQL_KEYWORD("COMMIT;"))
1635+ return sql_collate(output)
1636+
1637+ def handle_connection(self, connection_name, **options):
1638+ raise NotImplementedError()
1639+
1640 class LabelCommand(BaseCommand):
1641 args = '<label label ...>'
1642 label = 'label'
1643=== django/core/management/commands/sqlall.py
1644==================================================================
1645--- django/core/management/commands/sqlall.py (/mirror/django/trunk) (revision 5420)
1646
1647+++ django/core/management/commands/sqlall.py (/local/django/mymultidb) (revision 5420)
1648
1649@@ -7,4 +7,4 @@
1650
1651
1652 def handle_app(self, app, **options):
1653 from django.core.management.sql import sql_all
1654- return '\n'.join(sql_all(app, self.style))
1655+ return sql_all(app, self.style)
1656=== django/core/management/commands/sqlcustom.py
1657==================================================================
1658--- django/core/management/commands/sqlcustom.py (/mirror/django/trunk) (revision 5420)
1659
1660+++ django/core/management/commands/sqlcustom.py (/local/django/mymultidb) (revision 5420)
1661
1662@@ -7,4 +7,5 @@
1663
1664
1665 def handle_app(self, app, **options):
1666 from django.core.management.sql import sql_custom
1667- return '\n'.join(sql_custom(app))
1668+ return sql_custom(app)
1669+
1670=== django/core/management/commands/sql.py
1671==================================================================
1672--- django/core/management/commands/sql.py (/mirror/django/trunk) (revision 5420)
1673
1674+++ django/core/management/commands/sql.py (/local/django/mymultidb) (revision 5420)
1675
1676@@ -7,4 +7,4 @@
1677
1678
1679 def handle_app(self, app, **options):
1680 from django.core.management.sql import sql_create
1681- return '\n'.join(sql_create(app, self.style))
1682+ return sql_create(app, self.style)
1683=== django/core/management/commands/createcachetable.py
1684==================================================================
1685--- django/core/management/commands/createcachetable.py (/mirror/django/trunk) (revision 5420)
1686
1687+++ django/core/management/commands/createcachetable.py (/local/django/mymultidb) (revision 5420)
1688
1689@@ -8,7 +8,7 @@
1690
1691 requires_model_validation = False
1692
1693 def handle_label(self, tablename, **options):
1694- from django.db import connection, transaction, models
1695+ from django.db import connections, transaction, models, _default
1696 fields = (
1697 # "key" is a reserved word in MySQL, so use "cache_key" instead.
1698 models.CharField(name='cache_key', max_length=255, unique=True, primary_key=True),
1699@@ -17,9 +17,9 @@
1700
1701 )
1702 table_output = []
1703 index_output = []
1704- qn = connection.ops.quote_name
1705+ qn = connections[_default].connection.ops.quote_name
1706 for f in fields:
1707- field_output = [qn(f.name), f.db_type()]
1708+ field_output = [qn(f.name), f.db_type(_default)]
1709 field_output.append("%sNULL" % (not f.null and "NOT " or ""))
1710 if f.unique:
1711 field_output.append("UNIQUE")
1712@@ -35,7 +35,7 @@
1713
1714 for i, line in enumerate(table_output):
1715 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
1716 full_statement.append(');')
1717- curs = connection.cursor()
1718+ curs = connection[_default].connection.cursor()
1719 curs.execute("\n".join(full_statement))
1720 for statement in index_output:
1721 curs.execute(statement)
1722=== django/core/management/commands/sqlflush.py
1723==================================================================
1724--- django/core/management/commands/sqlflush.py (/mirror/django/trunk) (revision 5420)
1725
1726+++ django/core/management/commands/sqlflush.py (/local/django/mymultidb) (revision 5420)
1727
1728@@ -1,10 +1,10 @@
1729
1730-from django.core.management.base import NoArgsCommand
1731+from django.core.management.base import ConnectionCommand
1732
1733-class Command(NoArgsCommand):
1734+class Command(ConnectionCommand):
1735 help = "Returns a list of the SQL statements required to return all tables in the database to the state they were in just after they were installed."
1736
1737 output_transaction = True
1738
1739- def handle_noargs(self, **options):
1740+ def handle_connection(self, connection, **options):
1741 from django.core.management.sql import sql_flush
1742- return '\n'.join(sql_flush(self.style, only_django=True))
1743+ return sql_flush(connection, self.style, only_django=True)
1744=== django/core/management/commands/sqlreset.py
1745==================================================================
1746--- django/core/management/commands/sqlreset.py (/mirror/django/trunk) (revision 5420)
1747
1748+++ django/core/management/commands/sqlreset.py (/local/django/mymultidb) (revision 5420)
1749
1750@@ -7,4 +7,4 @@
1751
1752
1753 def handle_app(self, app, **options):
1754 from django.core.management.sql import sql_reset
1755- return '\n'.join(sql_reset(app, self.style))
1756+ return sql_reset(app, self.style)
1757=== django/core/management/commands/sqlclear.py
1758==================================================================
1759--- django/core/management/commands/sqlclear.py (/mirror/django/trunk) (revision 5420)
1760
1761+++ django/core/management/commands/sqlclear.py (/local/django/mymultidb) (revision 5420)
1762
1763@@ -7,4 +7,4 @@
1764
1765
1766 def handle_app(self, app, **options):
1767 from django.core.management.sql import sql_delete
1768- return '\n'.join(sql_delete(app, self.style))
1769+ return sql_delete(app, self.style)
1770=== django/core/management/commands/loaddata.py
1771==================================================================
1772--- django/core/management/commands/loaddata.py (/mirror/django/trunk) (revision 5420)
1773
1774+++ django/core/management/commands/loaddata.py (/local/django/mymultidb) (revision 5420)
1775
1776@@ -21,7 +21,9 @@
1777
1778 def handle(self, *fixture_labels, **options):
1779 from django.db.models import get_apps
1780 from django.core import serializers
1781- from django.db import connection, transaction
1782+ from django.db import connections, model_connection_name, transaction
1783+# MULTIDB: TO REMOVE
1784+ from django.db import connection
1785 from django.conf import settings
1786
1787 self.style = no_style()
1788@@ -39,7 +41,7 @@
1789
1790 # Get a cursor (even though we don't need one yet). This has
1791 # the side effect of initializing the test database (if
1792 # it isn't already initialized).
1793- cursor = connection.cursor()
1794+## cursor = connection.cursor()
1795
1796 # Start transaction management. All fixtures are installed in a
1797 # single transaction to ensure that all references are resolved.
1798@@ -121,6 +123,7 @@
1799
1800 print "No %s fixture '%s' in %s." % \
1801 (format, fixture_name, humanize(fixture_dir))
1802
1803+# MULTIDB TODO: use sql_sequence_reset
1804 if object_count > 0:
1805 sequence_sql = connection.ops.sequence_reset_sql(self.style, models)
1806 if sequence_sql:
1807=== django/core/management/commands/inspectdb.py
1808==================================================================
1809--- django/core/management/commands/inspectdb.py (/mirror/django/trunk) (revision 5420)
1810
1811+++ django/core/management/commands/inspectdb.py (/local/django/mymultidb) (revision 5420)
1812
1813@@ -1,22 +1,23 @@
1814
1815-from django.core.management.base import NoArgsCommand, CommandError
1816+from django.core.management.base import ConnectionCommand, CommandError
1817
1818-class Command(NoArgsCommand):
1819+class Command(ConnectionCommand):
1820 help = "Introspects the database tables in the given database and outputs a Django model module."
1821
1822 requires_model_validation = False
1823
1824- def handle_noargs(self, **options):
1825+ def handle_connection(self, connection, **options):
1826 try:
1827- for line in self.handle_inspection():
1828+ for line in self.handle_inspection(connection):
1829 print line
1830 except NotImplementedError:
1831 raise CommandError("Database inspection isn't supported for the currently selected database backend.")
1832
1833- def handle_inspection(self):
1834- from django.db import connection, get_introspection_module
1835+ def handle_inspection(self, conn):
1836+ from django.db import connections, get_introspection_module
1837 import keyword
1838
1839- introspection_module = get_introspection_module()
1840+ connection = connections[conn].connection
1841+ introspection_module = connections[conn].get_introspection_module()
1842
1843 table2model = lambda table_name: table_name.title().replace('_', '')
1844
1845=== django/core/management/commands/sqlindexes.py
1846==================================================================
1847--- django/core/management/commands/sqlindexes.py (/mirror/django/trunk) (revision 5420)
1848
1849+++ django/core/management/commands/sqlindexes.py (/local/django/mymultidb) (revision 5420)
1850
1851@@ -7,4 +7,4 @@
1852
1853
1854 def handle_app(self, app, **options):
1855 from django.core.management.sql import sql_indexes
1856- return '\n'.join(sql_indexes(app, self.style))
1857+ return sql_indexes(app, self.style)
1858=== django/core/management/commands/flush.py
1859==================================================================
1860--- django/core/management/commands/flush.py (/mirror/django/trunk) (revision 5420)
1861
1862+++ django/core/management/commands/flush.py (/local/django/mymultidb) (revision 5420)
1863
1864@@ -1,9 +1,9 @@
1865
1866-from django.core.management.base import NoArgsCommand, CommandError
1867+from django.core.management.base import ConnectionCommand, CommandError
1868 from django.core.management.color import no_style
1869 from optparse import make_option
1870
1871-class Command(NoArgsCommand):
1872- option_list = NoArgsCommand.option_list + (
1873+class Command(ConnectionCommand):
1874+ option_list = ConnectionCommand.option_list + (
1875 make_option('--verbosity', action='store', dest='verbosity', default='1',
1876 type='choice', choices=['0', '1', '2'],
1877 help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
1878@@ -12,9 +12,9 @@
1879
1880 )
1881 help = "Executes ``sqlflush`` on the current database."
1882
1883- def handle_noargs(self, **options):
1884+ def handle_connection(self, connection, **options):
1885 from django.conf import settings
1886- from django.db import connection, transaction, models
1887+ from django.db import transaction, models, connections
1888 from django.dispatch import dispatcher
1889 from django.core.management.sql import sql_flush, emit_post_sync_signal
1890
1891@@ -31,7 +31,7 @@
1892
1893 except ImportError:
1894 pass
1895
1896- sql_list = sql_flush(self.style, only_django=True)
1897+ sql_list = sql_flush(connection, self.style, only_django=True)
1898
1899 if interactive:
1900 confirm = raw_input("""You have requested a flush of the database.
1901@@ -39,14 +39,14 @@
1902
1903 and return each table to the state it was in after syncdb.
1904 Are you sure you want to do this?
1905
1906- Type 'yes' to continue, or 'no' to cancel: """ % settings.DATABASE_NAME)
1907+ Type 'yes' to continue, or 'no' to cancel: """ % connection)
1908 else:
1909 confirm = 'yes'
1910
1911 if confirm == 'yes':
1912 try:
1913- cursor = connection.cursor()
1914 for sql in sql_list:
1915+ cursor = connections[connection].connection.cursor()
1916 cursor.execute(sql)
1917 except Exception, e:
1918 transaction.rollback_unless_managed()
1919@@ -55,7 +55,7 @@
1920
1921 * At least one of the expected database tables doesn't exist.
1922 * The SQL was invalid.
1923 Hint: Look at the output of 'django-admin.py sqlflush'. That's the SQL this command wasn't able to run.
1924- The full error: %s""" % (settings.DATABASE_NAME, e))
1925+ The full error: %s""" % (connection, e))
1926 transaction.commit_unless_managed()
1927
1928 # Emit the post sync signal. This allows individual
1929=== django/core/management/commands/reset.py
1930==================================================================
1931--- django/core/management/commands/reset.py (/mirror/django/trunk) (revision 5420)
1932
1933+++ django/core/management/commands/reset.py (/local/django/mymultidb) (revision 5420)
1934
1935@@ -10,10 +10,8 @@
1936
1937 help = "Executes ``sqlreset`` for the given app(s) in the current database."
1938 args = '[appname ...]'
1939
1940- output_transaction = True
1941-
1942 def handle_app(self, app, **options):
1943- from django.db import connection, transaction
1944+ from django.db import connections, transaction, _default
1945 from django.conf import settings
1946 from django.core.management.sql import sql_reset
1947
1948@@ -21,32 +19,37 @@
1949
1950
1951 self.style = no_style()
1952
1953- sql_list = sql_reset(app, self.style)
1954+ sql_dict = sql_reset(app, self.style)
1955
1956- if options.get('interactive'):
1957- confirm = raw_input("""
1958+ for connection_name, sql_list in sql_dict.items():
1959+ if options.get('interactive'):
1960+ if connection_name == _default:
1961+ dbname = settings.DATABASE_NAME
1962+ else:
1963+ dbname = settings.OTHER_DATABASES[connection_name][DATABASE_NAME]
1964+ confirm = raw_input("""
1965 You have requested a database reset.
1966 This will IRREVERSIBLY DESTROY any data for
1967 the "%s" application in the database "%s".
1968 Are you sure you want to do this?
1969
1970-Type 'yes' to continue, or 'no' to cancel: """ % (app_name, settings.DATABASE_NAME))
1971- else:
1972- confirm = 'yes'
1973+Type 'yes' to continue, or 'no' to cancel: """ % (app_name, dbname))
1974+ else:
1975+ confirm = 'yes'
1976
1977- if confirm == 'yes':
1978- try:
1979- cursor = connection.cursor()
1980- for sql in sql_list:
1981- cursor.execute(sql)
1982- except Exception, e:
1983- transaction.rollback_unless_managed()
1984- raise CommandError("""Error: %s couldn't be reset. Possible reasons:
1985+ if confirm == 'yes':
1986+ try:
1987+ cursor = connections[connection_name].connection.cursor()
1988+ for sql in sql_list:
1989+ cursor.execute(sql)
1990+ except Exception, e:
1991+ transaction.rollback_unless_managed()
1992+ raise CommandError("""Error: %s couldn't be reset. Possible reasons:
1993 * The database isn't running or isn't configured correctly.
1994 * At least one of the database tables doesn't exist.
1995 * The SQL was invalid.
1996 Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run.
1997 The full error: %s""" % (app_name, app_name, e))
1998- transaction.commit_unless_managed()
1999- else:
2000- print "Reset cancelled."
2001+ transaction.commit_unless_managed()
2002+ else:
2003+ print "Reset cancelled."
2004=== django/core/management/commands/syncdb.py
2005==================================================================
2006--- django/core/management/commands/syncdb.py (/mirror/django/trunk) (revision 5420)
2007
2008+++ django/core/management/commands/syncdb.py (/local/django/mymultidb) (revision 5420)
2009
2010@@ -19,7 +19,7 @@
2011
2012 help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
2013
2014 def handle_noargs(self, **options):
2015- from django.db import connection, transaction, models
2016+ from django.db import model_connection_name, transaction, models, connections
2017 from django.conf import settings
2018 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
2019
2020@@ -37,16 +37,10 @@
2021
2022 if not exc.args[0].startswith('No module named management'):
2023 raise
2024
2025- cursor = connection.cursor()
2026-
2027- if connection.features.uses_case_insensitive_names:
2028- table_name_converter = lambda x: x.upper()
2029- else:
2030- table_name_converter = lambda x: x
2031 # Get a list of all existing database tables, so we know what needs to
2032 # be added.
2033- tables = [table_name_converter(name) for name in table_list()]
2034-
2035+ tables = table_list()
2036+
2037 # Get a list of already installed *models* so that references work right.
2038 seen_models = installed_models(tables)
2039 created_models = set()
2040@@ -59,8 +53,15 @@
2041
2042 for model in model_list:
2043 # Create the model's database table, if it doesn't already exist.
2044 if verbosity >= 2:
2045- print "Processing %s.%s model" % (app_name, model._meta.object_name)
2046- if table_name_converter(model._meta.db_table) in tables:
2047+ print "Processing %s.%s model (%s))" % (app_name, model._meta.object_name, model_connection_name(model))
2048+ connection = model._default_manager.db.connection
2049+ cursor = connection.cursor()
2050+ if connection.features.uses_case_insensitive_names:
2051+ table_name_converter = lambda x: x.upper()
2052+ else:
2053+ table_name_converter = lambda x: x
2054+ ctables = [table_name_converter(name) for name in tables]
2055+ if table_name_converter(model._meta.db_table) in ctables:
2056 continue
2057 sql, references = sql_model_create(model, self.style, seen_models)
2058 seen_models.add(model)
2059@@ -83,6 +84,8 @@
2060
2061 model_list = models.get_models(app)
2062 for model in model_list:
2063 if model in created_models:
2064+ connection = model._default_manager.db.connection
2065+ cursor = connection.cursor()
2066 sql = many_to_many_sql_for_model(model, self.style)
2067 if sql:
2068 if verbosity >= 2:
2069@@ -102,6 +105,8 @@
2070
2071 app_name = app.__name__.split('.')[-2]
2072 for model in models.get_models(app):
2073 if model in created_models:
2074+ connection = model._default_manager.db.connection
2075+ cursor = connection.cursor()
2076 custom_sql = custom_sql_for_model(model)
2077 if custom_sql:
2078 if verbosity >= 1:
2079@@ -121,6 +126,8 @@
2080
2081 app_name = app.__name__.split('.')[-2]
2082 for model in models.get_models(app):
2083 if model in created_models:
2084+ connection = model._default_manager.db.connection
2085+ cursor = connection.cursor()
2086 index_sql = sql_indexes_for_model(model, self.style)
2087 if index_sql:
2088 if verbosity >= 1:
2089=== django/core/management/validation.py
2090==================================================================
2091--- django/core/management/validation.py (/mirror/django/trunk) (revision 5420)
2092
2093+++ django/core/management/validation.py (/local/django/mymultidb) (revision 5420)
2094
2095@@ -19,7 +19,7 @@
2096
2097 Returns number of errors.
2098 """
2099 from django.conf import settings
2100- from django.db import models, connection
2101+ from django.db import connections, models, model_connection_name
2102 from django.db.models.loading import get_app_errors
2103 from django.db.models.fields.related import RelatedObject
2104
2105@@ -30,6 +30,8 @@
2106
2107
2108 for cls in models.get_models(app):
2109 opts = cls._meta
2110+ connection_name = model_connection_name(cls)
2111+ connection = connections[connection_name].connection
2112
2113 # Do field-specific validation.
2114 for f in opts.local_fields:
2115@@ -74,6 +76,12 @@
2116
2117 if f.rel:
2118 if f.rel.to not in models.get_models():
2119 e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, f.rel.to))
2120+
2121+#MULTIDB TODO: Fix this to allow relations that span databases by splitting querys up
2122+ rel_connection = model_connection_name(f.rel.to)
2123+ if rel_connection != connection_name:
2124+ 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))
2125+
2126 # it is a string and we could not find the model it refers to
2127 # so skip the next section
2128 if isinstance(f.rel.to, (str, unicode)):
2129=== django/core/management/sql.py
2130==================================================================
2131--- django/core/management/sql.py (/mirror/django/trunk) (revision 5420)
2132
2133+++ django/core/management/sql.py (/local/django/mymultidb) (revision 5420)
2134
2135@@ -8,11 +8,21 @@
2136
2137 from sets import Set as set # Python 2.3 fallback
2138
2139 def table_list():
2140- "Returns a list of all table names that exist in the database."
2141- from django.db import connection, get_introspection_module
2142- cursor = connection.cursor()
2143- return get_introspection_module().get_table_list(cursor)
2144+ """Returns a list of all table names that exist in the database."""
2145+ from django.db import connections
2146+ table_list = []
2147+ for conn in connections:
2148+ table_list.extend(table_list_conn(conn))
2149+ return table_list
2150
2151+def table_list_conn(conn):
2152+ from django.db import connections
2153+ try:
2154+ cursor = connections[conn].connection.cursor()
2155+ return connections[conn].get_introspection_module().get_table_list(cursor)
2156+ except:
2157+ return []
2158+
2159 def django_table_list(only_existing=False):
2160 """
2161 Returns a list of all table names that have associated Django models and
2162@@ -32,18 +42,34 @@
2163
2164 tables = [t for t in tables if t in existing]
2165 return tables
2166
2167+def django_table_list_conn(conn, only_existing=False):
2168+ from django.db import models
2169+ from django.db import model_connection_name
2170+ tables = []
2171+ for app in models.get_apps():
2172+ for model in models.get_models(app):
2173+ if model_connection_name(model)==conn:
2174+ tables.append(model._meta.db_table)
2175+ tables.extend([f.m2m_db_table() for f in model._meta.many_to_many])
2176+ if only_existing:
2177+ existing = table_list_conn(conn)
2178+ tables = [t for t in tables if t in existing]
2179+ return tables
2180+
2181 def installed_models(table_list):
2182 "Returns a set of all models that are installed, given a list of existing table names."
2183- from django.db import connection, models
2184+ from django.db import models
2185 all_models = []
2186 for app in models.get_apps():
2187 for model in models.get_models(app):
2188- all_models.append(model)
2189- if connection.features.uses_case_insensitive_names:
2190- converter = lambda x: x.upper()
2191- else:
2192- converter = lambda x: x
2193- return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)])
2194+ connection = model._default_manager.db.connection
2195+ if connection.features.uses_case_insensitive_names:
2196+ converter = lambda x: x.upper()
2197+ else:
2198+ converter = lambda x: x
2199+ if converter(model._meta.db_table) in map(converter, table_list):
2200+ all_models.append(converter(model))
2201+ return set(all_models)
2202
2203 def sequence_list():
2204 "Returns a list of information about all DB sequences for all models in all apps."
2205@@ -66,7 +92,7 @@
2206
2207
2208 def sql_create(app, style):
2209 "Returns a list of the CREATE TABLE SQL statements for the given app."
2210- from django.db import models
2211+ from django.db import models, model_connection_name, _default
2212 from django.conf import settings
2213
2214 if settings.DATABASE_ENGINE == 'dummy':
2215@@ -81,71 +107,79 @@
2216
2217 # generate invalid SQL (leaving models out of known_models is harmless, so
2218 # we can be conservative).
2219 app_models = models.get_models(app)
2220- final_output = []
2221 known_models = set([model for model in installed_models(table_list()) if model not in app_models])
2222 pending_references = {}
2223
2224+ connection_output = {}
2225+
2226 for model in app_models:
2227+ connection_name = model_connection_name(model)
2228+ f_output = connection_output.setdefault(connection_name, [])
2229 output, references = sql_model_create(model, style, known_models)
2230- final_output.extend(output)
2231+ f_output.extend(output)
2232 for refto, refs in references.items():
2233 pending_references.setdefault(refto, []).extend(refs)
2234 if refto in known_models:
2235- final_output.extend(sql_for_pending_references(refto, style, pending_references))
2236- final_output.extend(sql_for_pending_references(model, style, pending_references))
2237+ f_output.extend(sql_for_pending_references(refto, style, pending_references))
2238+ f_output.extend(sql_for_pending_references(model, style, pending_references))
2239 # Keep track of the fact that we've created the table for this model.
2240 known_models.add(model)
2241
2242 # Create the many-to-many join tables.
2243 for model in app_models:
2244- final_output.extend(many_to_many_sql_for_model(model, style))
2245+ connection_name = model_connection_name(model)
2246+ f_output = connection_output.setdefault(connection_name, [])
2247+ f_output.extend(many_to_many_sql_for_model(model,style))
2248
2249+
2250 # Handle references to tables that are from other apps
2251 # but don't exist physically.
2252 not_installed_models = set(pending_references.keys())
2253 if not_installed_models:
2254+ f_output = connection_output.setdefault(_default, [])
2255 alter_sql = []
2256 for model in not_installed_models:
2257 alter_sql.extend(['-- ' + sql for sql in
2258 sql_for_pending_references(model, style, pending_references)])
2259 if alter_sql:
2260- final_output.append('-- The following references should be added but depend on non-existent tables:')
2261- final_output.extend(alter_sql)
2262+ f_output.append('-- The following references should be added but depend on non-existent tables:')
2263+ f_output.extend(alter_sql)
2264
2265- return final_output
2266+ return connection_output
2267
2268 def sql_delete(app, style):
2269 "Returns a list of the DROP TABLE SQL statements for the given app."
2270- from django.db import connection, models, get_introspection_module
2271+ from django.db import models, model_connection_name
2272 from django.db.backends.util import truncate_name
2273 from django.contrib.contenttypes import generic
2274- introspection = get_introspection_module()
2275
2276- # This should work even if a connection isn't available
2277- try:
2278- cursor = connection.cursor()
2279- except:
2280- cursor = None
2281+ connection_output = {}
2282
2283- # Figure out which tables already exist
2284- if cursor:
2285- table_names = introspection.get_table_list(cursor)
2286- else:
2287- table_names = []
2288- if connection.features.uses_case_insensitive_names:
2289- table_name_converter = lambda x: x.upper()
2290- else:
2291- table_name_converter = lambda x: x
2292-
2293- output = []
2294- qn = connection.ops.quote_name
2295-
2296 # Output DROP TABLE statements for standard application tables.
2297 to_delete = set()
2298
2299 references_to_delete = {}
2300 app_models = models.get_models(app)
2301 for model in app_models:
2302+ connection = model._default_manager.db.connection
2303+ introspection = model._default_manager.db.get_introspection_module()
2304+ # This should work even if a connection isn't available
2305+ try:
2306+ cursor = connection.cursor()
2307+ except:
2308+ cursor = None
2309+ # Figure out which tables already exist
2310+ if cursor:
2311+ table_names = introspection.get_table_list(cursor)
2312+ else:
2313+ table_names = []
2314+ if connection.features.uses_case_insensitive_names:
2315+ table_name_converter = lambda x: x.upper()
2316+ else:
2317+ table_name_converter = lambda x: x
2318+
2319+ qn = connection.ops.quote_name
2320+
2321 if cursor and table_name_converter(model._meta.db_table) in table_names:
2322 # The table exists, so it needs to be dropped
2323 opts = model._meta
2324@@ -156,6 +190,29 @@
2325
2326 to_delete.add(model)
2327
2328 for model in app_models:
2329+ connection = model._default_manager.db.connection
2330+ connection_name = model_connection_name(model)
2331+ introspection = model._default_manager.db.get_introspection_module()
2332+
2333+ output = connection_output.setdefault(connection_name, [])
2334+
2335+ # This should work even if a connection isn't available
2336+ try:
2337+ cursor = connection.cursor()
2338+ except:
2339+ cursor = None
2340+ # Figure out which tables already exist
2341+ if cursor:
2342+ table_names = introspection.get_table_list(cursor)
2343+ else:
2344+ table_names = []
2345+ if connection.features.uses_case_insensitive_names:
2346+ table_name_converter = str.upper
2347+ else:
2348+ table_name_converter = lambda x: x
2349+
2350+ qn = connection.ops.quote_name
2351+
2352 if cursor and table_name_converter(model._meta.db_table) in table_names:
2353 # Drop the table now
2354 output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
2355@@ -180,6 +237,29 @@
2356
2357
2358 # Output DROP TABLE statements for many-to-many tables.
2359 for model in app_models:
2360+ connection = model._default_manager.db.connection
2361+ connection_name = model_connection_name(model)
2362+ introspection = model._default_manager.db.get_introspection_module()
2363+
2364+ output = connection_output.setdefault(connection_name, [])
2365+
2366+ # This should work even if a connection isn't available
2367+ try:
2368+ cursor = connection.cursor()
2369+ except:
2370+ cursor = None
2371+ # Figure out which tables already exist
2372+ if cursor:
2373+ table_names = introspection.get_table_list(cursor)
2374+ else:
2375+ table_names = []
2376+ if connection.features.uses_case_insensitive_names:
2377+ table_name_converter = str.upper
2378+ else:
2379+ table_name_converter = lambda x: x
2380+
2381+ qn = connection.ops.quote_name
2382+
2383 opts = model._meta
2384 for f in opts.local_many_to_many:
2385 if isinstance(f.rel, generic.GenericRel):
2386@@ -195,71 +275,106 @@
2387
2388
2389 # Close database connection explicitly, in case this output is being piped
2390 # directly into a database client, to avoid locking issues.
2391- if cursor:
2392- cursor.close()
2393- connection.close()
2394+ for model in app_models:
2395+ connection = model._default_manager.db.connection
2396+ try:
2397+ cursor = connection.cursor()
2398+ except:
2399+ cursor = None
2400+ if cursor:
2401+ cursor.close()
2402+ connection.close()
2403
2404- return output[::-1] # Reverse it, to deal with table dependencies.
2405+ return connection_output
2406
2407 def sql_reset(app, style):
2408 "Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module."
2409- return sql_delete(app, style) + sql_all(app, style)
2410+ connection_output = {}
2411+ sql_dict = sql_delete(app, style)
2412+ for connection_name, sql_list in sql_dict.items():
2413+ output = connection_output.setdefault(connection_name, [])
2414+ output.extend(sql_list)
2415+ sql_dict = sql_all(app, style)
2416+ for connection_name, sql_list in sql_dict.items():
2417+ output = connection_output.setdefault(connection_name, [])
2418+ output.extend(sql_list)
2419+ return connection_output
2420
2421-def sql_flush(style, only_django=False):
2422+def sql_flush(connection, style, only_django=False):
2423 """
2424 Returns a list of the SQL statements used to flush the database.
2425
2426 If only_django is True, then only table names that have associated Django
2427 models and are in INSTALLED_APPS will be included.
2428 """
2429- from django.db import connection
2430+ from django.db import connections
2431 if only_django:
2432- tables = django_table_list()
2433+ tables = django_table_list_conn(connection)
2434 else:
2435- tables = table_list()
2436- statements = connection.ops.sql_flush(style, tables, sequence_list())
2437- return statements
2438+ tables = table_list_conn(connection)
2439+ return [f for f in connections[connection].connection.ops.sql_flush(style, tables, sequence_list())]
2440
2441 def sql_custom(app):
2442 "Returns a list of the custom table modifying SQL statements for the given app."
2443 from django.db.models import get_models
2444- output = []
2445+ from django.db import model_connection_name
2446+ connection_output = {}
2447
2448 app_models = get_models(app)
2449 app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql'))
2450
2451 for model in app_models:
2452- output.extend(custom_sql_for_model(model))
2453+ connection_name = model_connection_name(model)
2454+ output = connection_output.setdefault(connection_name, [])
2455+ output.extend(map(str, custom_sql_for_model(model)))
2456+ return connection_output
2457
2458- return output
2459-
2460 def sql_indexes(app, style):
2461 "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
2462- from django.db import models
2463- output = []
2464- for model in models.get_models(app):
2465- output.extend(sql_indexes_for_model(model, style))
2466- return output
2467+ from django.db import model_connection_name
2468+ from django.db.models import get_models
2469+ connection_output = {}
2470+ for model in get_models(app):
2471+ opts = model._meta
2472+ connection_name = model_connection_name(model)
2473+ output = connection_output.setdefault(connection_name, [])
2474+ output.extend(map(str, sql_indexes_for_model(model, style)))
2475+ return connection_output
2476
2477 def sql_all(app, style):
2478 "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
2479- return sql_create(app, style) + sql_custom(app) + sql_indexes(app, style)
2480+ connection_output = {}
2481+ sql_dict = sql_create(app, style)
2482+ for connection_name, sql_list in sql_dict.items():
2483+ output = connection_output.setdefault(connection_name, [])
2484+ output.extend(sql_list)
2485+ sql_dict = sql_custom(app)
2486+ for connection_name, sql_list in sql_dict.items():
2487+ output = connection_output.setdefault(connection_name, [])
2488+ output.extend(sql_list)
2489+ sql_dict = sql_indexes(app, style)
2490+ for connection_name, sql_list in sql_dict.items():
2491+ output = connection_output.setdefault(connection_name, [])
2492+ output.extend(sql_list)
2493+ return connection_output
2494
2495 def sql_model_create(model, style, known_models=set()):
2496 """
2497 Returns the SQL required to create a single model, as a tuple of:
2498 (list_of_sql, pending_references_dict)
2499 """
2500- from django.db import connection, models
2501+ from django.db import models, model_connection_name, connections
2502
2503 opts = model._meta
2504 final_output = []
2505 table_output = []
2506 pending_references = {}
2507+ connection_name = model_connection_name(model)
2508+ connection = connections[connection_name].connection
2509 qn = connection.ops.quote_name
2510 inline_references = connection.features.inline_fk_references
2511 for f in opts.local_fields:
2512- col_type = f.db_type()
2513+ col_type = f.db_type(connection_name)
2514 tablespace = f.db_tablespace or opts.db_tablespace
2515 if col_type is None:
2516 # Skip ManyToManyFields, because they're not represented as
2517@@ -291,7 +406,7 @@
2518
2519 table_output.append(' '.join(field_output))
2520 if opts.order_with_respect_to:
2521 table_output.append(style.SQL_FIELD(qn('_order')) + ' ' + \
2522- style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \
2523+ style.SQL_COLTYPE(models.IntegerField().db_type(connection_name)) + ' ' + \
2524 style.SQL_KEYWORD('NULL'))
2525 for field_constraints in opts.unique_together:
2526 table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
2527@@ -320,10 +435,16 @@
2528
2529 """
2530 Returns any ALTER TABLE statements to add constraints after the fact.
2531 """
2532- from django.db import connection
2533+ from django.db import connections, model_connection_name
2534 from django.db.backends.util import truncate_name
2535
2536+ connection_name = model_connection_name(model)
2537+ connection = connections[connection_name].connection
2538 qn = connection.ops.quote_name
2539+## if hasattr(connection.ops, 'max_constraint_length'):
2540+## mnl = connection.ops.max_constraint_length
2541+## else:
2542+## mnl = connection.ops.max_name_length
2543 final_output = []
2544 if connection.features.supports_constraints:
2545 opts = model._meta
2546@@ -345,12 +466,14 @@
2547
2548 return final_output
2549
2550 def many_to_many_sql_for_model(model, style):
2551- from django.db import connection, models
2552+ from django.db import models, connections, model_connection_name
2553 from django.contrib.contenttypes import generic
2554 from django.db.backends.util import truncate_name
2555
2556 opts = model._meta
2557 final_output = []
2558+ connection_name = model_connection_name(model)
2559+ connection = connections[connection_name].connection
2560 qn = connection.ops.quote_name
2561 inline_references = connection.features.inline_fk_references
2562 for f in opts.local_many_to_many:
2563@@ -364,21 +487,21 @@
2564
2565 style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
2566 table_output.append(' %s %s %s%s,' %
2567 (style.SQL_FIELD(qn('id')),
2568- style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
2569+ style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type(connection_name)),
2570 style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
2571 tablespace_sql))
2572 if inline_references:
2573 deferred = []
2574 table_output.append(' %s %s %s %s (%s)%s,' %
2575 (style.SQL_FIELD(qn(f.m2m_column_name())),
2576- style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
2577+ style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection_name)),
2578 style.SQL_KEYWORD('NOT NULL REFERENCES'),
2579 style.SQL_TABLE(qn(opts.db_table)),
2580 style.SQL_FIELD(qn(opts.pk.column)),
2581 connection.ops.deferrable_sql()))
2582 table_output.append(' %s %s %s %s (%s)%s,' %
2583 (style.SQL_FIELD(qn(f.m2m_reverse_name())),
2584- style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
2585+ style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type(connection_name)),
2586 style.SQL_KEYWORD('NOT NULL REFERENCES'),
2587 style.SQL_TABLE(qn(f.rel.to._meta.db_table)),
2588 style.SQL_FIELD(qn(f.rel.to._meta.pk.column)),
2589@@ -386,11 +509,11 @@
2590
2591 else:
2592 table_output.append(' %s %s %s,' %
2593 (style.SQL_FIELD(qn(f.m2m_column_name())),
2594- style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
2595+ style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection_name)),
2596 style.SQL_KEYWORD('NOT NULL')))
2597 table_output.append(' %s %s %s,' %
2598 (style.SQL_FIELD(qn(f.m2m_reverse_name())),
2599- style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
2600+ style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type(connection_name)),
2601 style.SQL_KEYWORD('NOT NULL')))
2602 deferred = [
2603 (f.m2m_db_table(), f.m2m_column_name(), opts.db_table,
2604@@ -456,9 +579,9 @@
2605
2606
2607 def sql_indexes_for_model(model, style):
2608 "Returns the CREATE INDEX SQL statements for a single model"
2609- from django.db import connection
2610 output = []
2611
2612+ connection = model._default_manager.db.connection
2613 qn = connection.ops.quote_name
2614 for f in model._meta.local_fields:
2615 if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys):
2616@@ -489,3 +612,24 @@
2617
2618 dispatcher.send(signal=models.signals.post_syncdb, sender=app,
2619 app=app, created_models=created_models,
2620 verbosity=verbosity, interactive=interactive)
2621+
2622+def sql_collate(connection_output, reverse=False):
2623+ from django.db import _default
2624+ final_output = []
2625+ if len(connection_output.keys()) == 1:
2626+ # all for the default connection
2627+ for statements in connection_output.values():
2628+ final_output.extend(statements)
2629+ if reverse:
2630+ final_output.reverse()
2631+ else:
2632+ for connection_name, statements in connection_output.items():
2633+ if not statements:
2634+ continue
2635+ final_output.append(' -- The following statements are for connection: %s' % connection_name)
2636+ if reverse:
2637+ statements.reverse()
2638+ final_output.extend(statements)
2639+ final_output.append(' -- END statements for %s\n' %
2640+ connection_name)
2641+ return '\n'.join(map(str, final_output))
2642=== django/core/context_processors.py
2643==================================================================
2644--- django/core/context_processors.py (/mirror/django/trunk) (revision 5420)
2645
2646+++ django/core/context_processors.py (/local/django/mymultidb) (revision 5420)
2647
2648@@ -33,8 +33,11 @@
2649
2650 context_extras = {}
2651 if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
2652 context_extras['debug'] = True
2653- from django.db import connection
2654- context_extras['sql_queries'] = connection.queries
2655+ from django.db import connections
2656+ queries = []
2657+ for conn in connections:
2658+ queries.extend(connections[conn].connection.queries)
2659+ context_extras['sql_queries'] = queries
2660 return context_extras
2661
2662 def i18n(request):
2663=== django/contrib/contenttypes/generic.py
2664==================================================================
2665--- django/contrib/contenttypes/generic.py (/mirror/django/trunk) (revision 5420)
2666
2667+++ django/contrib/contenttypes/generic.py (/local/django/mymultidb) (revision 5420)
2668
2669@@ -4,7 +4,6 @@
2670
2671
2672 from django import oldforms
2673 from django.core.exceptions import ObjectDoesNotExist
2674-from django.db import connection
2675 from django.db.models import signals
2676 from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
2677 from django.db.models.loading import get_model
2678@@ -154,7 +153,7 @@
2679
2680 def get_internal_type(self):
2681 return "ManyToManyField"
2682
2683- def db_type(self):
2684+ def db_type(self, connection):
2685 # Since we're simulating a ManyToManyField, in effect, best return the
2686 # same db_type as well.
2687 return None
2688@@ -184,7 +183,7 @@
2689
2690 superclass = rel_model._default_manager.__class__
2691 RelatedManager = create_generic_related_manager(superclass)
2692
2693- qn = connection.ops.quote_name
2694+ qn = rel_model._default_manager.db.connection.ops.quote_name
2695
2696 manager = RelatedManager(
2697 model = rel_model,
2698=== django/contrib/auth/backends.py
2699==================================================================
2700--- django/contrib/auth/backends.py (/mirror/django/trunk) (revision 5420)
2701
2702+++ django/contrib/auth/backends.py (/local/django/mymultidb) (revision 5420)
2703
2704@@ -1,4 +1,3 @@
2705
2706-from django.db import connection
2707 from django.contrib.auth.models import User
2708
2709 try:
2710@@ -23,6 +22,7 @@
2711
2712 def get_group_permissions(self, user_obj):
2713 "Returns a list of permission strings that this user has through his/her groups."
2714 if not hasattr(user_obj, '_group_perm_cache'):
2715+ connection = User._default_manager.db.connection
2716 cursor = connection.cursor()
2717 # The SQL below works out to the following, after DB quoting:
2718 # cursor.execute("""
2719=== tests/modeltests/multiple_databases (new directory)
2720==================================================================
2721=== tests/modeltests/multiple_databases/__init__.py
2722==================================================================
2723--- tests/modeltests/multiple_databases/__init__.py (/mirror/django/trunk) (revision 5420)
2724
2725+++ tests/modeltests/multiple_databases/__init__.py (/local/django/mymultidb) (revision 5420)
2726
2727@@ -0,0 +1 @@
2728
2729+pass
2730
2731=== tests/modeltests/multiple_databases/models.py
2732==================================================================
2733--- tests/modeltests/multiple_databases/models.py (/mirror/django/trunk) (revision 5420)
2734
2735+++ tests/modeltests/multiple_databases/models.py (/local/django/mymultidb) (revision 5420)
2736
2737@@ -0,0 +1,221 @@
2738
2739+"""
2740+XXX. Using multiple database connections
2741+
2742+Django normally uses only a single database connection. However,
2743+support is available for using any number of different, named
2744+connections. Multiple database support is entirely optional and has
2745+no impact on your application if you don't use it.
2746+
2747+Named connections are defined in your settings module. Create a
2748+`OTHER_DATABASES` variable that is a dict, mapping connection names to their
2749+particulars. The particulars are defined in a dict with the same keys
2750+as the variable names as are used to define the default connection, with one
2751+addition: MODELS.
2752+
2753+The MODELS item in an OTHER_DATABASES entry is a list of the apps and models
2754+that will use that connection.
2755+
2756+Access to named connections is through `django.db.connections`, which
2757+behaves like a dict: you access connections by name. Connections are
2758+established lazily, when accessed. `django.db.connections[database]`
2759+holds a `ConnectionInfo` instance, with the attributes:
2760+`DatabaseError`, `backend`, `get_introspection_module`,
2761+`get_creation_module`, and `runshell`.
2762+
2763+To access a model's connection, use its manager. The connection is available
2764+at `model._default_manager.db.connection`. To find the backend or other
2765+connection metadata, use `model._meta.db` to access the full ConnectionInfo
2766+with connection metadata.
2767+"""
2768+
2769+from django.db import models
2770+
2771+class Artist(models.Model):
2772+ name = models.CharField(max_length=100)
2773+ alive = models.BooleanField(default=True)
2774+
2775+ def __str__(self):
2776+ return self.name
2777+
2778+
2779+class Opus(models.Model):
2780+ artist = models.ForeignKey(Artist)
2781+ name = models.CharField(max_length=100)
2782+ year = models.IntegerField()
2783+
2784+ def __str__(self):
2785+ return "%s (%s)" % (self.name, self.year)
2786+
2787+
2788+class Widget(models.Model):
2789+ code = models.CharField(max_length=10, unique=True)
2790+ weight = models.IntegerField()
2791+
2792+ def __str__(self):
2793+ return self.code
2794+
2795+
2796+class DooHickey(models.Model):
2797+ name = models.CharField(max_length=50)
2798+ widgets = models.ManyToManyField(Widget, related_name='doohickeys')
2799+
2800+ def __str__(self):
2801+ return self.name
2802+
2803+
2804+class Vehicle(models.Model):
2805+ make = models.CharField(max_length=20)
2806+ model = models.CharField(max_length=20)
2807+ year = models.IntegerField()
2808+
2809+ def __str__(self):
2810+ return "%d %s %s" % (self.year, self.make, self.model)
2811+
2812+
2813+__test__ = {'API_TESTS': """
2814+
2815+# See what connections are defined. django.db.connections acts like a dict.
2816+>>> from django.db import connection, connections, _default, model_connection_name
2817+>>> from django.conf import settings
2818+
2819+# Connections are referenced by name
2820+>>> connections['_a']
2821+Connection: ...
2822+>>> connections['_b']
2823+Connection: ...
2824+
2825+# Let's see what connections are available. The default connection is always
2826+# included in connections as well, and may be accessed as connections[_default].
2827+
2828+>>> connection_names = connections.keys()
2829+>>> connection_names.sort()
2830+>>> connection_names
2831+[<default>, '_a', '_b']
2832+
2833+# Invalid connection names raise ImproperlyConfigured
2834+
2835+>>> connections['bad']
2836+Traceback (most recent call last):
2837+ ...
2838+ImproperlyConfigured: No database connection 'bad' has been configured
2839+
2840+# The model_connection_name() function will tell you the name of the
2841+# connection that a model is configured to use.
2842+
2843+>>> model_connection_name(Artist)
2844+'_a'
2845+>>> model_connection_name(Widget)
2846+'_b'
2847+>>> model_connection_name(Vehicle) is _default
2848+True
2849+>>> a = Artist(name="Paul Klee", alive=False)
2850+>>> a.save()
2851+>>> w = Widget(code='100x2r', weight=1000)
2852+>>> w.save()
2853+>>> v = Vehicle(make='Chevy', model='Camaro', year='1966')
2854+>>> v.save()
2855+>>> artists = Artist.objects.all()
2856+>>> list(artists)
2857+[<Artist: Paul Klee>]
2858+
2859+# Models can access their connections through the db property of their
2860+# default manager.
2861+
2862+>>> paul = _[0]
2863+>>> Artist.objects.db
2864+Connection: ... (ENGINE=... NAME=...)
2865+>>> paul._default_manager.db
2866+Connection: ... (ENGINE=... NAME=...)
2867+
2868+# When transactions are not managed, model save will commit only
2869+# for the model's connection.
2870+
2871+>>> from django.db import transaction
2872+>>> transaction.enter_transaction_management()
2873+>>> transaction.managed(False)
2874+>>> a = Artist(name="Joan Miro", alive=False)
2875+>>> w = Widget(code="99rbln", weight=1)
2876+>>> a.save()
2877+
2878+# Only connection '_a' is committed, so if we rollback
2879+# all connections we'll forget the new Widget.
2880+
2881+>>> transaction.rollback()
2882+>>> list(Artist.objects.all())
2883+[<Artist: Paul Klee>, <Artist: Joan Miro>]
2884+>>> list(Widget.objects.all())
2885+[<Widget: 100x2r>]
2886+
2887+# Managed transaction state applies across all connections.
2888+
2889+>>> transaction.managed(True)
2890+
2891+# When managed, just as when using a single connection, updates are
2892+# not committed until a commit is issued.
2893+
2894+>>> a = Artist(name="Pablo Picasso", alive=False)
2895+>>> a.save()
2896+>>> w = Widget(code="99rbln", weight=1)
2897+>>> w.save()
2898+>>> v = Vehicle(make='Pontiac', model='Fiero', year='1987')
2899+>>> v.save()
2900+
2901+# The connections argument may be passed to commit, rollback, and the
2902+# commit_on_success decorator as a keyword argument, as the first (for
2903+# commit and rollback) or second (for the decorator) positional
2904+# argument. It may be passed as a ConnectionInfo object, a connection
2905+# (DatabaseWrapper) object, a connection name, or a list or dict of
2906+# ConnectionInfo objects, connection objects, or connection names. If a
2907+# dict is passed, the keys are ignored and the values used as the list
2908+# of connections to commit, rollback, etc.
2909+
2910+>>> transaction.commit(connections['_b'])
2911+>>> transaction.commit('_b')
2912+>>> transaction.commit(connections='_b')
2913+>>> transaction.commit(connections=['_b'])
2914+>>> transaction.commit(['_a', '_b'])
2915+>>> transaction.commit(connections)
2916+
2917+# When the connections argument is omitted entirely, the transaction
2918+# command applies to all connections. Here we have committed
2919+# connections 'django_test_db_a' and 'django_test_db_b', but not the
2920+# default connection, so the new vehicle is lost on rollback.
2921+
2922+>>> transaction.rollback()
2923+>>> list(Artist.objects.all())
2924+[<Artist: Paul Klee>, <Artist: Joan Miro>, <Artist: Pablo Picasso>]
2925+>>> list(Widget.objects.all())
2926+[<Widget: 100x2r>, <Widget: 99rbln>]
2927+>>> list(Vehicle.objects.all())
2928+[<Vehicle: 1966 Chevy Camaro>]
2929+>>> transaction.rollback()
2930+>>> transaction.managed(False)
2931+>>> transaction.leave_transaction_management()
2932+
2933+# Of course, relations and all other normal database operations work
2934+# with models that use named connections just the same as with models
2935+# that use the default connection. The only caveat is that you can't
2936+# use a relation between two models that are stored in different
2937+# databases. Note that that doesn't mean that two models using
2938+# different connection *names* can't be related; only that in the the
2939+# context in which they are used, if you use the relation, the
2940+# connections named by the two models must resolve to the same
2941+# database.
2942+
2943+>>> a = Artist.objects.get(name="Paul Klee")
2944+>>> list(a.opus_set.all())
2945+[]
2946+>>> a.opus_set.create(name="Magic Garden", year="1926")
2947+<Opus: Magic Garden (1926)>
2948+>>> list(a.opus_set.all())
2949+[<Opus: Magic Garden (1926)>]
2950+>>> d = DooHickey(name='Thing')
2951+>>> d.save()
2952+>>> d.widgets.create(code='d101', weight=92)
2953+<Widget: d101>
2954+>>> list(d.widgets.all())
2955+[<Widget: d101>]
2956+>>> w = Widget.objects.get(code='d101')
2957+>>> list(w.doohickeys.all())
2958+[<DooHickey: Thing>]
2959+"""}
2960
2961Property changes on: tests/modeltests/multiple_databases
2962___________________________________________________________________
2963Name: svn:ignore
2964 +*.pyc
2965 +
2966
2967=== tests/regressiontests/manager_db (new directory)
2968==================================================================
2969=== tests/regressiontests/manager_db/__init__.py
2970==================================================================
2971=== tests/regressiontests/manager_db/tests.py
2972==================================================================
2973--- tests/regressiontests/manager_db/tests.py (/mirror/django/trunk) (revision 5420)
2974
2975+++ tests/regressiontests/manager_db/tests.py (/local/django/mymultidb) (revision 5420)
2976
2977@@ -0,0 +1,17 @@
2978
2979+import unittest
2980+from regressiontests.manager_db.models import Insect
2981+
2982+class TestManagerDBAccess(unittest.TestCase):
2983+
2984+ def test_db_property(self):
2985+ m = Insect.objects
2986+ db = Insect.objects.db
2987+ assert db
2988+ assert db.connection
2989+ assert db.connection.cursor
2990+ assert db.backend
2991+ assert db.connection.ops.quote_name
2992+ assert db.get_creation_module
2993+
2994+if __name__ == '__main__':
2995+ unittest.main()
2996=== tests/regressiontests/manager_db/models.py
2997==================================================================
2998--- tests/regressiontests/manager_db/models.py (/mirror/django/trunk) (revision 5420)
2999
3000+++ tests/regressiontests/manager_db/models.py (/local/django/mymultidb) (revision 5420)
3001
3002@@ -0,0 +1,5 @@
3003
3004+from django.db import models
3005+
3006+class Insect(models.Model):
3007+ common_name = models.CharField(max_length=64)
3008+ latin_name = models.CharField(max_length=128)
3009
3010Property changes on: tests/regressiontests/manager_db
3011___________________________________________________________________
3012Name: svn:ignore
3013 +*.pyc
3014 +
3015
3016=== tests/regressiontests/thread_isolation (new directory)
3017==================================================================
3018=== tests/regressiontests/thread_isolation/__init__.py
3019==================================================================
3020=== tests/regressiontests/thread_isolation/tests.py
3021==================================================================
3022--- tests/regressiontests/thread_isolation/tests.py (/mirror/django/trunk) (revision 5420)
3023
3024+++ tests/regressiontests/thread_isolation/tests.py (/local/django/mymultidb) (revision 5420)
3025
3026@@ -0,0 +1,251 @@
3027
3028+# tests that db settings can be different in different threads
3029+#
3030+#
3031+# What's going on here:
3032+#
3033+# Simulating multiple web requests in a threaded environment, one in
3034+# which settings are different for each request. So we replace
3035+# django.conf.settings with a thread local, with different
3036+# configurations in each thread, and then fire off three
3037+# simultaneous requests (using a condition to sync them up), and
3038+# test that each thread sees its own settings and the models in each
3039+# thread attempt to connect to the correct database as per their
3040+# settings.
3041+#
3042+
3043+
3044+import copy
3045+import os
3046+import sys
3047+import threading
3048+import unittest
3049+from thread import get_ident
3050+
3051+from django.conf import settings, UserSettingsHolder
3052+from django.core.handlers.wsgi import WSGIHandler
3053+from django.db import model_connection_name, _default, connection, connections
3054+from regressiontests.request_isolation.tests import MockHandler
3055+from regressiontests.thread_isolation.models import *
3056+
3057+try:
3058+ # Only exists in Python 2.4+
3059+ from threading import local
3060+except ImportError:
3061+ # Import copy of _thread_local.py from Python 2.4
3062+ from django.utils._threading_local import local
3063+
3064+# helpers
3065+EV = threading.Event()
3066+
3067+class LocalSettings:
3068+ """Settings holder that allows thread-local overrides of defaults.
3069+ """
3070+ def __init__(self, defaults):
3071+ self._defaults = defaults
3072+ self._local = local()
3073+
3074+ def __getattr__(self, attr):
3075+ if attr in ('_defaults', '_local'):
3076+ return self.__dict__[attr]
3077+ _local = self.__dict__['_local']
3078+ _defaults = self.__dict__['_defaults']
3079+ debug("LS get %s (%s)", attr, hasattr(_local, attr))
3080+ if not hasattr(_local, attr):
3081+ # Make sure everything we return is the local version; this
3082+ # avoids sets to deep datastructures overwriting the defaults
3083+ setattr(_local, attr, copy.deepcopy(getattr(_defaults, attr)))
3084+ return getattr(_local, attr)
3085+
3086+ def __setattr__(self, attr, val):
3087+ if attr in ('_defaults', '_local'):
3088+ self.__dict__[attr] = val
3089+ else:
3090+ debug("LS set local %s = %s", attr, val)
3091+ setattr(self.__dict__['_local'], attr, val)
3092+
3093+def thread_two(func, *arg):
3094+ def start():
3095+ # from django.conf import settings
3096+ settings.OTHER_DATABASES['_b']['MODELS'] = []
3097+
3098+ debug("t2 ODB: %s", settings.OTHER_DATABASES)
3099+ debug("t2 waiting")
3100+ EV.wait(2.0)
3101+ func(*arg)
3102+ debug("t2 complete")
3103+ t2 = threading.Thread(target=start)
3104+ t2.start()
3105+ return t2
3106+
3107+def thread_three(func, *arg):
3108+ def start():
3109+ # from django.conf import settings
3110+ settings.OTHER_DATABASES['_b']['MODELS'] = ['ti.MY']
3111+ settings.OTHER_DATABASES['_b'], \
3112+ settings.OTHER_DATABASES['_a'] = \
3113+ settings.OTHER_DATABASES['_a'], \
3114+ settings.OTHER_DATABASES['_b']
3115+
3116+ settings.DATABASE_NAME = \
3117+ settings.OTHER_DATABASES['_a']['DATABASE_NAME']
3118+
3119+ debug("t3 ODB: %s", settings.OTHER_DATABASES)
3120+ debug("3 %s: start: default: %s", get_ident(), settings.DATABASE_NAME)
3121+ debug("3 %s: start: conn: %s", get_ident(),
3122+ connection.settings.DATABASE_NAME)
3123+
3124+ debug("t3 waiting")
3125+ EV.wait(2.0)
3126+ func(*arg)
3127+ debug("t3 complete")
3128+ t3 = threading.Thread(target=start)
3129+ t3.start()
3130+ return t3
3131+
3132+def debug(*arg):
3133+ pass
3134+## msg, arg = arg[0], arg[1:]
3135+## print msg % arg
3136+
3137+def start_response(code, headers):
3138+ debug("start response: %s %s", code, headers)
3139+ pass
3140+
3141+class TestThreadIsolation(unittest.TestCase):
3142+ # event used to synchronize threads so we can be sure they are running
3143+ # together
3144+ lock = threading.RLock()
3145+ errors = []
3146+
3147+ def setUp(self):
3148+ debug("setup")
3149+ self.settings = settings._target
3150+ settings._target = UserSettingsHolder(copy.deepcopy(settings._target))
3151+ settings.OTHER_DATABASES['_a']['MODELS'] = ['ti.MX']
3152+ settings.OTHER_DATABASES['_b']['MODELS'] = ['ti.MY']
3153+
3154+ # normal settings holders aren't thread-safe, so we need to substitute
3155+ # one that is (and so allows per-thread settings)
3156+ holder = settings._target
3157+ settings._target = LocalSettings(holder)
3158+
3159+ def teardown(self):
3160+ debug("teardown")
3161+ settings._target = self.settings
3162+
3163+ def add_thread_error(self, err):
3164+ self.lock.acquire()
3165+ try:
3166+ self.errors.append(err)
3167+ finally:
3168+ self.lock.release()
3169+
3170+ def thread_errors(self):
3171+ self.lock.acquire()
3172+ try:
3173+ return self.errors[:]
3174+ finally:
3175+ self.lock.release()
3176+
3177+ def request_one(self, request):
3178+ """Start out with settings as originally configured"""
3179+ from django.conf import settings
3180+ debug("request_one: %s", settings.OTHER_DATABASES)
3181+
3182+ self.assertEqual(model_connection_name(MQ), _default)
3183+ self.assertEqual(model_connection_name(MX), '_a')
3184+ self.assertEqual(
3185+ MX._default_manager.db.connection.settings.DATABASE_NAME,
3186+ settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
3187+ self.assertEqual(model_connection_name(MY), '_b')
3188+ self.assertEqual(
3189+ MY._default_manager.db.connection.settings.DATABASE_NAME,
3190+ settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
3191+ self.assert_(MQ._default_manager.db.connection is
3192+ connections[_default].connection)
3193+ self.assertEqual(
3194+ MQ._default_manager.db.connection.settings.DATABASE_NAME,
3195+ settings.DATABASE_NAME)
3196+ self.assertEqual(connection.settings.DATABASE_NAME,
3197+ settings.DATABASE_NAME)
3198+
3199+ def request_two(self, request):
3200+ """Between the first and second requests, settings change to assign
3201+ model MY to a different connection
3202+ """
3203+ # from django.conf import settings
3204+ debug("request_two: %s", settings.OTHER_DATABASES)
3205+
3206+ try:
3207+ self.assertEqual(model_connection_name(MQ), _default)
3208+ self.assertEqual(model_connection_name(MX), '_a')
3209+ self.assertEqual(
3210+ MX._default_manager.db.connection.settings.DATABASE_NAME,
3211+ settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
3212+ self.assertEqual(model_connection_name(MY), _default)
3213+ self.assertEqual(
3214+ MY._default_manager.db.connection.settings.DATABASE_NAME,
3215+ settings.DATABASE_NAME)
3216+ self.assert_(MQ._default_manager.db.connection is
3217+ connections[_default].connection)
3218+ self.assertEqual(
3219+ MQ._default_manager.db.connection.settings.DATABASE_NAME,
3220+ settings.DATABASE_NAME)
3221+ self.assertEqual(connection.settings.DATABASE_NAME,
3222+ settings.DATABASE_NAME)
3223+ except:
3224+ self.add_thread_error(sys.exc_info())
3225+
3226+ def request_three(self, request):
3227+ """Between the 2nd and 3rd requests, the settings at the names in
3228+ OTHER_DATABASES have changed.
3229+ """
3230+ # from django.conf import settings
3231+ debug("3 %s: %s", get_ident(), settings.OTHER_DATABASES)
3232+ debug("3 %s: default: %s", get_ident(), settings.DATABASE_NAME)
3233+ debug("3 %s: conn: %s", get_ident(),
3234+ connection.settings.DATABASE_NAME)
3235+ try:
3236+ self.assertEqual(model_connection_name(MQ), _default)
3237+ self.assertEqual(model_connection_name(MX), '_b')
3238+ self.assertEqual(
3239+ MX._default_manager.db.connection.settings.DATABASE_NAME,
3240+ settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
3241+ self.assertEqual(model_connection_name(MY), '_a')
3242+ self.assertEqual(
3243+ MY._default_manager.db.connection.settings.DATABASE_NAME,
3244+ settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
3245+ self.assert_(MQ._default_manager.db.connection is
3246+ connections[_default].connection)
3247+ self.assertEqual(
3248+ connection.settings.DATABASE_NAME,
3249+ settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
3250+ except:
3251+ self.add_thread_error(sys.exc_info())
3252+
3253+ def test_thread_isolation(self):
3254+
3255+ debug("running tests")
3256+
3257+ env = os.environ.copy()
3258+ env['PATH_INFO'] = '/'
3259+ env['REQUEST_METHOD'] = 'GET'
3260+
3261+ t2 = thread_two(MockHandler(self.request_two), env, start_response)
3262+ t3 = thread_three(MockHandler(self.request_three), env, start_response)
3263+
3264+ try:
3265+ EV.set()
3266+ MockHandler(self.request_one)(env, start_response)
3267+ finally:
3268+ t2.join()
3269+ t3.join()
3270+ err = self.thread_errors()
3271+ if err:
3272+ import traceback
3273+ for e in err:
3274+ traceback.print_exception(*e)
3275+ raise AssertionError("%s thread%s failed" %
3276+ (len(err), len(err) > 1 and 's' or
3277+ ''))
3278+
3279=== tests/regressiontests/thread_isolation/models.py
3280==================================================================
3281--- tests/regressiontests/thread_isolation/models.py (/mirror/django/trunk) (revision 5420)
3282
3283+++ tests/regressiontests/thread_isolation/models.py (/local/django/mymultidb) (revision 5420)
3284
3285@@ -0,0 +1,19 @@
3286
3287+from django.db import models
3288+
3289+# models
3290+class MQ(models.Model):
3291+ val = models.CharField(max_length=10)
3292+ class Meta:
3293+ app_label = 'ti'
3294+
3295+
3296+class MX(models.Model):
3297+ val = models.CharField(max_length=10)
3298+ class Meta:
3299+ app_label = 'ti'
3300+
3301+
3302+class MY(models.Model):
3303+ val = models.CharField(max_length=10)
3304+ class Meta:
3305+ app_label = 'ti'
3306
3307Property changes on: tests/regressiontests/thread_isolation
3308___________________________________________________________________
3309Name: svn:ignore
3310 +*.pyc
3311 +
3312
3313=== tests/regressiontests/request_isolation (new directory)
3314==================================================================
3315=== tests/regressiontests/request_isolation/__init__.py
3316==================================================================
3317=== tests/regressiontests/request_isolation/tests.py
3318==================================================================
3319--- tests/regressiontests/request_isolation/tests.py (/mirror/django/trunk) (revision 5420)
3320
3321+++ tests/regressiontests/request_isolation/tests.py (/local/django/mymultidb) (revision 5420)
3322
3323@@ -0,0 +1,100 @@
3324
3325+# tests that db settings can change between requests
3326+import copy
3327+import os
3328+import unittest
3329+from django.conf import settings, UserSettingsHolder
3330+from django.core.handlers.wsgi import WSGIHandler
3331+from django.db import models, model_connection_name, _default, connection
3332+from django.http import HttpResponse
3333+from regressiontests.request_isolation.models import *
3334+
3335+
3336+# helpers
3337+class MockHandler(WSGIHandler):
3338+
3339+ def __init__(self, test):
3340+ self.test = test
3341+ super(MockHandler, self).__init__()
3342+
3343+ def get_response(self, request):
3344+ # debug("mock handler answering %s, %s", path, request)
3345+ return HttpResponse(self.test(request))
3346+
3347+
3348+def debug(*arg):
3349+ pass
3350+ # msg, arg = arg[0], arg[1:]
3351+ # print msg % arg
3352+
3353+
3354+def start_response(code, headers):
3355+ debug("start response: %s %s", code, headers)
3356+ pass
3357+
3358+# tests
3359+class TestRequestIsolation(unittest.TestCase):
3360+
3361+ def setUp(self):
3362+ debug("setup")
3363+ self.settings = settings._target
3364+ settings._target = UserSettingsHolder(copy.deepcopy(settings._target))
3365+ settings.OTHER_DATABASES['_a']['MODELS'] = ['ri.MX']
3366+ settings.OTHER_DATABASES['_b']['MODELS'] = ['ri.MY']
3367+
3368+ def tearDown(self):
3369+ debug("teardown")
3370+ settings._target = self.settings
3371+
3372+ def testRequestIsolation(self):
3373+ env = os.environ.copy()
3374+ env['PATH_INFO'] = '/'
3375+ env['REQUEST_METHOD'] = 'GET'
3376+
3377+ def request_one(request):
3378+ """Start out with settings as originally configured"""
3379+ self.assertEqual(model_connection_name(MX), '_a')
3380+ self.assertEqual(
3381+ MX._default_manager.db.connection.settings.DATABASE_NAME,
3382+ settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
3383+ self.assertEqual(model_connection_name(MY), '_b')
3384+ self.assertEqual(
3385+ MY._default_manager.db.connection.settings.DATABASE_NAME,
3386+ settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
3387+
3388+ def request_two(request):
3389+ """Between the first and second requests, settings change to assign
3390+ model MY to a different connection
3391+ """
3392+ self.assertEqual(model_connection_name(MX), '_a')
3393+ self.assertEqual(
3394+ MX._default_manager.db.connection.settings.DATABASE_NAME,
3395+ settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
3396+ self.assertEqual(model_connection_name(MY), _default)
3397+ self.assertEqual(
3398+ MY._default_manager.db.connection.settings.DATABASE_NAME,
3399+ settings.DATABASE_NAME)
3400+
3401+ def request_three(request):
3402+ """Between the 2nd and 3rd requests, the settings at the names in
3403+ OTHER_DATABASES have changed.
3404+ """
3405+ self.assertEqual(model_connection_name(MX), '_b')
3406+ self.assertEqual(
3407+ MX._default_manager.db.connection.settings.DATABASE_NAME,
3408+ settings.OTHER_DATABASES['_b']['DATABASE_NAME'])
3409+ self.assertEqual(model_connection_name(MY), '_a')
3410+ self.assertEqual(
3411+ MY._default_manager.db.connection.settings.DATABASE_NAME,
3412+ settings.OTHER_DATABASES['_a']['DATABASE_NAME'])
3413+
3414+ MockHandler(request_one)(env, start_response)
3415+
3416+ settings.OTHER_DATABASES['_b']['MODELS'] = []
3417+ MockHandler(request_two)(env, start_response)
3418+
3419+ settings.OTHER_DATABASES['_b']['MODELS'] = ['ri.MY']
3420+ settings.OTHER_DATABASES['_b'], \
3421+ settings.OTHER_DATABASES['_a'] = \
3422+ settings.OTHER_DATABASES['_a'], \
3423+ settings.OTHER_DATABASES['_b']
3424+ MockHandler(request_three)(env, start_response)
3425=== tests/regressiontests/request_isolation/models.py
3426==================================================================
3427--- tests/regressiontests/request_isolation/models.py (/mirror/django/trunk) (revision 5420)
3428
3429+++ tests/regressiontests/request_isolation/models.py (/local/django/mymultidb) (revision 5420)
3430
3431@@ -0,0 +1,13 @@
3432
3433+from django.db import models
3434+
3435+# models
3436+class MX(models.Model):
3437+ val = models.CharField(max_length=10)
3438+ class Meta:
3439+ app_label = 'ri'
3440+
3441+
3442+class MY(models.Model):
3443+ val = models.CharField(max_length=10)
3444+ class Meta:
3445+ app_label = 'ri'
3446
3447Property changes on: tests/regressiontests/request_isolation
3448___________________________________________________________________
3449Name: svn:ignore
3450 +*.pyc
3451 +
3452
3453=== tests/runtests.py
3454==================================================================
3455--- tests/runtests.py (/mirror/django/trunk) (revision 5420)
3456
3457+++ tests/runtests.py (/local/django/mymultidb) (revision 5420)
3458
3459@@ -12,6 +12,14 @@
3460
3461
3462
3463 CONTRIB_DIR_NAME = 'django.contrib'
3464+TEST_OTHER_DATABASES = {
3465+ '_a': { 'DATABASE_NAME': 'django_test_a.db',
3466+ 'MODELS': [ 'multiple_databases.Artist',
3467+ 'multiple_databases.Opus' ]},
3468+ '_b': { 'DATABASE_NAME': 'django_test_b.db',
3469+ 'MODELS': [ 'multiple_databases.Widget',
3470+ 'multiple_databases.DooHickey' ]}
3471+}
3472 MODEL_TESTS_DIR_NAME = 'modeltests'
3473 REGRESSION_TESTS_DIR_NAME = 'regressiontests'
3474
3475@@ -99,6 +107,7 @@
3476
3477
3478 # Redirect some settings for the duration of these tests.
3479 settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
3480+ settings.TEST_OTHER_DATABASES = TEST_OTHER_DATABASES
3481 settings.ROOT_URLCONF = 'urls'
3482 settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
3483 settings.USE_I18N = True
3484=== docs/settings.txt
3485==================================================================
3486--- docs/settings.txt (/mirror/django/trunk) (revision 5420)
3487
3488+++ docs/settings.txt (/local/django/mymultidb) (revision 5420)
3489
3490@@ -733,6 +733,13 @@
3491
3492 See `allowed date format strings`_. See also ``DATE_FORMAT``,
3493 ``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``YEAR_MONTH_FORMAT``.
3494
3495+OTHER_DATABASES
3496+---------------
3497+
3498+Default: ``{}``
3499+
3500+Other database connections to use in addition to the default connection. See the `multiple database support docs`_.
3501+
3502 PREPEND_WWW
3503 -----------
3504
3505=== docs/multiple_database_support.txt
3506==================================================================
3507--- docs/multiple_database_support.txt (/mirror/django/trunk) (revision 5420)
3508
3509+++ docs/multiple_database_support.txt (/local/django/mymultidb) (revision 5420)
3510
3511@@ -0,0 +1,163 @@
3512
3513+========================
3514+Using Multiple Databases
3515+========================
3516+
3517+Standard Django practice is to use a single database connection for
3518+all models in all applications. However, Django supports configuring
3519+and using multiple database connections on a per-application, per-model
3520+or an ad-hoc basis. Using multiple database connections is optional.
3521+
3522+Configuring other database connections
3523+======================================
3524+
3525+Django's default database connection is configured via the settings
3526+DATABASE_ENGINE, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD,
3527+DATABASE_HOST, and DATABASE_PORT. Other connections are configured via
3528+the OTHER_DATABASES setting. Define OTHER_DATABASES as a dict, with a
3529+name for each connection as the key and a dict of settings as the
3530+value. In each OTHER_DATABASES entry (called a "named connection"),
3531+the keys are the same as the DATABASE_ENGINE, etc, settings used to
3532+configure the default connection. All keys are optional; any that are
3533+missing in a named connection's settings will inherit their values
3534+from the default connection.
3535+
3536+Here's an example::
3537+
3538+ DATABASE_ENGINE = 'postgresql'
3539+ DATABASE_NAME = 'django_apps'
3540+ DATABASE_USER = 'default_user'
3541+ DATABASE_PASSWORD = 'xxx'
3542+
3543+ OTHER_DATABASES = {
3544+ 'local': { 'DATABASE_ENGINE': 'sqlite3',
3545+ 'DATABASE_NAME': '/tmp/cache.db' },
3546+ 'public': { 'DATABASE_HOST': 'public',
3547+ 'DATABASE_USER': 'public_user',
3548+ 'DATABASE_PASSWORD': 'xxx' }
3549+ 'private': { 'DATABASE_HOST': 'private',
3550+ 'DATABASE_USER': 'private_user',
3551+ 'DATABASE_PASSWORD': 'xxx' }
3552+ }
3553+
3554+In addition to the DATABASE_* settings, each named connection in
3555+OTHER_DATABASES may optionally include a MODELS setting. This should
3556+be a list of app or app.model names, and is used to configure which
3557+models should use this connection instead of the default connection.
3558+
3559+Here's the example above, with ``MODELS``::
3560+
3561+ OTHER_DATABASES = {
3562+ 'local': { 'DATABASE_ENGINE': 'sqlite3',
3563+ 'DATABASE_NAME': '/tmp/cache.db',
3564+ # A model name: only the model ContentItem
3565+ # with the app_label myapp will use this connection
3566+ 'MODELS': ['myapp.ContentItem'] },
3567+ 'public': { 'DATABASE_HOST': 'public',
3568+ 'DATABASE_USER': 'public_user',
3569+ 'DATABASE_PASSWORD': 'xxx',
3570+ # Two models in myapp will use the connection
3571+ # named 'public', as will ALL models in
3572+ # django.contribe.comments
3573+ 'MODELS': ['myapp.Blog','myapp.Article',
3574+ 'django.contrib.comments' ] }
3575+ # No models or apps are configured to use the private db
3576+ 'private': { 'DATABASE_HOST': 'private',
3577+ 'DATABASE_USER': 'private_user',
3578+ 'DATABASE_PASSWORD': 'xxx' }
3579+ }
3580+
3581+Accessing a model's connection
3582+==============================
3583+
3584+Each manager has a ``db`` attribute that can be used to access the model's
3585+connection. Access the ``db`` attribute of a model's manager to obtain the
3586+model's currently configured connection.
3587+
3588+Example::
3589+
3590+ from django.db import models
3591+
3592+ class Blog(models.Model)
3593+ name = models.CharField(maxlength=50)
3594+
3595+ class Article(models.Model)
3596+ blog = models.ForeignKey(Blog)
3597+ title = models.CharField(maxlength=100)
3598+ slug = models.SlugField()
3599+ summary = models.CharField(maxlength=500)
3600+ body = models.TextField()
3601+
3602+ class ContentItem(models.Model)
3603+ slug = models.SlugField()
3604+ mimetype = models.CharField(maxlength=50)
3605+ file = models.FileField()
3606+
3607+ # Get a ConnectionInfo instance that describes the connection
3608+ article_db = Article.objects.db
3609+
3610+ # Get a connection and a cursor
3611+ connection = article_db.connection
3612+ cursor = connection.cursor()
3613+
3614+ # Get the ``quote_name`` function from the backend
3615+ qn = article_db.backend.quote_name
3616+
3617+Ordinarily you won't have to access a model's connection directly;
3618+just use the model and manager normally and they will use the
3619+connection configured for the model.
3620+
3621+ConnectionInfo objects
3622+======================
3623+
3624+FIXME Describe the ConnectionInfo object and each of its attributes.
3625+
3626+
3627+Accessing connections by name
3628+=============================
3629+
3630+Access named connections directly through
3631+``django.db.connections``. Each entry in ``django.db.connections`` is
3632+a ``ConnectionInfo`` instance bound to the settings configured in the
3633+OTHER_DATABASES entry under the same key.
3634+
3635+Example::
3636+
3637+ from django.db import connections
3638+
3639+ private_db = connections['private']
3640+ cursor = private_db.connection.cursor()
3641+
3642+
3643+Using transactions with other database connections
3644+==================================================
3645+
3646+Transaction managed state applies across all connections
3647+commit/rollback apply to all connections by default
3648+but you can specify individual connections or lists or dicts of connections
3649+
3650+
3651+Changing model connections on the fly
3652+=====================================
3653+
3654+Here's an example of primitive mirroring::
3655+
3656+ # Read all articles from the private db
3657+ # Note that we pull the articles into a list; this is necessary
3658+ # because query sets are lazy. If we were to change the model's
3659+ # connection without copying the articles into a local list, we'd
3660+ # wind up reading from public instead of private.
3661+
3662+ Article.objects.db = connections['private']
3663+ all_articles = list(Article.objects.all())
3664+
3665+ # Save each article in the public db
3666+ Article.objects.db = connections['public']
3667+ for article in all_articles:
3668+ article.save()
3669+
3670+Thread and request isolation
3671+============================
3672+
3673+connections close after each request
3674+connection settings are thread-local
3675+
Back to Top