Use vários bancos de dados no Django com apenas uma tabela “django_migrations”

11

Para um projeto no Django, tenho que usar dois bancos de dados: padrão e remoto . Eu criei routers.pye tudo funciona bem.

Havia um requisito para criar uma tabela no banco de dados remoto e eu criei a migração, execute-a e a tabela django_migrationsfoi criada. Eu quero ter apenas uma tabela django_migrations, no banco de dados padrão.

A parte relevante routers.pyé aqui:

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

Eu executo a migração assim:

python manage.py migrate my_app --database=remote

Agora quando eu faço:

python manage.py runserver

Recebo o seguinte aviso:

Você tem 1 migração (s) não aplicada (s). Seu projeto pode não funcionar corretamente até você aplicar as migrações para o (s) aplicativo (s): my_app.
Execute 'python manage.py migrate' para aplicá-los.

As tabelas para my_appsão criadas no remotebanco de dados e, django_migrationsdentro do remotebanco de dados, as migrações são marcadas como aplicadas.

EDIT:
Como forçar o Django a usar apenas uma tabela django_migrations, mas ainda aplicar as migrações em diferentes bancos de dados?

Como aplicar as migrações em diferentes bancos de dados para que nenhum aviso seja gerado?

cezar
fonte
11
para outros aplicativos que não são 'my_app', allow_migrate está retornando Nenhum. Talvez você queira fazer outra verificação lá? Pelo que entendi no seu roteador, 'my_app' usa o banco de dados 'remoto' e todos os outros aplicativos usarão o banco de dados 'padrão'?
Martin Taleski 7/11
@cezar Você pede quase impossível. Para ter uma django_migrationstabela compartilhada , será necessário diferenciar entre linhas com migrações para defaulte remotedb. Isso é bem profundo nos internos do django. Eu correria o risco de afirmar que isso exigiria grande reescrita do código de migração.
Kamil Niski 07/11/19
@KamilNiski obrigado por compartilhar seus pensamentos. Vou reformular a pergunta.
Cezar
Esse problema pode ser relevante.
22619 Kevin Smith Henry

Respostas:

2

Graças aos comentários sobre minha pergunta, fiz algumas pesquisas e descobri as seguintes conclusões.

O uso de vários bancos de dados resulta na criação de uma tabela django_migrationsquando as migrações são usadas. Não há opção para registrar as migrações em apenas uma tabela django_migrations, como explica o comentário de Kamil Niski . Isso fica claro após a leitura do arquivo django/db/migrations/recorder.py.

Ilustrarei um exemplo com um projeto fooe um aplicativo bardentro do projeto. O aplicativo barpossui apenas um modelo Baz.

Criamos o projeto:

django-admin startproject foo

Agora, temos este conteúdo dentro do diretório principal do projeto:

- foo
- manage.py

Eu tenho o hábito de agrupar todos os aplicativos dentro do diretório do projeto:

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

No arquivo foo/settings.py, ajustamos as configurações para usar dois bancos de dados diferentes. Para os fins deste exemplo, usamos 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'),
    }
}

Agora, executamos as migrações:

python manage.py migrate --database=default

Isso executa todas as migrações, a parte --database=defaulté opcional, porque se não for especificado, o Django usa o banco de dados padrão.

Operações a serem executadas: 
  aplique todas as migrações: admin, auth, contenttypes, sessions
 Executando migrações:
  Aplicando contenttypes.0001_initial ... OK
  Aplicando auth.0001_initial ... OK
  Aplicando admin.0001_initial ... OK
  Aplicando admin.0002_logentry_remove_auto_add ... OK
  Aplicando admin.0003_logentry_add_action_flag_choices ... OK
  Aplicando contenttypes.0002_remove_content_type_name ... OK
  Aplicando auth.0002_alter_permission_name_max_length ... OK
  Aplicando auth.0003_alter_user_email_max_length ... OK
  Aplicando auth.0004_alter_user_username_opts ... OK
  Aplicando auth.0005_alter_user_last_login_null ... OK
  Aplicando auth.0006_require_contenttypes_0002 ... OK
  Aplicando auth.0007_alter_validators_add_error_messages ... OK
  Aplicando auth.0008_alter_user_username_max_length ... OK
  Aplicando auth.0009_alter_user_last_name_max_length ... OK
  Aplicando auth.0010_alter_group_name_max_length ... OK
  Aplicando auth.0011_update_proxy_permissions ... OK
  Aplicando sessions.0001_initial ... OK

O Django aplicou todas as migrações ao banco de dados padrão:

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 tipos de conteúdo 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 sessões 0001_initial 2019-11-13 16: 51: 05.063868

Agora criamos o modelo Baz:

models.py:

from django.db import models

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

registre o aplicativo barem INSTALLED_APPS( foo/settings.py) e crie as migrações:

python manage.py makemigrations bar

Antes de executar as migrações que criamos routers.pydentro do baraplicativo:

classe BarRouter (objeto):
    def db_for_read (self, model, ** dicas):
        if model._meta.app_label == 'bar':
            retornar 'remoto'
        retornar Nenhum

    def db_for_write (self, model, ** dicas):
        if model._meta.app_label == 'bar':
            retornar 'remoto'
        retornar Nenhum

    def allow_relation (auto, obj1, obj2, ** dicas):
        retornar Nenhum

    def allow_migrate (self, db, app_label, model_name = None, ** dicas):
        se app_label == 'bar':
            return db == 'remoto'
        se db == 'remoto':
            retorna falso
        retornar Nenhum

e registre-o em foo/settings.py:

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

Agora, a abordagem ingênua seria executar as migrações para baro remotebanco de dados:

python manage.py migrate bar --database=remote
Operações a serem executadas: 
  Aplicar todas as migrações: bar
 Executando migrações:
  Aplicando bar.0001_initial ... OK

As migrações foram aplicadas ao remotebanco de dados:

1 bar 0001_initial 2019-11-13 17: 32: 39.701784

Quando corremos:

python manage.py runserver

o seguinte aviso será gerado:

Você tem 1 migração (s) não aplicada (s). Seu projeto pode não funcionar corretamente até você aplicar as migrações para o (s) aplicativo (s): bar.
Execute 'python manage.py migrate' para aplicá-los.

Tudo parece funcionar bem embora. No entanto, não é satisfatório receber esse aviso.

A maneira correta seria executar todas as migrações para cada banco de dados, conforme sugerido nesta resposta .

Seria assim:

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

e depois de criar as migrações para bar:

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

O roteador cuidará para que a tabela bar_bazseja criada apenas no remotebanco de dados, mas o Django marcará as migrações como aplicadas nos dois bancos de dados. Também as mesas para auth, admin, sessions, etc. será criado apenas no defaultbanco de dados, conforme especificado no routers.py. A tabela django_migrationsno remotebanco de dados também terá registros para essas migrações.

É uma leitura longa, mas espero que ajude a esclarecer isso, na minha opinião, questão não completamente explicada na documentação oficial .

cezar
fonte