操作

动作定义了系统对用户操作(登录、动作按钮、选择发票等)的响应行为。

动作可以存储在数据库中,也可以直接以字典形式返回,例如在按钮方法中返回。所有动作都共享两个必需属性:

type

当前动作的类别,决定可以使用哪些字段以及如何解释该动作

name

动作的简短、用户可读的描述,可能会显示在客户端界面中

客户端可以通过 4 种形式获取动作:

  • False

    如果当前有任何动作对话框处于打开状态,则将其关闭

  • 字符串

    如果匹配某个 客户端动作,则解释为客户端动作的标签,否则按数字处理

  • 数字

    从数据库中读取对应的动作记录,可以是数据库标识符或 外部标识符

  • 字典

    视为客户端动作描述符并执行

绑定

除了两个必需属性之外,所有动作还共享一些 可选 属性,用于在任意模型的上下文菜单中呈现动作:

binding_model_id

指定该动作绑定到哪个模型

注解

对于服务器动作,请使用 model_id。

binding_type

指定绑定类型,主要指动作将出现在哪个上下文菜单中

action (默认)

指定该动作将出现在所绑定模型的 动作 上下文菜单中。

report

指定该动作将出现在所绑定模型的 打印 上下文菜单中。

binding_view_types

以逗号分隔的视图类型列表,动作会在这些视图类型的上下文菜单中出现,主要是 “list” 和/或 “form”。默认为 list,form (列表和表单)

窗口动作(ir.actions.act_window)

最常见的动作类型,用于通过 视图 呈现模型的数据:窗口动作为一个模型(以及该模型的特定记录)定义一组视图类型(以及可能的特定视图)。

其字段如下:

res_model

要为其呈现视图的模型

views

由 (view_id, view_type) 对组成的列表。每一对的第二个元素是视图类别(list、form、graph 等),第一个元素是可选的数据库 id(或 False)。如果未提供 id,客户端应获取所请求模型指定类型的默认视图(这会由 fields_view_get() 自动完成)。列表中的第一个类型是默认视图类型,动作执行时会默认打开该视图。每种视图类型在列表中最多只能出现一次

res_id (可选)

如果默认视图是 form,则指定要加载的记录(否则应创建新记录)

search_view_id (可选)

(id, name) 对,id 是要为该动作加载的特定搜索视图的数据库标识符。默认为获取该模型的默认搜索视图

target (可选)

视图应在主内容区域(current)、全屏模式(fullscreen)还是对话框/弹出窗口(new)中打开。使用 main 而不是 current 可清除面包屑导航。默认为 current。

context (可选)

要传递给视图的附加上下文数据

domain (可选)

要隐式添加到所有视图搜索查询中的过滤域

limit (可选)

列表中默认显示的记录数量。在 Web 客户端中默认为 80

例如,要用列表视图和表单视图打开客户(设置了 customer 标志的合作伙伴):

{
    "type": "ir.actions.act_window",
    "res_model": "res.partner",
    "views": [[False, "list"], [False, "form"]],
    "domain": [["customer", "=", true]],
}

或者在新对话框中打开某个特定产品(另行获取)的表单视图:

{
    "type": "ir.actions.act_window",
    "res_model": "product.product",
    "views": [[False, "form"]],
    "res_id": a_product_id,
    "target": "new",
}

数据库中的窗口动作还有几个不同的字段,客户端应忽略它们,它们主要用于组合 views 列表:

view_mode (默认= list,form)

以字符串形式给出、用逗号分隔的视图类型列表(/!不能有空格 /!)。所有这些类型都会出现在生成的 views 列表中(至少包含一个 False view_id)

view_ids

M2M1 到视图对象,定义 views 的初始内容

注解

窗口动作的视图也可以通过 ir.actions.act_window.view 清晰地定义。

如果你打算为模型允许多个视图,请优先使用 ir.actions.act_window.view,而不是动作的 view_ids

<record model="ir.actions.act_window.view" id="test_action_tree">
   <field name="sequence" eval="1"/>
   <field name="view_mode">list</field>
   <field name="view_id" ref="view_test_tree"/>
   <field name="act_window_id" ref="test_action"/>
</record>
view_id

添加到 views 列表中的特定视图,前提是其类型属于 view_mode 列表,且尚未被 view_ids 中的某个视图填充

这些字段主要用于从 数据文件 定义动作时:

<record model="ir.actions.act_window" id="test_action">
    <field name="name">A Test Action</field>
    <field name="res_model">some.model</field>
    <field name="view_mode">graph</field>
    <field name="view_id" ref="my_specific_view"/>
</record>

