构建一个模块

危险

本教程已过时。我们建议阅读 服务器框架 101

警告

本教程需要:doc:已安装 Odoo </administration/on_premise>

启动/停止 Odoo 服务器

Odoo 使用客户端/服务器架构,其中客户端是通过 RPC 访问 Odoo 服务器的网页浏览器。

业务逻辑和扩展通常在服务器端进行,尽管可以在客户端添加支持性功能(例如新的数据表示形式,如交互式地图)。

为了启动服务器,只需在 shell 中调用命令 odoo-bin,如需的话,添加文件的完整路径:

odoo-bin

服务器通过在终端中连续按下两次 Ctrl-C 停止,或者通过终止相应的操作系统进程来停止。

构建一个 Odoo 模块

服务器和客户端扩展都作为 模块 进行打包,这些模块可选择性地在 数据库 中加载。

Odoo 模块可以向 Odoo 系统中添加全新的业务逻辑,也可以修改和扩展现有的业务逻辑:可以创建一个模块,将您国家的会计规则添加到 Odoo 的通用会计支持中,而下一个模块则可以添加对公交车队实时可视化支持。

因此,Odoo 中的一切都始于模块,也终于模块。

模块的组成

一个 Odoo 模块可以包含以下一些元素:

业务对象

声明为 Python 类,这些资源会根据其配置由 Odoo 自动持久化。

对象视图

业务对象的定义及用户界面显示

数据文件

XML 或 CSV 文件,用于声明模型元数据:

Web 控制器

处理来自网页浏览器的请求

静态网页数据

用于网页界面或网站的图片、CSS 或 JavaScript 文件

模块结构

每个模块都是 模块目录 中的一个目录。模块目录通过使用 --addons-path 选项进行指定。

小技巧

大多数命令行选项也可以通过 配置文件 进行设置

一个 Odoo 模块通过其 清单 进行声明。

一个模块也是一个 Python 包,其中包含一个 __init__.py 文件,用于指定模块中各种 Python 文件的导入指令。

例如,如果模块有一个单独的 mymodule.py 文件,__init__.py 可能包含:

from . import mymodule

Odoo 提供了一种机制,用于帮助设置新模块,odoo-bin 具有一个子命令 scaffold,用于创建一个空模块:

$ odoo-bin scaffold <module name> <where to put it>

该命令会为您的模块创建一个子目录,并自动生成模块所需的一组标准文件。其中大部分文件仅包含注释过的代码或 XML。本教程将逐步解释这些文件的使用方法。

Exercise

模块创建

使用上面的命令行创建一个名为“Open Academy”的空模块,并在 Odoo 中安装它。

对象-关系映射

Odoo 的一个重要组件是 ORM(对象-关系映射) 层。该层避免了手动编写大部分 SQL(结构化查询语言),并提供了可扩展性和安全性服务2

业务对象被声明为继承 Model 的 Python 类,这将它们集成到自动持久化系统中。

模型可以通过在其定义中设置多个属性来进行配置。最重要的属性是 _name,它是必填项,用于在 Odoo 系统中定义模型的名称。以下是一个最小完整的模型定义示例:

from odoo import models
class MinimalModel(models.Model):
    _name = 'test.model'

模型字段

字段用于定义模型可以存储什么内容以及存储的位置。字段作为模型类的属性进行定义::

from odoo import models, fields

class LessMinimalModel(models.Model):
    _name = 'test.model2'

    name = fields.Char()

常见属性

就像模型本身一样,其字段也可以通过将配置属性作为参数传递来进行配置::

name = fields.Char(required=True)

以下是一些可用于所有字段的常用属性:

string`(``unicode`,默认:字段名称)

字段在用户界面中的标签(对用户可见)。

required`(``bool`,默认:False

如果为 True,则该字段不能为空,必须要么具有默认值,要么在创建记录时始终提供一个值。

help`(``unicode`,默认值:''

长格式,为用户提供UI中的帮助提示。

index`(``bool`,默认:False

请求 Odoo 在该列上创建 数据库索引

简单字段

有两种主要类型的字段:”简单” 字段,它们是直接存储在模型表中的原子值,以及 “关系” 字段,用于链接记录(可以是同一模型的记录,也可以是不同模型的记录)。

简单字段的示例包括 BooleanDateChar

保留字段

Odoo 在所有模型中会自动创建一些字段 1。这些字段由系统管理,不应直接写入。如果有必要或有用,可以读取它们:

id (Id)

记录在其模型中的唯一标识符。

create_date (日期时间)

记录的创建日期。

create_uid (Many2one)

创建该记录的用户。

write_date (Datetime)

记录的最后修改日期。

write_uid (Many2one)

最后修改该记录的用户。

特殊字段

