在Odoo开发过程中,我们有时需要修改数据库结构,包括添加或删除约束。本文将介绍如何通过编写迁移脚本来安全地删除数据库中的约束。

理解Odoo的SQL约束

Odoo模型中的_sql_constraints属性允许我们定义数据库级别的约束,如唯一性约束。例如,如果我们有一个res.partner模型,并且我们想要确保某个字段(比如some_field)是唯一的,我们可以这样定义约束:

class ResPartner(models.Model):
    _inherit = 'res.partner'

    _sql_constraints = [
        ('some_unique_constraint', 'UNIQUE(some_field)', 'Some field must be unique.')
    ]

    some_field = fields.Char('Some Field')

删除约束的场景

随着业务的发展,我们可能会发现之前定义的约束不再适用,或者需要进行调整。在这种情况下,我们需要删除旧的约束。直接在数据库中操作可能会对系统造成风险,因此最好的做法是通过Odoo的迁移机制来安全地进行更改。

触发迁移脚本

Odoo模块的迁移脚本是在模块升级时自动执行的。为了触发迁移脚本的执行,我们需要更新模块的manifest文件中的版本号。这告诉Odoo该模块已经更新,需要执行迁移脚本来应用这些更新。

__manifest__.py文件中,找到'version'键并更新其值:

{
    'name': "Your Module Name",
    'version': "1.1",  # Increase this version number from the previous one
    'depends': ['base'],
    ...
}

迁移脚本目录结构

Odoo期望迁移脚本位于特定的目录结构中,以便正确地识别和执行它们。迁移脚本应该放在模块目录下的migrations/目录中,该目录下又应该有一个以版本号命名的子目录。版本号应该对应于触发迁移脚本执行的版本更新。

结构示例如下:

your_module/
├── __init__.py
├── __manifest__.py
├── models/
│   ├── __init__.py
│   └── your_model.py
└── migrations/
    └── 1.1/  # 版本号与__manifest__.py中的相对应
        └── pre-migrate.py  # 这是迁移脚本,可以根据需要命名

编写迁移脚本

为了删除约束,我们可以编写一个迁移脚本,该脚本将在模块升级时执行。以下是一个迁移脚本的示例:

# -*- coding: utf-8 -*-
import logging

_logger = logging.getLogger(__name__)

def migrate(cr, version):
    try:
        cr.execute("ALTER TABLE res_partner DROP CONSTRAINT IF EXISTS res_partner_some_unique_constraint")
        cr.commit()
    except Exception:
        _logger.exception('Error in pre-migration')
    finally:
        cr.close()

在这个脚本中,我们使用cr.execute()来执行SQL命令,删除名为res_partner_some_unique_constraint的约束。我们使用IF EXISTS来确保即使约束不存在,SQL命令也不会失败。

查询数据库对应模型约束的sql

SELECT conname
FROM pg_constraint
WHERE conrelid = (
    SELECT oid
    FROM pg_class
    WHERE relname = 'res_partner'
);

注意事项

  • 事务管理:Odoo通常会自动处理事务的提交和回滚。在迁移脚本中,我们通常不需要调用cr.commit(),因为Odoo会在整个升级过程结束时进行提交。但在这个例子中,我们显式地调用了cr.commit()来确保我们的更改被提交。这是因为我们在finally块中关闭了游标,这会结束当前的事务。如果你不关闭游标,那么你应该避免调用cr.commit(),让Odoo来管理事务。
  • 日志记录:我们使用了_logger.exception()来记录任何异常,这有助于调试过程中发现问题。
  • 关闭游标:在finally块中,我们确保游标被关闭。这是一个好习惯,可以防止资源泄漏。这里是临时关闭游标,用于测试,如果不关闭游标会导致数据库没有删除约束成功。如果后续要用到游标则不能轻易关闭。