升级工具

升级工具 是一个包含辅助函数的库,用于简化升级脚本的编写。该库被 Odoo 用于标准模块的升级脚本,提供了可靠性并有助于加快升级过程:

  • 辅助函数帮助确保数据库中的数据一致性。

  • 它处理更新记录的间接引用。

  • 允许调用函数以避免编写代码,节省时间并降低开发风险。

  • 辅助工具使您可以专注于升级的重要部分,而无需考虑细节。

安装

克隆 升级工具仓库 到本地,并在启动 odoo 时将 src 目录添加到 --upgrade-path 选项之前。

$ ./odoo-bin --upgrade-path=/path/to/upgrade-util/src,/path/to/other/upgrade/script/directory [...]

在您不自行管理 Odoo 的平台上,可以通过 pip 安装此库:

$ python3 -m pip install git+https://github.com/odoo/upgrade-util@master

在 Odoo.sh 上,建议将其添加到自定义仓库的 requirements.txt 文件中。为此,请在文件中添加以下行:

odoo_upgrade @ git+https://github.com/odoo/upgrade-util@master

使用升级工具

安装完成后,以下包可供升级脚本使用:

要在升级脚本中使用它,只需导入即可:

from odoo.upgrade import util


def migrate(cr, version):
   # Rest of the script

现在,可以通过 util 调用辅助函数。

工具函数

升级工具提供了许多有用的函数来简化升级过程。在此,我们将描述一些最有用的函数。有关辅助函数的完整声明,请参阅 util 文件夹 。

注解

工具函数中的 cr 参数始终指代数据库游标。传递在 migrate() 中接收到的游标。并非所有函数都需要此参数。

模块

用于模块级操作的实用函数。

在大多数情况下,模块操作(重命名、合并、删除等)应在 base 脚本中执行。原因是一旦 base 模块升级完成,所有与模块相关的信息应该已经设置在数据库中,以确保升级过程正常运行。命令行选项 --pre-upgrade-scripts (从 Odoo 16 开始可用)允许在加载 base 模块之前运行升级脚本。这是在主要升级后执行模块操作的推荐方式。

force_install_module(cr, module, if_installed=None, reason='it has been explicitly asked for')[源代码]

强制 ORM 安装模块。

参数
  • module (str) – 要安装的模块名称

  • if_installed (list(str) or None) – 仅当这些模块已安装时才强制安装

返回字符串

模块的 新 状态

force_upgrade_of_fresh_module(cr, module, init=True)[源代码]

强制执行正在安装的模块的升级脚本。

标准 Odoo 在安装模块时不运行升级脚本。这在技术上是有意义的,因为模块并未真正升级。然而,有时(新)模块需要执行一些操作才能正确安装,例如从其他模块获取数据。这种情况在模块功能被 拆分 为多个模块时很常见。

参数
  • module (str) – 要强制执行升级脚本的模块名称

  • init (bool) – 是否将模块设置为 init 模式

处于 init 模式的一个副作用是不遵守 XML 文件或 ir_model_data 中的 noupdate 标志。

merge_module(cr, old, into, update_dependers=True, xmlid_mapping=None)[源代码]

将一个模块合并到另一个模块中。

此函数将源模块中的所有引用和记录移动到目标模块中。

警告

此函数不会删除任何记录。它会移除源模块中与目标模块中名称冲突的 XMLID,除非通过 xmlid_mapping 参数显式重命名。

参数
  • old (str) – 要合并的模块名称

  • into (str) – 要合并到的目标模块名称

  • update_dependers (bool) – 是否更新依赖于 old 的模块的依赖项

  • xmlid_mapping (dict) – 可选的 {old_name: new_name} XMLID 映射,用于在合并过程中重命名。当来自 old 的 XMLID 否则会按 名称 与 into 中已有的 XMLID 冲突时使用。参见 rename_xmlid()

module_installed(cr, module)[源代码]

返回模块是否已安装。

参数

module (str) – 要检查的模块名称

返回类型

bool

参见 modules_installed() 。

modules_installed(cr, *modules)[源代码]

返回是否安装了 所有 给定模块。

参数

modules (str) – 要检查的一个或多个模块名称(可变参数)

返回类型

bool

注解

在升级的上下文中,如果模块被标记为升级或安装,则即使尚未完全安装,也会被视为已安装。

move_model(cr, model, from_module, to_module, move_data=False, keep=())[源代码]

将模型从一个模块移动到另一个模块。

参数
  • model (str) – 要移动的模型名称

  • from_module (str) – 模型最初定义的模块名称

  • to_module (str) – 模型要移动到的目标模块名称

  • move_data (bool) – 是否同时更新该模型记录的 ir_model_data

  • keep (list(str)) – 要保留(不移动)的 XML ID 列表

此函数可用于将模型的覆盖移动到另一个模块。由于它无法区分源模型或继承模型,如果目标模块未安装,则会引发异常。

remove_module(cr, module)[源代码]

完全删除模块。

此操作等同于卸载并移除模块的所有引用——数据库中不会留下任何痕迹。

参数

module (str) – 要移除的模块名称

警告

由于此函数会移除与模块关联的所有数据,请在调用此函数之前确保重新分配记录。

remove_theme(cr, theme, base_theme=None)[源代码]

卸载主题模块。

警告

此函数只能在 post- 脚本中使用。

参见 remove_module() 和 uninstall_theme() 。

rename_module(cr, old, new)[源代码]

重命名模块及其所有引用。

参数
  • old (str) – 要重命名的模块的当前名称

  • new (str) – 要重命名的模块的新名称

uninstall_module(cr, module)[源代码]

卸载并删除模块拥有的所有记录。

参数

module (str) – 要卸载的模块名称

uninstall_theme(cr, theme, base_theme=None)[源代码]

卸载主题模块并将其从网站中移除。

参数
  • theme (str) – 要卸载的主题模块名称

  • base_theme (str or None) – 如果不是 None ,则首先卸载此基础主题

警告

此函数只能在 website 模块的 post- 脚本中使用,因为它依赖于 ORM。

参见 remove_theme() 和 uninstall_module() 。

模型

用于修改模型的实用函数。

模型操作最好在相关模块的 pre- 脚本中完成。

merge_model(cr, source, target, drop_table=True, fields_mapping=None, ignore_m2m=())[源代码]

将一个模型合并到另一个模型中。

此函数将所有引用从 source 模型移动到 target 模型,并 删除 source 模型及其引用。默认情况下,仅移动两个模型中同名的字段;可选地,可以提供不同字段名称的映射。

警告

此函数不会将记录从 source 模型移动到 target 模型。

参数
  • source (str) – 要合并的模型名称

  • target (str) – 要合并到的目标模型名称

  • drop_table (bool) – 是否删除源模型的表

  • fields_mapping (dict or None) – 从源模型到目标模型的字段名称映射,当为 None 时,仅移动同名字段

  • ignore_m2m (list(str) or str) – 从源模型中忽略(不删除)的多对多表列表。

remove_inherit_from_model(cr, model, inherit, keep=(), skip_inherit=(), with_inherit_parents=True, skip_update_references=())[源代码]

从 model 中移除 inherit 。

此函数会从 model 及其所有子模型中移除通过 inherit 继承的所有字段。包括继承模型及其父模型中的所有字段也将被移除,除非未设置 with_inherit_parents 模式。在这种情况下,仅移除 inherit 中的字段,而保留其父模型中的字段。在 keep 中列出的字段永远不会被移除。如果 model 的某些子模型列在 skip_inherit 中,它们将保留来自 inherit 的字段。

参数
  • model (str) – 要从中移除继承的模型名称

  • inherit (str) – 要移除的继承模型(或混入)名称

  • keep (tuple(str)) – 包含要保留字段名称的元组

  • skip_inherit (tuple(str)) – 不处理的 model 的子模型列表

  • with_inherit_parents (boolean) – 如果未设置,则仅移除来自 inherit 的字段,保留其父模型中的所有字段

  • skip_update_references (tuple(str) or "*") – 其引用在删除时不应更新的字段名称。使用 "*" 表示所有字段。

