0

在具有一些自制(但接近可用的插件方法)多租户实现的 Django 应用程序上,我想使用 South 运行可以应用于所有模式的迁移(这次是一个简单的 add_column)。我有一个非常接近这个的配置。

如果可能,我想跳过任何纯 SQL 查询。我可以正确地从 ORM 中获取模式名称列表,但是我想知道我是否有可能以某种适当的方式从各种模式中访问表。

我有一个钩子可以在某种程度上通过参数更改 DB_HOST 和 DB_SCHEMA,但我认为在 South 的前向迁移方法中不能以这种方式干净地循环。

这个问题相当高级,但我主要想知道是否有人必须面对同样的问题,我很想知道是否有一些聪明的方法来处理它!

问候, 马特

4

2 回答 2

1

这是一个解决方案的大纲,发布在 South 邮件列表中。措辞的问题与列表中发布的问题略有不同:在那里,还提到了在单独的模式中在所有租户之间共享的“公共”表。Rmatt 自己的回答将此称为公共模式。

我的解决方案的基本思想:在架构中保存每个数据库(架构)的迁移历史记录。为此,我们需要使用一些数据库和 Django 技巧。

这意味着公共架构上应用程序迁移的历史记录保存在公共架构中,而租户应用程序迁移的历史记录保存在租户架构中——有效地对迁移历史表进行分片。Django 并不真正支持这种分片;按实例内容设置写入很容易,但无法设置读取。

所以我建议为每个租户创建一个“租户助手”模式,其中包含一个名为的视图,south_migrationhistory它是south_migrationhistory来自公共模式和租户模式的表的联合。然后,为 South MigrationHistory 模型设置一个数据库路由器,指示它:

  • 同步到公共和租户模式
  • 总是从租户助手模式中读取
  • 根据迁移所属的应用程序写入公共或租户架构

结果允许正确处理从租户应用程序迁移到公共应用程序迁移的依赖关系;这意味着您需要做的所有事情,向前迁移,就是循环migrate --all(或syncdb --migrate)命令——无需伪造向后迁移。公共模式的迁移将与循环中第一个租户的迁移一起运行,所有其他租户将“看到”它们。

作为事后的想法,也有可能在没有帮助模式的情况下执行此操作 - 通过重命名south_migrationhistory租户模式中的表,并在模式中安装具有该名称的视图,该视图在查询时返回上述联合,并且具有“而不是插入”触发器写入重命名的表。

于 2012-11-26T12:29:54.170 回答
0

好吧,似乎没有多少人有经验或关心这个非常具体的问题。我在这里和那里尝试了一些东西,我还从南方邮件列表中获得了一些支持,帮助我理解了一些观点。

基本上,我实施的解决方案如下:

我有一个通过 South 的schemamigration自动生成的非常正常的迁移文件。但我已将add_columndelete_column的表名更改为schema.table_name. 通过导入多租户中间件提供模式。

仅当架构未针对公共架构运行时,才会应用迁移。它实际上并不意味着独立运行,或者仅与数据库和模式 kwargs 一起运行,而是来自一个新的 django 命令的迁移运行器。

不幸的是,跑步者不得不在外部调用迁移,以便每次再次通过中间件。另一个技巧是我们必须获取先前的迁移状态,以便在每次租户迁移后将其伪造回南部的先前状态。

这是我的片段:

from subprocess import call
import os
from django.core.management.base import BaseCommand    
from south.models import MigrationHistory
from myapp.models import MyModel

class Command(BaseCommand):

    def handle(self, *args, **options):
        #the only allowed arg is the prefix version and it should have a length of 4 (i.e. 0002)
        applied = MigrationHistory.objects.filter(app_name='myapp').latest('applied')
        current_version = applied.migration[:4]
        call_args = ['python', os.path.join('bin', 'manage.py'), 'migrate', 'myorderbird.app.backups']
        if len(args) == 1 and len(args[0]) == 4:
            call_args.append(args[0])

        obje_call_args = None
        for obje in MyModel.objects.all():
            if obje.schema_exists:
                # fake the migration of the previous venue back to the current version
                if obje_call_args:
                    obje_call_args = obje_call_args[:4] + [current_version, '--fake'] + obje_call_args[len(obje_call_args)-3:]
                    call(obje_call_args)
                # migrate the venue in the loop
                obje_call_args = list(call_args)
                obje_call_args.extend(['--database={}'.format(obje.db), '--schema={}'.format(obje.schema)])
                call(venue_call_args)
于 2012-11-23T17:28:04.040 回答