默认情况下,Odoo 还要求所有模型上有一个 name 字段,用于各种显示和搜索行为。用于这些目的的字段可以通过设置 _rec_name 来重写。

Exercise

定义一个模型

openacademy 模块中定义一个新的数据模型 Course。课程具有标题和描述。课程必须有标题。

数据文件

Odoo 是一个高度依赖数据的系统。尽管行为是通过 Python 代码进行自定义的,但模块的价值部分在于它在加载时设置的数据。

小技巧

一些模块仅用于向 Odoo 添加数据

模块数据通过 数据文件 声明,这些是包含 <record> 元素的 XML 文件。每个 <record> 元素用于创建或更新数据库记录。

<odoo>

        <record model="{model name}" id="{record identifier}">
            <field name="{a field name}">{a value}</field>
        </record>

</odoo>
  • model 是记录的 Odoo 模型名称。

  • id 是一个 外部标识符,它允许引用记录(而无需知道其数据库中的标识符)。

  • <field> 元素有一个 name 属性,该属性表示模型中的字段名称(例如 description)。其内容是字段的值。

数据文件必须在清单文件中声明才能被加载,它们可以声明在 'data' 列表中(始终被加载)或在 'demo' 列表中(仅在演示模式下被加载)。

Exercise

定义演示数据

创建演示数据,向 课程 模型中填充几个演示课程。

小技巧

数据文件的内容仅在安装或更新模块时加载。

在进行一些更改后,请不要忘记使用 odoo-bin -u openacademy 将更改保存到您的数据库中。

操作和菜单

操作和菜单是数据库中的常规记录,通常通过数据文件进行声明。操作可以通过以下三种方式触发:

  1. 通过单击菜单项(链接到特定操作)

  2. 通过点击视图中的按钮(如果这些按钮与操作相关)

  3. 作为对象的上下文操作

由于菜单的声明相对复杂,因此提供了一个 <menuitem> 快捷方式来声明一个 ir.ui.menu,并更轻松地将其与相应的动作关联起来。

<record model="ir.actions.act_window" id="action_list_ideas">
    <field name="name">Ideas</field>
    <field name="res_model">idea.idea</field>
    <field name="view_mode">list,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
          action="action_list_ideas"/>

危险

动作必须在对应的菜单之前在 XML 文件中进行声明。

数据文件是按顺序执行的,动作的 id 必须在数据库中存在之后,才能创建菜单。

Exercise

定义新的菜单项

在 “OpenAcademy” 菜单项下定义新的菜单项,以访问课程。用户应能够:

  • 显示所有课程的列表

  • 创建/修改课程

基础视图

视图定义了模型记录的显示方式。每种类型的视图代表一种可视化模式(例如记录列表、它们的聚合图表等)。视图可以通过其类型(例如“合作伙伴列表”)进行通用请求,也可以通过其 ID 进行特定请求。对于通用请求,将使用具有正确类型且优先级最低的视图(因此,每个类型的最低优先级视图是该类型的默认视图)。

视图继承 允许修改在其他地方声明的视图(添加或删除内容)。

通用视图声明

视图被声明为模型 ir.ui.view 的一条记录。视图类型由 arch 字段的根元素决定:

<record model="ir.ui.view" id="view_id">
    <field name="name">view.name</field>
    <field name="model">object_name</field>
    <field name="priority" eval="16"/>
    <field name="arch" type="xml">
        <!-- view content: <form>, <list>, <graph>, ... -->
    </field>
</record>

危险

视图的内容是 XML。

因此,arch 字段必须声明为 type="xml",以便正确解析。

列表视图

列表视图,也称为列表视图,以表格形式显示记录。

它们的根元素是 <list>。列表视图的最简单形式只是在表格中列出所有要显示的字段(每个字段作为一个列):

<list string="Idea list">
    <field name="name"/>
    <field name="inventor_id"/>
</list>

表单视图

表单用于创建和编辑单个记录。

它们的根元素是 <form>。它们由高层次结构元素(组、笔记本)和交互元素(按钮和字段)组成:

<form string="Idea form">
    <group colspan="4">
        <group colspan="2" col="2">
            <separator string="General stuff" colspan="2"/>
            <field name="name"/>
            <field name="inventor_id"/>
        </group>

        <group colspan="2" col="2">
            <separator string="Dates" colspan="2"/>
            <field name="active"/>
            <field name="invent_date" readonly="1"/>
        </group>

        <notebook colspan="4">
            <page string="Description">
                <field name="description" nolabel="1"/>
            </page>
        </notebook>

        <field name="state"/>
    </group>
</form>

Exercise

使用 XML 自定义表单视图

为 Course 对象创建自定义的表单视图。显示的数据应包括课程名称和课程描述。

