升级工具

升级工具 是一个包含辅助函数的库,用于简化升级脚本的编写。该库被 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

使用升级工具

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

  • odoo.upgrade.util: 辅助工具本身。

  • odoo.upgrade.testing: 基础 TestCase 类。

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

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 模块之前运行升级脚本。这是在主要升级后执行模块操作的推荐方式。

odoo.upgrade.util.modules.modules_installed(cr, *modules)[源代码]

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

参数

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

返回类型

bool

注解

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

odoo.upgrade.util.modules.module_installed(cr, module)[源代码]

返回模块是否已安装。

参数

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

返回类型

bool

参见 modules_installed()

odoo.upgrade.util.modules.uninstall_module(cr, module)[源代码]

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

参数

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

odoo.upgrade.util.modules.uninstall_theme(cr, theme, base_theme=None)[源代码]

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

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

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

警告

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

参见 remove_theme()uninstall_module()

odoo.upgrade.util.modules.remove_module(cr, module)[源代码]

完全删除模块。

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

参数

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

警告

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

odoo.upgrade.util.modules.remove_theme(cr, theme, base_theme=None)[源代码]

卸载主题模块。

警告

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

参见 remove_module()uninstall_theme()

odoo.upgrade.util.modules.rename_module(cr, old, new)[源代码]

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

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

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

odoo.upgrade.util.modules.merge_module(cr, old, into, update_dependers=True)[源代码]

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

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

警告

此函数不会删除任何记录,但会从源模块中移除与目标模块中名称冲突的 xml_ids。

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

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

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

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

强制 ORM 安装模块。

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

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

返回字符串

模块的 状态

odoo.upgrade.util.modules.force_upgrade_of_fresh_module(cr, module, init=True)[源代码]

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

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

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

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

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

odoo.upgrade.util.modules.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 列表

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

模型

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

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

odoo.upgrade.util.models.remove_model(cr, model, drop_table=True, ignore_m2m=())[源代码]

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

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

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

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

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

odoo.upgrade.util.models.delete_model(cr, model, drop_table=True, ignore_m2m=())[源代码]

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

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

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

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

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

odoo.upgrade.util.models.rename_model(cr, old, new, rename_table=True)[源代码]

重命名模型。

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

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

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

odoo.upgrade.util.models.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) – 从源模型中忽略(不删除)的多对多表列表。

odoo.upgrade.util.models.remove_inherit_from_model(cr, model, inherit, keep=(), skip_inherit=(), with_inherit_parents=True)[源代码]

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 的字段,保留其父模型中的所有字段

字段

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

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

odoo.upgrade.util.fields.remove_field(cr, model, fieldname, cascade=False, drop_column=True, skip_inherit=(), keep_as_attachments=False)[源代码]

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

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

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

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

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

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

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

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

odoo.upgrade.util.fields.make_field_non_stored(cr, model, field, selectable=False)[源代码]

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

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

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

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

odoo.upgrade.util.fields.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) – 字段不被移动的继承模型列表,使用 "*" 跳过所有模型

odoo.upgrade.util.fields.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) – 在继承模型中重命名字段时要跳过的模型,使用 "*" 跳过所有模型

odoo.upgrade.util.fields.invert_boolean_field(cr, model, old, new, skip_inherit=())[源代码]

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

odoo.upgrade.util.fields.change_field_selection_values(cr, model, field, mapping, skip_inherit=())[源代码]

替换选择字段值的引用。

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

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

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

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

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

odoo.upgrade.util.fields.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(仪表板)

  • 域(使用 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) – 在继承模型中重命名字段时要跳过的模型,使用 "*" 跳过所有模型

记录

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

odoo.upgrade.util.records.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_idview_id。同时指定两者将引发错误。

odoo.upgrade.util.records.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"

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

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

如果 active 参数为 TrueFalse,则视图的 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 则保持不变。

返回

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

odoo.upgrade.util.records.remove_record(cr, name)[源代码]

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

参数

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

odoo.upgrade.util.records.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

odoo.upgrade.util.records.if_unchanged(cr, xmlid, callback, interval='1 minute', **kwargs)[源代码]

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