remove_model(cr, model, drop_table=True, ignore_m2m=())[源代码]

从数据库中删除模型及其引用。

某些对模型的必要间接引用会被替换为 未知模型 —— 一个作为空占位符的空模型,用于替代已移除的模型。

参数
  • model (str) – 要移除的模型名称

  • drop_table (bool) – 是否删除该模型的表

  • ignore_m2m (list(str) or str) – 要忽略(不删除)的多对多表列表,使用 "*" 来忽略(保留)所有多对多表

rename_model(cr, old, new, rename_table=True, ignored_m2ms='ALL_BEFORE_18_1')[源代码]

重命名模型。

更新数据库中所有对模型名称的引用。

如果请求重命名表,从 saas~18.1+ 开始,m2m 表也会更新,除非被忽略。在旧版本中,除非传递空列表,否则会跳过 m2m 表。

参数
  • old (str) – 当前模型名称

  • new (str) – 新模型名称

  • rename_table (bool) – 是否同时重命名模型的表

  • ignored_m2ms – 要跳过的 m2m 表。默认为 "ALL_BEFORE_18_1" ,在 Odoo 18 或更低版本中跳过所有,在 saa~18.1+ 中不跳过任何。对于所有版本,如果值不是默认值,则仅跳过指定的 m2m 表。

字段

用于修改模型字段的实用函数。

字段操作最好在相关模块的 pre- 脚本中完成。在某些情况下,初步操作可以在 pre 中完成,然后在 post 中完成。一个常见的例子是在 pre- 中移除字段但保留其列,稍后在 post 中当列最终被删除时使用。

change_field_selection_values(cr, model, field, mapping, skip_inherit=())[源代码]

替换选择字段值的引用。

此函数根据映射替换对选择值的所有引用。域也会被更新。

参数
  • model (str) – 要更新的选择字段的模型名称

  • field (str) – 要更新的选择字段的名称

  • mapping (dict) – 要更新的选择值,键值将被映射中的对应值替换

  • skip_inherit (list(str) or str) – 在更新选择值时要跳过的继承模型列表,使用 "*" 跳过所有模型

convert_m2o_field_to_m2m(cr, model, field, new_name=None, m2m_table=None, col1=None, col2=None)[源代码]

将 Many2one 字段转换为 Many2many。

它会创建关系表并填充它。

参数
  • model (str) – 要转换字段的模型名称

  • field (str) – 要转换的字段的当前名称

  • new_name (str) – 要转换的字段的新名称。如果未提供,则为 field 加上尾随的 “s”。

  • m2m_table (str) – 关系表的名称。如果未提供,则自动生成。

  • col1 (str) – 引用 model 的列的名称。

  • col2 (str) – 引用 field 目标的列的名称。

dump_field_to_chatter(cr, model, field, res_id='id', res_model=None, label='%s', fk_display=None, fallback_column=None, where=None, html_escape=True, message_type='notification', subtype_xmlid=None)[源代码]

在每条记录上发布一条讨论消息,以在列被删除之前保留其值。

当迁移过程中要移除某个字段,但客户端可能需要恢复其值时,这很有用。该值会作为 mail_message 发布到记录的讨论中。

如果未安装 mail 模块(没有 mail_message 表),则在提供了 fallback_column 时把值转储到其中,否则该函数会静默地不做任何事。

Example

# Simple varchar column
util.dump_field_to_chatter(
    cr, "crm_lead", "mobile", label="Previous Mobile", where="{0}.phone != {0}.mobile"
)

# Drop team_id, save the team name
util.dump_field_to_chatter(
    cr, "res_partner", "team_id", label="Former Sales Team", fk_display="name"
)

# Dump from hr_version onto hr_employee's chatter
util.dump_field_to_chatter(
    cr, "hr_employee", "current_version_id", label="SSN", fk_display="ssnid"
)

# Raw HTML body (no label prefix)
util.dump_field_to_chatter(
    cr, "hr_employee", "notes", html_escape=False,
    message_type="comment", subtype_xmlid="mail.mt_note"
)
参数
  • model (str) – 要转储的字段所属的模型

  • field (str) – 要保留其值的字段

  • label (str) – 消息格式。允许使用任何包含单个 %s 的有效 PG format 字符串。如果缺少 %s,会自动追加 : %s。默认的 "%s" 生成不带前缀的原始值。

  • res_id (str) – 标识要放入讨论中的记录 ID 的列。

  • res_model (str or None) – 要发布到讨论中的记录的模型,默认从 res_id 目标推断。

  • fk_display (str or None) – 当 column 是外键时,用于显示值的 FK 目标表中的列(例如 "name")。FK 目标表会自动检测。省略时仅显示 ID。ID 始终以括号形式追加。

  • fallback_column (str or None) – 当未安装 mail 模块(没有 mail_message 表)时,用于转储该值的同一表上的列。该列必须存在(例如 res_partner.comment),否则会抛出异常。翻译(jsonb)列通过其 en_US 键写入。

  • where (str) – 源记录上额外的 SQL WHERE 条件。它是一个 str.format 模板,其中 {0} 是源表别名;用它来限定每个列引用,例如 where="{0}.phone != {0}.mobile"。默认情况下,只包含被转储列不为 NULL (文本类型时也不为空)的行。

  • html_escape (bool) – 是否对值进行 HTML 转义(默认 True)。

  • message_type (str) – mail_message.message_type (默认 "notification")。

  • subtype_xmlid (str) – 消息子类型的可选 XML ID(例如 "mail.mt_note")。

invert_boolean_field(cr, model, old, new, skip_inherit=())[源代码]

重命名布尔字段并反转其值。

make_field_non_stored(cr, model, field, selectable=False)[源代码]

将字段转换为非存储字段。

参数
  • model (str) – 要转换字段的模型名称

  • fieldname (str) – 要转换字段的名称

  • selectable (bool) – 字段是否仍然可选,如果为 True,则自定义 ir.filters 不会被更新

move_field_to_module(cr, model, fieldname, old_module, new_module, skip_inherit=())[源代码]

将字段从一个模块移动到另一个模块。

此函数更新对特定字段的所有引用,从源模块切换到目标模块。它避免了在注册表完全加载后数据丢失。继承模型中的字段也会被移动,除非跳过。

参数
  • model (str) – 要移动字段的所属模型名称

  • fieldname (str) – 要移动字段的名称

  • old_module (str) – 字段从其中移动的源模块名称

  • new_module (str) – 字段移动到的目标模块名称

  • skip_inherit (list(str) or str) – 字段不被移动的继承模型列表,使用 "*" 跳过所有模型

remove_field(cr, model, fieldname, cascade=False, drop_column=True, skip_inherit=(), keep_as_attachments=False, update_references=True)[源代码]

从数据库中移除字段及其引用。

此函数还会从继承模型中移除字段,除非在 skip_inherit 中指定了例外情况。当字段已存储时,我们可以选择不删除该列。

参数
  • model (str) – 要移除字段的模型名称

  • fieldname (str) – 要移除字段的名称

  • cascade (bool) – 是否以 CASCADE 模式移除字段列

  • drop_column (bool) – 是否删除字段的列

  • skip_inherit (list(str) or str) – 跳过字段移除的继承模型列表,使用 "*" 跳过所有模型

  • keep_as_attachments (bool) – 对于二进制字段,是否应将数据保留为附件

  • update_references (bool) – 是否更新所有引用。若为 False,则不更新仪表板、过滤器、导出路径、域以及关联字段的关系。

rename_field(cr, model, old, new, update_references=True, domain_adapter=None, skip_inherit=())[源代码]

在给定的 model 上将字段及其引用从 old 重命名为 new 。

该字段会在所有继承模型中更新,但 skip_inherit 中指定的模型除外。

此函数还会更新直接或间接的引用,包括过滤器、服务器操作、相关字段、电子邮件、仪表板、域等更多内容。参见 update_field_usage()

可以使用特殊的适配器函数来更新域。默认适配器仅在每个域叶中将 old 替换为 new 。有关域适配器的信息,请参阅 adapt_domains() 。