Exercise

笔记本

在课程表单视图中,将描述字段放在一个选项卡下,这样以后添加包含其他信息的选项卡会更加方便。

表单视图也可以使用纯 HTML 实现更灵活的布局:

<form string="Idea Form">
    <header>
        <button string="Confirm" type="object" name="action_confirm"
                invisible="state != 'draft'" class="oe_highlight" />
        <button string="Mark as done" type="object" name="action_done"
                invisible="state != 'confirmed'" class="oe_highlight"/>
        <button string="Reset to draft" type="object" name="action_draft"
                invisible="state not in ['confirmed', 'done']" />
        <field name="state" widget="statusbar"/>
    </header>
    <sheet>
        <div class="oe_title">
            <label for="name" class="oe_edit_only" string="Idea Name" />
            <h1><field name="name" /></h1>
        </div>
        <separator string="General" colspan="2" />
        <group colspan="2" col="2">
            <field name="description" placeholder="Idea description..." />
        </group>
    </sheet>
</form>

搜索视图

搜索视图用于自定义与列表视图(以及其他聚合视图)关联的搜索字段。其根元素是 <search>,并且由定义哪些字段可以进行搜索的字段组成:

<search>
    <field name="name"/>
    <field name="inventor_id"/>
</search>

如果该模型没有定义搜索视图,Odoo 会自动生成一个仅允许在 name 字段上进行搜索的视图。

Exercise

搜索课程

允许根据课程标题或描述进行搜索。

模型之间的关系

一个模型中的记录可能与另一个模型中的记录相关联。例如,销售订单记录与包含客户数据的客户记录相关联;它还与对应的销售订单行记录相关联。

Exercise

创建一个会话模型

对于模块 Open Academy,我们考虑一个用于 会话 的模型:会话是指在特定时间针对特定受众进行的课程一次授课。

创建一个用于 会话 的模型。一个会话有名称、开始日期、持续时间和座位数。添加一个动作和一个菜单项以显示它们。通过菜单项使新模型可见。

关系字段

关系字段用于链接记录,可以是同一模型的记录(层级结构),也可以是不同模型之间的记录。

关系字段类型包括:

Many2one(其他模型, ondelete='设为 null')

一个指向其他对象的简单链接:

print(foo.other_id.name)

另请参见

外键

One2many(其他模型, 关联字段)

一个虚拟关系,是 Many2one 的反向关系。One2many 表现为记录的容器,访问它将返回一个(可能为空)的记录集合:

for other in foo.other_ids:
    print(other.name)

危险

由于 One2many 是一个虚拟关系,因此在 other_model必须 存在一个 Many2one 字段,且其名称 必须related_field

Many2many(其他模型)

双向多对多关系,任一侧的记录都可以与另一侧的任意数量的记录相关联。它表现为一个记录的容器,访问它将返回一个可能为空的记录集合:

for other in foo.other_ids:
    print(other.name)

Exercise

许多对一关系

使用 many2one 字段,修改 课程会话 模型,以反映它们与其他模型的关系:

  • 一门课程有一个*负责人*用户;该字段的值是内置模型 res.users 的一条记录。

  • 一个会话有一个 讲师;该字段的值是内置模型 res.partner 的一条记录。

  • 一个会话与一个 课程 相关联;该字段的值是模型 openacademy.course 的一条记录,并且是必填项。

  • 调整视图。

Exercise

反向一对多关系

使用反向关系字段 one2many,修改模型以反映课程和会话之间的关系。

Exercise

多对多关系

使用关系字段 many2many,修改 会话 模型,使其与一组 参与者 相关联。参与者将由业务伙伴记录表示,因此我们将与内置模型 res.partner 进行关联。相应地调整视图。

继承

模型继承

Odoo 提供了两种 继承 机制,以模块化的方式扩展现有模型。

第一个继承机制允许一个模块修改另一个模块中定义的模型的行为:

  • 向模型中添加字段,

  • 在模型上覆盖字段的定义,

  • 向模型添加约束条件,

  • 向模型添加方法,

  • 覆盖模型上的现有方法。

第二种继承机制(委托)允许将模型中的每条记录与父模型中的一条记录关联,并提供对父记录字段的透明访问。

../../_images/inheritance_methods1.png

另请参见

  • _inherit

  • _inherits

视图继承

与其直接修改现有视图(通过覆盖它们),Odoo 提供了视图继承机制,其中子“扩展”视图会应用在根视图之上,可以向其父视图添加或删除内容。

扩展视图使用 inherit_id 字段引用其父视图,与其父视图的 arch 字段不同,它由任意数量的 xpath 元素组成,用于选择并修改其父视图的内容:

