Utilisez plusieurs bases de données dans Django avec une seule table «django_migrations»

11

Pour un projet à Django, je dois utiliser deux bases de données: par défaut et à distance . J'ai créérouters.py et tout fonctionne bien.

Il était nécessaire de créer une table sur la base de données distante et j'ai créé la migration, l'exécuter et la table a django_migrationsété créée. Je veux avoir une seule tabledjango_migrations , dans la base de données par défaut.

La partie pertinente de routers.pyest ici:

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

Je lance la migration comme ceci:

python manage.py migrate my_app --database=remote

Maintenant, quand je fais:

python manage.py runserver

J'obtiens l'avertissement suivant:

Vous avez 1 migration (s) non appliquée (s). Votre projet peut ne pas fonctionner correctement tant que vous n'avez pas appliqué les migrations pour les applications: my_app.
Exécutez 'python manage.py migrate' pour les appliquer.

Les tables pour my_appsont créées dans la remotebase de données et à l' django_migrationsintérieur duremote base de données, les migrations sont marquées comme appliquées.

EDIT:
Comment forcer Django à utiliser une seule table django_migrations, mais toujours appliquer les migrations dans différentes bases de données?

Comment appliquer les migrations dans différentes bases de données afin qu'aucun avertissement ne soit émis?

cezar
la source
1
pour les autres applications qui ne sont pas «my_app», allow_migrate renvoie None. Peut-être que vous voulez faire un autre contrôle là-bas? D'après ce que je comprends de votre routeur, «my_app» utilise la base de données «distante» et toutes les autres applications utiliseront la base de données «par défaut»?
Martin Taleski
@cezar Vous demandez presque impossible. Afin d'avoir une django_migrationstable partagée , il sera nécessaire de différencier les lignes avec les migrations pour defaultet remotedb. C'est assez profond dans les internes de Django. Je risquerais même d'affirmer que cela nécessiterait une réécriture majeure du code de migration.
Kamil Niski
@KamilNiski merci d'avoir partagé vos réflexions. Je reformulerai la question.
cezar
Ce problème peut être pertinent.
Kevin Christopher Henry

Réponses:

2

Grâce aux commentaires sur ma question, j'ai fait quelques recherches et suis arrivé aux conclusions suivantes.

L'utilisation de plusieurs bases de données entraîne la création d'une table django_migrationslorsque des migrations sont utilisées. Il n'y a pas d'option pour enregistrer les migrations dans une seule table django_migrations, comme l' explique le commentaire de Kamil Niski . C'est clair après avoir lu le fichier django/db/migrations/recorder.py.

Je vais illustrer un exemple avec un projet fooet une application barà l'intérieur du projet. L'application barn'a qu'un seul modèle Baz.

Nous créons le projet:

django-admin startproject foo

Nous avons maintenant ces contenus dans le répertoire principal du projet:

- foo
- manage.py

J'ai l'habitude de regrouper toutes les applications dans le répertoire du projet:

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

Dans le fichier, foo/settings.pynous ajustons les paramètres pour utiliser deux bases de données différentes, aux fins de cet exemple, nous utilisons 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'),
    }
}

Maintenant, nous exécutons les migrations:

python manage.py migrate --database=default

Cela exécute toutes les migrations, la partie --database=defaultest facultative, car si non spécifié, Django utilise la base de données par défaut.

Opérations à effectuer: 
  Appliquer toutes les migrations: admin, auth, contenttypes, sessions
 Exécution des migrations:
  Application de contenttypes.0001_initial ... OK
  Appliquer auth.0001_initial ... OK
  Application de admin.0001_initial ... OK
  Application de admin.0002_logentry_remove_auto_add ... OK
  Application de admin.0003_logentry_add_action_flag_choices ... OK
  Application de contenttypes.0002_remove_content_type_name ... OK
  Appliquer auth.0002_alter_permission_name_max_length ... OK
  Appliquer auth.0003_alter_user_email_max_length ... OK
  Appliquer auth.0004_alter_user_username_opts ... OK
  Appliquer auth.0005_alter_user_last_login_null ... OK
  Appliquer auth.0006_require_contenttypes_0002 ... OK
  Appliquer auth.0007_alter_validators_add_error_messages ... OK
  Appliquer auth.0008_alter_user_username_max_length ... OK
  Appliquer auth.0009_alter_user_last_name_max_length ... OK
  Appliquer auth.0010_alter_group_name_max_length ... OK
  Application d'auth.0011_update_proxy_permissions ... OK
  Application de sessions.0001_initial ... OK

Django a appliqué toutes les migrations à la base de données par défaut:

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

Maintenant, nous créons le modèle Baz:

models.py:

from django.db import models

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

enregistrer l'application bardans INSTALLED_APPS( foo/settings.py) et créer des migrations:

python manage.py makemigrations bar

Avant d'exécuter les migrations que nous créons routers.pydans l' barapplication:

classe BarRouter (objet):
    def db_for_read (auto, modèle, ** astuces):
        si model._meta.app_label == 'bar':
            retourner «à distance»
        retour Aucun

    def db_for_write (auto, modèle, ** astuces):
        si model._meta.app_label == 'bar':
            retourner «à distance»
        retour Aucun

    def allow_relation (self, obj1, obj2, ** hints):
        retour Aucun

    def allow_migrate (self, db, app_label, model_name = None, ** hints):
        si app_label == 'bar':
            return db == 'remote'
        si db == 'distant':
            retour Faux
        retour Aucun

et enregistrez-le dans foo/settings.py:

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

Maintenant, l'approche naïve serait d'exécuter les migrations bardans la remotebase de données:

python manage.py migrate bar --database=remote
Opérations à effectuer: 
  Appliquer toutes les migrations: barre
 Exécution des migrations:
  Application de la barre 0001_initial ... OK

Les migrations ont été appliquées à la remotebase de données:

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

Quand nous courons:

python manage.py runserver

l'avertissement suivant sera émis:

Vous avez 1 migration (s) non appliquée (s). Votre projet peut ne pas fonctionner correctement tant que vous n'avez pas appliqué les migrations pour les applications: bar.
Exécutez 'python manage.py migrate' pour les appliquer.

Cependant, tout semble bien fonctionner. Cependant, il n'est pas satisfaisant d'avoir cet avertissement.

La bonne façon serait d'exécuter toutes les migrations pour chaque base de données comme suggéré dans cette réponse .

Cela ressemblerait à ceci:

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

et après avoir créé les migrations pour bar:

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

Le routeur veillera à ce que la table bar_bazsoit créée uniquement dans la remotebase de données, mais Django marquera les migrations comme appliquées dans les deux bases de données. Aussi les tables pour auth, admin, sessions, etc. seront créés uniquement dans la defaultbase de données, comme indiqué dans routers.py. La table django_migrationsde la remotebase de données contiendra également des enregistrements pour ces migrations.

C'est une longue lecture, mais j'espère que cela jette un peu de lumière sur ce problème, à mon avis, pas complètement expliqué dans la documentation officielle .

cezar
la source