构建模块

危险

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

警告

本教程需要 having installed Odoo

启动/停止 Odoo 服务器

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

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

为了启动服务器,只需在 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 界面或网站使用的图像、CSS 或 javascript 文件

模块结构

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

小技巧

大多数命令行选项也可以使用 a configuration file 设置

Odoo 模块由其 manifest 声明。

模块也是一个 Python package 和一个 __init__.py` 文件,包含模块中各种 Python 文件的导入指令。

例如,如果模块有一个 mymodule.py file __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,默认:字段名称)

UI 中字段的标签(用户可见)。

required (bool, default: False)

如果为“True”,则该字段不能为空,它必须具有默认值或在创建记录时始终指定一个值。

help (unicode, default: '')

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

index (bool, default: False)

请求 Odoo 在列上创建 database index

简单字段

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

简单字段的示例有 BooleanDateChar

保留字段

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> elements. Each <record> 元素的 XML 文件创建或更新数据库记录。

<odoo>

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

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

  • id 是一个 external identifier,它允许引用记录(无需知道其数据库内标识符)。

  • <field> elements have a name which is the name of the field in the model (e.g. description)。他们的身体就是场域的价值。

数据文件必须在清单文件中声明才能加载,它们可以在``’data’`` list (always loaded) or in the ``’demo’``列表中声明(仅在演示模式下加载)。

Exercise

定义演示数据

使用一些演示课程创建填充*课程*模型的演示数据。

小技巧

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

进行一些更改后,不要忘记使用 odoo-bin -u openacademy 将更改保存到数据库中。

操作和菜单

操作和菜单是数据库中的常规记录,通常通过数据文件声明。可以通过三种方式触发操作:

  1. 通过单击菜单项(链接到特定操作)

  2. 通过单击视图中的按钮(如果这些按钮连接到操作)

  3. 作为对象上的上下文操作

因为菜单的声明有些复杂,所以有一个 <menuitem> shortcut to declare an 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 专门请求。对于通用请求,将使用具有正确类型和最低优先级的视图(因此每种类型的最低优先级视图是该类型的默认视图)。

View inheritance 允许更改在其他地方声明的视图(添加或删除内容)。

通用视图声明

视图被声明为模型``ir.ui.view``. The view type is implied by the root element of the ``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`` field must thus be declared as 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

创建会话模型

对于开放学院模块,我们考虑*会话*的模型:会话是在给定时间针对给定受众教授的课程的发生。

为*会话*创建模型。会议有名称、开始日期、持续时间和席位数。添加一个操作和一个菜单项来显示它们。通过菜单项使新模型可见。

关系字段

关系字段链接同一模型(层次结构)或不同模型之间的记录。

关系字段类型有:

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”的记录。

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

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

  • 调整观点。

Exercise

逆一二多关系

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

Exercise

多个多对多关系

使用关系字段 Many2many,修改 Session 模型,将每个会话与一组 attendees 相关联。与会者将由合作伙伴记录代表,因此我们将与内置模型“res.partner”相关。相应地调整观点。

遗产

模型继承

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

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

  • 将字段添加到模型中,

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

  • 向模型添加约束,

  • 向模型添加方法,

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

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

../../_images/inheritance_methods.png

其他资料

  • _inherit

  • _inherits

查看继承

Odoo 不是直接修改现有视图(通过覆盖它们),而是提供视图继承,其中子“扩展”视图应用于根视图之上,并且可以从其父视图添加或删除内容。

扩展视图使用 inherit_id field, and instead of a single view its arch field is composed of any number of 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`’s body, replacing any ``$0`”节点匹配项替换为原始元素

before

xpath 的主体作为同级元素插入到匹配元素之前

after

xpaths 的主体作为同级元素插入到匹配元素之后

attributes

使用特殊的``attribute`` elements in the ``xpath``的主体改变匹配元素的属性

小技巧

匹配单个元素时,可以直接在要查找的元素上设置``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 布尔字段,以及对应于会话-合作伙伴关系的 Many2many 字段

  • 使用视图继承,在合作伙伴表单视图中显示此字段

域名

在 Odoo 中,搜索域 是对记录条件进行编码的值。域是用于选择模型记录子集的标准列表。每个条件都是一个包含字段名称、运算符和值的三元组。

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

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

默认情况下,条件与隐式 AND 组合。逻辑运算符 & (AND), | (OR) and ! (NOT) 可用于显式组合条件。它们用在前缀位置(运算符插入在其参数之前而不是之间)。例如,选择“属于服务*或*单价*不*在 1000 到 2000 之间的产品”:

['|',
    ('product_type', '=', 'service'),
    '!', '&',
        ('unit_price', '>=', 1000),
        ('unit_price', '<', 2000)]

可以将“domain”参数添加到关系字段,以在尝试在客户端界面中选择记录时限制关系的有效记录。

Exercise

关系字段上的域

选择*课程*的讲师时,只有讲师(带有“instructor` set to ``True`”的合作伙伴)应该可见。

Exercise

更复杂的领域

创建新的合作伙伴类别*教师/1级*和*教师/2级*。课程的讲师可以是讲师或教师(任何级别)。

计算字段和默认值

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

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

危险

self 是一个集合

对象``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` gives the records one by one, where each record is itself a collection of size 1. You can access/assign fields on single records by using the dot notation, like ``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 where X 可以是 Python 文字值(布尔值、整数、浮点数、字符串),也可以是采用记录集并返回值的函数:

