构建一个模块

危险

本教程已过时。我们建议阅读 开始 代替。

警告

This tutorial requires having installed Odoo

启动/停止Odoo服务器

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

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

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

odoo-bin

停止服务器的方法是从终端中按两次 Ctrl-C ,或者杀死相应的操作系统进程。

构建一个Odoo模块

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

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

Odoo 中的一切都始于模块,也以模块结束。

模块的组成

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

业务对象

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

Object views

业务对象UI显示的定义

Data files

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

Web controllers

处理来自Web浏览器的请求

静态网页数据

网站或 Web 界面使用的图片、CSS 或 JavaScript 文件

模块结构

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

小技巧

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

一个Odoo模块通过其 manifest 声明。

模块也是一个 Python package,它包含一个 __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)

如果 True,字段不能为空,它必须要么有一个默认值,要么在创建记录时始终给定一个值。

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

长格式,为用户在UI中提供帮助工具提示。

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

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

简单字段

有两种广泛的字段类别:”简单”字段是直接存储在模型表中的原子值,而”关系”字段则链接记录(同一模型或不同模型的记录)。

简单字段的示例有 Boolean, Date, Char.

保留字段

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

id (Id)

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

create_date (Datetime)

记录创建日期。

create_uid (Many2one)

创建记录的用户。

write_date (Datetime)

记录的最后修改日期。

write_uid (Many2one)

最后修改记录的用户。

特殊字段

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

Exercise

定义一个模型

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

数据文件

Odoo是一个高度数据驱动的系统。虽然行为是使用Python_代码定制的,但模块的价值部分在于加载时设置的数据。

小技巧

一些模块仅用于将数据添加到Odoo中

模块数据通过 data files 声明,这些是带有 <record> 元素的 XML 文件。每个 <record> 元素都会创建或更新一个数据库记录。

<odoo>

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

</odoo>
  • model is the name of the Odoo model for the record.

  • 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">tree,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
          action="action_list_ideas"/>

危险

在 XML 文件中,菜单对应的操作必须先声明。

数据文件按顺序执行,菜单创建之前必须在数据库中存在操作的 id

Exercise

定义新的菜单项

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

  • 显示所有课程的列表

  • 创建/修改课程

基本视图

Views define the way the records of a model are displayed. Each type of view represents a mode of visualization (a list of records, a graph of their aggregation, …). Views can either be requested generically via their type (e.g. a list of partners) or specifically via their id. For generic requests, the view with the correct type and the lowest priority will be used (so the lowest-priority view of each type is the default view for that type).

View inheritance allows altering views declared elsewhere (adding or removing content).

通用视图声明

视图被声明为模型 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>, <tree>, <graph>, ... -->
    </field>
</record>

危险

视图的内容是 XML。

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

树形视图

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

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

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

表单视图

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

它们的根元素是 <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"
                states="draft" class="oe_highlight" />
        <button string="Mark as done" type="object" name="action_done"
                states="confirmed" class="oe_highlight"/>
        <button string="Reset to draft" type="object" name="action_draft"
                states="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模块,我们考虑一个 sessions 模型:一个session是在给定时间为给定受众教授的课程的发生。

创建一个 sessions 模型。一个 session 有一个名称,一个开始日期,一个持续时间和一个座位数。添加一个操作和一个菜单项来显示它们。通过一个菜单项使新模型可见。

关联字段

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

关系型字段类型有:

Many2one(other_model, ondelete='set null')

一个简单的链接到另一个对象:

print(foo.other_id.name)

另请参阅

foreign keys

One2many(other_model, related_field)

一个虚拟关系,是 Many2one 的反向关系。一个 One2many 表现为记录的容器,访问它会得到一组(可能为空的)记录:

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

危险

因为 One2many 是一个虚拟关系,所以 在 other_model必须 有一个 Many2one 字段,并且它的名称 必须related_field

Many2many(other_model)

双向多关系,一侧的任何记录都可以与另一侧的任意数量的记录相关联。它的行为类似于记录的容器,访问它也可能会导致一个空的记录集合::

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

Exercise

多对一关系

使用 many2one,修改 CourseSession 模型以反映它们与其他模型的关系:

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

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

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

  • 调整视图。

Exercise

反向一对多关系

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

Exercise

多个 many2many 关系

使用关系字段 many2many,修改 Session 模型以将每个会话与一组 attendees 关联起来。参与者将由合作伙伴记录表示,因此我们将与内置模型 res.partner 相关联。相应地调整视图。

继承

模型继承

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

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

  • 向模型添加字段,

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

  • 向模型添加约束条件,

  • 向模型添加方法,

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

第二种继承机制(委托)允许将模型中的每个记录链接到父模型中的记录,并提供对父记录字段的透明访问。

../../_images/inheritance_methods.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_表达式,在父视图中选择一个单独的元素。如果匹配不到元素或匹配到多个元素,则会引发错误。

position

要应用于匹配元素的操作:

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中, 搜索域 是编码记录条件的值。一个域是一个标准列表,用于选择模型记录的子集。每个标准是一个三元组,包含字段名、操作符和值。

例如,当应用于 Product 模型时,以下领域选择所有单价超过 1000services

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

默认情况下,条件会隐式使用 AND 进行组合。逻辑运算符 & (AND)、 | (OR)和 ! (NOT)可用于显式组合条件。它们在前缀位置使用(运算符插入在其参数之前而不是之间)。例如,要选择“是服务 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 是一个 recordset,即有序的记录集合。它支持标准的 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 是数据库 cursor 对象;它用于查询数据库

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

  • self.env.user is the current user’s record

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

  • self.env.ref(xml_id) returns the record corresponding to an XML id

  • self.env[model_name] returns an instance of the given model

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 行为是内置的,可以通过与 Session 表单进行交互来看到:更改座位数或参与者数量, 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: the id of the current user,

  • today:当前本地日期,格式为``YYYY-MM-DD``,

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

{$name} 可以是 bf (font-weight: bold), it (font-style: italic), 或任何 bootstrap contextual color (danger, info, muted, primary, successwarning).

<tree string="Idea Categories" decoration-info="state=='draft'"
    decoration-danger="state=='trashed'">
    <field name="name"/>
    <field name="state"/>
</tree>
editable

要么是 "top""bottom"。使树视图可以原地编辑(而不必通过表单视图),值是新行出现的位置。

Exercise

列表着色

修改Session树视图,使持续时间少于5天的会话呈蓝色,持续时间大于15天的会话呈红色。

日历

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

color

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

date_start

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

date_stop (optional)

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

string

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

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

Exercise

日历视图

Session 模型添加一个日历视图,使用户能够查看与 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 views (元素 <pivot>) 是一个多维表格,允许选择过滤器和维度以获取正确的聚合数据集,然后再转到更图形化的概览。透视图与图形视图共享相同的内容定义。

图形视图有4种显示模式,可以使用 @type 属性选择默认模式。

默认的 Bar

在条形图中,第一个维度用于定义水平轴上的分组,其他维度定义每个分组内的聚合条形。

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

行号

二维折线图

饼图

二维饼图

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

row (default)

该字段默认应该进行聚合

measure

应该对该字段进行聚合而不是分组

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

警告

图形视图对数据库值执行聚合,不支持非存储计算字段。

Exercise

图形视图

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

看板

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

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

例如,项目任务可以按阶段(每列是一个阶段)或按负责人(每列是一个用户)等方式组织。

Kanban 视图将每个卡片的结构定义为表单元素(包括基本的 HTML)和 QWeb模板 的混合。

Exercise

看板视图

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

安全性

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

基于组的访问控制机制

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

访问权限

访问权限被定义为模型 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 / Manager 的用户组,该组对所有 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

记录规则

为课程模型和组 “OpenAcademy / Manager” 添加记录规则,限制对课程负责人的 writeunlink 访问权限。如果课程没有负责人,则该组的所有用户都可以修改它。

向导

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

  • 向导记录不是持久的;它们在一定时间后会自动从数据库中删除。这就是为什么它们被称为“ transient ”。

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

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

Exercise

定义向导

创建一个向导模型,该模型与 Session 模型具有多对一的关系,并与 Partner 模型具有多对多的关系。

启动向导

向导只是具有 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. 将该操作添加到以 Session 模型为上下文的环境中启动。

  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 Bootstrap _ 和 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, 指向从您的服务器可访问的URL(可能是 http://localhost:8069 或类似的URL)。它仅用于此特定目的。

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

Exercise

为Session模型创建报告

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

仪表盘

Exercise

定义一个仪表盘

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

1

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

2

编写原始的 SQL 查询是可能的,但需要小心,因为它会绕过所有的 Odoo 认证和安全机制。