参数
  • model (str) – 要重命名的字段的模型名称

  • old (str) – 要重命名的字段的当前名称

  • new (str) – 要重命名的字段的新名称

  • update_references (bool) – 是否更新所有引用

  • domain_adapter (function) – 用于域的适配器,参见 adapt_domains()

  • skip_inherit (list(str) or str) – 在继承模型中重命名字段时要跳过的模型,使用 "*" 跳过所有模型

update_field_usage(cr, model, old, new, domain_adapter=None, skip_inherit=())[源代码]

在不同位置将字段 old 的所有引用替换为 new 。

搜索范围:
  • ir_filters

  • ir_exports_line

  • ir_act_server

  • mail_alias

  • ir_ui_view_custom(仪表板)

  • sign_item_type

  • sign_item

  • 域(使用 domain_adapter )

  • 相关字段

此函数可用于替换字段的使用。域会使用 domain_adapter 进行更新。默认情况下,域适配器仅在域叶中将 old 替换为 new 。有关域适配器的更多信息,请参阅 adapt_domains() 。

参数
  • model (str) – 字段的模型名称

  • old (str) – 要替换的字段的源名称

  • new (str) – 要设置的字段的目标名称

  • domain_adapter (function) – 用于域的适配器,参见 adapt_domains()

  • skip_inherit (list(str) or str) – 在继承模型中重命名字段时要跳过的模型,使用 "*" 跳过所有模型

记录

用于记录级操作的实用函数。

delete_unused(cr, *xmlids, **kwargs)[源代码]

删除未使用的记录。

此函数仅在 xmlids 指向的记录未被任何表引用时才会删除这些记录。对于具有层级结构的记录(例如产品分类),它会检查标记为级联删除的子记录是否也未被引用。在这种情况下,记录及其子记录将全部删除。

注解

无法删除的记录将被设置为 noupdate=True 。

参数
  • xmlids (list(str)) – 要检查以进行删除的 xml_ids 列表

  • deactivate (bool) – 是否停用因被引用而无法删除的记录,默认为 False

  • keep_xmlids (bool) – 是否保留无法删除的记录的 xml_ids。默认情况下,18.0 及以下版本为 True ,从 saas~18.1 开始为 False 。

  • include_m2m (list(str) or str) – 要包含在搜索中的 m2m 表列表。"*" 表示全部。

返回

已删除记录的 ID 列表(如果有)

返回类型

list(int)

edit_view(cr, xmlid=None, view_id=None, skip_if_not_noupdate=True, active='auto')[源代码]

用于编辑视图架构的上下文管理器。

此函数返回一个上下文管理器,可能会生成视图的解析架构,作为 etree 元素 。返回对象中的任何更改将在退出上下文管理器时写回数据库,并更新架构的翻译版本。由于函数可能不会生成内容,可以使用 skippable_cm() 来避免错误。

with util.skippable_cm(), util.edit_view(cr, "xml.id") as arch:
    arch.attrib["string"] = "My Form"

要选择要编辑的目标视图,请使用 xmlid 或 view_id,但不能同时使用两者。

当通过 view_id 确定视图时,如果视图存在,则始终生成其架构,而不考虑其可能关联的 noupdate 标志。当设置了 xmlid 时,如果视图的 noupdate 标志为 False ,则不会生成架构,除非将 skip_if_not_noupdate 设置为 False 。如果 noupdate 为 True ,则生成视图以供编辑。

如果 active 参数为 True 或 False ,则视图的 active 标志将相应设置。

注解

如果 active 为 “auto”(默认值),则通过 xmlid 选择时视图将被激活,而通过 view_id 选择时保持不变。

参数
  • xmlid (str) – 可选,要编辑的视图的 xml_id

  • view_id (int) – 可选,要编辑的视图的 ID

  • skip_if_not_noupdate (bool) – 是否强制编辑通过 xmlid 参数请求的视图,即使它们被标记为 noupdate=True ,如果设置了 view_id 则忽略。

  • active (bool or None or "auto") – active 标志值,如果为 None 则保持不变。

返回

一个生成解析后的架构的上下文管理器,在退出时将更改写回。

ensure_xmlid_match_record(cr, xmlid, model, values)[源代码]

确保 xml_id 引用具有特定值的记录。

此函数确保 xml_id 指向的记录与 values 参数中指定字段的值匹配。如果 xmlid 存在,但它指向的记录与值不匹配,则会将 xmlid 更新为指向一个匹配的记录(如果找到)。如果 xmlid 不存在,则使用找到的记录创建它。如果未找到匹配的记录,则不会执行任何操作。在所有情况下,都会返回 xml_id 引用的记录(可能已更新)。

参数
  • xmlid (str) – 记录的 xml_id,格式为 module.name

  • model (str) – 记录的模型名称

  • values (dict) – 记录必须满足的字段名称到值的映射 .. 示例:: .. code-block:: python values = {“id”: 123} values = {“name”: “INV/2024/0001”, “company_id”: 1}

返回

匹配记录的 ID,如果未找到记录则为 None

返回类型

int or None

小技巧

在将数据库中的记录迁移到自定义模块时,此函数非常有用,可以在模块更新之前创建 xml_ids 并避免重复。

force_noupdate(cr, xmlid, noupdate=True, warn=False)[源代码]

更新记录的 noupdate 标志。

参数
  • xmlid (str) – 记录的 xml_id,格式为 module.name

  • noupdate (bool) – 要设置到 noupdate 标志上的值

  • warn – 当标志从 True 切换为 False 时,是否在日志中输出警告

if_unchanged(cr, xmlid, callback, interval='1 minute', **kwargs)[源代码]

如果记录未更改,则运行 callback 。

当引用的记录未更改时,此函数将运行 callback 。xmlid 和任何其他参数(但不包括 interval )将传递给 callback 。如果记录已更改,则会将其标记为 noupdate=True 。另请参见 is_changed() 和 force_noupdate() 。

此函数在记录未更新时执行操作非常有用,一个常见示例是即使记录为 noupdate=True ,也强制从 XML 更新。

util.if_unchanged(cr, "mymodule.myrecord", util.update_record_from_xml)
参数
  • xmlid (str) – 要检查的记录的 xml_id

  • callback (function) – 如果记录未更改时要执行的回调函数,传递给此函数的所有额外参数都会传递给回调函数

  • interval (str) – create_date 后的时间间隔,在此间隔内记录被视为已更改,参见 is_changed()

is_changed(cr, xmlid, interval='1 minute')[源代码]

返回记录是否已更改。

此函数检查记录是否在当前升级开始时间之前已更改。参见 upgrade-util/src/base/0.0.0/pre-00-upgrade-start.py

对于符合以下条件的记录的 xmlid,此工具将返回假阳性:

  • 已在当前升级之前的升级中更新过

  • 尚未在当前升级中更新

如果 xmlid 不存在于数据库中,此函数返回 None 。

参数
  • xmlid (str) – 要检查的记录的 xmlid

  • interval (str) – SQL 时间间隔,如果 write_date > create_date + interval ,则认为记录已更改

返回类型

bool or None

ref(cr, xmlid)[源代码]

返回与给定 xml_id 对应的 ID。

参数

xml_id (str) – 记录的 xml_id,格式为 module.name

返回

引用记录的 ID,如果未找到则为 None 。

返回类型

int or None

refs(cr, xmlids, strict=False)[源代码]

返回给定 xmlid 列表的 xmlid -> res_id 映射。

参数
  • xmlids (list) – 完全限定的 xml_id 列表,每个的格式为 module.name

  • strict (bool) – 如果为 True,则只包含数据库中存在的 xmlid。

返回

将每个 xmlid 映射到其 res_id 的字典;缺失的 xmlid 映射为 None,除非 strict=True,此时它们会从结果中省略。

返回类型

dict

remove_record(cr, name)[源代码]

删除与给定 xml_id 对应的记录及其引用。

参数

name (str) – 记录的 xml_id,格式为 module.name

remove_view(cr, xml_id=None, view_id=None, silent=False, key=None)[源代码]

