Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #12540, #12541 -- Added database routers, allowing for configur…

…able database use behavior in a multi-db setup, and improved error checking for cross-database joins.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12272 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 1b3dc8ad9a28486542f766ff93318aa6b4f5999b 1 parent acc095c
Russell Keith-Magee authored January 22, 2010
5  django/conf/global_settings.py
@@ -128,6 +128,7 @@
128 128
 SEND_BROKEN_LINK_EMAILS = False
129 129
 
130 130
 # Database connection info.
  131
+# Legacy format
131 132
 DATABASE_ENGINE = ''           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
132 133
 DATABASE_NAME = ''             # Or path to database file if using sqlite3.
133 134
 DATABASE_USER = ''             # Not used with sqlite3.
@@ -136,9 +137,13 @@
136 137
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
137 138
 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
138 139
 
  140
+# New format
139 141
 DATABASES = {
140 142
 }
141 143
 
  144
+# Classes used to implement db routing behaviour
  145
+DATABASE_ROUTERS = []
  146
+
142 147
 # The email backend to use. For possible shortcuts see django.core.mail.
143 148
 # The default is to use the SMTP backend.
144 149
 # Third-party backends can be specified by providing a Python path
2  django/contrib/auth/models.py
@@ -3,7 +3,7 @@
3 3
 
4 4
 from django.contrib import auth
5 5
 from django.core.exceptions import ImproperlyConfigured
6  
-from django.db import models, DEFAULT_DB_ALIAS
  6
+from django.db import models
7 7
 from django.db.models.manager import EmptyManager
8 8
 from django.contrib.contenttypes.models import ContentType
9 9
 from django.utils.encoding import smart_str
4  django/contrib/contenttypes/generic.py
@@ -5,7 +5,7 @@
5 5
 from django.core.exceptions import ObjectDoesNotExist
6 6
 from django.db import connection
7 7
 from django.db.models import signals
8  
-from django.db import models, DEFAULT_DB_ALIAS
  8
+from django.db import models
9 9
 from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
10 10
 from django.db.models.loading import get_model
11 11
 from django.forms import ModelForm
@@ -255,7 +255,7 @@ def add(self, *objs):
255 255
                     raise TypeError("'%s' instance expected" % self.model._meta.object_name)
256 256
                 setattr(obj, self.content_type_field_name, self.content_type)
257 257
                 setattr(obj, self.object_id_field_name, self.pk_val)
258  
-                obj.save(using=self.instance._state.db)
  258
+                obj.save()
259 259
         add.alters_data = True
260 260
 
261 261
         def remove(self, *objs):
2  django/contrib/contenttypes/models.py
... ...
@@ -1,4 +1,4 @@
1  
-from django.db import models, DEFAULT_DB_ALIAS
  1
+from django.db import models
2 2
 from django.utils.translation import ugettext_lazy as _
3 3
 from django.utils.encoding import smart_unicode
4 4
 
2  django/contrib/gis/db/models/sql/query.py
... ...
@@ -1,4 +1,4 @@
1  
-from django.db import connections, DEFAULT_DB_ALIAS
  1
+from django.db import connections
2 2
 from django.db.models.query import sql
3 3
 
4 4
 from django.contrib.gis.db.models.fields import GeometryField
6  django/db/__init__.py
... ...
@@ -1,13 +1,12 @@
1 1
 from django.conf import settings
2 2
 from django.core import signals
3 3
 from django.core.exceptions import ImproperlyConfigured
4  
-from django.db.utils import ConnectionHandler, load_backend
  4
+from django.db.utils import ConnectionHandler, ConnectionRouter, load_backend, DEFAULT_DB_ALIAS
5 5
 from django.utils.functional import curry
6 6
 
7  
-__all__ = ('backend', 'connection', 'connections', 'DatabaseError',
  7
+__all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
8 8
     'IntegrityError', 'DEFAULT_DB_ALIAS')
9 9
 
10  
-DEFAULT_DB_ALIAS = 'default'
11 10
 
12 11
 # For backwards compatibility - Port any old database settings over to
13 12
 # the new values.
@@ -61,6 +60,7 @@
61 60
 
62 61
 connections = ConnectionHandler(settings.DATABASES)
63 62
 
  63
+router = ConnectionRouter(settings.DATABASE_ROUTERS)
64 64
 
65 65
 # `connection`, `DatabaseError` and `IntegrityError` are convenient aliases
66 66
 # for backend bits.
8  django/db/models/base.py
@@ -10,7 +10,7 @@
10 10
 from django.db.models.query import delete_objects, Q
11 11
 from django.db.models.query_utils import CollectedObjects, DeferredAttribute
12 12
 from django.db.models.options import Options
13  
-from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS
  13
+from django.db import connections, router, transaction, DatabaseError, DEFAULT_DB_ALIAS
14 14
 from django.db.models import signals
15 15
 from django.db.models.loading import register_models, get_model
16 16
 from django.utils.translation import ugettext_lazy as _
@@ -439,7 +439,7 @@ def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
439 439
         need for overrides of save() to pass around internal-only parameters
