在一个带有一些自制(但接近可用的插件方法)多租户实现的Django应用程序中,我想用South运行一个迁移(这次是一个简单的add_column),可以应用于所有模式。我的配置非常接近this one。
如果可能的话,我想跳过任何纯SQL查询。我可以正确地从ORM获取模式名称列表,但后来我想知道我是否有可能以某种方式从各种模式中访问表。
我有一个钩子能够在某个级别通过参数更改DB_HOST和DB_SCHEMA,但我认为不能在南方的前向迁移方法中以这种方式干净地循环。
这个问题相当高级,但我主要想知道是否有人不得不面对同样的问题,我很想知道是否有一些聪明的方法来处理它!
此致 马特
答案 0 :(得分:1)
这是南方邮件列表上公布的解决方案概要。措辞的问题与列表中的问题略有不同:在那里,还提到了在所有租户之间共享的“共同”表,在单独的模式中。 Rmatt自己的回答将此称为 public 架构。
我的解决方案的基本思想:保存架构中每个数据库(架构)的迁移历史记录。为此,我们需要使用一些数据库和Django技巧。
这意味着公共模式上的应用程序迁移的历史记录保存在公共模式中,而租户应用程序的迁移历史记录保存在租户模式中 - 有效地分割迁移历史记录表。 Django并不真正支持这种分片;通过实例内容设置写入很容易,但是没有办法设置读数。
所以我建议每个租户创建一个“租户助手”模式,其中包含一个名为south_migrationhistory
的视图,它是来自公共和租户模式的south_migrationhistory
表的联合。然后,为South MigrationHistory模型设置数据库路由器,指示它:
结果可以正确处理从租户应用迁移到公共应用迁移的依赖关系;这意味着你需要做的就是向前迁移,在migrate --all
(或syncdb --migrate
)命令上循环 - 不需要伪造向后迁移。公共模式的迁移将与循环中第一个租户的迁移一起运行,所有其他租户将“看到”它们。
作为事后的想法,通过重命名租户架构中的south_migrationhistory
表,并在返回上述方案的架构中安装具有该名称的视图,也可以在没有辅助架构的情况下执行此操作。查询时联合,并具有“替代插入”触发器以写入重命名的表。
答案 1 :(得分:0)
很好,没有那么多人似乎有经验或关心这个非常具体的问题。我在这里和那里尝试了一些东西,我也得到了南方邮件列表的一些支持,这有助于我理解一些观点。
基本上,我实施的解决方案如下:
我有一个非常正常的迁移文件,通过South的 schemamigration 自动生成。但我已将 add_column 和 delete_column 的表名更改为schema.table_name
。通过导入多租户中间件提供架构。
只有在未针对 public 架构运行架构时,才会应用迁移。它实际上并不是独立运行,也不仅仅是数据库和模式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)