删除视图及其所有子视图。

只要给定视图及其继承视图属于某个模块,此函数就会递归删除它们。它还会删除多网站 COW 视图。

参数
  • xml_id (str) – 可选,要删除的视图的 xml_id

  • view_id (int) – 可选,要删除的视图的 ID

  • silent (bool) – 是否在日志中显示禁用的自定义视图

  • key (str or None) – 用于检测多网站 COW 视图的键,如果为 None ,则设置为提供的 xml_id;否则设置为引用 ID 为 view_id 的视图的 xml_id(如果有)。

警告

必须设置 xml_id 或 view_id 。同时指定两者将引发错误。

小技巧

当要删除多个视图时,请使用 remove_views(),它会批量执行开销较大的操作::

util.remove_views(cr, "module.view_one", "module.view_two")
remove_views(cr, *xml_ids, **kwargs)[源代码]

删除多个视图及其所有后代。

只要给定视图及其继承视图属于某个模块,此函数就会删除它们。它还会删除多网站 COW 视图。子视图或 COW 视图是被删除还是仅被禁用,取决于其关联的 xml_id;属于模块的视图会被删除,用户自定义的视图会被禁用。

参数

xml_ids (str) – 要删除的视图的 xml_ids

rename_xmlid(cr, old, new, noupdate=None, on_collision='fail')[源代码]

重命名记录的 external identifier (xml_id)。

如果目标名称已存在于数据库中,则无法进行重命名。在这种情况下,有两种选项可以控制此函数的行为:

注解

此函数不会删除记录,仅更新 xml_ids。

参数
  • old (str) – 记录的当前 xml_id,格式为 module.name

  • new (str) – 记录的新 xml_id,格式为 module.name

  • noupdate (bool or None) – 要设置到 xml_id 的 noupdate 标志上的值,如果为 None 则忽略

  • on_collision (str) – 如果 xml_id 已存在,如何处理,选项为 merge 或 fail (默认)

返回

新 xml_id 引用的记录的 ID,如果记录不存在则为 None

返回类型

int or None

replace_in_all_jsonb_values(cr, table, column, old, new, extra_filter=None)[源代码]

替换 JSONB 列中的值。

此函数将 JSONB 值中的 old 替换为 new 。它适用于替换翻译字段的所有翻译中的值。

参数
  • table (str) – 要替换值的表名称

  • column (str) – 要替换值的列名称

  • old (str) – 要替换的原始值,可以是简单术语(字符串)或由 PGRegexp 包装的 PostgreSQL 正则表达式

  • new (str) – 要设置的新值,可以是简单术语或使用 <数字> 表示法的表达式,用于引用 捕获组 (如果 old 是正则表达式)。

  • extra_filter (str) – 额外的兼容 WHERE 子句,用于过滤要更新的值,必须使用 t 作为表的别名,还可以包含 {parallel_filter} 以并行执行查询,参见 explode_execute() 。

replace_record_references_batch(cr, id_mapping, model_src, model_dst=None, replace_xmlid=True, ignores=(), parent_field='parent_id')[源代码]

替换对记录的所有引用。

此函数将对 model_src 记录的所有引用(直接或间接)替换为映射中的对应记录。如果映射的目标模型与源模型不同,则必须设置 model_dst 参数。

参数
  • id_mapping (dict(int, int)) – 要替换的 ID 映射,键值将被映射值替换

  • model_src (str) – 要替换记录的源模型名称

  • model_dst (str) – 要替换记录的目标模型名称,如果为 None ,则目标模型与源模型相同

  • replace_xmlid (bool) – 是否替换指向源记录的 xml_ids 中的引用

  • ignores (list(str)) – 更新引用值时要跳过的 表 名称列表

参数 str parent_field

当目标和源模型相同,并且模型表有 parent_path 列时,将使用此字段来更新它。

update_record_from_xml(cr, xmlid, reset_write_metadata=True, force_create=AUTO, from_module=None, reset_translations=(), ensure_references=False, fields=None)[源代码]

根据其在 数据文件(Data Files) 中的定义更新记录。

此函数忽略记录的 noupdate 标志。它会在 xmlid 或 from_module 参数(如果设置)所指定的模块清单中的所有 XML 文件中搜索匹配的定义。找到后,它会强制 ORM 按照数据文件中的规范更新记录。

可选地,此函数可以重置某些字段的翻译。

参数
  • xmlid (str) – 记录的 xml_id,格式为 module.name

  • reset_write_metadata (bool) – 是否更新记录的 write_date

  • force_create (bool) – 如果记录不存在是否创建记录。默认为 True ,除非 fields 不为 None。

  • from_module (str) – 从中更新记录的模块名称,仅当规范位于不同于 xml_id 所在模块的其他模块中时才需要。

  • reset_translations (set(str)) – 翻译被重置的字段名称

  • ensure_references (bool) – 通过 ref XML 属性引用的记录是否也应更新。

  • fields (set(str) or None) – 要包含在 XML 声明中的可选字段列表。若设置,则忽略所有其他字段。设置后,若记录缺失则不会被创建,且 XML 声明中未找到的所有字段将被设为 NULL/其默认值。

警告

此函数使用 ORM,因此只能在记录的数据规范中引用的所有模型均已加载之后使用。实际上,这意味着此函数应在 post- 或 end- 脚本中使用。

注解

ORM 的标准行为是如果记录不存在则创建记录,包括其 xml_id。此函数也会发生这种情况,除非将 force_create 设置为 False 。

ORM

通过 ORM 执行操作的实用函数。

此模块中的函数允许在升级期间安全地使用 ORM。它们增强或修补了 ORM,使其能够高效处理大量数据。在某些情况下,提供了完全不同于 ORM 自身功能的替代方案。此模块中的函数适用于 所有 支持版本的 ORM。

env(cr)[源代码]

创建一个新环境。

警告

此函数不会清空为超级用户维护的缓存(在空环境中)。每次直接在数据库中修改数据时,可能需要调用 invalidate_cache 。

返回

新环境

返回类型

Environment

get_inherit_model_names(model)[源代码]

获取该模型直接或间接 _inherit 的模型名称。

参数

model – 记录集

返回

该模型直接或间接 _inherit 的模型名称。如果 model._name 不在返回的集合中,则该模型只定义了一次。对于非 base 模型,base 始终在返回的集合中。

返回类型

list[str]

class iter_browse(model, *args, **kw)[源代码]

遍历记录集。

此类返回的 callable 对象可以用作迭代器,按块加载记录(到 recordset 中)。每个块耗尽后,其数据会被发送回数据库(刷新或提交),然后加载下一个块。

此类允许运行如下代码:

for record in env['my.model'].browse(ids):
    record.field1 = val

env['my.model'].browse(ids)._compute_field2()
env['my.model'].create(values)

以高效的方式运行,同时避免 MemoryError ,即使 ids 或 values 包含数百万条目。使用此类的替代方法是:

Example

MyModel = util.env(cr)['my.model']
for record in util.iter_browse(MyModel, ids):
    record.field1 = val

util.iter_browse(MyModel, ids)._compute_field2()
util.iter_browse(MyModel, ids).create(values)
参数
  • model (odoo.model.Model) – 要迭代的模型

  • ids (iterable(int)) – 要迭代的记录的 ID 可迭代对象

  • size (int) – ids 的长度。当 ids 没有 __length__ 时,可用它来启用进度日志记录

  • query (str) – ids 的替代方案,即可以生成它们的 SQL 查询。也可以是带 RETURNING 子句的 DML 语句。参见 query_ids()

  • chunk_size (int) – 每次迭代块中加载的记录数量,默认为 200

  • yield_chunks (bool) – 迭代时,按 chunk_size 分块产出记录,而不是逐个产出。默认为 False

  • logger (logging.Logger) – 用于报告进度的日志记录器,默认为 _logger

  • strategy (str) – 是否在每个块上执行 flush 或 commit ,默认为 flush

返回

此类返回的对象可用于安全地迭代或调用任何模型方法,即使是数百万条记录。

另请参见 env()

