Używaj wielu baz danych w Django z tylko jedną tabelą „django_migrations”

11

W przypadku projektu w Django muszę korzystać z dwóch baz danych: domyślnej i zdalnej . Stworzyłem routers.pyi wszystko działa dobrze.

Wymagano utworzenia tabeli w zdalnej bazie danych, a ja utworzyłem migrację, uruchomiłem ją i tabela django_migrationszostała utworzona. Chcę mieć tylko jedną tabelę django_migrationsw domyślnej bazie danych.

Odpowiednia część routers.pyznajduje się tutaj:

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

Przeprowadzam migrację w następujący sposób:

python manage.py migrate my_app --database=remote

Teraz kiedy to zrobię:

python manage.py runserver

Otrzymuję następujące ostrzeżenie:

Masz 1 niestosowanych migracji. Twój projekt może nie działać poprawnie, dopóki nie zastosujesz migracji aplikacji: my_app.
Uruchom „python manage.py migrate”, aby je zastosować.

Tabele dla my_appsą tworzone w remotebazie danych, a django_migrationswewnątrz remotebazy migracje są oznaczone jako zastosowane.

EDYCJA:
Jak zmusić Django do używania tylko jednej tabeli django_migrations, ale nadal stosować migracje do różnych baz danych?

Jak zastosować migrację w różnych bazach danych, aby nie były generowane żadne ostrzeżenia?

Cezar
źródło
1
w przypadku innych aplikacji, które nie są „my_app”, allow_migrate zwraca None. Może chcesz zrobić tam kolejną kontrolę? Z tego, co rozumiem z routera, „moja_aplikacja” korzysta ze „zdalnej” bazy danych, a wszystkie inne aplikacje będą korzystać z „domyślnej” bazy danych?
Martin Taleski,
@cezar Pytasz o prawie niemożliwe. Aby mieć wspólną django_migrationstabelę, konieczne będzie rozróżnienie między wierszami z migracjami dla defaulti remotedb. Jest to dość głęboko w wewnętrznych django. Zaryzykowałbym nawet stwierdzenie, że wymagałoby to poważnego przepisania kodu migracji.
Kamil Niski
@KamilNiski dziękuję za podzielenie się swoimi przemyśleniami. Przeredaguję pytanie.
cezar
Ten problem może być istotny.
Kevin Christopher Henry

Odpowiedzi:

2

Dzięki komentarzom do mojego pytania przeprowadziłem badania i opracowałem następujące ustalenia.

Korzystanie z wielu baz danych powoduje utworzenie tabeli django_migrationspodczas korzystania z migracji. Jak wyjaśnia django_migrationskomentarz Kamila Niski'ego, nie ma opcji rejestrowania migracji tylko w jednej tabeli . Wyraźnie widać to po odczytaniu pliku django/db/migrations/recorder.py.

Zilustruję przykład z projektem fooi aplikacją barwewnątrz projektu. Aplikacja barma tylko jeden model Baz.

Tworzymy projekt:

django-admin startproject foo

Teraz mamy te treści w głównym katalogu projektu:

- foo
- manage.py

Mam zwyczaj grupować wszystkie aplikacje w katalogu projektu:

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

W pliku foo/settings.pydostosowujemy ustawienia, aby używać dwóch różnych baz danych, do celów tego przykładu używamy 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'),
    }
}

Teraz uruchamiamy migracje:

python manage.py migrate --database=default

To uruchamia wszystkie migracje, część --database=defaultjest opcjonalna, ponieważ jeśli nie zostanie określony, Django używa domyślnej bazy danych.

Operacje do wykonania: 
  Zastosuj wszystkie migracje: admin, auth, typy zawartości, sesje
 Uruchamianie migracji:
  Stosuję typy treści 0001_inicjalne ... OK
  Zastosowanie auth.0001_initial ... OK
  Aplikuję admin.0001_initial ... OK
  Stosowanie admin.0002_logentry_remove_auto_add ... OK
  Stosowanie admin.0003_logentry_add_action_flag_choices ... OK
  Stosowanie typów treści 0002_usunąć_nazwa_typu treści ... OK
  Stosuję auth.0002_alter_permission_name_max_length ... OK
  Stosowanie auth.0003_alter_user_email_max_length ... OK
  Stosowanie auth.0004_alter_user_username_opts ... OK
  Stosowanie auth.0005_alter_user_last_login_null ... OK
  Zastosowanie auth.0006_require_contenttypes_0002 ... OK
  Stosowanie wiadomości auth.0007_alter_validators_add_error_messages ... OK
  Stosowanie auth.0008_nazwa_użytkownika_nazwa_użytkownika_maksymalna_długość ... OK
  Stosowanie auth.0009_alter_user_last_name_max_length ... OK
  Stosowanie auth.0010_alter_group_name_max_length ... OK
  Stosowanie auth.0011_update_proxy_permissions ... OK
  Stosowanie sesji 0001_inicjalne ... OK

Django zastosował wszystkie migracje do domyślnej bazy danych:

1 typy treści 0001_initial 13.11.2016 16: 51: 04.767382
2 auth 0001_initial 13.11.2010 16: 51: 04.792245
3 admin 0001_initial 13.11.2010 16: 51: 04.827454
4 admin 0002_logentr 13.11.2010 16: 51: 04.846627
5 admin 0003_logentr 13.11.2019 16: 51: 04.864458
6 typów treści 0002_remove_ 13.11.2016 16: 51: 04.892220
7 auth 0002_alter_p 2019-11-13 16: 51: 04.906449
8 auth 0003_alter_u 13.11.2010 16: 51: 04.923902
9 auth 0004_alter_u 2019-11-13 16: 51: 04.941707
10 auth 0005_alter_u 13.11.2010 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 13.11.2010 16: 51: 05.019705
15 auth 0010_alter_g 13.11.2010 16: 51: 05.037023
16 auth 0011_update_ 13.11.2010 16: 51: 05.054449
17 sesji 0001_initial 13.11.2016 16: 51: 05.063868

Teraz tworzymy model Baz:

models.py:

from django.db import models

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

zarejestruj aplikację barw INSTALLED_APPS( foo/settings.py) i utwórz migracje:

python manage.py makemigrations bar

Przed uruchomieniem migracji tworzymy routers.pyw baraplikacji:

klasa BarRouter (obiekt):
    def db_for_read (self, model, ** wskazówki):
        if model._meta.app_label == 'bar':
            zwróć „zdalne”
        zwrot Brak

    def db_for_write (self, model, ** wskazówki):
        if model._meta.app_label == 'bar':
            zwróć „zdalne”
        zwrot Brak

    def allow_relation (self, obj1, obj2, ** wskazówki):
        zwrot Brak

    def allow_migrate (self, db, app_label, model_name = None, ** wskazówki):
        if app_label == 'bar':
            return db == 'remote'
        jeśli db == 'remote':
            zwrócić False
        zwrot Brak

i zarejestruj go w foo/settings.py:

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

Teraz naiwnym podejściem byłoby uruchomienie migracji bardo remotebazy danych:

python manage.py migrate bar --database=remote
Operacje do wykonania: 
  Zastosuj wszystkie migracje: pasek
 Uruchamianie migracji:
  Stosowanie paska bar.0001_initial ... OK

Migracje zostały zastosowane do remotebazy danych:

1 takt 0001_inicjalny 13.11.2019 17: 32: 39.701784

Kiedy biegniemy:

python manage.py runserver

zostanie wygenerowane następujące ostrzeżenie:

Masz 1 niestosowanych migracji. Twój projekt może nie działać poprawnie, dopóki nie zastosujesz migracji aplikacji: bar.
Uruchom „python manage.py migrate”, aby je zastosować.

Wszystko wydaje się jednak działać dobrze. To ostrzeżenie nie jest jednak satysfakcjonujące.

Właściwym sposobem byłoby uruchomienie wszystkich migracji dla każdej bazy danych, jak sugerowano w tej odpowiedzi .

Wyglądałoby to tak:

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

i po utworzeniu migracji dla bar:

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

Router upewni się, że tabela bar_bazjest tworzona tylko w remotebazie danych, ale Django oznaczy migracje jako zastosowane w obu bazach danych. Także stoły do auth, admin, sessions, itd. Zostaną utworzone tylko w defaultbazie danych, jak określono w routers.py. Tabela django_migrationsw remotebazie danych będzie również zawierać rekordy dla tych migracji.

To długie czytanie, ale mam nadzieję, że rzuci nieco światła na to, moim zdaniem, nie do końca wyjaśnione zagadnienie w oficjalnej dokumentacji .

Cezar
źródło