2つのMySQLデータベースを使用することになっているDjangoアプリケーションを想定します。
default
-モデルA
およびB
(読み取り/書き込みアクセス)で表されるデータを格納するためsupport
-モデルC
およびD
(読み取り専用アクセス)で表されるデータをインポートする場合support
データベースは外部アプリケーションの一部であり、cannotを変更できます。
DjangoアプリケーションはモデルA
とB
に組み込みのORMを使用しているため、外部データベースのテーブルにマップされていても、モデルC
とD
には同じ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ライブラリを使用する必要がありますか?
同じ問題が発生し(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です。
Django 1.10.1時間枠のようです、Tim Graham(主要なDjangoメンテナ))は、この特定の例外を抑制するパッチを受け入れましたが、後でパッチを取り下げましたこの問題を回避し、Django ORM。
ルーターに関するDjangoのドキュメントの説明に従ってデータベースルーターを定義します モデルの「アプリ」フラグに基づいて別のデータベースにルーティングするルーターの例を以下に添付しましたメタ。
ルーターのallow_migrationsメソッドで、読み取り専用データベースに対応するdb引数に対してFalseを返します。これにより、ルーティング先に関係なく、モデルテーブルの移行が防止されます。
この次の部分は少し奇妙ですが、ゴムが道にぶつかり、実際に元の質問に答えます。 makemigrationsが読み取り専用データベースにDjango_migrationsテーブルを作成しようとしないようにするには、データベーストラフィックをルーティングしないでください。この例のルーターでは、DATABASE_APPS_MAPPINGの「read_only」がnotであることを意味します。
したがって、代わりに、読み取り専用データベースは「using」を使用して明示的にアクセスされます(例:MyReadOnlyModel.objects.using( 'read_only')。all()
同じ問題がありました。 Djangoは、すべてのDBに「Django_migrations」テーブルを作成しようとしています。これは、読み取り専用DBに関連付けられたモデルがなく、すべてのルーターが別のDBを指している場合でも発生します。
私もpeeweeを使用してしまいました。