create(values=None, query=None, **kw)[源代码]

创建记录。

ORM 默认 create 方法的一种替代方案,可以安全地用于创建数百万条记录。

参数
  • values (iterable(dict)) – 要创建的记录值的可迭代对象

  • size (int) – values 或 query 产生的元素数量。当 ids 没有 __length__ 或使用 query 时,可用它来启用进度日志记录

  • query (str) – values 的替代方案,即可以生成它们的 SQL 查询。也可以是带 RETURNING 子句的 DML 语句。

  • multi (bool) – 是否使用 create 的多版本,默认情况下从 Odoo 12 及以上版本为 True

recompute_fields(cr, model, fields, ids=None, logger=<Logger odoo.upgrade.util.orm (WARNING)>, chunk_size=256, strategy='auto', query=None)[源代码]

重新计算字段值。

此函数将重新计算模型的字段值,限制为一组记录或所有记录。重新计算不会一次性处理所有记录,而是分成多个批次(块)。这样可以避免性能问题,并在最坏情况下避免因 MemoryError 导致失败。每个块处理完成后,数据会根据以下策略之一返回数据库:

  • flush :使用 ORM 的 flush 方法

  • commit :提交游标(同时也会刷新)

  • auto :根据要计算的记录数量和是否存在跟踪字段,从上述两种策略中选择最佳选项。

commit 策略在处理大量数据时更不容易导致 MemoryError 。

参数
  • model (str) – 要重新计算的模型名称

  • fields (list(str)) – 要重新计算的字段名称列表

  • ids (list(int) or None) – 要重新计算的记录的 ID 列表,当为 None 时重新计算 所有 记录,除非也设置了 query (见下文)

  • logger (logging.Logger) – 用于报告进度的日志记录器

  • chunk_size (int) – 每批记录的数量——用于拆分处理

  • strategy (str) – 用于处理重新计算的策略

  • query (str) – 用于获取要重新计算的记录 ID 的查询,同时设置 ids 和 query 是错误的。请注意,处理将始终按升序进行。如果不需要这样,必须改用 ids 。

adapt_domains(cr, model, old, new, adapter=None, skip_inherit=(), force_adapt=False)[源代码]

在域中使用 model 和继承模型将 old 替换为 new 。

adapter 是一个用于调整叶子节点的回调函数。适配器函数必须接受三个参数,并返回一个替换原始叶子节点的 域 。参数如下:

  • leaf :一个域叶子节点,形式为 (left, op, right) 的元组

  • in_or :布尔值,当为 True 时表示该叶子节点是 OR ("|") 域的一部分,否则是 AND ("&") 域的一部分

  • negated :布尔值,当为 True 时表示该叶子节点被取反 ("!")

Example

def adapter(leaf, in_or, negated):
    left, op, right = leaf
    ok, ko = (1, 2) if not negated else (2, 1)
    if op == "="
        return [(left, "=", ok)]
    elif op == "!=":
        return [(left, "=", ko)]
    return [leaf]

除非 force_adapt 为 True ,否则 adapter 仅对那些将 model 的 old 字段作为叶子节点 left 部分的 最后一部分 使用的叶子节点调用。在后一种情况下,如果字段出现在路径中的任何位置,则会调用适配器,这仅对关系字段有用。

适配器返回的域不需要在输入叶子节点的 left 部分将 old 字段替换为 new 。无论如何,替换都会应用于适配器返回的整个域。适配器的通常用途是修改操作符和输入叶子节点的 right 部分。如果未设置 adapter ,则仅执行替换操作。

Example

当将 "field1" 替换为 "field2" 时,会发生以下情况:

  • ("foo.bar.baz.field1", "=", 1) 仅在 foo.bar.baz 指向的记录属于请求的 model 时才会被调整。

  • ("foo.field1.baz", "=", 1) 即使 foo 指向 model ,也不会被调整,除非 force_adapt 为 True ,因为 field1 不是此叶子节点中 left 的最后一部分。

注解

此函数将替换所有 标准 域字段中的域,包括过滤器、仪表板和已知表示域的标准字段。

参数
  • model (str) – 需要调整域的模型名称

  • old (str) – 需要调整的字段名称

  • new (str) – 应替换 old 的字段名称

  • adapter (function) – 叶子节点的适配器

  • skip_inherit (list(str)) – 不需要调整(跳过)的继承模型名称列表

  • force_adapt (bool) – 当为 True 时,在所有 left 部分(路径)包含 new 的叶子节点上运行 adapter ,这在删除字段时很有用(在这种情况下忽略 new )。

SQL

用于与 PostgreSQL 交互的实用函数。

class ColumnList(list_=(), quoted=())[源代码]

封装表示列名称的元素列表。

生成的对象可以渲染为带有前导/尾随逗号或别名的字符串。

参数
  • list (list(str)) – 列名称列表

  • quoted (list(str)) – 带引号的列名称列表,必须与 list_ 参数对应

Example

>>> columns = ColumnList(["id", "field_Yx"], ["id", '"field_Yx"'])
>>> list(columns)
['id', '"field_Yx"']
>>> columns.using(alias="t").as_string(cr._obj)
'"t"."id", "t"."field_Yx"'
>>> columns.using(leading_comma=True).as_string(cr._obj)
', "id", "field_Yx"'
>>> util.format_query(cr, "SELECT {} t.name FROM table t", columns.using(alias="t", trailing_comma=True))
'SELECT "t"."id", "t"."field_Yx", t.name FROM table t'
>>> columns = ColumnList.from_unquoted(cr, ["foo", "BAR"])
>>> list(columns)
['"foo"', '"BAR"']
>>> list(columns.iter_unquoted())
['foo', 'BAR']

注解

此类最好通过 get_columns() 使用

classmethod from_unquoted(cr, list_)[源代码]

从可能需要加引号的列名称列表构建 ColumnList。

参数

list (list(str)) – 未加引号的列名称列表

using(leading_comma=KEEP_CURRENT, trailing_comma=KEEP_CURRENT, alias=KEEP_CURRENT)[源代码]

设置参数以将此列表渲染为字符串。

参数
  • leading_comma (bool) – 是否在此列表前面渲染一个前导逗号

  • trailing_comma (bool) – 是否渲染尾随逗号

  • alias (str or None) – 列所属表的别名,如果设置为 None 则不添加别名

返回

设置了参数的列表副本

返回类型

ColumnList

iter_unquoted()[源代码]

迭代原始列名称,不加引号。

如果引号处理在该对象外部完成,则此功能非常有用。还可以用于获取 PostgreSQL 目录中的原始列名称。

返回

原始列名称的迭代器

class PGRegexp[源代码]

参数语义意义的包装器:此字符串是一个 PostgreSQL 正则表达式。

参见 replace_in_all_jsonb_values()

class SQLStr[源代码]

参数语义意义的包装器:此字符串是一个有效的 SQL 片段。

参见 format_query()

alter_column_type(cr, table, column, type, using=None, where=None, logger=<Logger odoo.upgrade.util.pg (WARNING)>)[源代码]

更改列的类型。

通过使用临时列和并行 UPDATE 查询高效完成。

参数
  • table (str) – 受影响表的名称。

  • column (str) – 要更改类型的列的名称。

  • type (str) – 列的新类型。

  • using (str) – 定义如何将值转换为新类型的 SQL 表达式。占位符 {0} 将被列名替换。

  • where (str) – 用于限制通过 using 转换的值。

  • logger (logging.Logger) – 用于报告进度的日志记录器。

bulk_update_table(cr, table, columns, mapping, key_col='id')[源代码]

基于映射更新表。

每个 mapping 条目为 key_col 值与键匹配的行定义指定 columns 的新值。

Example

# single column update
util.bulk_update_table(cr, "res_users", "active", {42: False, 27: True})

# multi-column update
util.bulk_update_table(
    cr,
    "res_users",
    ["active", "password"],
    {
        "admin": [True, "1234"],
        "demo": [True, "5678"],
    },
    key_col="login",
)
参数
  • table (str) – 要更新的表。

  • columns (str | list(str)) – 更新的列规范。它可以是单个列名或列名列表。mapping 必须与规范匹配。

  • mapping (dict) – 要设置的值,必须与 columns 中的规范匹配,遵循 相同 的顺序

  • key_col (str) – 在更新期间用作从 mapping 获取值的键的列。