440 440
         ('raw', 'cls', and 'origin').
441 441
         """
442  
-        using = using or self._state.db or DEFAULT_DB_ALIAS
  442
+        using = using or router.db_for_write(self.__class__, instance=self)
443 443
         connection = connections[using]
444 444
         assert not (force_insert and force_update)
445 445
         if cls is None:
@@ -592,7 +592,7 @@ def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
592 592
             parent_obj._collect_sub_objects(seen_objs)
593 593
 
594 594
     def delete(self, using=None):
595  
-        using = using or self._state.db or DEFAULT_DB_ALIAS
  595
+        using = using or router.db_for_write(self.__class__, instance=self)
596 596
         connection = connections[using]
597 597
         assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
598 598
 
@@ -719,7 +719,7 @@ def _perform_unique_checks(self, unique_checks):
719 719
                     # no value, skip the lookup
720 720
                     continue
721 721
                 if f.primary_key and not getattr(self, '_adding', False):
722  
-                    # no need to check for unique primary key when editting 
  722
+                    # no need to check for unique primary key when editing
723 723
                     continue
724 724
                 lookup_kwargs[str(field_name)] = lookup_value
725 725
 
76  django/db/models/fields/related.py
... ...
@@ -1,4 +1,5 @@
@@ -197,7 +198,8 @@ def __get__(self, instance, instance_type=None):
@@ -218,6 +220,15 @@ def __set__(self, instance, value):
@@ -260,11 +271,11 @@ def __get__(self, instance, instance_type=None):
@@ -281,14 +292,15 @@ def __set__(self, instance, value):
@@ -370,15 +382,15 @@ def create_manager(self, instance, superclass):
@@ -390,8 +402,8 @@ def get_or_create(self, **kwargs):
@@ -402,7 +414,7 @@ def remove(self, *objs):
@@ -410,7 +422,7 @@ def remove(self, *objs):
@@ -443,7 +455,8 @@ def __init__(self, model=None, core_filters=None, instance=None, symmetrical=Non
@@ -478,14 +491,16 @@ def create(self, **kwargs):
@@ -505,15 +520,16 @@ def _add_items(self, source_field_name, target_field_name, *objs):
@@ -521,7 +537,7 @@ def _add_items(self, source_field_name, target_field_name, *objs):
@@ -547,7 +563,8 @@ def _remove_items(self, source_field_name, target_field_name, *objs):
@@ -566,7 +583,8 @@ def _clear_items(self, source_field_name):
20  django/db/models/manager.py
... ...
@@ -1,10 +1,11 @@
1 1
 from django.utils import copycompat as copy
2  
-
3  
-from django.db import DEFAULT_DB_ALIAS
  2
+from django.conf import settings
  3
+from django.db import router
4 4
 from django.db.models.query import QuerySet, EmptyQuerySet, insert_query, RawQuerySet
5 5
 from django.db.models import signals
6 6
 from django.db.models.fields import FieldDoesNotExist
7 7
 
  8
+
8 9
 def ensure_default_manager(sender, **kwargs):
9 10
     """
10 11
     Ensures that a Model subclass contains a default manager  and sets the
@@ -87,30 +88,27 @@ def _copy_to_model(self, model):
87 88
         mgr._inherited = True
88 89
         return mgr
89 90
 
90  
-    def db_manager(self, alias):
  91
+    def db_manager(self, using):
91 92
         obj = copy.copy(self)
92  
-        obj._db = alias
  93
+        obj._db = using
93 94
         return obj
94 95
 
95 96
     @property
96 97
     def db(self):
97  
-        return self._db or DEFAULT_DB_ALIAS
  98
+        return self._db or router.db_for_read(self.model)
98 99
 
99 100
     #######################
100 101
     # PROXIES TO QUERYSET #
101 102
     #######################
102 103
 
103 104
     def get_empty_query_set(self):
104  
-        return EmptyQuerySet(self.model)
  105
+        return EmptyQuerySet(self.model, using=self._db)
105 106
 
106 107
     def get_query_set(self):
107 108
         """Returns a new QuerySet object.  Subclasses can override this method
108 109
         to easily customize the behavior of the Manager.
109 110
         """
110  
-        qs = QuerySet(self.model)
111  
-        if self._db is not None:
112  
-            qs = qs.using(self._db)
113  
-        return qs
  111
+        return QuerySet(self.model, using=self._db)
114 112
 
115 113
     def none(self):
116 114
         return self.get_empty_query_set()
@@ -200,7 +198,7 @@ def _update(self, values, **kwargs):
200 198
         return self.get_query_set()._update(values, **kwargs)
201 199
 
202 200
     def raw(self, raw_query, params=None, *args, **kwargs):
203  
-        return RawQuerySet(raw_query=raw_query, model=self.model, params=params, using=self.db, *args, **kwargs)
  201
+        return RawQuerySet(raw_query=raw_query, model=self.model, params=params, using=self._db, *args, **kwargs)
204 202
 
205 203
 class ManagerDescriptor(object):
206 204
     # This class ensures managers aren't accessible via model instances.
