For a project in Django I have to use two databases: default and remote. I have created routers.py
and everything works fine.
There was a requirement to create a table on the remote database and I created migration, run it and the table django_migrations
was created. I want to have only one table django_migrations
, in the default database.
The relevant part of routers.py
is here:
class MyRouter(object):
# ...
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label == 'my_app':
return db == 'remote'
return None
I run the migration like this:
python manage.py migrate my_app --database=remote
Now when I do:
python manage.py runserver
I get the following warning:
You have 1 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): my_app.
Run 'python manage.py migrate' to apply them.
The tables for my_app
are created in the remote
database, and in django_migrations
inside the remote
database the migrations are marked as applied.
EDIT:
How to force Django to use only one table django_migrations
, but still apply the migrations into different databases?
How to apply the migrations in different databases so that no warnings are raised?
Thanks to the comments on my question I did some research and came up with the following findings.
Using multiple databases results in creating a table django_migrations
when migrations are used. There is no option to record the migrations in only one table django_migrations
, as the comment from Kamil Niski explains. This is clear after reading the file django/db/migrations/recorder.py
.
I will illustrate an example with a project foo
and an app bar
inside the project. The app bar
has only one model Baz
.
We create the project:
django-admin startproject foo
Now we have these contents inside the main project directory:
- foo
- manage.py
I have a habit to group all apps inside the project directory:
mkdir foo/bar
python manage.py bar foo/bar
In the file foo/settings.py
we adjust the settings to use two different databases, for the purposes of this example we use sqlite3
:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db1.sqlite3'),
},
'remote': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db2.sqlite3'),
}
}
Now we run the migrations:
python manage.py migrate --database=default
This runs all migrations, the part --database=default
is optional, because if not specified Django uses the default database.
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying sessions.0001_initial... OK
Django has applied all migrations to the default database:
1 contenttypes 0001_initial 2019-11-13 16:51:04.767382
2 auth 0001_initial 2019-11-13 16:51:04.792245
3 admin 0001_initial 2019-11-13 16:51:04.827454
4 admin 0002_logentr 2019-11-13 16:51:04.846627
5 admin 0003_logentr 2019-11-13 16:51:04.864458
6 contenttypes 0002_remove_ 2019-11-13 16:51:04.892220
7 auth 0002_alter_p 2019-11-13 16:51:04.906449
8 auth 0003_alter_u 2019-11-13 16:51:04.923902
9 auth 0004_alter_u 2019-11-13 16:51:04.941707
10 auth 0005_alter_u 2019-11-13 16:51:04.958371
11 auth 0006_require 2019-11-13 16:51:04.965527
12 auth 0007_alter_v 2019-11-13 16:51:04.981532
13 auth 0008_alter_u 2019-11-13 16:51:05.004149
14 auth 0009_alter_u 2019-11-13 16:51:05.019705
15 auth 0010_alter_g 2019-11-13 16:51:05.037023
16 auth 0011_update_ 2019-11-13 16:51:05.054449
17 sessions 0001_initial 2019-11-13 16:51:05.063868
Now we create the model Baz
:
models.py
:
from django.db import models
class Baz(models.Model):
name = models.CharField(max_length=255, unique=True)
register the app bar
into INSTALLED_APPS
(foo/settings.py
) and create the migrations:
python manage.py makemigrations bar
Before we run the migrations we create routers.py
inside the bar
app:
class BarRouter(object):
def db_for_read(self, model, **hints):
if model._meta.app_label == 'bar':
return 'remote'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == 'bar':
return 'remote'
return None
def allow_relation(self, obj1, obj2, **hints):
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label == 'bar':
return db == 'remote'
if db == 'remote':
return False
return None
and register it in foo/settings.py
:
DATABASE_ROUTERS = ['foo.bar.routers.BarRouter']
Now the naive approach would be to run the migrations for bar
into the remote
database:
python manage.py migrate bar --database=remote
Operations to perform:
Apply all migrations: bar
Running migrations:
Applying bar.0001_initial... OK
The migrations has been applied to the remote
database:
1 bar 0001_initial 2019-11-13 17:32:39.701784
When we run:
python manage.py runserver
the following warning will be raised:
You have 1 unapplied migration(s). Your project may not work properly
until you apply the migrations for app(s): bar.
Run 'python manage.py migrate' to apply them.
Everything seems to work fine though. However it isn't satisfying having this warning.
The proper way would be to run all migrations for each database as suggested in this answer.
It would look like this:
python manage.py migrate --database=default
python manage.py migrate --database=remote
and after creating the migrations for bar
:
python manage.py migrate bar --database=default
python manage.py migrate bar --database=remote
The router will take care that the table bar_baz
is created only in the remote
database, but Django will mark the migrations as applied in both databases. Also the tables for auth
, admin
, sessions
, etc. will be created only in the default
database, as specified in routers.py
. The table django_migrations
in the remote
database will have records for these migrations too.
It is a long reading, but I hope it sheds some light on this, in my opinion, not thoroughly explained issue in the official documentation.