summaryrefslogtreecommitdiff
path: root/docs/topics/db/multi-db.txt
blob: 2e848f34300860c510ea1bb928368fd4f5e826ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
.. _topics-db-multi-db:

==================
Multiple Databases
==================

.. versionadded:: 1.2

This topic guide describes Django's support for interacting with multiple
databases. Most of the rest of Django's documentation assumes you are
interacting with a single database. If you want to interact with multiple
databases, some additional steps must be taken.

Defining your databases
=======================

The first step to using more than one database with Django is to tell
Django about the database servers you'll be using. This is done using
the :setting:`DATABASES` setting. This setting maps database aliases,
which are a way to refer to a specific database throughout Django, to
a dictionary of settings for that specific connection. The settings in
the inner dictionaries are described fully in the :setting:`DATABASES`
documentation.

Regardless of how many databases you have, you *must* have a database
named ``'default'``. Any additional databases you have can have
whatever alias you choose.

The following is an example ``settings.py`` snippet defining two
databases - a default Postgres database, and a MySQL database called
``users``::

    DATABASES = {
        'default': {
            'NAME': 'app_data',
            'BACKEND': 'django.db.backends.postgres_psycopg2',
            'USER': 'postgres_user',
            'PASSWORD': 's3krit'
        },
        'users': {
            'NAME': 'user_data'
            'BACKEND': 'django.db.backends.mysql',
            'USER': 'mysql_user',
            'PASSWORD': 'priv4te'
        }
    }

If you attempt to access a database that you haven't defined in your
:setting:`DATABASES` setting then Django will raise a
``django.db.utils.ConnectionDoesNotExist`` exception.

Selecting a database for a ``QuerySet``
=======================================

It is possible to select the database for a ``QuerySet`` at any point
during it's construction. To choose the database that a query will be
preformed against simply call the ``using()`` method on the
``QuerySet``. ``using()`` takes a single argument: the alias of the
database on which you want to run the query. For example::

    # This will run on the 'default' database...
    >>> Author.objects.all()
    # So will this...
    >>> Author.objects.using('default').all()
    # This will run on the 'other' database
    >>> Author.objects.using('other').all()

Select a database to save to
============================

To choose what database to save a model to, provide a ``using`` keyword
argument to ``Model.save()``. For example if you had a user model that you
wanted to save to the ``'legacy_users'`` database you would save the user
by calling::

    >>> user_obj.save(using='legacy_users')

Moving an object from one database to another
---------------------------------------------

If you have saved an instance to one database, it might be tempting to use
``save(using=...)`` as a way to migrate the instance to a new database. However,
if you don't take appropriate steps, this could have some unexpected consequences.

Consider the following example::

    >>> p = Person(name='Fred')
    >>> p.save(using='first') # (1)
    # some other processing ...
    >>> p.save(using='second') # (2)

In statement 1, a new Person object is saved to the ``first``
database. At this time, ``p`` doesn't have a primary key, so Django
issues a SQL ``INSERT`` statement. This creates a primary key, and
Django assigns that primary key to ``p``.

When the save occurs in statement 2, ``p`` already has a primary key
value, and Django will attempt to use that primary key on the new
database. If the primary key value isn't in use in the ``second``
database, then you won't have any problems -- the object will be
copied to the new databse.

However, if the primary key of ``p`` is already in use on the
``second`` database, the existing object on the ``second`` database
will be lost when ``p`` is saved.

There are two ways to avoid this outcome. Firstly, you can clear the
primary key of the instance. If an object has no primary key, Django
will treat it as a new object, avoiding any loss of data on the
``second`` database::

    >>> p = Person(name='Fred')
    >>> p.save(using='first')
    # some other processing ...
    >>> p.pk = None # Clear the PK
    >>> p.save(using='second') # Write a completely new object

Secondly, you can use the ``force_insert`` option to ``save()`` to ensure that
Django does a SQL ``INSERT``::

    >>> p = Person(name='Fred')
    >>> p.save(using='first')
    # some other processing ...
    >>> p.save(using='second', force_insert=True)

This will ensure that the person named ``Fred`` will have the same
primary key on both databases. If that primary key is already in use
when you try to save onto the ``second`` database, an error will be
raised.

Select a database to delete from
================================