25  django/db/models/query.py
@@ -4,7 +4,7 @@
4 4
 
5 5
 from copy import deepcopy
6 6
 
7  
-from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS
  7
+from django.db import connections, router, transaction, IntegrityError
8 8
 from django.db.models.aggregates import Aggregate
9 9
 from django.db.models.fields import DateField
10 10
 from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory, InvalidQuery
@@ -34,6 +34,7 @@ def __init__(self, model=None, query=None, using=None):
34 34
         self._result_cache = None
35 35
         self._iter = None
36 36
         self._sticky_filter = False
  37
+        self._for_write = False
37 38
 
38 39
     ########################
39 40
     # PYTHON MAGIC METHODS #
@@ -345,6 +346,7 @@ def create(self, **kwargs):
345 346
         and returning the created object.
346 347
         """
347 348
         obj = self.model(**kwargs)
  349
+        self._for_write = True
348 350
         obj.save(force_insert=True, using=self.db)
349 351
         return obj
350 352
 
@@ -358,6 +360,7 @@ def get_or_create(self, **kwargs):
358 360
                 'get_or_create() must be passed at least one keyword argument'
359 361
         defaults = kwargs.pop('defaults', {})
360 362
         try:
  363
+            self._for_write = True
361 364
             return self.get(**kwargs), False
362 365
         except self.model.DoesNotExist:
363 366
             try:
@@ -413,6 +416,11 @@ def delete(self):
413 416
 
414 417
         del_query = self._clone()
415 418
 
  419
+        # The delete is actually 2 queries - one to find related objects,
  420
+        # and one to delete. Make sure that the discovery of related
  421
+        # objects is performed on the same database as the deletion.
  422
+        del_query._for_write = True
  423
+
416 424
         # Disable non-supported fields.
417 425
         del_query.query.select_related = False
418 426
         del_query.query.clear_ordering()
@@ -442,6 +450,7 @@ def update(self, **kwargs):
442 450
         """
443 451
         assert self.query.can_filter(), \
444 452
                 "Cannot update a query once a slice has been taken."
  453
+        self._for_write = True
445 454
         query = self.query.clone(sql.UpdateQuery)
446 455
         query.add_update_values(kwargs)
447 456
         if not transaction.is_managed(using=self.db):
@@ -714,7 +723,9 @@ def ordered(self):
714 723
     @property
715 724
     def db(self):
716 725
         "Return the database that will be used if this query is executed now"
717  
-        return self._db or DEFAULT_DB_ALIAS
  726
+        if self._for_write:
  727
+            return self._db or router.db_for_write(self.model)
  728
+        return self._db or router.db_for_read(self.model)
718 729
 
719 730
     ###################
720 731
     # PRIVATE METHODS #
@@ -726,8 +737,8 @@ def _clone(self, klass=None, setup=False, **kwargs):
726 737
         query = self.query.clone()
727 738
         if self._sticky_filter:
728 739
             query.filter_is_sticky = True
729  
-        c = klass(model=self.model, query=query)
730  
-        c._db = self._db
  740
+        c = klass(model=self.model, query=query, using=self._db)
  741
+        c._for_write = self._for_write
731 742
         c.__dict__.update(kwargs)
732 743
         if setup and hasattr(c, '_setup_query'):
733 744
             c._setup_query()
@@ -988,8 +999,8 @@ def _clone(self, klass=None, setup=False, **kwargs):
988 999
 
989 1000
 
990 1001
 class EmptyQuerySet(QuerySet):
991  
-    def __init__(self, model=None, query=None):
992  
-        super(EmptyQuerySet, self).__init__(model, query)
  1002
+    def __init__(self, model=None, query=None, using=None):
  1003
+        super(EmptyQuerySet, self).__init__(model, query, using)
993 1004
         self._result_cache = []
994 1005
 
995 1006
     def __and__(self, other):
@@ -1254,7 +1265,7 @@ def __repr__(self):
1254 1265
     @property
1255 1266
     def db(self):
1256 1267
         "Return the database that will be used if this query is executed now"
1257  
-        return self._db or DEFAULT_DB_ALIAS
  1268
+        return self._db or router.db_for_read(self.model)
1258 1269
 
1259 1270
     def using(self, alias):
