Используйте несколько баз данных в Django только с одной таблицей «django_migrations»

11

Для проекта в Django мне нужно использовать две базы данных: по умолчанию и удаленную . Я создал routers.pyи все работает отлично.

Было требование создать таблицу в удаленной базе данных, и я создал миграцию, запустил ее и таблица django_migrationsбыла создана. Я хочу иметь только одну таблицу django_migrationsв базе данных по умолчанию.

Соответствующая часть routers.pyздесь:

class MyRouter(object):
     # ...
     def allow_migrate(self, db, app_label, model_name=None, **hints):
         if app_label == 'my_app':
             return db == 'remote'
         return None

Я запускаю миграцию так:

python manage.py migrate my_app --database=remote

Теперь, когда я делаю:

python manage.py runserver

Я получаю следующее предупреждение:

У вас есть 1 не примененная миграция. Ваш проект может работать некорректно, пока вы не примените миграции для приложений: my_app.
Запустите «python manage.py migrate», чтобы применить их.

Таблицы для my_appсоздаются в remoteбазе данных, а django_migrationsвнутри remoteбазы миграции помечаются как примененные.

РЕДАКТИРОВАТЬ:
Как заставить Django использовать только одну таблицу django_migrations, но по-прежнему применять миграции в разные базы данных?

Как применить миграции в разных базах данных, чтобы не вызывать предупреждений?

Cezar
источник
1
для других приложений, которые не являются «my_app», allow_migrate возвращает None. Может быть, вы хотите сделать еще одну проверку там? Насколько я понимаю из вашего роутера, my_app использует базу данных remote, а все другие приложения будут использовать базу данных по умолчанию?
Мартин Талески
@cezar Вы просите почти невозможно. Чтобы иметь общую django_migrationsтаблицу, необходимо различать строки с миграциями для defaultи remotedb. Это довольно глубоко во внутренностях Джанго. Я бы даже рискнул заявить, что это потребует значительного изменения кода миграции.
Камиль Ниски
@KamilNiski спасибо, что поделились своими мыслями. Я перефразирую вопрос.
Цезарь
Эта проблема может быть актуальной.
Кевин Кристофер Генри

Ответы:

2

Благодаря комментариям на мой вопрос я провел небольшое исследование и сделал следующие выводы.

Использование нескольких баз данных приводит к созданию таблицы django_migrationsпри использовании миграций. Там нет возможности записывать миграции только в одной таблице django_migrations, как комментарий от Kamil Niski объясняет. Это понятно после прочтения файла django/db/migrations/recorder.py.

Я проиллюстрирую пример с проектом fooи приложением barвнутри проекта. Приложение barимеет только одну модель Baz.

Мы создаем проект:

django-admin startproject foo

Теперь у нас есть это содержимое в главном каталоге проекта:

- foo
- manage.py

У меня есть привычка группировать все приложения в каталоге проекта:

mkdir foo/bar
python manage.py bar foo/bar

В файле foo/settings.pyмы настраиваем параметры для использования двух разных баз данных, для целей этого примера мы используем 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'),
    }
}

Теперь мы запускаем миграции:

python manage.py migrate --database=default

При этом выполняются все миграции, часть --database=defaultявляется необязательной, поскольку, если не указано, Django использует базу данных по умолчанию.


  Выполняемые операции: применить все миграции: admin, auth, типы содержимого, сеансы.
 Выполнение миграции:
  Применение contenttypes.0001_initial ... ОК
  Применяем auth.0001_initial ... ОК
  Применяем admin.0001_initial ... ОК
  Применение admin.0002_logentry_remove_auto_add ... ОК
  Применение admin.0003_logentry_add_action_flag_choices ... ОК
  Применение contenttypes.0002_remove_content_type_name ... OK
  Применение auth.0002_alter_permission_name_max_length ... OK
  Применение auth.0003_alter_user_email_max_length ... OK
  Применение auth.0004_alter_user_username_opts ... ОК
  Применение auth.0005_alter_user_last_login_null ... ОК
  Применение auth.0006_require_contenttypes_0002 ... ОК
  Применение auth.0007_alter_validators_add_error_messages ... ОК
  Применение auth.0008_alter_user_username_max_length ... OK
  Применение auth.0009_alter_user_last_name_max_length ... OK
  Применение auth.0010_alter_group_name_max_length ... OK
  Применение auth.0011_update_proxy_permissions ... OK
  Применение сеансов.0001_initial ... ОК

Django применил все миграции к базе данных по умолчанию:

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 сессий 0001_ininial 2019-11-13 16: 51: 05.063868

Теперь мы создаем модель Baz:

models.py:

from django.db import models

class Baz(models.Model):
    name = models.CharField(max_length=255, unique=True)

зарегистрируйте приложение barв INSTALLED_APPS( foo/settings.py) и создайте миграции:

python manage.py makemigrations bar

Перед запуском миграций мы создаем routers.pyвнутри barприложения:

Класс BarRouter (объект):
    def db_for_read (self, model, ** hints):
        if model._meta.app_label == 'bar':
            вернуть «удаленный»
        возврат Нет

    def db_for_write (self, model, ** hints):
        if model._meta.app_label == 'bar':
            вернуть «удаленный»
        возврат Нет

    def allow_relation (self, obj1, obj2, ** hints):
        возврат Нет

    def allow_migrate (self, db, app_label, имя_модели = нет, ** подсказки):
        if app_label == 'bar':
            return db == 'remote'
        если db == 'remote':
            вернуть Ложь
        возврат Нет

и зарегистрируйте его в foo/settings.py:

DATABASE_ROUTERS = ['foo.bar.routers.BarRouter']

Теперь наивным подходом было бы запустить миграцию barв remoteбазу данных:

python manage.py migrate bar --database=remote

  Выполняемые операции: применить все миграции: bar
 Запуск миграций:
  Применение bar.0001_initial ... ОК

Миграции были применены к remoteбазе данных:

1 бар 0001_initial 2019-11-13 17: 32: 39.701784

Когда мы бежим:

python manage.py runserver

будет выдано следующее предупреждение:

У вас есть 1 не примененная миграция. Ваш проект может не работать должным образом, пока вы не примените миграции для app (s): bar.
Запустите «python manage.py migrate», чтобы применить их.

Кажется, все работает нормально. Однако это предупреждение не удовлетворяет.

Правильный способ - запустить все миграции для каждой базы данных, как указано в этом ответе .

Это будет выглядеть так:

python manage.py migrate --database=default
python manage.py migrate --database=remote

и после создания миграций для bar:

python manage.py migrate bar --database=default
python manage.py migrate bar --database=remote

Маршрутизатор позаботится о том, чтобы таблица bar_bazсоздавалась только в remoteбазе данных, но Django помечает миграции как применяемые в обеих базах данных. Также столы для auth, admin, sessionsи т.д. , будут созданы только в defaultбазе данных, как указано в routers.py. Таблица django_migrationsв remoteбазе данных также будет содержать записи для этих миграций.

Это долгое чтение, но я надеюсь, что оно проливает некоторый свет на эту, по моему мнению, не полностью объясненную проблему в официальной документации .

Cezar
источник