即使 “my_specific_view” 不是该模型的默认视图,也会使用 “my_specific_view” 视图。

views 序列在服务器端的组合方式如下:

  • 从 view_ids 中获取每个 (id, type) (按 sequence 排序)

  • 如果定义了 view_id 且其类型尚未填充,则追加其 (id, type)

  • 对于 view_mode 中每个未填充的类型,追加 (False, type)

1

严格来说并不是 M2M:它增加了一个 sequence 字段,并且可以只由一个视图类型组成,而不带视图 id。

URL 动作(ir.actions.act_url)

允许通过 Odoo 动作打开 URL(网站/网页)。可通过两个字段进行自定义:

url

激活动作时要打开的地址

target (默认值 = new)

可用的取值包括:

  • new:在新窗口/新页面中打开该 URL

  • self:在当前窗口/页面中打开该 URL(替换实际内容)

  • download:重定向到下载 URL

示例:

{
    "type": "ir.actions.act_url",
    "url": "https://odoo.com",
    "target": "self",
}

这将用 Odoo 主页替换当前的内容区域。

服务器动作(ir.actions.server)

允许从任何有效的动作位置触发复杂的服务器代码。只有两个字段与客户端相关:

id

要运行的服务器动作在数据库中的标识符

context (可选)

运行该服务器动作时要使用的上下文数据

数据库中的记录功能更为丰富,可根据其 state 执行多种特定或通用动作。有些字段(及相应行为)在各状态之间是共用的:

model_id

与该动作关联的 Odoo 模型。

state

  • code:执行通过 code 参数提供的 Python 代码。

  • object_create:按 fields_lines 的规定创建模型 crud_model_id 的新记录。

  • object_write:按 fields_lines 的规定更新当前记录

  • multi:执行通过 child_ids 参数给出的多个动作。

状态字段

根据其状态,行为由不同的字段定义。相关状态在每个字段之后给出。

code (code)

指定在调用该动作时要执行的一段 Python 代码

<record model="ir.actions.server" id="print_instance">
    <field name="name">Res Partner Server Action</field>
    <field name="model_id" ref="model_res_partner"/>
    <field name="state">code</field>
    <field name="code">
        raise Warning(record.name)
    </field>
</record>

注解

代码段可以定义一个名为 action 的变量,它将被返回给客户端,作为下一个要执行的动作:

<record model="ir.actions.server" id="print_instance">
    <field name="name">Res Partner Server Action</field>
    <field name="model_id" ref="model_res_partner"/>
    <field name="state">code</field>
    <field name="code">
        if record.some_condition():
            action = {
                "type": "ir.actions.act_window",
                "view_mode": "form",
                "res_model": record._name,
                "res_id": record.id,
            }
    </field>
</record>

如果满足某个条件,将要求客户端为该记录打开表单

crud_model_id (create)(必填)

在其中创建新记录的模型

link_field_id (create)

指向 ir.model.fields 的 many2one,指定当前记录上用于设置新建记录的 m2o 字段(模型应匹配)

fields_lines (create/write)

创建或复制记录时要覆盖的字段。One2many,包含以下字段:

col1

要在相关模型中设置的 ir.model.fields (创建时为 crud_model_id,更新时为 model_id)

value

该字段的值,通过 type 进行解释

type (取值为 value|reference|equation)

若为 value,则 value 字段被解释为字面值(可能经过转换);若为 equation,则 value 字段被解释为 Python 表达式并求值

child_ids (multi)

指定在 multi 状态下要执行的多个子动作(ir.actions.server)。如果子动作本身返回动作,则最后一个将作为该 multi 自身的下一个动作返回给客户端

求值上下文

服务器动作及其周边的求值上下文中可使用若干键:

  • model 通过 model_id 与该动作关联的模型对象

  • record/records 触发该动作所针对的记录/记录集,可能为空。

  • env Odoo 环境

  • datetime、dateutil、time、timezone 对应的 Python 模块

  • log: log(message, level='info') 日志记录函数,用于在 ir.logging 表中记录调试信息

  • UserError 用于 UserError 异常的构造函数

报表动作(ir.actions.report)

触发报表的打印。

如果你通过 <record> 而非 <report> 标签定义报表,并希望该动作出现在模型视图的打印菜单中,则还需要指定 绑定 中的 binding_model_id。无需将 binding_type 设为 report,因为 ir.actions.report 会隐式默认如此。

name (必填)

如果未指定 print_report_name,则用作文件名。否则,它仅在某种列表中查找报表时作为助记/描述之用

model (必填)

报表所针对的模型

report_type (默认值=qweb-pdf)

