Django和只读数据库连接

Kon*_*wau 15 python mysql database django

假设一个应该使用两个MySQL数据库的Django应用程序:

  • default- 用于存储由模型表示的数据AB(读写访问)
  • support- 用于导入由模型表示的数据CD(只读访问)

support数据库是一个外部应用程序的一部分,并且不能修改.

由于Django的应用程序使用内置的ORM的模型AB我想它应该使用同样的ORM的模型CD,即使它们映射到外部数据库表(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'
Run Code Online (Sandbox Code Playgroud)

然后我定义了数据库路由器:

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')
Run Code Online (Sandbox Code Playgroud)

最后调整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']

# (...)
Run Code Online (Sandbox Code Playgroud)

support.confsupport数据库指定的用户已分配了只读权限.

但是当我运行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'"))
Run Code Online (Sandbox Code Playgroud)

看来Django试图django_migrations在只读数据库中创建表support.

是否有任何干净的方法来阻止迁移机制尝试?或者我是否必须使用另一个ORM库来进行对support数据库的只读访问?

小智 9

我遇到了同样的问题(使用Django 1.11),这个问题在我的谷歌搜索结果中排名第一.

您的初始解决方案只缺少一个关键部分.你需要告诉Django什么数据库模型'C'和'D'正在使用.什么对我有用:

class ExternalModel(models.Model):
    class Meta:
        managed = False
        abstract = True    
        app_label = 'support'
Run Code Online (Sandbox Code Playgroud)

然后告诉数据库路由器在allow_migrate()部分遇到app_label时的行为:

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

我不确定这是Django团队眼中最正确的解决方案,但是效果是allow_migrate()为使用该app_label属性值定义的任何模型返回False.

关于路由器的Django 文档没有明确提到这一点(或者,至少在模型代码示例中明确说明ORM如何将'db'的值传递给allow_migrate()),但在'app_label'和'managed'之间属性,你可以让它工作*.

*在我的情况下,默认是postgres,只读数据库是通过cx_Oracle的Oracle 12.


小智 7

似乎在Django 1.10.1时间框架内,Tim Graham(主要的Django维护者)接受了一个抑制了此特定异常的补丁,但后来撤回了该补丁,而采用(大致)以下方法来解决此问题并支持读取。 -使用Django ORM的数据库。

  1. 按照路由器的Django文档中的说明定义数据库路由器,我在下面附加了一个示例路由器,该路由器根据模型元数据中的“ app”标志路由到其他数据库。

  2. 在您的路由器allow_migrations方法中,对与只读数据库相对应的任何db参数返回False。这样可以防止迁移模型表,无论它们将被路由到何处。

  3. 下一部分有点奇怪,但是橡胶碰到了哪里,实际上回答了最初的问题。为了防止makemigrations尝试在只读数据库中创建django_migrations表,不应路由数据库流量。在示例路由器中,这意味着“ read_only” 不在 DATABASE_APPS_MAPPING中。

  4. 因此,相反,只读数据库通过“使用”显式访问(例如MyReadOnlyModel.objects.using('read_only')。all()

Django数据库应用路由器


rwm*_*wms 2

有同样的问题。Django 正在尝试在所有数据库中创建“django_migrations”表。即使没有与只读数据库关联的模型并且所有路由器都指向不同的数据库,也会发生这种情况。

我最终也使用了peewee。