警告

映射中的值将被转换为目标列的类型。此函数设计用于更新标量值,避免通过映射设置数组或 JSON 数据。

column_exists(cr, table, column)[源代码]

返回列是否存在。

参数
  • table (str) – 要检查的表

  • column (str) – 要检查的列

返回类型

bool

column_type(cr, table, column, sized=False)[源代码]

如果列存在,返回其类型。

参数
  • table (str) – 要检查的表

  • column (str) – 要检查的列

返回类型

SQL type of the column

copy_column(cr, table, column, new_name=AUTO)[源代码]

复制列。

如果列存在,此函数会复制它。否则会抛出错误。

参数
  • table (str) – 该列所属的表

  • column (str) – 列的名称

  • new_name (str) – 新列的名称,若未传入,则使用原列名加上后缀 _upg_copy。

返回

新列名称

返回类型

str

create_column(cr, table, column, definition, **kwargs)[源代码]

创建列。

此函数仅在列不存在时创建该列。如果现有列的类型不同,它会记录错误。如果设置了 fk_table ,它将确保外键已设置,并在必要时进行更新,同时应用正确的 on_delete_action (如果有)。

参数
  • table (str) – 新列所属的表

  • column (str) – 新列的名称

  • definition (str) – 新列的类型。使用 AUTO 从 fk_table 的 id 列推断类型。

  • default (bool) – 新列的默认值

  • fk_table (str) – 如果新列是外键,则为外键表的名称

  • on_delete_action (str) – ON DELETE 子句,默认 NO ACTION ,仅在列是外键时有效。

返回

是否已创建该列

返回类型

bool

create_m2m(cr, m2m, fk1, fk2, col1=None, col2=None)[源代码]

确保多对多表存在或被创建。

此函数创建与多对多字段关联的表。如果表已存在,则对其运行 fixup_m2m() 。表名可以自动生成,应用与 ORM 相同的逻辑。为此,请为 m2m 参数使用值 “auto”。

参数
  • m2m (str) – 要创建的表名,如果为 AUTO 则自动生成

  • fk1 (str) – 第一个外键表名

  • fk2 (str) – 第二个外键表名

  • col1 (str) – 引用 fk1 的列,默认为 "{fk1}_id"

  • col2 (str) – 引用 fk2 的列,默认为 "{fk2}_id"

返回

刚刚创建/修复的表的名称

返回类型

str

explode_execute(cr, query, table, alias=None, bucket_size=10000, logger=<Logger odoo.upgrade.util.pg (WARNING)>, qualifier='queries', explode_on='id')[源代码]

并行执行查询。

查询按 ID 分桶拆分,然后由工作线程并行处理。如果查询未包含特殊的 {parallel_filter} 值,则会将其添加到最后一个 WHERE 子句中(如果未找到 WHERE 子句,也可能新增)。如果查询已经包含过滤器,则不执行任何操作。过滤器始终扩展为拆分策略。拆分时,每个单独查询更新的 ID 数量不超过 bucket_size 。

Example

util.explode_execute(
    cr,
    '''
    UPDATE res_users u
       SET active = False
     WHERE (u.login LIKE 'dummy' OR u.login = 'bob')
       AND {parallel_filter}
    ''',
    table="res_users"
    alias="u",
)
参数
  • query (str) – 要执行的查询。

  • table (str) – 查询的主表名称,用于拆分处理

  • alias (str) – 查询中为主表使用的别名

  • explode_on (str) – 用于拆分查询的列

  • bucket_size (int) – 用于拆分处理的 ID 桶大小

  • logger (logging.Logger) – 用于报告进度的日志记录器

  • qualifier (str) – 查询的限定符。用于进度日志

返回

每个查询运行后的 cr.rowcount 总和

返回类型

int

警告

调用者需要确保查询不会在不同的桶中更新相同的记录。建议不要对具有自引用的表使用此函数执行 DELETE 查询,因为可能存在 ON DELETE 影响。更多详细信息,请参见 parallel_execute() 。

format_query(cr, query, *args, **kwargs)[源代码]

安全格式化查询。

传递给此函数的 str 参数假定为 SQL 标识符。在使用 str.format() 展开之前,它们会被包裹在双引号中。任何其他 psycopg2.sql.Composable 也是允许的。这包括 ColumnList ,另请参见 get_columns() 。

Example

>>> util.format_query(cr, "SELECT {0} FROM {table}", "id", table="res_users")
SELECT "id" FROM "res_users"
参数

query (str) – 要格式化的查询,可以像 str.format() 一样使用大括号 {}

get_columns(cr, table, ignore=('id',))[源代码]

返回表中的列列表。

参数
  • table (str) – 要检索列的表名称

  • ignore (list(str)) – 返回列表中要忽略的列名称列表

返回

表中存在的列名称列表

返回类型

ColumnList

get_common_columns(cr, table1, table2, ignore=('id',), type_check=True)[源代码]

返回两个表中都存在的列列表。

参数
  • table1 (str) – 要检索列的第一个表名称

  • table2 (str) – 要检索列的第二个表名称

  • ignore (list(str)) – 返回列表中要忽略的列名称列表

  • type_check (bool) – 只返回数据类型兼容的共有列

返回

两个表中都存在的列名列表

返回类型

ColumnList

get_m2m_on(cr, table)[源代码]

返回与 table 关联的 m2m 表列表。

我们把所有只有两列且两列都是外键的表识别为 m2m 表。此函数将返回其中一个外键指向 table 的 m2m 表。

注解

m2m 表只返回一次,无论是在同一列上定义了多个外键,还是该 m2m 的两列都指向 table。在后一种情况下,列按字母顺序返回。

返回

(m2m_table, fk_col_to_table, other_fk_col, other_table) 元组列表

class named_cursor(cr, itersize=None)[源代码]

服务器端游标。

此类包装了 psycopg2 服务器端游标。它添加了 dictfetchmany 和 dictfetchall 等便捷方法。应将其用作上下文管理器。

服务器端游标可用于分块从数据库获取海量数据,同时继续使用主升级游标。

参数

itersize (int) – 决定一次从 PG 获取的行数。

parallel_execute(cr, queries, logger=<Logger odoo.upgrade.util.pg (WARNING)>, qualifier='queries')[源代码]

并行执行查询。

Example

util.parallel_execute(cr, [util.format_query(cr, "REINDEX TABLE {}", t) for t in tables])

小技巧

如果希望加速单个查询,请参见 explode_execute() 。

参数
  • queries (list(str)) – 要并发执行的查询列表

  • logger (Logger) – 用于报告进度的日志记录器

  • qualifier (str) – 查询的限定符。用于进度日志

返回

每个查询运行后的 cr.rowcount 总和

返回类型

int

警告

  • 由于 cr.rowcount 的特性,此函数的返回值可能低估了实际受影响记录的数量。例如,当某些记录因 ondelete 子句而被删除或更新时,它们不会被计入。

  • 作为副作用,游标将被提交。

注解

如果发生并发问题,失败的查询将按顺序重试。

class query_ids(cr, query, itersize=None)[源代码]

对查询返回的 ID 的迭代器。

这允许在不耗尽内存的情况下迭代可能数量巨大的 ID。

参数
  • query (str) – 返回 ID 的查询。它可以是 DML,例如 UPDATE table WHERE ... RETURNING id。

  • itersize (int) – 决定一次从 PG 获取的行数,参见 named_cursor()。

remove_constraint(cr, table, name, cascade=False, warn=True)[源代码]

删除表约束。

此函数从 table 中删除名为 name 的约束。它还会从 ir_model_constraint 及其 xml_ids 中删除记录,并记录未找到的约束。

注解

如果没有名为 name 的约束,此函数将尝试删除 {table}_{name} ,后者是 ORM 为从 _sql_constraints 创建的约束使用的默认名称。

