Ticket #4747: multidb_6433.diff

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

patch for trunk r6433 (beware for management commands)

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