Skip to main content
 首页 » 编程设计

python之自动合并任意 Django 模型

2025年05月04日32虾米哥

我有两个 Django-ORM 管理的数据库,我想合并它们。两者都有非常相似的架构,并且都有标准的 auth_users 表,以及一些其他共享表,它们相互引用以及 auth_users,我想将它们自动合并到一个数据库中。

可以理解,这可能非常重要,具体取决于外键关系以及每个表中“唯一”记录的构成。

有谁知道是否存在执行此合并操作的工具?

如果目前不存在这样的东西,我正在考虑基于标准的 loaddata 命令编写自己的管理命令。本质上,您将使用标准的 dumpdata 命令从源数据库中导出表,然后使用修改后的 loaddata 版本将它们“合并”到目标数据库中。

比如我有数据库A和B,我想将数据库B合并到数据库A,那么我想按照伪代码的流程:

merge_database_dst = A 
merge_database_src = B 
for table in sorted(merge_database_dst.get_redundant_tables(merge_database_src), key=acyclic_dependency): 
    key = table.get_unique_column_key() 
    src_id_to_dst_id = {} 
    for record_src in merge_database_src.table.objects.all(): 
        src_key_value = record_src.get_key_value(key) 
        try: 
            record_dst = merge_database_dst.table.objects.get(key) 
            dst_key_value = record_dst.get_key_value(key) 
        except merge_database_dst.table.DoesNotExist: 
            record_dst = merge_database_dst.table(**[(k,convert_fk(v)) for k,v in record_src._meta.fields]) 
            record_dst.save() 
            dst_key_value = record_dst.get_key_value(key) 
        src_id_to_dst_id[(table,record_src.id)] = record_dst.id 

convert_fk() 函数将使用 src_id_to_dst_id 索引将源表中的外键引用转换为目标表中的等效 ID。

总而言之,该算法将按照依赖顺序迭代要合并的表,首先迭代父表。因此,如果我们想要合并表 auth_users 和依赖于 auth_users 的 mycustomprofile,我们将迭代 ['auth_users','mycustomprofile']。

每个合并表都需要某种指示符来记录表示普遍唯一记录(即“键”)的列组合。对于 auth_users,这可能是“用户名”和/或“电子邮件”列。

如果数据库B中key的值在A中已经存在,则不从B中导入记录,而是记录A中已有记录的ID。

如果数据库B中key的值在A中不存在,则从B中导入记录,并记录新记录的ID。

使用先前记录的 ID,创建一个映射,解释如何将 B 中对该特定记录的外键引用映射到 A 中新的合并/预先存在的记录。当将来的记录合并到 A 中时,此映射将用于转换外键。

我仍然可以设想一些情况,其中导入的记录引用了一个不包含在转储数据中的表,这可能会导致整个导入失败,因此需要某种“dryrun”选项来模拟导入以确保所有 FK引用文献可以翻译。

这看起来是一种实用的方法吗?有没有更好的办法?

编辑:这不是我要找的东西,但我认为其他人可能会觉得它很有趣。 Turbion project有一种机制可以在同一数据库中的不同 Django 模型中的等效记录之间复制更改。它通过在两个 Django 模型之间定义一个转换层(即 merging.ModelLayer)来工作,所以,如果您更新用​​户 bob@bob.com 的个人资料中的“www”字段,它将自动更新用户中的“url”字段bob@bob.com 的其他个人资料。

我正在寻找的功能有点不同,因为我想以不频繁的时间间隔合并整个(或部分)数据库快照,有点像加载数据管理命令的方式。

请您参考如下方法:

哇。无论如何,这将是一项复杂的工作。也就是说:

如果我正确理解了您的项目需求,这可以使用 South 中的数据迁移来完成。 .即便如此,如果我说这将是一个笑话,那我就是在撒谎。

我的建议是 - 这主要是你问题中假设的鹦鹉学舌,但我想说清楚 - 你有一个“主”表作为基础,并且有来自另一个的记录表添加到它。因此,表 A 保留其所有现有记录,并且仅从 B 获取添加。B 将添加提供给 A,一旦完成,B 将被删除。

我对是否为您编写示例代码犹豫不决,因为您的实际工作会比这复杂得多,但无论如何我都会尝试为您指明正确的方向。考虑类似...

import datetime 
from south.db import db 
from south.v2 import DataMigration 
from django.db import models 
 
class Migration(DataMigration): 
    def forwards(self, orm): 
        for b in orm.B.objects.all(): 
            # sanity check: does this item get copied into A at all? 
            if orm.A.objects.filter(username=b.username): 
                continue 
 
            # make an A record with the properties of my B record 
            a = orm.A( 
                first_name=b.first_name, 
                last_name=b.last_name, 
                email_address=b.email_address, 
                [...] 
            ) 
 
            # save the new A record, and delete the B record 
            a.save() 
            b.delete() 
 
    def backwards(self, orm): 
        # backwards method, if you write one 

这最终会将所有不在 A 中的 B 迁移到 A,并为您留下一个预期重复的 B 表,然后您可以在删除之前通过其他方式检查它。

就像我说的,这个示例并不完整。如果你决定走这条路,花时间在 South documentation ,尤其要确保您查看 data migrations .

那是我的 2¢。希望对您有所帮助。