参数
  • table (str) – 要从中删除约束的表

  • name (str) – 要删除的约束名称

  • cascade (bool) – 级联删除约束

  • warn (bool) – 记录未找到的约束时使用警告级别,否则使用信息级别

返回

是否已删除约束

返回类型

bool

rename_table(cr, old_table, new_table, remove_constraints=True)[源代码]

重命名表。

此函数将表 old_table 重命名为 new_table ,同时还会重命名其主键(和序列)、索引以及外键。

参数
  • old_table (str) – 要重命名的表名称

  • new_table (str) – 表的新名称

参数 bool remove_constraints

是否删除表约束

杂项

各种独立函数。

chunks(iterable, size, fmt=None)[源代码]

将 iterable 拆分为大小为 size 的块,并使用 fmt 函数包装每个块。

此函数对于将大量输入数据拆分为可独立处理的小块非常有用。

Example

>>> list(chunks(range(10), 4, fmt=tuple))
[(0, 1, 2, 3), (4, 5, 6, 7), (8, 9)]
>>> ' '.join(chunks('abcdefghijklm', 3))
'abc def ghi jkl m'
参数
  • iterable (iterable) – 要拆分的可迭代对象

  • size (int) – 块大小

  • fmt (function) – 应用于每个块的函数,当传入 None 时,fmt 在 iterable 为字符串时变为 "".join ,否则为 iter 。

返回

一个生成器,迭代 fmt 应用于每个块的结果

expand_braces(s)[源代码]

展开输入中的大括号。

Example

>>> util.expand_braces("a_{this,that}_b")
['a_this_b', 'a_that_b']
参数

s (str) – 要展开的字符串,必须包含且仅包含一对大括号。

返回

展开后的输入

import_code_upgrade(name)[源代码]

导入代码升级脚本。

允许导入 upgrade_code scripts 以在升级脚本内使用。

参数

name (str) – 要导入的脚本名称。

import_script(path, name=None)[源代码]

导入升级脚本。

此函数允许从其他升级脚本中导入函数到当前脚本中。

Example

在 mymodule/15.0.1.0/pre-migrate.py 中

def my_util(cr):
    # do stuff

在 myothermodule/16.0.1.0/post-migrate.py 中

from odoo.upgrade import util

script = util.import_script("mymodule/15.0.1.0/pre-migrate.py")

def migrate(cr, version):
    script.my_util(cr)  # reuse the function

此函数返回一个 Python module 。

>>> util.import_script("base/0.0.0/end-moved0.py", name="my-moved0")
<module 'my-moved0' from '/home/odoo/src/upgrade-util/src/base/0.0.0/end-moved0.py'>
参数
  • path (str) – 要导入脚本的相对路径,格式为 $module/$version/$script-name .. 注意:: 脚本必须在升级路径中可用。

  • name (str or None) – 要赋予返回模块的名称。为 None 时,会从 path 派生名称,以与 Odoo(>=16)加载迁移脚本时设置的名称一致:odoo.upgrade.<module>.<version>.<script>。

返回

从导入的升级脚本创建的模块

class once(lower, upper, logger=None)[源代码]

在多步骤升级期间,仅在某个版本区间内运行一次代码。

当升级源和目标已知时(通过 ODOO_UPG_DB_SOURCE_VERSION 和 ODOO_UPG_DB_TARGET_VERSION 环境变量),该对象仅在目标版本落在 [lower, upper] 内的 第一个 升级步骤中求值为真,其余每一步都求值为假。

当环境变量不存在时(例如在单步或独立运行中),当前版本处于区间 [lower, upper] 内时该对象为真。None 值表示无界。必须至少设置一个边界。

它既可以用作普通布尔值,也可以用作修饰符:

Example

作为布尔值,只在第一个到达 Odoo 17 的步骤中运行迁移:

def migrate(cr, version):
    if util.once("17.0", "18.0"):
        util.rename_field(cr, "sale.order", "old_name", "new_name")

作为修饰符,效果相同,会包装整个函数体:

@util.once("17.0", "18.0")
def migrate(cr, version):
    util.rename_field(cr, "sale.order", "old_name", "new_name")

注解

这只在多步骤升级中有意义,即针对 0.0.0 脚本或符号链接脚本。

参数
  • lower (str or None) – 版本区间的下界,包含端点。为 None 时默认为升级源版本。

  • upper (str or None) – 版本区间的上界,包含端点。为 None 时默认为升级目标版本。

  • logger (logging.Logger or None) – 设置后,每次求值时都会发出一条消息,报告被包装的代码在当前版本下是运行还是跳过。

parse_version(s: str) → tuple[str, ...][源代码]

将版本字符串转换为可按时间顺序排序的键

这是 distutils 的 StrictVersion 和 LooseVersion 的粗略混合体;如果传入的版本在 StrictVersion 下可用,它的行为与之相同;否则它的表现就像一个稍微聪明一些的 LooseVersion。有可能 构造出能骗过此解析器的病态版本编码方案,但在实践中它们应该非常罕见。

返回值是一个字符串元组。版本中的数字部分会补零到 8 位,以便按数值比较,而不依赖于数字与字符串的比较方式。点号会被丢弃,但短横线会保留。字母段或短横线之间的尾随零会被抑制,因此例如 “2.4.0” 被视为与 “2.4” 相同。字母数字部分会转换为小写。

该算法假定像 “-” 这样的字符串以及任何按字母顺序排在 “final” 之后的字母字符串都表示 “补丁级别”。因此,”2.4-1” 被视为 “2.4” 的一个分支或补丁,所以 “2.4.1” 被认为比 “2.4-1” 更新,而 “2.4-1” 又比 “2.4” 更新。

像 “a”、”b”、”c”、”alpha”、”beta”、”candidate” 等字符串(按字母顺序排在 “final” 之前)被视为预发布版本,因此版本 “2.4” 被认为比 “2.4a1” 更新。

最后,为处理各种杂项情况,字符串 “pre”、”preview” 和 “rc” 会被当作 “c” 处理,即视为候选发布版本,因此它们不如不包含这些字符串的版本新。

skippable_cm()[源代码]

返回一个上下文管理器,允许另一个上下文管理器不产生值。

参见 edit_view() 获取示例用法。

version_between(a, b)

返回当前运行的 Odoo 版本是否在范围 [a,b] 内。

另请参见 version_gte()

注解

边界值包含在内。

参数
  • a (str) – Odoo 版本,下限

  • b (str) – Odoo 版本,上限

返回类型

bool

version_gte(version)

返回当前运行的 Odoo 版本是否大于或等于 version 。

此函数对于适用于多个版本的升级脚本中的条件执行非常有用,例如 0.0.0 脚本。

参数

version (str) – Odoo 版本,必须遵循格式 [saas~]X.Y ,其中 X 是 Odoo 的主版本号,仅当 Y 非零时需要 saas~ 。

返回类型

bool

报表

用于报告升级变更的工具函数。

报告函数生成的信息在升级后会以帖子的形式显示在管理员频道中。

获取指向 Odoo UI 中某条记录的 HTML 链接。

参数
  • model (str) – 技术模型名称。

  • id (int) – 要链接到的记录 ID。

  • name (str) – 链接文本(可选);省略时默认为 “model(id=id)”。

  • action_id (int) – 用于打开该记录的可选操作。

返回

HTML 锚点标签,如果可用则包装在 Markup 中。

返回类型

字符串或 markupsafe.Markup

html_escape(s: Any, /) → markupsafe.Markup[源代码]

将字符串中的 &、<、>、' 和 " 字符替换为 HTML 安全的序列。如果需要在 HTML 中显示可能包含此类字符的文本,请使用此函数。

如果对象具有 __html__ 方法,则会调用该方法,并假定其返回值对 HTML 而言已经是安全的。

参数

s – 要转换为字符串并转义的对象。

返回

包含转义后文本的 Markup 字符串。

report_message(message, category='Other', format='text')[源代码]

向升级报告追加一条新条目,按照 format 进行格式化。