为 PDF 报表使用 qweb-pdf,或为 HTML 使用 qweb-html

report_name (必填)

用于渲染报表的 qweb 模板的名称(外部标识符)

print_report_name

定义报表名称的 Python 表达式。

groups_id

Many2many 字段,指向允许查看/使用当前报表的用户组

multi

如果设为 True,该动作将不会显示在表单视图中。

paperformat_id

Many2one 字段,指向你希望用于此报表的纸张格式(如果未指定,将使用公司格式)

attachment_use

如果设为 True,报表仅在首次被请求时生成一次,之后将从存储的报表重新打印,而不是每次都重新生成。

可用于必须只生成一次的报表(例如出于法律原因)

attachment

定义报表名称的 Python 表达式;记录可通过变量 object 访问

客户端动作(ir.actions.client)

触发一个完全在客户端实现的动作。

tag

该动作在客户端的标识符,是一个任意字符串,客户端应知道如何对其做出响应

params (可选)

与客户端动作标签一起发送给客户端的附加数据,为一个 Python 字典

target (可选)

客户端动作应打开在主内容区域(current)、全屏模式(fullscreen)还是对话框/弹出窗口(new)中。使用 main 而非 current 可清除面包屑导航。默认为 current。

{
    "type": "ir.actions.client",
    "tag": "pos.ui"
}

告诉客户端启动销售点(Point of Sale)界面,服务器并不了解 POS 界面如何工作。

计划动作(ir.cron)

按预定义频率自动触发的动作。

name

计划动作的名称(主要用于日志显示)

interval_number

两次执行该动作之间的 interval_type 计量单位数量

interval_type

频率间隔的计量单位(minutes、hours、days、weeks、months)

model_id

将在其上调用该动作的模型

code

动作的代码内容。可以是对模型方法的简单调用:

model.<method_name>()
nextcall

该动作的下次计划执行日期(日期/时间格式)

priority

同时执行多个动作时该动作的优先级

编写定时任务函数

运行计划动作时,建议尽量分批推进,以避免长时间占用一个工作进程,进而可能触碰到超时异常。因此,你应将处理过程拆分,使每次调用都能推进一部分待完成的工作。

编写此类函数时,应专注于处理单个批次。一个批次应处理一条或多条记录,通常耗时应不超过 几秒钟。

框架会在每个批次之后提交工作。框架将按需多次调用该函数以处理剩余工作。请勿自行重新调度该任务。

def _cron_do_something(self, *, limit=300):  # limit: allows for tweaking
    domain = [('state', '=', 'ready')]
    records = self.search(domain, limit=limit)
    records.do_something()
    # notify progression
    remaining = 0 if len(records) < limit else self.search_count(domain)
    self.env['ir.cron']._commit_progress(len(records), remaining=remaining)

在某些情况下,你可能需要在多个批次之间共享资源,或自行管理循环以处理异常。此时,你应通过调用 IrCron._commit_progress() 并检查返回值来告知调度器你的工作进度。该进度函数返回本次调用剩余的秒数;如果为 0,则必须尽快返回。

以下示例展示了如何在每处理完一条记录后提交,同时保持连接打开。

def _cron_do_something(self):
    assert self.env.context.get('cron_id'), "Run only inside cron jobs"
    domain = [('state', '=', 'ready')]
    records = self.search(domain)
    self.env['ir.cron']._commit_progress(remaining=len(records))

    with open_some_connection() as conn:
        for record in records:
            # You may have other needs; we do some common stuff here:
            # - lock record (also checks existence)
            # - prefetch: break prefetch in this case, we process one record
            # - filtered_domain: record may have changed
            record = record.try_lock_for_update().filtered_domain(domain)
            if not record:
                continue
            # Processing the batch here...
            try
                record.do_something(conn)
                if not self.env['ir.cron']._commit_progress(1):
                    break
            except Exception:
                # if you handle exceptions, the default stategy is to
                # rollback first the error
                self.env['ir.cron']._rollback_progress()
                _logger.warning(...)
                # you may commit some status using _commit_progress

运行定时任务函数

不应直接调用定时任务函数。运行函数有两种方式:

测试定时任务函数应在注册表测试模式下调用 IrCron.method_direct_trigger() 来完成。

安全

为了在计划动作之间保持资源的公平使用,一些安全措施可确保计划动作的正常运行。

  • 如果计划动作连续三次遇到错误或超时,它将跳过当前执行,并被视为失败。

  • 如果计划动作在至少七天的时间段内连续五次执行失败,它将被停用,并通知数据库管理员。

  • 数据库层面对定时任务的执行设有硬性限制,超过该限制后,执行定时任务的进程将被终止。