web-dev-qa-db-ja.com

Djangoおよび読み取り専用データベース接続

2つのMySQLデータベースを使用することになっているDjangoアプリケーションを想定します。

  • default-モデルAおよびB(読み取り/書き込みアクセス)で表されるデータを格納するため
  • support-モデルCおよびD(読み取り専用アクセス)で表されるデータをインポートする場合

supportデータベースは外部アプリケーションの一部であり、cannotを変更できます。

DjangoアプリケーションはモデルABに組み込みのORMを使用しているため、外部データベースのテーブルにマップされていても、モデルCDには同じORMを使用する必要があると考えました( support。)

これを実現するために、モデルCおよびDを次のように定義しました。

from Django.db import models


class ExternalModel(models.Model):
    class Meta:
        managed = False
        abstract = True


class ModelC(ExternalModel):
    some_field = models.TextField(db_column='some_field')

    class Meta(ExternalModel.Meta):
        db_table = 'some_table_c'


class ModelD(ExternalModel):
    some_other_field = models.TextField(db_column='some_other_field')

    class Meta(ExternalModel.Meta):
        db_table = 'some_table_d'

次に、データベースルーターを定義しました。

from myapp.myapp.models import ExternalModel


class DatabaseRouter(object):
    def db_for_read(self, model, **hints):
        if issubclass(model, ExternalModel):
            return 'support'

        return 'default'

    def db_for_write(self, model, **hints):
        if issubclass(model, ExternalModel):
            return None

        return 'default'

    def allow_relation(self, obj1, obj2, **hints):
        return (isinstance(obj1, ExternalModel) == isinstance(obj2, ExternalModel))

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return (db == 'default')

そして最後に調整されたsettings.py

# (...)

DATABASES = {
    'default': {
        'ENGINE': 'Django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': os.path.join(BASE_DIR, 'resources', 'default.cnf'),
        },
    },
    'support': {
        'ENGINE': 'Django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': os.path.join(BASE_DIR, 'resources', 'support.cnf'),
        },
    },
}

DATABASE_ROUTERS = ['myapp.database_router.DatabaseRouter']

# (...)

supportデータベースのsupport.confで指定されたユーザーには、読み取り専用権限が割り当てられています。

しかし、python manage.py makemigrationsを実行すると、次の出力で失敗します。

    Traceback (most recent call last):
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/utils.py", line 62, in execute
    return self.cursor.execute(sql)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/mysql/base.py", line 112, in execute
    return self.cursor.execute(query, args)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 226, in execute
    self.errorhandler(self, exc, value)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorvalue
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 217, in execute
    res = self._query(query)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 378, in _query
    rowcount = self._do_query(q)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 341, in _do_query
    db.query(q)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/connections.py", line 280, in query
    _mysql.connection.query(self, query)
_mysql_exceptions.OperationalError: (1142, "CREATE command denied to user 'somedbuser'@'somehost' for table 'Django_migrations'")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/migrations/recorder.py", line 57, in ensure_schema
    editor.create_model(self.Migration)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/base/schema.py", line 295, in create_model
    self.execute(sql, params or None)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/base/schema.py", line 112, in execute
    cursor.execute(sql, params)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/utils.py", line 79, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/utils.py", line 62, in execute
    return self.cursor.execute(sql)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/backends/mysql/base.py", line 112, in execute
    return self.cursor.execute(query, args)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 226, in execute
    self.errorhandler(self, exc, value)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorvalue
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 217, in execute
    res = self._query(query)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 378, in _query
    rowcount = self._do_query(q)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/cursors.py", line 341, in _do_query
    db.query(q)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/MySQLdb/connections.py", line 280, in query
    _mysql.connection.query(self, query)