1260 1271
         """
38  django/db/utils.py
@@ -5,6 +5,8 @@
5 5
 from django.core.exceptions import ImproperlyConfigured
6 6
 from django.utils.importlib import import_module
7 7
 
  8
+DEFAULT_DB_ALIAS = 'default'
  9
+
8 10
 def load_backend(backend_name):
9 11
     try:
10 12
         module = import_module('.base', 'django.db.backends.%s' % backend_name)
@@ -55,6 +57,7 @@ def ensure_defaults(self, alias):
55 57
             conn = self.databases[alias]
56 58
         except KeyError:
57 59
             raise ConnectionDoesNotExist("The connection %s doesn't exist" % alias)
  60
+
58 61
         conn.setdefault('ENGINE', 'django.db.backends.dummy')
59 62
         if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']:
60 63
             conn['ENGINE'] = 'django.db.backends.dummy'
@@ -82,3 +85,38 @@ def __iter__(self):
82 85
 
83 86
     def all(self):
84 87
         return [self[alias] for alias in self]
  88
+
  89
+class ConnectionRouter(object):
  90
+    def __init__(self, routers):
  91
+        self.routers = []
  92
+        for r in routers:
  93
+            if isinstance(r, basestring):
  94
+                module_name, klass_name = r.rsplit('.', 1)
  95
+                module = import_module(module_name)
  96
+                router = getattr(module, klass_name)()
  97
+            else:
  98
+                router = r
  99
+            self.routers.append(router)
  100
+
  101
+    def _router_func(action):
  102
+        def _route_db(self, model, **hints):
  103
+            chosen_db = None
  104
+            for router in self.routers:
  105
+                chosen_db = getattr(router, action)(model, **hints)
  106
+                if chosen_db:
  107
+                    return chosen_db
  108
+            try:
  109
+                return hints['instance']._state.db or DEFAULT_DB_ALIAS
  110
+            except KeyError:
  111
+                return DEFAULT_DB_ALIAS
  112
+        return _route_db
  113
+
  114
+    db_for_read = _router_func('db_for_read')
  115
+    db_for_write = _router_func('db_for_write')
  116
+
  117
+    def allow_relation(self, obj1, obj2, **hints):
  118
+        for router in self.routers:
  119
+            allow = router.allow_relation(obj1, obj2, **hints)
  120
+            if allow is not None:
  121
+                return allow
  122
+        return obj1._state.db == obj2._state.db
16  docs/ref/settings.txt
@@ -372,6 +372,22 @@ test database will use the name ``'test_' + DATABASE_NAME``.
372 372
 
373 373
 See :ref:`topics-testing`.
374 374
 
  375
+
  376
+.. setting:: DATABASE_ROUTERS
  377
+
  378
+DATABASE_ROUTERS
  379
+----------------
  380
+
  381
+.. versionadded: 1.2
  382
+
  383
+Default: ``[]`` (Empty list)
  384
+
  385
+The list of routers that will be used to determine which database
  386
+to use when performing a database queries.
  387
+
  388
+See the documentation on :ref:`automatic database routing in multi
  389
+database configurations <topics-db-multi-db-routing>`.
  390
+
375 391
 .. setting:: DATE_FORMAT
376 392
 
377 393
 DATE_FORMAT
336  docs/topics/db/multi-db.txt
@@ -6,10 +6,10 @@ Multiple databases
6 6
 
7 7
 .. versionadded:: 1.2
8 8
 
9  
-This topic guide describes Django's support for interacting with multiple
10  
-databases. Most of the rest of Django's documentation assumes you are
11  
-interacting with a single database. If you want to interact with multiple
12  
-databases, you'll need to take some additional steps.
  9
+This topic guide describes Django's support for interacting with
  10
+multiple databases. Most of the rest of Django's documentation assumes
  11
+you are interacting with a single database. If you want to interact
  12
+with multiple databases, you'll need to take some additional steps.
13 13
 
14 14
 Defining your databases
15 15
 =======================
@@ -22,9 +22,11 @@ a dictionary of settings for that specific connection. The settings in
22 22
 the inner dictionaries are described fully in the :setting:`DATABASES`
23 23
 documentation.
24 24
 
25  
-Regardless of how many databases you have, you *must* have a database
26  
-named ``'default'``. Any additional databases can have whatever alias
27  
-you choose.
  25
+Databases can have any alias you choose. However, the alias
  26
+``default`` has special significance. Django uses the database with
  27
+the alias of ``default`` when no other database has been selected. If
  28
+you don't have a ``default`` database, you need to be careful to
  29
+always specify the database that you want to use.
28 30
 
29 31
 The following is an example ``settings.py`` snippet defining two
30 32
 databases -- a default PostgreSQL database and a MySQL database called
@@ -65,10 +67,10 @@ all databases in our example, you would need to call::
65 67
 
66 68
 If you don't want every application to be synchronized onto a
67 69
 particular database. you can specify the :djadminopt:`--exclude`
68  
-argument to :djadmin:`syncdb`. The :djadminopt:`--exclude` option
69  
-lets you prevent a specific application or applications from
70  
-being synchronized. For example, if you don't want the ``sales``
71  
-application to be in the ``users`` database, you could run::
  70
+argument to :djadmin:`syncdb`. The :djadminopt:`--exclude` option lets
  71
+you prevent a specific application or applications from being
  72
+synchronized. For example, if you don't want the ``sales`` application
  73
+to be in the ``users`` database, you could run::
72 74
 
73 75
     $ ./manage.py syncdb --database=users --exclude=sales
74 76
 
@@ -86,46 +88,235 @@ operate in the same way as :djadmin:`syncdb` -- they only ever operate
86 88
 on one database at a time, using :djadminopt:`--database` to control
87 89
 the database used.
88 90
 
89  
-Selecting a database for a ``QuerySet``
90  
-=======================================
  91
+.. _topics-db-multi-db-routing:
91 92
 
92  
-You can select the database for a ``QuerySet`` at any point in the ``QuerySet``
93  
-"chain." Just call ``using()`` on the ``QuerySet`` to get another ``QuerySet``
94  
-that uses the specified database.
  93
+Automatic database routing
  94
+==========================
95 95
 
96  
-``using()`` takes a single argument: the alias of the database on which you
97  
-want to run the query. For example:
  96
+The easiest way to use multiple databases is to set up a database
  97
+routing scheme. The default routing scheme ensures that objects remain
  98
+'sticky' to their original database (i.e., an object retrieved from
  99
+the ``foo`` database will be saved on the same database). However, you
  100
+can implement more interesting behaviors by defining a different
  101
+routing scheme.
98 102
 
99  
-.. code-block:: python
  103
+Database routers
  104
+----------------
  105
+
  106
+A database Router is a class that provides three methods:
  107
+
  108
+.. method:: db_for_read(model, **hints)
  109
+
  110
+    Suggest the database that should be used for read operations for
  111
+    objects of type ``model``.
  112
+
  113
+    If a database operation is able to provide any additional
  114
+    information that might assist in selecting a database, it will be
  115
+    provided in the ``hints`` dictionary. Details on valid hints are
  116
+    provided :ref:`below <topics-db-multi-db-hints>`.
  117
+
  118
+    Returns None if there is no suggestion.
  119
+
  120
+.. method:: db_for_write(model, **hints)
  121
+
  122
+    Suggest the database that should be used for writes of objects of
  123
+    type Model.
  124
+
  125
+    If a database operation is able to provide any additional
  126
+    information that might assist in selecting a database, it will be
  127
+    provided in the ``hints`` dictionary. Details on valid hints are
  128
+    provided :ref:`below <topics-db-multi-db-hints>`.
  129
+
  130
+    Returns None if there is no suggestion.
  131
+
  132
+.. method:: allow_relation(obj1, obj2, **hints)
  133
+
  134
+    Return True if a relation between obj1 and obj2 should be
  135
+    allowed, False if the relation should be prevented, or None if
  136
+    the router has no opinion. This is purely a validation operation,
  137
+    used by foreign key and many to many operations to determine if a
  138
+    relation should be allowed between two objects.
  139
+
  140
+.. _topics-db-multi-db-hints:
  141
+
  142
+Hints
  143
+~~~~~
  144
+
  145
+The hints received by the database router can be used to decide which
  146
+database should receive a given request.
  147
+
  148
+At present, the only hint that will be provided is ``instance``, an
  149
+object instance that is related to the read or write operation that is
  150
+underway. This might be the instance that is being saved, or it might
  151
+be an instance that is being added in a many-to-many relation. In some
  152
+cases, no instance hint will be provided at all. The router check for
  153
+the existence of an instance hint, and determine if hat hint should be
  154
+used to alter routing behavior.
  155
+
  156
+Using routers
  157
+-------------
  158
+
  159
+Database routers are installed using the :setting:`DATABASE_ROUTERS`
  160
+setting. This setting defines a list of class names, each specifying a
  161
+router that should be used by the master router
  162
+(``django.db.router``).
  163
+
  164
+The master router is used by Django's database operations to allocate
  165
+database usage. Whenever a query needs to know which database to use,
  166
+it calls the master router, providing a model and a hint (if
  167
+available). Django then tries each router in turn until a database
  168
+suggestion can be found. If no suggestion can be found, it tries the
  169
+current ``_state.db`` of the hint instance. If a hint instance wasn't
  170
+provided, or the instance doesn't currently have database state, the
  171
+master router will allocate the ``default`` database.
  172
+
  173
+An example
  174
+----------
  175
+
  176
+.. admonition:: Example purposes only!
  177
+
  178
+    This example is intended as a demonstration of how the router
  179
+    infrastructure can be used to alter database usage. It
  180
+    intentionally ignores some complex issues in order to
  181
+    demonstrate how routers are used.
  182
+
  183
+    The approach of splitting ``contrib.auth`` onto a different
  184
+    database won't actually work on Postgres, Oracle, or MySQL with
  185
+    InnoDB tables. ForeignKeys to a remote database won't work due as
  186
+    they introduce referential integrity problems. If you're using
  187
+    SQLite or MySQL with MyISAM tables, there is no referential
  188
+    integrity checking, so you will be able to define cross-database
  189
+    foreign keys.
  190
+
  191
+    The master/slave configuration described is also flawed -- it
  192
+    doesn't provide any solution for handling replication lag (i.e.,
  193
+    query inconsistencies introduced because of the time taken for a
  194
+    write to propagate to the slaves). It also doesn't consider the
  195
+    interaction of transactions with the database utiliztion strategy.
100 196
 
101  
-    # This will run on the 'default' database.
  197
+So - what does this mean in practice? Say you want ``contrib.auth`` to
  198
+exist on the 'credentials' database, and you want all other models in a
  199
+master/slave relationship between the databses 'master', 'slave1' and
  200
+'slave2'. To implement this, you would need 2 routers::
  201
+
  202
+    class AuthRouter(object):
  203
+        """A router to control all database operations on models in
  204