By default, a call to delete an existing object will be executed on the
same database that was used to retrieve the object in the first place::

    >>> user_obj = User.objects.using('legacy_users').get(username='fred')
    >>> user_obj.delete() # will delete from the `legacy_users` database

If you want to specify the database from which a model will be
deleted, you can use a ``using`` keyword argument to the
``Model.delete()`` method. This argument is analogous to the ``using``
keyword argument to ``save()``. For example if you were migrating a
user from the ``'legacy_users'`` database to the ``'new_users'``
database you might use the commands::

    >>> user_obj.save(using='new_users')
    >>> user_obj.delete(using='legacy_users')

Using ``Managers`` with multiple databases
==========================================

When you call ``using()`` Django returns a ``QuerySet`` that will be
evaluated against that database. However, sometimes you want to direct
a manager to use a specific database chain ``using()``. If you call
``using()``, you won't have access to any of the methods on the
manager.

To overcome this limitation, managers provide a ``db_manager()``
method. This method returns a copy of the *manager* bound to that
specific database. So, if you want to load an object using it's
natural key (using the ``get_by_natural_key()`` method on the manager,
you can call::

    >>> Book.objects.db_mamanger("other").get_by_natural_key(...)

If you are overriding ``get_query_set()`` on your manager you must be sure to
either, call the method on the parent (using ``super()``), or do the
appropriate handling of the ``_db`` attribute on the manager. For example if
you wanted to return a custom ``QuerySet`` class from the ``get_query_set``
method you could do this::

    class MyManager(models.Manager):
        ...
        def get_query_set(self):
            qs = CustomQuerySet(self.model)
            if self._db is not None:
                qs = qs.using(self._db)
            return qs

Exposing multiple databases in Django's admin interface
=======================================================

Django's admin doesn't have any explicit support for multiple
databases. If you want to provide an admin interface for a model on a
database other than ``default``, you need to write custom
:class:`~django.contrib.admin.ModelAdmin` classes that will direct the
admin to use a specific database for content.

There are four methods that require customization on a ModelAdmin
object::

    class MultiDBModelAdmin(admin.ModelAdmin):
        # A handy constant for the name of the alternate database
        using = 'other'

        def save_model(self, request, obj, form, change):
            # Tell Django to save objects to the 'other' database
            obj.save(using=self.using)

        def queryset(self, request):
            # Tell Django to look for objects on the 'other' database
            return super(MultiDBModelAdmin, self).queryset(request).using(self.using)

        def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
            # Tell Django to populate ForeignKey widgets using a query
            # on the 'other' database
            return super(MultiDBModelAdmin, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)

        def formfield_for_manytomany(self, db_field, request=None, **kwargs):
            # Tell Django to populate ManyToMany widgets using a query
            # on the 'other' database
            return super(MultiDBModelAdmin, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)

The implementation provided here implements a multi-db strategy where
all objects of a given type are stored on a specific database (e.g.,
all ``User`` objects are on the ``other`` database). If your usage of
multi-db is more complex, your ModelAdmin will need to reflect that
strategy.

Inlines can be handled in a similar fashion -- they require just three
customized methods::

    class MultiDBTabularInline(admin.TabularInline):
        using = 'other'

        def queryset(self, request):
            # Tell Django to look for inline objects on the 'other' database
            return super(MultiDBTabularInline, self).queryset(request).using(self.using)

        def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
            # Tell Django to populate ForeignKey widgets using a query
            # on the 'other' database
            return super(MultiDBTabularInline, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)

        def formfield_for_manytomany(self, db_field, request=None, **kwargs):
            # Tell Django to populate ManyToMany widgets using a query
            # on the 'other' database
            return super(MultiDBTabularInline, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)

Once you have written your model admin definitions, they can be
registered with any Admin instance::

    from django.contrib import admin

    # Specialize the multi-db admin objects for use with specific models
    class BookInline(MultiDBTabularInline):
        model = Book

    class PublisherAdmin(MultiDBModelAdmin):
        inlines = [BookInline]

        admin.site.register

    admin.site.register(Author, MultiDBModelAdmin)
    admin.site.register(Publisher, PublisherAdmin)

    othersite = admin.Site('othersite')
    othersite.register(Publisher, MultiDBModelAdmin)

This example sets up two admin sites. On the first site, the
``Author`` and ``Publisher`` objects are exposed; ``Publisher``
objects have an tabular inline showing books published by that
publisher. The second site exposes just publishers, without the
inlines.