name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)

注解

对象“self.env”可以访问请求参数和其他有用的东西:

  • self.env.cr or self._cr 是数据库*光标*对象;它用于查询数据库

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

  • self.env.user 是当前用户的记录

  • self.env.context or self._context 是上下文字典

  • self.env.ref(xml_id) 返回 XML id 对应的记录

  • self.env[model_name] 返回给定模型的实例

Exercise

活动对象 – 默认值

  • 将 start_date 默认值定义为今天(请参阅 Date)。

  • 在Session类中添加一个字段``active``,并将Session默认设置为活动状态。

昂变化

“onchange”机制为客户端界面提供了一种在用户在字段中填写值时更新表单的方法,而无需将任何内容保存到数据库中。

例如,假设模型具有三个字段 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 将反映在表单上。

<!-- 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` behavior is built-in as can be seen by playing with the Session form: change the number of seats or participants, and the ``taken_seats`”进度条会自动更新。

Exercise

警告

添加显式 onchange 以警告无效值,例如负数席位或参与者多于席位。

模型约束

Odoo 提供了两种设置自动验证不变量的方法:Python constraintsSQL constraints。以类似的方式,您可以添加更复杂的 SQL indexes

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

添加一个约束,检查讲师是否出现在他/她自己的会话的与会者中。

约束和索引使用:ConstraintIndexUniqueIndex 定义。

Exercise

添加 SQL 约束

PostgreSQL’s documentation 的帮助下,添加以下约束:

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

  2. 使课程的名称独一无二

Exercise

练习 6 - 添加重复选项

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

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

高级视图

列表视图

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

decoration-{$name}

允许根据相应记录的属性更改行文本的样式。

值是 Python 表达式。对于每条记录,都会使用记录的属性作为上下文值来计算表达式,如果为“true”,则将相应的样式应用于该行。以下是上下文中可用的一些其他值:

  • uid:当前用户的id,

  • today: the current local date as a string of the form YYYY-MM-DD

  • now: same as today with the addition of the current time. This value is formatted as YYYY-MM-DD hh:mm:ss

