构建模块¶
危险
本教程已过时。我们建议阅读 服务器框架 101。
警告
本教程需要 安装 Odoo。
启动/停止 Odoo 服务器¶
Odoo 使用客户端/服务器架构,其中客户端是通过 RPC 访问 Odoo 服务器的 Web 浏览器。
业务逻辑和扩展通常在服务器端执行,但也可以向客户端添加支持功能(例如,新的数据表示形式,如交互式地图)。
要启动服务器,只需在终端中调用命令 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
,默认值:''
)长格式描述,在用户界面中向用户提供帮助提示。
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
是一个 external identifier ,它允许引用该记录(无需知道其数据库内部标识符)。<field>
元素具有一个name
,它是模型中的字段名称(例如description
)。其主体是字段的值。
数据文件必须在清单文件中声明才能被加载,它们可以声明在 'data'
列表中(始终加载)或 'demo'
列表中(仅在演示模式下加载)。
Exercise
定义演示数据
创建演示数据,用一些演示课程填充 Courses 模型。
基本视图¶
视图定义了模型记录的显示方式。每种类型的视图代表一种可视化模式(例如,记录列表、聚合图表等)。视图可以通过其类型(例如,合作伙伴列表)以通用方式请求,也可以通过其 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)
Many2many(其他模型)
双向多对多关系,一侧的任何记录都可以与另一侧的任意数量的记录相关联。表现为记录的容器,访问它时也会返回一组可能为空的记录:
for other in foo.other_ids: print(other.name)
Exercise
多对一关系
使用多对一关系,修改 课程 和 会话 模型以反映它们与其他模型的关系:
课程有一个 负责人 用户;该字段的值是内置模型
res.users
的一条记录。会话有一位 讲师;该字段的值是内置模型
res.partner
的一条记录。会话与 课程 相关联;该字段的值是模型
openacademy.course
的一条记录,并且是必填项。调整视图。
Exercise
反向一对多关系
使用反向关系字段一对多,修改模型以反映课程与会话之间的关系。
Exercise
多个多对多关系
使用多对多关系字段,修改 Session 模型,将每个会话与一组 参与者 关联起来。参与者将由合作伙伴记录表示,因此我们将关联到内置模型 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>
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 1 和 Teacher / 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.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]
返回给定模型的实例
字段变化触发机制¶
“字段变化触发机制” 提供了一种方式,允许在用户填写字段值时更新表单,而无需将任何内容保存到数据库。
例如,假设一个模型有三个字段 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",
}
}
对于计算字段,值变化触发行为是内置的,可以通过操作 会话 表单观察到:更改座位数或参与者数量后,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 - 添加复制选项
由于我们为课程名称添加了唯一性约束,因此无法再使用“复制”功能(
)。重新实现您自己的“复制”方法,允许复制课程对象,并将原始名称更改为“[原始名称] 的副本”。
高级视图¶
列表视图¶
列表视图可以接受补充属性以进一步自定义其行为:
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`
、info
、muted
、primary
、success
或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
表示用户输入的值。在下面的示例中,它用于同时搜索 name
和 description
两个字段。
搜索视图还可以包含 <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
搜索视图
在课程搜索视图中添加一个按钮,用于筛选当前用户负责的课程。默认情况下应选中该按钮。
添加一个按钮,按负责用户对课程进行分组。
甘特图¶
警告
甘特图视图需要 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 模型具有完全访问权限。
使所有用户都能读取 Session 和 Course。
记录规则¶
记录规则限制了对给定模型中部分记录的访问权限。规则是模型 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 / 管理员”添加一条记录规则,限制对课程负责人的 write
和 unlink
访问。如果课程没有负责人,则组中的所有用户都应能够修改它。
向导¶
向导通过动态表单描述与用户的交互会话(或对话框)。向导只是一个扩展了 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
启动向导
为向导定义表单视图。
添加操作以在 会话 模型的上下文中启动它。
为向导中的会话字段定义默认值;使用上下文参数
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
或类似地址)。它仅用于此特定目的。当设置为
True
时,web.base.url.freeze
将停止对web.base.url
的自动更新。
Exercise
为 Session 模型创建报告
对于每个会话,应显示会话名称、开始和结束时间,并列出会话的参与者。
仪表板¶
Exercise
定义仪表板
定义一个包含您创建的图表视图、会话日历视图以及课程列表视图(可切换为表单视图)的仪表板。此仪表板应通过菜单中的菜单项可用,并在选择 OpenAcademy 主菜单时自动显示在 Web 客户端中。
- 1
可以 禁用某些字段的自动创建
- 2
编写原始 SQL 查询是可行的,但由于它绕过了 Odoo 的所有身份验证和安全机制,因此需要谨慎处理。