当引用的记录未更改时,此函数将运行 callbackxmlid 和任何其他参数(但不包括 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()

odoo.upgrade.util.records.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

odoo.upgrade.util.records.ref(cr, xmlid)[源代码]

返回与给定 xml_id 对应的 ID。

参数

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

返回

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

返回类型

int or None

odoo.upgrade.util.records.force_noupdate(cr, xmlid, noupdate=True, warn=False)[源代码]

更新记录的 noupdate 标志。

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

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

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

odoo.upgrade.util.records.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 并避免重复。

odoo.upgrade.util.records.update_record_from_xml(cr, xmlid, reset_write_metadata=True, force_create=AUTOMATIC, 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 声明中包含的字段的可选列表。如果设置,所有其他字段将被忽略。如果设置且记录缺失,则不会创建记录。

警告

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

注解

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

odoo.upgrade.util.records.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

返回

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

返回类型

list(int)

odoo.upgrade.util.records.replace_record_references_batch(cr, id_mapping, model_src, model_dst=None, replace_xmlid=True, ignores=())[源代码]

替换对记录的所有引用。

此函数将对 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)) – 更新引用值时要跳过的 名称列表

odoo.upgrade.util.records.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()

ORM

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

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

odoo.upgrade.util.orm.env(cr)[源代码]

创建一个新环境。

警告

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

返回

新环境

返回类型

Environment

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

重新计算字段值。

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

  • flush:使用 ORM 的 flush 方法

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

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

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

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

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

  • ids (list(int) or None) – 要重新计算的记录 ID 列表,当为 None 时重新计算所有记录

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

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

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

class odoo.upgrade.util.orm.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,即使 idsvalues 包含数百万条目。使用此类的替代方法是:

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 (list(int)) – 要迭代的记录 ID 列表

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

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

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

返回

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

另请参见 env()

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

创建记录。

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

参数
  • values (list(dict)) – 要创建的记录值列表

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

odoo.upgrade.util.domains.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_adaptTrue,否则 adapter 仅对那些将 modelold 字段作为叶子节点 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_adaptTrue,因为 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 odoo.upgrade.util.pg.PGRegexp[源代码]

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

参见 replace_in_all_jsonb_values()

class odoo.upgrade.util.pg.SQLStr[源代码]

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

参见 format_query()

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

并行执行查询。

Example

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

小技巧

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

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

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

返回

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

返回类型

int

警告

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

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

注解

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

odoo.upgrade.util.pg.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() 一样使用大括号 {}

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

并行执行查询。

查询按 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) – 查询中为主表使用的别名

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

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

返回

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

返回类型

int

警告

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

odoo.upgrade.util.pg.column_exists(cr, table, column)[源代码]

返回列是否存在。

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

  • column (str) – 要检查的列

返回类型

bool

odoo.upgrade.util.pg.column_type(cr, table, column)[源代码]

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

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

  • column (str) – 要检查的列

返回类型

SQL type of the column

odoo.upgrade.util.pg.create_column(cr, table, column, definition, **kwargs)[源代码]

创建列。

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

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

  • column (str) – 新列的名称

  • definition (str) – 新列的列类型

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

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

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

返回

是否已创建该列

返回类型

bool

odoo.upgrade.util.pg.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) – 用于报告进度的日志记录器。

odoo.upgrade.util.pg.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

class odoo.upgrade.util.pg.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._cnx)
'"t"."id", "t"."field_Yx"'
>>> columns.using(leading_comma=True).as_string(cr._cnx)
', "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 目录中的原始列名称。

返回

原始列名称的迭代器

odoo.upgrade.util.pg.get_columns(cr, table, ignore=('id',))[源代码]

返回表中的列列表。

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

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

返回

表中存在的列名称列表

返回类型

ColumnList

odoo.upgrade.util.pg.get_common_columns(cr, table1, table2, ignore=('id',))[源代码]

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

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

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

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

返回

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

返回类型

ColumnList

odoo.upgrade.util.pg.rename_table(cr, old_table, new_table, remove_constraints=True)[源代码]

重命名表。

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

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

  • new_table (str) – 表的新名称

参数 bool remove_constraints

是否删除表约束

杂项

各种独立函数。

odoo.upgrade.util.misc.version_gte(version)[源代码]

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

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

参数

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

返回类型

bool

odoo.upgrade.util.misc.version_between(a, b)[源代码]

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

另请参见 version_gte()

注解

边界值包含在内。

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

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

返回类型

bool

odoo.upgrade.util.misc.expand_braces(s)[源代码]

展开输入中的大括号。

Example

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

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

返回

展开后的输入

odoo.upgrade.util.misc.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,则使用导入文件的名称。

返回

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

odoo.upgrade.util.misc.skippable_cm()[源代码]

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

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

odoo.upgrade.util.misc.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 时,fmtiterable 为字符串时变为 "".join,否则为 iter

返回

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