Django.db.utils.OperationalError: (1142, "CREATE command denied to user 'somedbuser'@'somehost' for table 'Django_migrations'")

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    execute_from_command_line(sys.argv)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/core/management/__init__.py", line 367, in execute_from_command_line
    utility.execute()
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/core/management/__init__.py", line 359, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/core/management/base.py", line 305, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/core/management/base.py", line 356, in execute
    output = self.handle(*args, **options)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/core/management/commands/makemigrations.py", line 100, in handle
    loader.check_consistent_history(connection)
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/migrations/loader.py", line 276, in check_consistent_history
    applied = recorder.applied_migrations()
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/migrations/recorder.py", line 65, in applied_migrations
    self.ensure_schema()
  File "/Users/username/Development/stuff/myapp/lib/python3.5/site-packages/Django/db/migrations/recorder.py", line 59, in ensure_schema
    raise MigrationSchemaMissing("Unable to create the Django_migrations table (%s)" % exc)
Django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the Django_migrations table ((1142, "CREATE command denied to user 'somedbuser'@'somehost' for table 'Django_migrations'"))

それにもかかわらず、DjangoはDjango_migrationsテーブルを読み取り専用データベースsupportに作成しようとしています。

移行メカニズムがそれを試みないようにするクリーンな方法はありますか?または、supportデータベースへのこの読み取り専用アクセスに別のORMライブラリを使用する必要がありますか?

17
Konowau

同じ問題が発生し(Django 1.11を使用)、この質問がGoogleの検索結果の一番上にありました。

最初のソリューションでは、重要な部分が1つだけ不足しています。 Django「C」と「D」が使用しているデータベースモデルを通知する必要があります。

class ExternalModel(models.Model):
    class Meta:
        managed = False
        abstract = True    
        app_label = 'support'

次に、allow_migrate()セクションでapp_labelが検出されたときの動作をデータベースルーターに伝えます。

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'support':
            return False
        return (db == 'default')

Djangoチームの目には、これが最も正しい解決策であるかどうかはわかりませんが、そのapp_label属性値で定義されたモデルに対して、allow_migrate()がFalseを返すのが効果です。

Django ルーターに関するドキュメント はこれについて明示的に言及していません(または、少なくともORMが 'db'の値を渡す方法を明確にするモデルコードサンプルでは) allow_migrate())に変更しますが、「app_label」属性と「managed」属性の間で機能させることができます*。

*私の場合、デフォルトはpostgresで、読み取り専用データベースはcx_Oracle経由のOracle 12です。

11
SirSteavis

Django 1.10.1時間枠のようです、Tim Graham(主要なDjangoメンテナ))は、この特定の例外を抑制するパッチを受け入れましたが、後でパッチを取り下げましたこの問題を回避し、Django ORM。

  1. ルーターに関するDjangoのドキュメントの説明に従ってデータベースルーターを定義します モデルの「アプリ」フラグに基づいて別のデータベースにルーティングするルーターの例を以下に添付しましたメタ。

  2. ルーターのallow_migrationsメソッドで、読み取り専用データベースに対応するdb引数に対してFalseを返します。これにより、ルーティング先に関係なく、モデルテーブルの移行が防止されます。

  3. この次の部分は少し奇妙ですが、ゴムが道にぶつかり、実際に元の質問に答えます。 makemigrationsが読み取り専用データベースにDjango_migrationsテーブルを作成しようとしないようにするには、データベーストラフィックをルーティングしないでください。この例のルーターでは、DATABASE_APPS_MAPPINGの「read_only」がnotであることを意味します。

  4. したがって、代わりに、読み取り専用データベースは「using」を使用して明示的にアクセスされます(例:MyReadOnlyModel.objects.using( 'read_only')。all()

Djangoデータベースアプリルーター

7
Roger Masse

同じ問題がありました。 Djangoは、すべてのDBに「Django_migrations」テーブルを作成しようとしています。これは、読み取り専用DBに関連付けられたモデルがなく、すべてのルーターが別のDBを指している場合でも発生します。

私もpeeweeを使用してしまいました。

2
rwms