<!-- improved idea categories list -->
<record id="idea_category_list2" model="ir.ui.view">
    <field name="name">id.category.list2</field>
    <field name="model">idea.category</field>
    <field name="inherit_id" ref="id_category_list"/>
    <field name="arch" type="xml">
        <!-- find field description and add the field
             idea_ids after it -->
        <xpath expr="//field[@name='description']" position="after">
          <field name="idea_ids" string="Number of ideas"/>
        </xpath>
    </field>
</record>
表达式

一个 XPath 表达式,用于在父视图中选择单个元素。如果它不匹配任何元素或匹配多个元素,则会引发错误。

位置

要应用到匹配元素上的操作:

内部

在匹配元素的末尾追加 xpath 的内容

替换

xpath 的内容替换匹配的元素,将新内容中的任何 $0 节点实例替换为原始元素

before

在匹配元素之前插入 xpath 的内容作为同级元素

在匹配元素之后,将 xpaths 的内容作为同级元素插入

属性

修改匹配元素的属性,使用 xpath 正文中的特殊 attribute 元素

小技巧

当匹配单个元素时,可以将 position 属性直接设置在要查找的元素上。下面的两种继承方式将产生相同的结果。

<xpath expr="//field[@name='description']" position="after">
    <field name="idea_ids" />
</xpath>

<field name="description" position="after">
    <field name="idea_ids" />
</field>

Exercise

修改现有内容

  • 使用模型继承,修改现有的 业务伙伴 模型,以添加一个 instructor 布尔字段,以及一个对应于会话-业务伙伴关系的多对多字段。

  • 使用视图继承,在业务伙伴表单视图中显示这些字段

在 Odoo 中,搜索条件 是用于对记录进行条件编码的值。域是一组用于选择模型记录子集的标准列表。每个标准是一个三元组,包含字段名、操作符和值。

例如,当在 产品 模型上使用时,以下域会选择所有 服务 且单价超过 1000 的记录:

[('product_type', '=', 'service'), ('unit_price', '>', 1000)]

默认情况下,条件是通过隐式的 AND 进行组合的。可以使用逻辑运算符 ``&``(AND)、``|``(OR)和 ``!``(NOT)显式地组合条件。它们以前缀形式使用(运算符插入到其操作数之前,而不是中间)。例如,要选择“是服务 单价 不在 1000 到 2000 之间的产品”:

['|',
    ('product_type', '=', 'service'),
    '!', '&',
        ('unit_price', '>=', 1000),
        ('unit_price', '<', 2000)]

可以向关系字段添加一个 domain 参数,以在客户端界面尝试选择记录时限制有效记录。

Exercise

关系字段上的域

当为一个 会话 选择讲师时,只有讲师(将 instructor 设置为 True 的合作伙伴)才应可见。

Exercise

更复杂的域

创建新的业务伙伴分类 教师 / 一级教师 / 二级。会话的讲师可以是讲师或教师(任何级别)。

计算字段和默认值

到目前为止,字段都是直接存储在数据库中并直接从数据库中检索的。字段也可以是 计算 的。在这种情况下,字段的值不是从数据库中检索的,而是通过调用模型的方法实时计算出来的。

要创建一个计算字段,需要创建一个字段并将它的属性 compute 设置为一个方法的名称。计算方法应简单地在 self 中的每条记录上设置要计算的字段的值。

危险

self 是一个集合

对象 self 是一个 记录集,即一组有序的记录。它支持标准的 Python 集合操作,如 len(self)iter(self),以及额外的集合操作,如 recs1 + recs2

遍历 self 会逐个获取记录,其中每条记录本身是一个大小为 1 的集合。你可以通过点号表示法访问/赋值单条记录的字段,例如 record.name

import random
from odoo import models, fields, api

class ComputedModel(models.Model):
    _name = 'test.computed'

    name = fields.Char(compute='_compute_name')

    def _compute_name(self):
        for record in self:
            record.name = str(random.randint(1, 1e6))

依赖项

计算字段的值通常取决于计算记录上其他字段的值。ORM 要求开发人员通过装饰器 depends() 在计算方法中指定这些依赖关系。给定的依赖关系由 ORM 使用,以便在某些依赖项发生更改时触发该字段的重新计算:

from odoo import models, fields, api

class ComputedModel(models.Model):
    _name = 'test.computed'

    name = fields.Char(compute='_compute_name')
    value = fields.Integer()

    @api.depends('value')
    def _compute_name(self):
        for record in self:
            record.name = "Record with value %s" % record.value

Exercise

计算字段

  • 会话 模型中添加已占用座位的百分比

  • 在列表视图和表单视图中显示该字段

  • 以进度条形式显示字段

默认值

任何字段都可以设置默认值。在字段定义中,添加选项 default=X,其中 X 可以是 Python 字面量值(布尔值、整数、浮点数、字符串),或者是一个接收记录集并返回值的函数:

name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)

注解

对象 self.env 提供对请求参数和其他有用内容的访问:

  • self.env.crself._cr 是数据库 游标 对象;它用于查询数据库

  • self.env.uidself._uid 是当前用户的数据库 ID

  • self.env.user 是当前用户的记录

  • self.env.contextself._context 是上下文字典

  • self.env.ref(xml_id) 返回与 XML ID 对应的记录

  • self.env[model_name] 返回给定模型的实例

Exercise

激活对象 – 默认值

  • 将 start_date 的默认值定义为今天(参见 Date)。

  • 在类 Session 中添加字段 active,并将会话默认设置为激活状态。

更改时

“onchange” 机制提供了一种方式,使客户端界面在用户填写字段值时能够更新表单,而无需将任何内容保存到数据库中。

例如,假设一个模型有三个字段 amountunit_priceprice,您希望在表单中任何其他字段被修改时更新价格。要实现此功能,请定义一个方法,其中 self 表示表单视图中的记录,并使用 onchange() 装饰器来指定在哪个字段上触发。对 self 所做的任何更改都会反映在表单中。

<!-- content of form view -->
<field name="amount"/>
<field name="unit_price"/>
<field name="price" readonly="1"/>
# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
    # set auto-changing field
    self.price = self.amount * self.unit_price
    # Can optionally return a warning and domains
    return {
        'warning': {
            'title': "Something bad happened",
            'message': "It was very bad indeed",
        }
    }

对于计算字段,onchange 行为是内置的,可以通过操作 会话 表单来观察:更改座位数或参与者数量,taken_seats 进度条会自动更新。

Exercise

警告

为无效值添加显式的 onchange 以发出警告,例如座位数为负数,或参与人数超过座位数。

模型约束

Odoo 提供了两种方式来设置自动验证的不变量:Python 约束SQL 约束

一个 Python 约束被定义为一个使用 constrains() 装饰器修饰的方法,并在记录集上被调用。装饰器指定了约束涉及的字段,这样当其中任何一个字段被修改时,该约束会自动被评估。如果该约束的不变性条件不满足,该方法应抛出异常:

from odoo.exceptions import ValidationError

@api.constrains('age')
def _check_something(self):
    for record in self:
        if record.age > 20:
            raise ValidationError("Your record is too old: %s" % record.age)
    # all records passed the test, don't return anything

Exercise

添加 Python 约束

添加一个约束条件,用于检查讲师不能出现在其本人会话的参与者中。

SQL 约束是通过模型属性 _sql_constraints 定义的。后者被赋值为一个由三个字符串组成的元组列表 (name, sql_definition, message), 其中 name 是一个有效的 SQL 约束名称,sql_definition 是一个 table_constraint 表达式,而 message 是错误消息。

Exercise

添加 SQL 约束

借助 PostgreSQL 的文档,添加以下约束:

  1. 检查课程描述和课程标题是否不同

  2. 将课程名称设为唯一

Exercise

练习 6 - 添加重复选项

由于我们为课程名称添加了唯一性约束,现在无法再使用“复制”功能 (表单 ‣ 复制)。

重新实现您自己的“复制”方法,该方法允许复制课程对象,并将原始名称更改为“[原始名称] 的副本”。

高级视图

列表视图

列表视图可以采用补充属性来进一步自定义其行为:

装饰-{$name}

允许根据相应记录的属性更改行中文本的样式。

值是 Python 表达式。对于每条记录,表达式会以记录的属性作为上下文值进行求值,如果结果为 true,则会将相应的样式应用到该行。以下是上下文中可用的其他一些值:

  • uid: 当前用户的ID,

  • today:当前本地日期,格式为 YYYY-MM-DD 的字符串,

  • now:与 today 相同,但增加了当前时间。此值的格式为 YYYY-MM-DD hh:mm:ss

{$name} 可以是 bf``(``font-weight: bold)、it``(``font-style: italic),或任何 Bootstrap 上下文颜色 <https://getbootstrap.com/docs/3.3/components/#available-variations>`_(``danger`infomutedprimarysuccesswarning)。

<list string="Idea Categories" decoration-info="state=='draft'"
    decoration-danger="state=='trashed'">
    <field name="name"/>
    <field name="state"/>
</list>
可编辑

可以是 "top""bottom"。使列表视图支持就地编辑(而不是必须通过表单视图),该值表示新行出现的位置。

Exercise

列表着色

修改会话列表视图,使得持续时间少于 5 天的会话显示为蓝色,持续时间超过 15 天的会话显示为红色。

日历

以日历事件的形式显示记录。其根元素为 <calendar>,最常见的属性包括:

颜色

用于*颜色分段*的字段名称。颜色会自动分配给事件,但同一颜色分段中的事件(其 @color 字段值相同的记录)将被赋予相同的颜色。

日期开始

记录中用于保存事件开始日期/时间的字段

date_stop (可选)

记录中保存事件结束日期/时间的字段

字符串

记录的字段,用于定义每个日历事件的标签

<calendar string="Ideas" date_start="invent_date" color="inventor_id">
    <field name="name"/>
</calendar>

Exercise

日历视图

会话 模型中添加一个日历视图,使用户能够查看与 Open Academy 相关的事件。

搜索视图

搜索视图 <field> 元素可以有一个 @filter_domain,该属性会覆盖在给定字段上进行搜索时生成的域。在给定的域中,self 表示用户输入的值。在下面的示例中,它用于在字段 namedescription 上进行搜索。

搜索视图还可以包含 <filter> 元素,这些元素作为预定义搜索的切换开关。筛选器必须具有以下属性之一:

将给定的域添加到当前搜索中

上下文

在当前搜索中添加一些上下文;使用键 group_by 按给定字段名对结果进行分组

<search string="Ideas">
    <field name="name"/>
    <field name="description" string="Name and description"
           filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
    <field name="inventor_id"/>
    <field name="country_id" widget="selection"/>

    <filter name="my_ideas" string="My Ideas"
            domain="[('inventor_id', '=', uid)]"/>
    <group string="Group By">
        <filter name="group_by_inventor" string="Inventor"
                context="{'group_by': 'inventor_id'}"/>
    </group>
</search>

要在动作中使用非默认的搜索视图,应通过动作记录的 search_view_id 字段进行关联。

该动作还可以通过其 context 字段为搜索字段设置默认值:形式为 search_default_field_name 的上下文键将使用提供的值初始化 field_name。搜索过滤器必须具有可选的 @name 才能设置默认值并作为布尔值使用(它们只能默认启用)。

Exercise

搜索视图

  1. 在课程搜索视图中添加一个按钮,用于筛选当前用户为负责人的课程。默认情况下选中该按钮。

  2. 在按负责人用户分组的课程上添加一个按钮。

甘特图

警告

甘特图视图需要 web_gantt 模块,该模块存在于 企业版 版本中。

横向条形图通常用于显示项目计划和进展,其根元素是 <gantt>

<gantt string="Ideas"
       date_start="invent_date"
       date_stop="date_finished"
       progress="progress"
       default_group_by="inventor_id" />

Exercise

甘特图

添加一个甘特图,使用户能够查看与 Open Academy 模块相关联的课程安排。课程应按讲师进行分组。

图表视图

图表视图允许对模型进行汇总概览和分析,其根元素是 <graph>

注解

透视表视图(元素 <pivot>)是一个多维表格,允许选择过滤器和维度,以获取正确的聚合数据集,然后再进入更图形化的概览。透视表视图与图表视图使用相同的内容定义。

图表视图有 4 种显示模式,默认模式通过 @type 属性进行选择。

栏(默认)

一个柱状图,第一个维度用于在水平轴上定义用户组,其他维度则在每个用户组内定义聚合的柱状图。

默认情况下,条形是并排显示的,可以通过在 <graph> 上使用 @stacked="True" 来实现堆叠显示。

二维折线图

饼图

二维饼图

图表视图包含带有必填 @type 属性的 <field>,其取值为:

(默认)

该字段默认应进行聚合

测量

该字段应进行聚合而非分组

<graph string="Total idea score by Inventor">
    <field name="inventor_id"/>
    <field name="score" type="measure"/>
</graph>

警告

图表视图对数据库值进行聚合,它们不处理非存储的计算字段。

Exercise

图表视图

在 Session 对象中添加一个图表视图,以柱状图的形式显示每个课程的参与人数。

看板

用于组织任务、生产流程等……其根元素是 <kanban>

看板视图显示一组可能按列分组的卡片。每张卡片代表一条记录,每列表示聚合字段的值。

例如,项目任务可以按阶段组织(每一列代表一个阶段),或按负责人组织(每一列代表一个用户),依此类推。

看板视图定义了每张卡片的结构,该结构是表单元素(包括基本 HTML)和 QWeb 模板 的组合。

Exercise

看板视图

添加一个看板视图,用于按课程显示会话(列即为课程)。

安全

访问控制机制必须进行配置,以实现一致的安全策略。

基于用户组的访问控制机制

组作为普通记录创建在模型 res.groups 上,并通过菜单定义授予菜单访问权限。然而,即使没有菜单,对象仍可能通过其他方式被访问,因此必须为组定义实际的对象级权限(读取、写入、创建、删除)。这些权限通常通过模块内的 CSV 文件插入。也可以通过字段的 groups 属性来限制对视图或对象上特定字段的访问。

访问权限

访问权限是通过模型 ir.model.access 的记录来定义的。每条访问权限都与一个模型、一个用户组(或无用户组以实现全局访问)以及一组权限(读取、写入、创建、删除)相关联。此类访问权限通常通过一个以对应模型命名的 CSV 文件创建:ir.model.access.csv

id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_idea_idea,idea.idea,model_idea_idea,base.group_user,1,1,1,0
access_idea_vote,idea.vote,model_idea_vote,base.group_user,1,1,1,0

Exercise

通过 Odoo 界面添加访问控制

创建一个新用户 “John Smith”。然后创建一个用户组 “OpenAcademy / Session Read”,该组对 Session 模型具有访问权限。

Exercise

在您的模块中通过数据文件添加访问控制

使用数据文件,

  • 创建一个用户组 OpenAcademy / 管理员,拥有对所有 OpenAcademy 模型的完全访问权限

  • 会话课程 设置为所有用户可读

记录规则

一条记录规则限制对给定模型中部分记录的访问权限。规则是模型 ir.rule 的一条记录,并与一个模型、多个组(多对多字段)、该限制适用的权限以及一个域相关联。域指定了访问权限所限制的记录范围。

以下是一个示例规则,用于防止删除状态不是 cancel 的线索。请注意,字段 groups 的值必须遵循与 ORM 中方法 write() 相同的约定。

<record id="delete_cancelled_only" model="ir.rule">
    <field name="name">Only cancelled leads may be deleted</field>
    <field name="model_id" ref="crm.model_crm_lead"/>
    <field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
    <field name="perm_read" eval="0"/>
    <field name="perm_write" eval="0"/>
    <field name="perm_create" eval="0"/>
    <field name="perm_unlink" eval="1" />
    <field name="domain_force">[('state','=','cancel')]</field>
</record>

Exercise

记录规则

为模型 “Course” 和用户组 “OpenAcademy / Manager” 添加一条记录规则,该规则限制对课程负责人的 writeunlink 访问权限。如果某门课程没有负责人,则该组的所有用户都必须能够对其进行修改。

向导

向导通过动态表单描述与用户的交互会话(或对话框)。向导本质上是一个继承自 TransientModel 类的模型,而不是继承自 Model 类。TransientModel 类继承自 Model 类,并复用其所有现有机制,具有以下特点:

  • 向导记录并非用于持久化存储;它们在一段时间后会从数据库中自动删除。这就是它们被称为 临时 的原因。

  • 向导记录可以通过关系字段(如 many2one 或 many2many)引用普通记录或向导记录,但普通记录不能通过 many2one 字段引用向导记录。

我们想要创建一个向导,允许用户为特定会话创建与会者,或一次性为多个会话创建与会者。

Exercise

定义向导

创建一个向导模型,该模型与 会话 模型具有 many2one 关系,并与 业务伙伴 模型具有 many2many 关系。

启动向导

向导只是将 target 字段设置为 new 值的 窗口动作,这会在单独的对话框中打开视图(通常是 表单)。该动作可以通过菜单项触发,但更常见的是通过按钮触发。

另一种启动向导的方式是通过列表或表单视图中的 动作 菜单。这是通过动作的 binding_model_id 字段实现的。设置此字段将使该动作出现在该动作“绑定”到的模型的视图中。

<record id="launch_the_wizard" model="ir.actions.act_window">
    <field name="name">Launch the Wizard</field>
    <field name="res_model">wizard.model.name</field>
    <field name="view_mode">form</field>
    <field name="target">new</field>
    <field name="binding_model_id" ref="model_context_model_ref"/>
</record>

小技巧

虽然向导使用常规视图和按钮,但通常在表单中点击任何按钮都会先保存表单,然后关闭对话框。由于在向导中这通常是不希望的,因此提供了一个特殊的属性 special="cancel",该属性会立即关闭向导而不会保存表单。

Exercise

启动向导

  1. 为向导定义一个表单视图。

  2. 会话 模型的上下文中添加用于启动它的动作。

  3. 在向导中为会话字段定义默认值;使用上下文参数 self._context 获取当前会话。

Exercise

注册参与者

在向导中添加按钮,并实现相应的方法,将参与者添加到指定的会话中。

Exercise

注册多个会话的参与者

修改向导模型,以便可以将参与者注册到多个会话。

国际化

