构建模块

危险

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

警告

本教程需要 安装 Odoo

启动/停止 Odoo 服务器

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

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

要启动服务器,只需在终端中调用命令 odoo-bin,并在必要时添加文件的完整路径:

odoo-bin

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

构建一个 Odoo 模块

服务器和客户端扩展都打包为 模块,它们可以选择性地加载到 数据库 中。

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

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

模块的组成

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

业务对象

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

对象视图

业务对象 UI 显示的定义

数据文件

声明模型元数据的 XML 或 CSV 文件:

Web 控制器

处理来自 Web 浏览器的请求

静态 Web 数据

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)

一些属性适用于所有字段,以下是其中最常见的几个:

stringunicode ,默认值:字段名称)

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

requiredbool ,默认值: False

如果为 True ,该字段不能为空,必须具有默认值或在创建记录时始终赋予一个值。

helpunicode ,默认值: ''

长格式描述,在用户界面中向用户提供帮助提示。

indexbool ,默认值: False

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

简单字段

字段分为两大类:“简单字段”,即直接存储在模型表中的原子值;“关系字段”,即链接记录(同一模型或不同模型的记录)。

简单字段的示例包括 BooleanDateChar

保留字段

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

idId

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

create_dateDatetime

记录的创建日期。

create_uidMany2one

创建记录的用户。

write_dateDatetime

记录的最后修改日期。

write_uidMany2one

最后修改记录的用户。

特殊字段

默认情况下,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 是一个 external identifier ,它允许引用该记录(无需知道其数据库内部标识符)。

  • <field> 元素具有一个 name,它是模型中的字段名称(例如 description)。其主体是字段的值。

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

Exercise

定义演示数据

创建演示数据,用一些演示课程填充 Courses 模型。

小技巧

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

在做出一些更改后,别忘了使用 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 自定义表单视图

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

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='set 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

多对一关系

使用多对一关系,修改 课程会话 模型以反映它们与其他模型的关系:

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

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

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

  • 调整视图。

Exercise

反向一对多关系

使用反向关系字段一对多,修改模型以反映课程与会话之间的关系。

Exercise

多个多对多关系

使用多对多关系字段,修改 Session 模型,将每个会话与一组 参与者 关联起来。参与者将由合作伙伴记录表示,因此我们将关联到内置模型 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>
expr

一个 XPath 表达式,用于选择父视图中的单个元素。如果没有匹配到元素或匹配到多个元素,则会引发错误。

位置

应用于匹配元素的操作:

inside

xpath 的主体追加到匹配元素的末尾

replace

xpath 的主体替换匹配的元素,并将新主体中的任何 $0 节点替换为原始元素

before

xpath 的主体作为匹配元素的前一个兄弟节点插入

after

xpaths 的主体作为匹配元素的后一个兄弟节点插入

attributes

使用 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

修改现有内容

  • 使用模型继承,修改现有的 Partner 模型,添加一个布尔字段 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

关系字段上的域

在为 Session 选择讲师时,只有讲师(instructor 设置为 True 的合作伙伴)应可见。

Exercise

更复杂的域

创建新的合作伙伴类别 Teacher / Level 1Teacher / Level 2。会话的讲师可以是讲师或任何级别的教师。

计算字段和默认值

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

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

危险

self is a collection

对象 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

计算字段

  • 将占用座位的百分比添加到 Session 模型中

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

  • 以进度条形式显示该字段

默认值

任何字段都可以赋予默认值。在字段定义中,添加选项 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,并将会话默认设置为活动状态。

字段变化触发机制

“字段变化触发机制” 提供了一种方式,允许在用户填写字段值时更新表单,而无需将任何内容保存到数据库。

例如,假设一个模型有三个字段 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",
        }
    }

对于计算字段,值变化触发行为是内置的,可以通过操作 会话 表单观察到:更改座位数或参与者数量后,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_definitiontable_constraint 表达式,message 是错误消息。

Exercise

添加 SQL 约束

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

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

  2. 使课程名称唯一

Exercise

练习 6 - 添加复制选项

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

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

高级视图

列表视图

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

decoration-{$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>
editable

可以是 "top""bottom"。使列表视图可直接编辑(无需通过表单视图),该值表示新行出现的位置。

Exercise

列表着色

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

日历视图

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

color

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

date_start

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

date_stop (optional)

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

字符串

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

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

Exercise

日历视图

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

搜索视图

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

搜索视图还可以包含 <filter> 元素,这些元素充当预定义搜索的开关。过滤器必须具有以下属性之一:

domain

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

context

为当前搜索添加上下文;使用键 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>,其值为:

row (默认)

该字段默认应被聚合

measure

该字段应被聚合而不是分组

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

警告

图表视图对数据库值执行聚合操作,它们不适用于未存储的计算字段。

Exercise

图表视图

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

看板

用于组织任务、生产流程等,其根元素是 <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 模型具有读取权限。

Exercise

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

使用数据文件,

  • 创建一个组 OpenAcademy / 管理员,该组对所有 OpenAcademy 模型具有完全访问权限。

  • 使所有用户都能读取 SessionCourse

记录规则

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

以下是一个规则示例,该规则防止删除状态不为 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 / 管理员”添加一条记录规则,限制对课程负责人的 writeunlink 访问。如果课程没有负责人,则组中的所有用户都应能够修改它。

向导

向导通过动态表单描述与用户的交互会话(或对话框)。向导只是一个扩展了 TransientModel 类而非 Model 类的模型。TransientModel 类扩展了 Model 并重用了其所有现有机制,具有以下特点:

  • 向导记录并非设计为持久化存储;它们会在一定时间后自动从数据库中删除。这就是为什么它们被称为 临时 记录。

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

我们希望创建一个向导,允许用户为特定会话或会话列表一次性创建参与者。

Exercise

定义向导

创建一个向导模型,与 Session 模型建立 many2one 关系,并与 Partner 模型建立 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 生成的 Portable Object 文件发布在 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>
    

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

    docs

    打印报表所涉及的记录

    user

    打印报表的用户

由于报表是标准网页,它们可以通过 URL 访问,并且可以通过该 URL 操作输出参数。例如,发票 报表的 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 系统参数作为所有链接文件的 根路径,但每次管理员登录时,此参数都会自动更新。如果您的服务器位于某种代理之后,可能会导致不可访问。您可以通过添加以下系统参数之一来解决此问题:

  • report.url,指向服务器可访问的 URL(可能是 http://localhost:8069 或类似地址)。它仅用于此特定目的。

  • 当设置为 True 时,web.base.url.freeze 将停止对 web.base.url 的自动更新。

Exercise

为 Session 模型创建报告

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

仪表板

Exercise

定义仪表板

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

1

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

2

编写原始 SQL 查询是可行的,但由于它绕过了 Odoo 的所有身份验证和安全机制,因此需要谨慎处理。