+        the contrib.auth application"""
  205
+
  206
+        def db_for_read(self, model, **hints):
  207
+            "Point all operations on auth models to 'credentials'"
  208
+            if model._meta.app_label == 'auth':
  209
+                return 'credentials'
  210
+            return None
  211
+
  212
+        def db_for_write(self, model, **hints):
  213
+            "Point all operations on auth models to 'credentials'"
  214
+            if model._meta.app_label == 'auth':
  215
+                return 'credentials'
  216
+            return None
  217
+
  218
+        def allow_relation(self, obj1, obj2, **hints):
  219
+            "Allow any relation if a model in Auth is involved"
  220
+            if obj1._meta.app_label == 'auth' or obj2._meta.app_label == 'auth':
  221
+                return True
  222
+            return None
  223
+
  224
+
  225
+     class MasterSlaveRouter(object):
  226
+        """A router that sets up a simple master/slave configuration"""
  227
+
  228
+        def db_for_read(self, model, **hints):
  229
+            "Point all read operations to a random slave"
  230
+            return random.choice(['slave1','slave2'])
  231
+
  232
+        def db_for_write(self, model, **hints):
  233
+            "Point all write operations to the master"
  234
+            return 'master'
  235
+
  236
+        def allow_relation(self, obj1, obj2, **hints):
  237
+            "Allow any relation between two objects in the db pool"
  238
+            db_list = ('master','slave1','slave2')
  239
+            if obj1 in db_list and obj2 in db_list:
  240
+                return True
  241
+            return None
  242
+
  243
+Then, in your settings file, add the following (substituting ``path.to.`` with
  244
+the actual python path to the module where you define the routers)::
  245
+
  246
+    DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter']
  247
+
  248
+With this setup installed, lets run some Django code::
  249
+
  250
+    >>> # This retrieval will be performed on the 'credentials' database
  251
+    >>> fred = User.objects.get(username='fred')
  252
+    >>> fred.first_name = 'Frederick'
  253
+
  254
+    >>> # This save will also be directed to 'credentials'
  255
+    >>> fred.save()
  256
+
  257
+    >>> # These retrieval will be randomly allocated to a slave database
  258
+    >>> dna = Person.objects.get(name='Douglas Adams')
  259
+
  260
+    >>> # A new object has no database allocation when created
  261
+    >>> mh = Book(title='Mostly Harmless')
  262
+
  263
+    >>> # This assignment will consult the router, and set mh onto
  264
+    >>> # the same database as the author object
  265
+    >>> mh.author = dna
  266
+
  267
+    >>> # This save will force the 'mh' instance onto the master database...
  268
+    >>> mh.save()
  269
+
  270
+    >>> # ... but if we re-retrieve the object, it will come back on a slave
  271
+    >>> mh = Book.objects.get(title='Mostly Harmless')
  272
+
  273
+Manually selecting a database
  274
+=============================
  275
+
  276
+Django also provides an API that allows you to maintain complete control
  277
+over database usage in your code. A manually specified database allocation
  278
+will take priority over a database allocated by a router.
  279
+
  280
+Manually selecting a database for a ``QuerySet``
  281
+------------------------------------------------
  282
+
  283
+You can select the database for a ``QuerySet`` at any point in the
  284
+``QuerySet`` "chain." Just call ``using()`` on the ``QuerySet`` to get
  285
+another ``QuerySet`` that uses the specified database.
  286
+
  287
+``using()`` takes a single argument: the alias of the database on
  288
+which you want to run the query. For example::
  289
+
  290
+    >>> # This will run on the 'default' database.
102 291
     >>> Author.objects.all()
103  
-    
104  
-    # So will this.
  292
+
  293
+    >>> # So will this.
105 294
     >>> Author.objects.using('default').all()
106  
-    
107  
-    # This will run on the 'other' database.
  295
+
  296
+    >>> # This will run on the 'other' database.
108 297
     >>> Author.objects.using('other').all()
109 298
 
110 299
 Selecting a database for ``save()``
111  
-===================================
  300
+-----------------------------------
112 301
 
113  
-Use the ``using`` keyword to ``Model.save()`` to specify to which database the
114  
-data should be saved.
  302
+Use the ``using`` keyword to ``Model.save()`` to specify to which
  303
+database the data should be saved.
115 304
 
116  
-For example, to save an object to the ``legacy_users`` database, you'd use this::
  305
+For example, to save an object to the ``legacy_users`` database, you'd
  306
+use this::
117 307
 
118 308
     >>> my_object.save(using='legacy_users')
119 309
 
120  
-If you don't specify ``using``, the ``save()`` method will always save into the
121  
-default database.
  310
+If you don't specify ``using``, the ``save()`` method will save into
  311
+the default database allocated by the routers.
122 312
 
123 313
 Moving an object from one database to another
124  
----------------------------------------------
  314
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125 315
 
126  
-If you've saved an instance to one database, it might be tempting to use
127  
-``save(using=...)`` as a way to migrate the instance to a new database. However,
128  
-if you don't take appropriate steps, this could have some unexpected consequences.
  316
+If you've saved an instance to one database, it might be tempting to
  317
+use ``save(using=...)`` as a way to migrate the instance to a new
  318
+database. However, if you don't take appropriate steps, this could
  319
+have some unexpected consequences.
129 320
 
130 321
 Consider the following example::
131 322
 
@@ -149,16 +340,17 @@ However, if the primary key of ``p`` is already in use on the
149 340
 will be overridden when ``p`` is saved.
150 341
 
151 342
 You can avoid this in two ways. First, you can clear the primary key
152  
-of the instance. If an object has no primary key, Django will treat it as
153  
-a new object, avoiding any loss of data on the ``second`` database::
  343
+of the instance. If an object has no primary key, Django will treat it
  344
+as a new object, avoiding any loss of data on the ``second``
  345
+database::
154 346
 
155 347
     >>> p = Person(name='Fred')
156 348
     >>> p.save(using='first')
157 349
     >>> p.pk = None # Clear the primary key.
158 350
     >>> p.save(using='second') # Write a completely new object.
159 351
 
160  
-The second option is to use the ``force_insert`` option to ``save()`` to ensure
161  
-that Django does a SQL ``INSERT``::
  352
+The second option is to use the ``force_insert`` option to ``save()``
  353
+to ensure that Django does a SQL ``INSERT``::
162 354
 
163 355
     >>> p = Person(name='Fred')
164 356
     >>> p.save(using='first')
@@ -170,51 +362,53 @@ when you try to save onto the ``second`` database, an error will be
170 362
 raised.
171 363
 
172 364
 Selecting a database to delete from
173  
-===================================
  365
+-----------------------------------
174 366
 
175  
-By default, a call to delete an existing object will be executed on the
176  
-same database that was used to retrieve the object in the first place::
  367
+By default, a call to delete an existing object will be executed on
  368
+the same database that was used to retrieve the object in the first
  369
+place::
177 370
 
178 371
     >>> u = User.objects.using('legacy_users').get(username='fred')
179 372
     >>> u.delete() # will delete from the `legacy_users` database
180 373
 
181 374
 To specify the database from which a model will be deleted, pass a
182  
-``using`` keyword argument to the ``Model.delete()`` method. This argument
183  
-works just like the ``using`` keyword argument to ``save()``.
  375
+``using`` keyword argument to the ``Model.delete()`` method. This
  376
+argument works just like the ``using`` keyword argument to ``save()``.
184 377
 
185  
-For example, if you're migrating a user from the ``legacy_users`` database
186  
-to the ``new_users`` database, you might use these commands::
  378
+For example, if you're migrating a user from the ``legacy_users``
  379
+database to the ``new_users`` database, you might use these commands::
187 380
 
188 381
     >>> user_obj.save(using='new_users')
189 382
     >>> user_obj.delete(using='legacy_users')
190 383
 
191 384
 Using managers with multiple databases
192  
-======================================
  385
+--------------------------------------
193 386
 
194  
-Use the ``db_manager()`` method on managers to give managers access to a
195  
-non-default database.
  387
+Use the ``db_manager()`` method on managers to give managers access to
  388
+a non-default database.
196 389
 
197  
-For example, say you have a custom manager method that touches the database --
198  
-``User.objects.create_user()``. Because ``create_user()`` is a
199  
-manager method, not a ``QuerySet`` method, you can't do
200  
-``User.objects.using('new_users').create_user()``. (The ``create_user()`` method
201  
-is only available on ``User.objects``, the manager, not on ``QuerySet`` objects
202  
-derived from the manager.) The solution is to use ``db_manager()``, like this::
  390
+For example, say you have a custom manager method that touches the
  391
+database -- ``User.objects.create_user()``. Because ``create_user()``
  392
+is a manager method, not a ``QuerySet`` method, you can't do
  393
+``User.objects.using('new_users').create_user()``. (The
  394
+``create_user()`` method is only available on ``User.objects``, the
  395
+manager, not on ``QuerySet`` objects derived from the manager.) The
  396
+solution is to use ``db_manager()``, like this::
203 397
 
204 398
     User.objects.db_manager('new_users').create_user(...)
205 399
 
206 400
 ``db_manager()`` returns a copy of the manager bound to the database you specify.
207 401
 
208 402
 Using ``get_query_set()`` with multiple databases
209  
--------------------------------------------------
  403
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
210 404
 
211  
-If you're overriding ``get_query_set()`` on your manager, be sure to either
212  
-call the method on the parent (using ``super()``) or do the appropriate
213  
-handling of the ``_db`` attribute on the manager (a string containing the name
214  
-of the database to use).
  405
+If you're overriding ``get_query_set()`` on your manager, be sure to
  406
+either call the method on the parent (using ``super()``) or do the
  407
+appropriate handling of the ``_db`` attribute on the manager (a string
  408
+containing the name of the database to use).
215 409
 
216  
-For example, if you want to return a custom ``QuerySet`` class from the
217  
-``get_query_set`` method, you could do this::
  410
+For example, if you want to return a custom ``QuerySet`` class from
  411
+the ``get_query_set`` method, you could do this::
218 412
 
219 413
     class MyManager(models.Manager):
220 414
         def get_query_set(self):
@@ -228,9 +422,9 @@ Exposing multiple databases in Django's admin interface
228 422
 
229 423
 Django's admin doesn't have any explicit support for multiple
230 424
 databases. If you want to provide an admin interface for a model on a
231  
-database other than ``default``, you'll need to write custom
232  
-:class:`~django.contrib.admin.ModelAdmin` classes that will direct the
233  
-admin to use a specific database for content.
  425
+database other than that that specified by your router chain, you'll
  426
+need to write custom :class:`~django.contrib.admin.ModelAdmin` classes
  427
+that will direct the admin to use a specific database for content.
234 428
 
235 429
 ``ModelAdmin`` objects have four methods that require customization for
236 430
 multiple-database support::
@@ -257,11 +451,11 @@ multiple-database support::
257 451
             # on the 'other' database.
258 452
             return super(MultiDBModelAdmin, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
259 453
 
260  
-The implementation provided here implements a multi-database strategy where
261  
-all objects of a given type are stored on a specific database (e.g.,
262  
-all ``User`` objects are in the ``other`` database). If your usage of
263  
-multiple databases is more complex, your ``ModelAdmin`` will need to reflect
264  
-that strategy.
  454
+The implementation provided here implements a multi-database strategy
  455
+where all objects of a given type are stored on a specific database
  456
+(e.g., all ``User`` objects are in the ``other`` database). If your
  457
+usage of multiple databases is more complex, your ``ModelAdmin`` will
  458
+need to reflect that strategy.
265 459
 
266 460
 Inlines can be handled in a similar fashion. They require three customized methods::
267 461
 
@@ -282,8 +476,8 @@ Inlines can be handled in a similar fashion. They require three customized metho
282 476
             # on the 'other' database.
283 477
             return super(MultiDBTabularInline, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
284 478
 
285  
-Once you've written your model admin definitions, they can be registered with
286  
-any ``Admin`` instance::
  479
+Once you've written your model admin definitions, they can be
  480
+registered with any ``Admin`` instance::
287 481
 
288 482
     from django.contrib import admin
289 483
 
3  tests/regressiontests/multiple_database/models.py
@@ -2,7 +2,7 @@
2 2
 from django.contrib.auth.models import User
3 3
 from django.contrib.contenttypes.models import ContentType
4 4
 from django.contrib.contenttypes import generic
5  
-from django.db import models, DEFAULT_DB_ALIAS
  5
+from django.db import models
6 6
 
7 7
 class Review(models.Model):
8 8
     source = models.CharField(max_length=100)
@@ -36,6 +36,7 @@ class Book(models.Model):
36 36
     authors = models.ManyToManyField(Person)
37 37
     editor = models.ForeignKey(Person, null=True, related_name='edited')
38 38
     reviews = generic.GenericRelation(Review)
  39
+    pages = models.IntegerField(default=100)
39 40
 
40 41
     def __unicode__(self):
41 42
         return self.title
753  tests/regressiontests/multiple_database/tests.py
@@ -3,7 +3,8 @@
3 3
 
4 4
 from django.conf import settings
5 5
 from django.contrib.auth.models import User
6  
-from django.db import connections
  6
+from django.db import connections, router, DEFAULT_DB_ALIAS
  7
+from django.db.utils import ConnectionRouter
7 8
 from django.test import TestCase
8 9
 
9 10
 from models import Book, Person, Review, UserProfile
@@ -18,6 +19,16 @@
18 19
 class QueryTestCase(TestCase):
19 20
     multi_db = True
20 21
 
  22
+    def test_db_selection(self):
  23
+        "Check that querysets will use the default databse by default"
  24
+        self.assertEquals(Book.objects.db, DEFAULT_DB_ALIAS)
  25
+        self.assertEquals(Book.objects.all().db, DEFAULT_DB_ALIAS)
  26
+
  27
+        self.assertEquals(Book.objects.using('other').db, 'other')
  28
+
  29
+        self.assertEquals(Book.objects.db_manager('other').db, 'other')
  30
+        self.assertEquals(Book.objects.db_manager('other').all().db, 'other')
  31
+
21 32
     def test_default_creation(self):
22 33
         "Objects created on the default database don't leak onto other databases"
23 34
         # Create a book on the default database using create()
@@ -259,53 +270,53 @@ def test_m2m_reverse_operations(self):
259 270
         self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into HTML5').values_list('name', flat=True)),
260 271
                           [u'Mark Pilgrim'])
261 272
 
262  
-#    def test_m2m_cross_database_protection(self):
263  
-#        "Operations that involve sharing M2M objects across databases raise an error"
264  
-#        # Create a book and author on the default database
265  
-#        pro = Book.objects.create(title="Pro Django",
266  
-#                                  published=datetime.date(2008, 12, 16))
267  
-
268  
-#        marty = Person.objects.create(name="Marty Alchin")