每个模块可以在 i18n 目录中提供自己的翻译,方法是创建名为 LANG.po 的文件,其中 LANG 是语言的区域代码,或者在语言和国家/地区不同时使用语言和国家/地区的组合(例如 pt.po 或 pt_BR.po)。Odoo 会自动为所有启用的语言加载这些翻译。开发人员在创建模块时始终使用英文,然后通过 Odoo 的 gettext POT 导出功能(设置 ‣ 翻译 ‣ 导入/导出 ‣ 导出翻译,不指定语言)导出模块术语,以创建模块模板 POT 文件,然后生成已翻译的 PO 文件。许多 IDE 都有用于编辑和合并 PO/POT 文件的插件或模式。

小技巧

由 Odoo 生成的可移植对象文件发布在 Transifex 上,使得翻译软件变得非常容易。

|- idea/ # The module directory
   |- i18n/ # Translation files
      | - idea.pot # Translation Template (exported from Odoo)
      | - fr.po # French translation
      | - pt_BR.po # Brazilian Portuguese translation
      | (...)

小技巧

默认情况下,Odoo 的 POT 导出仅提取 XML 文件中的标签或 Python 代码中字段定义的标签,但可以通过将任何 Python 字符串用函数 odoo._() 包裹起来(例如 _("Label"))来实现该字符串的翻译。

Exercise

将一个模块翻译为中文

选择一个用于您 Odoo 安装的第二种语言。使用 Odoo 提供的工具来翻译您的模块。

报告

打印报表

Odoo 使用基于 QWeb 模板Twitter BootstrapWkhtmltopdf 的报表引擎。

报表是两个元素的组合:

  • 一个 ir.actions.report,用于配置报表的各种基本参数(默认类型、报表生成后是否应保存到数据库中,…)

    <record id="account_invoices" model="ir.actions.report">
        <field name="name">Invoices</field>
        <field name="model">account.invoice</field>
        <field name="report_type">qweb-pdf</field>
        <field name="report_name">account.report_invoice</field>
        <field name="report_file">account.report_invoice</field>
        <field name="attachment_use" eval="True"/>
        <field name="attachment">(object.state in ('open','paid')) and
            ('INV'+(object.number or '').replace('/','')+'.pdf')</field>
        <field name="binding_model_id" ref="model_account_invoice"/>
        <field name="binding_type">report</field>
    </record>
    

    小技巧

    由于它是一个标准动作,就像 向导 中所述,通常有用的是通过 binding_model_id 字段将报表作为 上下文项 添加到被报告模型的列表和/或表单视图中。

    在这里,我们还使用 binding_type 以便报表出现在 报表 上下文菜单中,而不是 动作 菜单中。虽然在技术上没有区别,但将元素放在正确的位置有助于用户使用。

  • 一个用于实际报表的标准 QWeb 视图

    <t t-call="web.html_container">
        <t t-foreach="docs" t-as="o">
            <t t-call="web.external_layout">
                <div class="page">
                    <h2>Report title</h2>
                </div>
            </t>
        </t>
    </t>
    

    标准渲染上下文提供了一些元素,其中最重要的是:

    文档

    打印报表的记录

    用户

    用户打印报表

由于报表是标准的网页,它们可以通过网址访问,并且可以通过该网址操作输出参数。例如,发票 报表的 HTML 版本可通过 http://localhost:8069/report/html/account.report_invoice/1 访问(如果已安装 account 模块),而 PDF 版本则通过 http://localhost:8069/report/pdf/account.report_invoice/1 访问。

危险

如果您的 PDF 报表似乎缺少样式(即文本显示但样式/布局与 HTML 版本不同),可能是您的 wkhtmltopdf 进程无法访问您的 Web 服务器以下载这些样式。

如果你在查看服务器日志时发现生成 PDF 报表时 CSS 样式无法被下载,这很可能是问题所在。

wkhtmltopdf 进程将使用 web.base.url 系统参数作为所有链接文件的 根路径,但此参数在管理员每次登录时会自动更新。如果您的服务器位于某种代理之后,可能会无法访问。您可以通过添加以下其中一个系统参数来解决此问题:

  • 报表.网址,指向您的服务器可访问的网址(可能是 http://localhost:8069 或类似内容)。它仅用于此特定用途。

  • web.base.url.freezeTrue 时,将停止对 web.base.url 的自动更新。

Exercise

为 Session 模型创建报表

对于每个会话,应显示会话名称、开始和结束时间,并列出会话的参与者。

仪表盘

Exercise

定义仪表板

创建一个包含你所创建的图表视图、课程会话日历视图以及课程列表视图(可切换为表单视图)的仪表板。该仪表板应通过菜单中的菜单项可用,并在选择 OpenAcademy 主菜单时在网页客户端中自动显示。

1

可以 禁用某些字段的自动创建

2

编写原始 SQL 查询是可能的,但需要谨慎,因为它会绕过所有 Odoo 身份验证和安全机制。