簡體   English   中英

Django 和只讀數據庫連接

[英]Django and read-only database connections

假設一個 Django 應用程序應該使用兩個 MySQL 數據庫:

  • default - 用於存儲模型AB表示的數據(讀寫訪問)
  • support - 用於導入由模型CD表示的數據(只讀訪問)

support數據庫是外部應用程序的一部分,無法修改。

由於 Django 應用程序對模型AB使用內置 ORM,我認為它應該對模型CD使用完全相同的 ORM,即使它們映射到外部數據庫中的表( support 。)

為了實現這一點,我將模型CD定義如下:

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.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'"))

盡管如此,Django 似乎django_migrations嘗試在只讀數據庫support創建django_migrations表。

有什么干凈的方法可以防止遷移機制嘗試這樣做嗎? 或者我是否必須使用另一個 ORM 庫來進行這種對support數據庫的只讀訪問?

我遇到了同樣的問題(使用 Django 1.11),這個問題是我的谷歌搜索結果的頂部。

您的初始解決方案僅缺少一個關鍵部分。 你需要告訴 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 團隊眼中最正確的解決方案,但效果是 allow_migrate() 對於使用該 app_label 屬性值定義的任何模型返回 False 。

關於路由器的 Django 文檔沒有明確提到這一點(或者,至少在模型代碼示例中明確了 ORM 如何將 'db' 的值傳遞給 allow_migrate()),而是在 'app_label' 和 'managed' 之間你可以讓它工作的屬性*。

* 在我的情況下,默認值是 postgres,只讀數據庫是通過 cx_Oracle 的 Oracle 12。

似乎在 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 數據庫應用路由器

有同樣的問題。 Django 正在嘗試在所有數據庫中創建 'django_migrations' 表。 即使沒有與只讀數據庫關聯的模型並且所有路由器都指向不同的數據庫,也會發生這種情況。

我也最終使用了peewee。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM