构建一个模块¶
危险
本教程已过时。我们建议阅读 服务器框架 101。
警告
本教程需要:doc:已安装 Odoo </administration/on_premise>
启动/停止 Odoo 服务器¶
Odoo 使用客户端/服务器架构,其中客户端是通过 RPC 访问 Odoo 服务器的网页浏览器。
业务逻辑和扩展通常在服务器端进行,尽管可以在客户端添加支持性功能(例如新的数据表示形式,如交互式地图)。
为了启动服务器,只需在 shell 中调用命令 odoo-bin,如需的话,添加文件的完整路径:
odoo-bin
服务器通过在终端中连续按下两次 Ctrl-C
停止,或者通过终止相应的操作系统进程来停止。
构建一个 Odoo 模块¶
服务器和客户端扩展都作为 模块 进行打包,这些模块可选择性地在 数据库 中加载。
Odoo 模块可以向 Odoo 系统中添加全新的业务逻辑,也可以修改和扩展现有的业务逻辑:可以创建一个模块,将您国家的会计规则添加到 Odoo 的通用会计支持中,而下一个模块则可以添加对公交车队实时可视化支持。
因此,Odoo 中的一切都始于模块,也终于模块。
模块的组成¶
一个 Odoo 模块可以包含以下一些元素:
模块结构¶
每个模块都是 模块目录 中的一个目录。模块目录通过使用 --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 在该列上创建 数据库索引。
简单字段¶
有两种主要类型的字段:”简单” 字段,它们是直接存储在模型表中的原子值,以及 “关系” 字段,用于链接记录(可以是同一模型的记录,也可以是不同模型的记录)。
保留字段¶
Odoo 在所有模型中会自动创建一些字段 1。这些字段由系统管理,不应直接写入。如果有必要或有用,可以读取它们:
特殊字段¶
默认情况下,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
定义演示数据
创建演示数据,向 课程 模型中填充几个演示课程。
基础视图¶
视图定义了模型记录的显示方式。每种类型的视图代表一种可视化模式(例如记录列表、它们的聚合图表等)。视图可以通过其类型(例如“合作伙伴列表”)进行通用请求,也可以通过其 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)
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 提供了两种 继承 机制,以模块化的方式扩展现有模型。
第一个继承机制允许一个模块修改另一个模块中定义的模型的行为:
向模型中添加字段,
在模型上覆盖字段的定义,
向模型添加约束条件,
向模型添加方法,
覆盖模型上的现有方法。
第二种继承机制(委托)允许将模型中的每条记录与父模型中的一条记录关联,并提供对父记录字段的透明访问。

另请参见
_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.cr
或self._cr
是数据库 游标 对象;它用于查询数据库
self.env.uid
或self._uid
是当前用户的数据库 ID
self.env.user
是当前用户的记录
self.env.context
或self._context
是上下文字典
self.env.ref(xml_id)
返回与 XML ID 对应的记录
self.env[model_name]
返回给定模型的实例
更改时¶
“onchange” 机制提供了一种方式,使客户端界面在用户填写字段值时能够更新表单,而无需将任何内容保存到数据库中。
例如,假设一个模型有三个字段 amount
、unit_price
和 price
,您希望在表单中任何其他字段被修改时更新价格。要实现此功能,请定义一个方法,其中 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
练习 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`
、info
、muted
、primary
、success
或warning
)。<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
表示用户输入的值。在下面的示例中,它用于在字段 name
和 description
上进行搜索。
搜索视图还可以包含 <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
搜索视图
在课程搜索视图中添加一个按钮,用于筛选当前用户为负责人的课程。默认情况下选中该按钮。
在按负责人用户分组的课程上添加一个按钮。
甘特图¶
警告
甘特图视图需要 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” 添加一条记录规则,该规则限制对课程负责人的 write
和 unlink
访问权限。如果某门课程没有负责人,则该组的所有用户都必须能够对其进行修改。
向导¶
向导通过动态表单描述与用户的交互会话(或对话框)。向导本质上是一个继承自 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
启动向导
为向导定义一个表单视图。
在 会话 模型的上下文中添加用于启动它的动作。
在向导中为会话字段定义默认值;使用上下文参数
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 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>
标准渲染上下文提供了一些元素,其中最重要的是:
文档
打印报表的记录
用户
用户打印报表
由于报表是标准的网页,它们可以通过网址访问,并且可以通过该网址操作输出参数。例如,发票 报表的 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.freeze
为True
时,将停止对web.base.url
的自动更新。
Exercise
为 Session 模型创建报表
对于每个会话,应显示会话名称、开始和结束时间,并列出会话的参与者。
仪表盘¶
Exercise
定义仪表板
创建一个包含你所创建的图表视图、课程会话日历视图以及课程列表视图(可切换为表单视图)的仪表板。该仪表板应通过菜单中的菜单项可用,并在选择 OpenAcademy 主菜单时在网页客户端中自动显示。
- 1
可以 禁用某些字段的自动创建
- 2
编写原始 SQL 查询是可能的,但需要谨慎,因为它会绕过所有 Odoo 身份验证和安全机制。