构建模块

危险

本教程已过时。我们建议阅读 服务器框架 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)

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

string (unicode, default: field’s name)

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

required (bool, default: False)

If True, the field can not be empty, it must either have a default value or always be given a value when creating a record.

help (unicode, default: '')

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

index (bool, default: False)

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

简单字段

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

Example of simple fields are Boolean, Date, Char.

保留字段

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

idId

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

create_dateDatetime

记录的创建日期。

create_uidMany2one

创建记录的用户。

write_dateDatetime

记录的最后修改日期。

write_uidMany2one

最后修改记录的用户。

特殊字段

默认情况下,Odoo 还要求所有模型都包含一个 name 字段,用于各种显示和搜索行为。用于此目的的字段可以通过设置 _rec_name 来覆盖。

Exercise

定义模型

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

数据文件

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

小技巧

一些模块的存在仅仅是为了向 Odoo 添加数据。

Module data is declared via data files, XML files with <record> elements. Each <record> element creates or updates a database record.

<odoo>

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

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

  • id is an external identifier, it allows referring to the record (without having to know its in-database 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)

危险

Because a One2many is a virtual relationship, there must be a Many2one field in the {other_model}, and its name must be {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

The object self is a recordset, i.e., an ordered collection of records. It supports the standard Python operations on collections, like len(self) and iter(self), plus extra set operations like 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 ,并将会话默认设置为活动状态。

字段变化触发机制

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

For instance, suppose a model has three fields amount, unit_price and price, and you want to update the price on the form when any of the other fields is modified. To achieve this, define a method where self represents the record in the form view, and decorate it with onchange() to specify on which field it has to be triggered. Any change you make on self will be reflected on the form.

<!-- 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 约束 。类似地,您可以添加更复杂的 SQL 索引

A Python constraint is defined as a method decorated with constrains(), and invoked on a recordset. The decorator specifies which fields are involved in the constraint, so that the constraint is automatically evaluated when one of them is modified. The method is expected to raise an exception if its invariant is not satisfied:

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 约束

添加一个约束,检查讲师是否未出现在其自身会话的参与者中。

约束和索引使用以下定义:ConstraintIndexUniqueIndex

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} can be bf (font-weight: bold), it (font-style: italic), or any bootstrap contextual color (danger, info, muted, primary, success or warning).

<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

看板视图

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

安全性

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

基于组的访问控制机制

Groups are created as normal records on the model res.groups, and granted menu access via menu definitions. However even without a menu, objects may still be accessible indirectly, so actual object-level permissions (read, write, create, unlink) must be defined for groups. They are usually inserted via CSV files inside modules. It is also possible to restrict access to specific fields on a view or object using the field’s groups attribute.

访问权限

访问权限被定义为模型 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

记录规则

A record rule restricts the access rights to a subset of records of the given model. A rule is a record of the model ir.rule, and is associated to a model, a number of groups (many2many field), permissions to which the restriction applies, and a domain. The domain specifies to which records the access rights are limited.

以下是一个规则示例,该规则防止删除状态不为 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 关系。

启动向导

Wizards are simply window actions with a target field set to the value new, which opens the view (usually a form) in a separate dialog. The action may be triggered via a menu item, but is more generally triggered by a button.

另一种启动向导的方式是通过列表或表单视图的 操作 菜单。这是通过动作的 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 生成的便携对象文件发布在 Odoo 翻译平台 上,使得翻译软件变得容易。

|- 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 uses a report engine based on QWeb 模板, Twitter Bootstrap and Wkhtmltopdf.

一个报表由两个元素组成:

  • 一个 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, pointing to an URL reachable from your server (probably http://localhost:8069 or something similar). It will be used for this particular purpose only.

  • web.base.url.freeze, when set to True, will stop the automatic updates to web.base.url.

Exercise

为 Session 模型创建报告

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

仪表板

Exercise

定义仪表板

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

1

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

2

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