参数
  • message (str) – 要报告的内容。

  • category (str) – 报告条目的标题。

  • format (str) – 用于渲染 message 的格式。支持的值: - text:纯文本,转义后按原样显示。 - html:作为原始 HTML 标记渲染。 - md:作为 Markdown 渲染并转换为 HTML。 - rst:作为 reStructuredText 渲染并转换为 HTML。

report_with_list(summary, data, columns, row_format, links=None, total=None, limit=100, category='Other')[源代码]

向升级报告追加一条新条目,用于显示记录列表。

该条目由类别(标题)和摘要(正文)组成。该条目显示先前由 SQL 查询返回的记录列表,或任意列表。

警告

如果要渲染的列表为空,则报告中不会发布任何内容。

Example

total = cr.rowcount
data = cr.fetchmany(20)
util.report_with_list(
    summary="The following records were altered.",
    data=data,
    columns=("id", "name", "city", "comment", "company_id", "company_name"),
    row_format="Partner with id {partner_link} works at company {company_link} in {city}, ({comment})",
    links={"company_link": ("res.company", "company_id", "company_name"), "partner_link": ("res.partner", "id", "name")},
    total=total,
    category="Accounting"
)
参数
  • summary (str) – 报告条目的描述。

  • data (list(tuple)) – 要报告的数据,每个条目将成为报告中的一行。

  • columns (tuple(str)) – data 中的列,可在 row_format 中引用

  • row_format (str) – 行的格式,可使用 columns 或 links 中的任意名称,例如:”住在 {city} 的合作伙伴 {partner_link} 在 {company_link} 公司工作”。

  • links (dict(str, tuple(str, str, str))) – 可选的模型/记录链接规范,其键可在 row_format 中引用。

  • total (int) – 可选,记录总数。当传入 None 时取 len(data)。当调用方限制了 data 时很有用。

  • limit (int) – 报告中列出的最大记录数。如果 data 包含的记录多于 limit,则报告中也会包含 total 数量。设置为 None 表示不限制。

  • category (str) – 报告条目的标题。

report_with_summary(summary, details, category='Other')[源代码]

向升级报告追加一条新条目。

参数
  • summary (str) – 报告条目的描述。

  • details (str) – 默认会被折叠的详细描述。

  • category (str) – 报告条目的标题。

测试升级脚本

升级期间用于测试的主要有两个类。

子类必须实现:

  • 对于 UpgradeCase:

    • prepare 方法:在升级前准备数据,

    • check 方法:检查数据是否已正确升级。

  • 对于 IntegrityCase:

    • invariant 方法:计算一个在升级前后必须保持一致的值。

将您的测试类放在任意包含模块升级脚本的文件夹下的 tests Python 模块(文件夹)中。包含测试的脚本必须带有 test_ 前缀。tests 模块必须包含 __init__.py 文件,才能被 Odoo 检测到。

示例目录结构:

myupgrades/
└── mymodule1/
    ├── 18.0.1.1.2/
    │   └── pre-myupgrade.py
    └── tests/
        ├── __init__.py
        └── test_myupgrade.py

注解

仅当正在 升级 mymodule1 时,才会加载上例中的测试。

运行升级测试

在收到升级后的数据库(其中所有标准 Odoo 模块都已升级到目标版本)后,您可以按照以下三步流程测试自定义模块的升级:

  1. 准备测试数据

    $ ~/odoo/$version/odoo-bin -d DB --test-tags=$prepare_test_tag \
      --upgrade-path=~/upgrade-util/src,~/myupgrades \
      --addons=~/odoo/$version/addons,~/enterprise/$version,~/mymodules --stop
    
  2. 升级模块

    $ ~/odoo/$version/odoo-bin -d DB -u mymodule1,mymodule2 \
      --upgrade-path=~/upgrade-util/src,~/myupgrades \
      --addons=~/odoo/$version/addons,~/enterprise/$version,~/mymodules --stop
    
  3. 检查升级后的数据

    $ ~/odoo/$version/odoo-bin -d DB --test-tags=$check_test_tag \
      --upgrade-path=~/upgrade-util/src,~/myupgrades \
      --addons=~/odoo/$version/addons,~/enterprise/$version,~/mymodules --stop
    

上例假定 $version 是您升级的目标版本(例如 18.0),DB 是数据库的名称,mymodule1,mymodule2 是您要升级的模块。该目录结构假定 ~/odoo/$version 和 ~/enterprise/$version 分别包含目标 Odoo 版本的社区版和企业版源代码。~/mymodules 包含您的自定义模块(mymodule1 等)的代码,~/myupgrades 包含您的自定义升级脚本,~/upgrade-util 包含 upgrade utils 仓库。

变量 $prepare_test_tag 和 $check_test_tag 必须按以下方式设置:

变量

UpgradeCase

IntegrityCase

$prepare_test_tag

upgrade.test_prepare

integrity_case.test_prepare

$check_test_tag

upgrade.test_check

integrity_test.test_check

注解

upgrade.test_prepare 也会运行 IntegrityCase 测试,因此仅使用此标签即可为 UpgradeCase 和 IntegrityCase 测试准备数据。

警告

在将数据库发送到 upgrade.odoo.com 进行 生产环境 升级之前,不要 运行 UpgradeCase 的任何 prepare 方法。这样做可能导致您的升级被阻止并标记为失败。

API 文档

class IntegrityCase(methodName='runTest')[源代码]

用于验证升级不变量的测试用例。

重写:

  • invariant,返回一个可 JSON 序列化的值,用于表示要检查的不变量。

invariant 方法会在升级前后各调用一次,并比较结果。如果存在任何差异,测试将失败。

Example

from odoo.upgrade.testing import IntegrityCase


class NoNewUsers(IntegrityCase):
    def invariant(self):
        return self.env["res.users"].search_count([])
class UpgradeCase(methodName='runTest')[源代码]

用于验证升级流程的测试用例。

重写:

  • prepare,用于设置数据,

  • check,用于在升级后断言预期结果。

在这些方法中可以使用 ORM 来执行被测的功能流程。prepare 的返回值会被持久化,并作为参数传递给 check。它必须是可 JSON 序列化的。

注解

由于 prepare 会注入或修改数据,此类测试 仅用于开发。可在开发升级脚本时用它来测试脚本。不要 在生产环境升级中运行这些测试。要验证升级在生产环境中是否保留了重要的不变量,请改用 IntegrityCase 测试。

Example

from odoo.upgrade.testing import UpgradeCase, change_version


class DeactivateBobUsers(UpgradeCase):

    def prepare(self):
        u = self.env["res.users"].create({"login": "bob", "name": "Bob"})
        return u.id  # will be passed to check

    def check(self, uid):  # uid is the value returned by prepare
        self.env.cr.execute(
            "SELECT * FROM res_users WHERE id=%s AND NOT active", [uid]
        )
        self.assertEqual(self.env.cr.rowcount, 1)
change_version(version_str)[源代码]

用于指定测试所适用版本的类修饰符。

使用 @change_version(version) 表示:

  • 仅当当前 Odoo 版本位于 [next_major_version-1, version) 范围内时,才会运行 test_prepare。

  • 仅当当前 Odoo 版本位于 [version, next_major_version) 范围内时,才会运行 test_check。

next_major_version 是 version 之后的下一个主版本,例如对于 saas~17.2 即为 18.0。

注解

如果您的升级在同一主版本内进行,请不要使用此修饰符。否则您的测试将不会运行。

parametrize(argvalues)[源代码]

对测试函数进行参数化。

用于升级测试函数的修饰符,可对其进行参数化并由此生成多个测试。新的测试函数会被注入到所属的类中。灵感来自 parameterized 包。

Example

@parametrize([(1, 2), (2, 4), (-1, -2), (0, 0)])
def test_double(self, input, expected):
    self.assertEqual(input * 2, expected)

这将生成四个测试方法:test_double__0、test_double__1、test_double__2 和 test_double__3,每个方法的参数值都不同。

参数

argvalues (list(tuple)) – 每个测试用例的参数。每个元组都会被解包,并作为参数传递给测试函数。