{$name} can be bf (font-weight: bold), it (font-style: italic), or any `引导上下文颜色<https://getbootstrap.com/docs/3.3/components/#available-variations>`_ (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"` or ``”bottom”`”。使列表视图可就地编辑(而不必通过表单视图),该值是新行出现的位置。

Exercise

列表着色

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

日历

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

color

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

date_start

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

``date_stop``(可选)

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

string

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

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

Exercise

日历视图

将日历视图添加到*会话*模型中,使用户能够查看与开放学院相关的事件。

搜索视图

搜索视图``<field>`` elements can have a @filter_domain that overrides the domain generated for searching on the given field. In the given domain, self represents the value entered by the user. In the example below, it is used to search on both fields name and 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` field: context keys of the form search_default_field_name will initialize field_name with the provided value. Search filters must have an optional ``@name`”设置搜索字段的默认值,以具有默认值并表现为布尔值(它们只能在默认情况下启用)。

Exercise

搜索视图

  1. 添加一个按钮,用于在课程搜索视图中过滤当前用户负责的课程。使其默认选中。

  2. 添加一个按钮以按负责用户对课程进行分组。

甘特图

警告

甘特图需要 enterprise edition 版本中存在的 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”属性选择默认模式。

栏(默认)

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

默认情况下,条形图是并排的,可以使用“@stacked="True"` on the ``<graph>`”进行堆叠

线

二维折线图

馅饼

二维饼图

图形视图包含 <field> with a mandatory @type 属性,其值如下:

``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 文件插入模块内。还可以使用字段的组属性来限制对视图或对象上特定字段的访问。

访问权

访问权限定义为模型“ir.model.access`. Each access right is associated to a model, a group (or no group for global access), and a set of permissions: read, write, create, unlink. Such access rights are usually created by a CSV file named after its model: ``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界面添加访问控制

创建一个新用户“约翰·史密斯”。然后创建一个组“OpenAcademy / Session Read”,具有 Session 模型的读取权限。

Exercise

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

使用数据文件,

  • 创建一个组 OpenAcademy / Manager,拥有对所有 OpenAcademy 模型的完全访问权限

  • 使 SessionCourse 可供所有用户读取

记录规则

记录规则限制对给定模型的记录子集的访问权限。规则是模型“ir.rule”的记录,与模型、多个组(many2many 字段)、限制适用的权限和域相关联。域指定访问权限仅限于哪些记录。

以下是防止删除不处于状态“cancel`. Notice that the value of the field ``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”组添加一条记录规则,限制“write` and ``unlink`”对课程负责人的访问。如果课程没有责任,则该组的所有用户都必须能够修改它。

奇才

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

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

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

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

Exercise

定义向导

创建一个与 Session 模型具有多对一关系以及与 Partner 模型具有多对多关系的向导模型。

启动向导

向导只是 window actions 和一个 target field set to the value new,它在单独的对话框中打开视图(通常是 a form)。该动作可以通过菜单项触发,但更通常由按钮触发。

启动向导的另一种方法是通过列表或表单视图的 Action 菜单。这是通过操作的“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

将与会者注册到多个会议

修改向导模型,以便与会者可以注册到多个会话。

国际化

每个模块都可以通过名为 LANG.po 的文件在 i18n 目录中提供自己的翻译,其中 LANG 是语言的区域设置代码,或者语言和国家/地区不同时的组合(例如 pt.po 或 pt_BR.po)。 Odoo 将自动加载所有启用语言的翻译。开发人员在创建模块时总是使用英语,然后使用 Odoo 的 gettext POT 导出功能(Settings ‣ Translations ‣ Import/Export ‣ Export Translation 不指定语言)导出模块术语,创建模块模板 POT 文件,然后导出翻译后的 PO 文件。许多 IDE 都有用于编辑和合并 PO/POT 文件的插件或模式。

小技巧

Odoo 生成的可移植对象文件发布在 Odoo’s Translations Platform 上,使得翻译该软件变得容易。

|- 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 view

    <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 操作输出参数,例如,可以通过 http://localhost:8069/report/html/account.report_invoice/1 获取 Invoice 报告的 HTML 版本(如果安装了“account”),通过 http://localhost:8069/report/pdf/account.report_invoice/1 获取 PDF 版本。

危险

如果您的 PDF 报告似乎缺少样式(即文本出现,但样式/布局与 html 版本不同),则您的 wkhtmltopdf 进程可能无法到达您的 Web 服务器来下载它们。

如果您检查服务器日志并发现生成 PDF 报告时未下载 CSS 样式,那么很可能就是问题所在。

wkhtmltopdf 进程将使用“web.base.url”系统参数作为所有链接文件的“根路径”,但该参数会在管理员每次登录时自动更新。如果您的服务器驻留在某种代理后面,则无法访问该服务器。您可以通过添加以下系统参数之一来解决此问题:

  • report.url, pointing to an URL reachable from your server (probably http://localhost:8069 或类似的东西)。它将仅用于此特定目的。

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

Exercise

为会话模型创建报告

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

仪表板

Exercise

定义仪表板

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

1

有可能 disable the automatic creation of some fields

2

可以编写原始 SQL 查询,但需要小心,因为它会绕过所有 Odoo 身份验证和安全机制。