odoo中的数据库迁移管理
前言
在最近odoo开发时,出现了模型结构变更导致数据库升级失败的问题。在Boss的指导下,发现了那个在Django中使用了无数次的关键字migrate 。原来odoo也提供了数据库迁移脚本工具,这个工具很简单,从开发的角度来说,有些地方的处理可以说很粗糙。但按照一定的规则去做odoo开发,不管的是大版本之间的迁移,还是小版本之间的迁移,这个工具起到的效果还是很棒的。
odoo 提供了 MigrationManager 类来管理数据库迁移。脚本路径:odoo/modules/migration.py 。
文档环境
源码分析
MigrationManager
在odoo中,数据库迁移的实现是通过MigrationManager 类实现的,这里分析下odoo/modules/migration.py 这个文件。
_init_(self, cr, graph)
def __init__(self, cr, graph):
self.cr = cr
self.graph = graph
self.migrations = defaultdict(dict)
self._get_files()
该方法为MigrationManager 的构造方法。它接受两个参数:
- cr:当前数据库游标(
db.cursor() ),供后续处理数据库迁移操作数据库使用。 - graph:要加载的模块节点图,供后续处理数据库迁移判断状态使用。
_get_files(self)
该方法在实例化MigrationManager 时被调用,其作用是通过遍历模块节点图,获取每个模块的数据库迁移脚本文件路径,初始化到migrations 中。
for pkg in self.graph:
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade' or getattr(pkg, 'load_state', None) == 'to upgrade'):
continue
self.migrations[pkg.name] = {
'module': get_scripts(get_resource_path(pkg.name, 'migrations')),
'module_upgrades': get_scripts(get_resource_path(pkg.name, 'upgrades')),
'upgrade': get_scripts(_get_upgrade_path(pkg.name)),
}
另外在该方法中提供了以下两个闭包:
- _get_upgrade_path(pkg):该闭包在
odoo/odoo/upgrade 和odoo/odoo/addons/base/maintenance/migrations 文件夹下扫描,如果在任一文件夹下找到以给定模块名命名的子文件夹,则返回子文件夹完整路径,否则返回None。供get_scripts 闭包使用。 - get_scripts(path):该闭包返回给定路径下后缀为
.py 的文件路径列表。
migrate_module(package, state)
该方法migrate_module ,是处理数据库迁移的核心方法,接受以下两个参数:
- package: 即模块或插件名。
- stage:即处理阶段。数据库迁移分了以下三个阶段,用于在不同时机处理数据库迁移操作:
- pre:在模块初始化之前执行。
migrate_module(package, 'pre') - post:在模块初始化之后执行。
migrate_module(package, 'post') - end:在所有模块更新后运行。
migrate_module(package, 'end')
该方法提供了三个闭包方法
- convert_version(version):该闭包将给定版本字符串简单处理并返回(迁移脚本中version的处理)。
- _get_migration_versions(pkg, stage):该闭包处理并返回数据库迁移版本号码。
- get_migration_files(pkg, version, stage):该闭包通过处理给定模块、给定版本和给定处理阶段,获取相应迁移脚本文件的完整路径。
脚本流程
当实例数据库的模型注册表发生变更时,会通过load_modules 方法重新加载实例中的模块或插件,在此加载过程中执行了数据库迁移功能,即调用了MigrationManager.migrate_module 方法。
核心处理流程如下:
- 数据库迁移条件检查:判断给定package对应的模块状态是否符合迁移条件,若不符合,不会继续以下步骤。
- 获取并格式化迁移前模块版本号,该版本号为
__manifest__.py 文件中的version 值(odoo处理规则)。 - 获取并格式化给定模块的当前迁移版本号。
- 比较迁移前模块版本号和给定模块的当前迁移版本号,如果当前迁移版本号比迁移前模块版本号小,不会继续以下步骤。
- 获取给定模块对应的模块文件夹下的迁移脚本文件路径。
- 执行迁移脚本文件中的逻辑处理。
odoo处理version规则
manifest 文件中的版本号version 的值,如果不是以当前主版本号(即服务版本号,比如14.0)开头,会被认定为模块版本号。但odoo会处理成以当前主版本号(即服务版本号,比如14.0)开头的完整版本号,存储到数据库。比如:
- 在服务版本为
14.0 时,12.0.0.1 会被认定为模块版本号,并最终被处理的完整版本号为14.0.12.0.0.1 - 在服务版本为
14.0 时,0.1 会被认定为模块版本号,并最终被处理的完整版本号为14.0.0.1 - 在服务版本为
14.0 时,14.0.0.1 会被认定为完整版本号,不会被处理。
比较好的实践是开发团队间约定一套版本规则,尽量在开发时,书写完整的版本号。避免出现不必要的麻烦。
迁移脚本中version的处理
该处理在convert_version 中实现,处理时根据判断给定版本号字符串中. 的数量处理出版本号,当数量>=2 时,直接返回。否则,将当前主版本号(比如14.0)作为前缀拼接到给定版本号字符串返回。比如:
- 给定版本字符串为
0.0.1 ,将直接返回0.0.1 。 - 给定版本字符串为
0.1 ,将直接返回14.0.0.1 。
在获取迁移脚本路径时,会根据返回的版本字符串扫描文件夹,如果没找到,迁移程序就不会执行。既然如此,还是建议开发团队间约定一套版本规则,尽量在开发时,书写完整的版本号。避免出现不必要的麻烦。
使用规范
使用此数据库迁移功能,需要遵循以下规则,约定此文档对应的主版本(即服务版本)为14.0 :
-
迁移管理代码遵循以下目录树结构: module_name
|-- migrations
|-- 1.0
| |-- pre-update_table_x.py
| |-- pre-update_table_y.py
| |-- post-create_plop_records.py
| |-- end-cleanup.py
| `-- README.txt
|-- 13.0.1.1
| |-- pre-delete_table_z.py
| `-- post-clean-data.py
|-- 0.0.0
| `-- end-invariants.py
`-- foo.py
在需要执行数据库迁移的模块根目录下必须创建名为migrations 的文件夹,其中包含按版本划分的文件夹。
-
版本文件夹名可以不含服务版本号,只含模块版本号。 比如:1.0 。如果没有主版本数据库迁移需求(比如13.0 升级到14.0 ),可以这样命名。因为程序处理会自动拼接主版本号。 -
版本文件夹名也可以即含服务版本号,有含模块版本号。 比如:13.0.1.1 。 -
版本文件夹名还可以是一个特殊号码:0.0.0 ,此文件夹下的迁移文件将在任何版本更改时运行。 -
迁移处理文件必须是以pre- 、post- 或end- 开头的Python文件。
pre- 在模块初始化之前执行。post- 在模块初始化之后执行。end- 在所有模块更新后运行。 -
迁移文件必须是包含migrate(cr,installed_version) 函数的python文件。
注意:在 pre 阶段,0.0.0 脚本首先运行,而在 post 和 end 中,0.0.0 脚本最后运行。
|