参考资料点击这里.
构建Odoo模块
模块组成
-
业务对象
业务对象声明为Python类, 由Odoo自动载入.
-
数据文件
XML或CSV文件格式, 在其中声明了元数据(视图或工作流)、配置数据(模块参数)、演示数据等.
-
Web控制器
处理Web浏览器发来的requests.
-
静态web数据
Web用到的图像, CSS或JavaScript文件.
模块结构
一个Odoo模块也是一个Python模块, 存放在一个目录中, 包含一个__init__.py文件, 用于导入其他Python模块.
from . import mymodule
odoo.py提供了一个子命令scaffold可以方便地创建一个空的模块.
$ odoo.py scaffold <module name> <where to put it>
命令执行后, 将会创建一个子目录并且其中包括了Odoo模块所需的一些基本文件.
练习 #1
执行 ./odoo.py scaffold openacademy addons, 在addons目录下创建一个名为openacademy的模块, 生成的目录文件结构如下.
openacademy├── __init__.py├── __openerp__.py├── controllers.py├── demo.xml├── models.py├── security│ └── ir.model.access.csv└── templates.xml
各文件内容请查看文件或查看原文, 然后对__openerp__.py中的几种标识文本进行修改.
对象关系映射
ORM层是Odoo的一个关键组件, 它可以避免大部分的SQL语句编写从而提高扩展性和安全性.
业务对象用派生自Model的Python类(模型)来编写, 该类的_name属性定义了模型在Odoo系统中的名称.
from openerp import modelsclass MinimalModel(models.Model): _name = 'test.model'
字段
字段定义模型能够存储什么以及在哪里存储, 字段在模型类中用属性来定义.
from openerp import models, fieldsclass LessMinimalModel(models.Model): _name = 'test.model2' name = fields.Char()
通用属性
与模型类似, 字段也可以通过参数传递对其进行设定:
name = field.Char(required=True)
字段的常用属性有:
-
string (unicode, default: field’s name)
The label of the field in UI (visible by users).
-
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: ‘’)
Long-form, provides a help tooltip to users in the UI.
-
index (bool, default: False)
Requests that Odoo create a database index on the column
简单字段
字段可以分为两类: 简单字段和关系字段. 前者为原子值, 直接保存在模型对应的数据库表中; 后者连接到其他的记录上(可以是相同的模型也可以是不同的模型).
Boolean, Date, Char这些都是简单字段.
保留字段
Odoo在模型中自动创建并维护一些字段, 这些字段就是保留字段, 这些字段数据不需要也不应该手动去修改.
-
id (Id)
the unique identifier for a record in its model
-
create_date (Datetime)
creation date of the record
-
create_uid (Many2one)
user who created the record
-
write_date (Datetime)
last modification date of the record
-
write_uid (Many2one)
user who last modified the record
特殊字段
默认情况下, Odoo要求模型中有一个name字段, 用于显示和搜索, 通过设置_rec_name也可以达到这样的目的.
练习 #2
在openacademy模块中定义一个新的模型Course, openacademy/models.py内容如下:
# -*- coding: utf-8 -*-from openerp import models, fields, apiclass Course(models.Model): _name = 'openacademy.course' name = fields.Char(string="Title", required=True) description = fields.Text()
数据文件
Odoo是一个高度数据驱动的系统, 虽然使用Python代码来定制模块行为, 但很多模块数据是在其载入时setup的, 并且有些模块仅仅为Odoo添加数据.
通过数据文件来定义模块数据, 例如可以使用XML文件中的<record>元素定义数据, 每一个<record>元素创建或者更新数据库中的一条记录, 形式如下:
<openerp> <data> <record model="{model name}" id="{record identifier}"> <field name="{a field name}">{a value}</field> </record> </data><openerp>
-
model
Odoo模型名.
-
id
外部ID(External Identifier), 通过它可以引用到记录(并且不需要知道记录所在的数据库ID).
-
元素 name属性用于确定字段名称(例如description), 该元素的body给出字段的值.
数据文件必须在模块载入清单文件列表中, 也就是__openerp__.py的’data’列表(全部载入)或’demo’列表(只有设定为载入演示数据才会载入)中.
练习 #3
创建一个数据文件来向Course中添加数据, 编辑openacademy/demo.xml, 并确认__openerp__.py的’demo’列表中有该文件.
<openerp> <data> <record model="openacademy.course" id="course0"> <field name="name">Course 0</field> <field name="description">Course 0's descriptionCan have multiple lines </field> </record> <record model="openacademy.course" id="course1"> <field name="name">Course 1</field> <!-- no description for this one --> </record> <record model="openacademy.course" id="course2"> <field name="name">Course 2</field> <field name="description">Course 2's description</field> </record> </data></openerp>
动作和菜单
在Odoo中, 动作和菜单都是定义在数据库中的数据记录, 一般通过数据文件来定义.
动作可以由三种方式触发:
- 点击菜单项(菜单项链接到特定动作)
- 点击视图上的按钮(如果按钮连接到动作)
- 作为对象的上下文动作
使用<menuitem>声明一个ir.ui.menu并将其连接到一个action, 可以用下面的形式的代码.
<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"/>
注意: action必须先于menu的连接使用定义, 数据文件在载入时顺序地执行, 所以动作的ID必须首先存在于数据库中才能使用.
练习 #4
定义一个新的菜单项访问OpenAcademy课程.
创建openacademy/views/openacademy.xml文件, 并在其中添加动作和菜单.
<?xml version="1.0" encoding="UTF-8"?><openerp> <data> <!-- window action --> <!-- The following tag is an action definition for a "window action", that is an action opening a view or a set of views --> <record model="ir.actions.act_window" id="course_list_action"> <field name="name">Courses</field> <field name="res_model">openacademy.course</field> <field name="view_type">form</field> <field name="view_mode">tree,form</field> <field name="help" type="html"> <p class="oe_view_nocontent_create">Create the first course </p> </field> </record> <!-- top level menu: no parent --> <menuitem id="main_openacademy_menu" name="Open Academy"/> <!-- A first level in the left side menu is needed before using action= attribute --> <menuitem id="openacademy_menu" name="Open Academy" parent="main_openacademy_menu"/> <!-- the following menuitem should appear *after* its parent openacademy_menu and *after* its action course_list_action --> <menuitem id="courses_menu" name="Courses" parent="openacademy_menu" action="course_list_action"/> <!-- Full id location: action="openacademy.course_list_action" It is not required when it is the same module --> </data></openerp>
在__openerp__.py中添加这个数据文件名到’data’.
'data': [ # 'security/ir.model.access.csv', 'templates.xml', 'views/openacademy.xml', ],
更新模块后可以看到菜单, 操作看看效果.
基本视图
视图定义了模型数据如何显示, 每种类型的视图代表一种数据可视化模式.
基本的视图定义
一个视图是以一条ir.ui.view模型数据的形式定义的.
<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>
Tree views
Tree view也被称为list views, 在一个表格中显示记录. 根元素是<tree>, 最简形式的tree view只是简单地列出每条记录的多个字段, 每个字段为一列.
<tree string="Idea list"> <field name="name"/> <field name="inventor_id"/></tree>
Form views
Form用于创建或编辑单条记录, 根元素是<form>, 可以在form中组合各种高层结构元素(如groups, notebooks)以及交互元素(如buttons, fields).
<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>
练习 #5
为openacademy创建form view, views/openacademy.xml数据文件中增加<record model=”ir.ui.view”…>内容.
<?xml version="1.0" encoding="UTF-8"?><openerp> <data> <record model="ir.ui.view" id="course_form_view"> <field name="name">course.form</field> <field name="model">openacademy.course</field> <field name="arch" type="xml"> <form string="Course Form"> <sheet> <group> <field name="name"/> <field name="description"/> </group> </sheet> </form> </field> </record> <!-- window action --> <!-- The following tag is an action definition for a "window action",
更新模块, 创建一个Course, 可以看到form view变了.
练习 #6
使用notebook. 在form view中, 将description字段放在一个tab中, 方便随后添加其他tabs, 对练习#5的form view数据做如下修改.
<sheet> <group> <field name="name"/> </group> <notebook> <page string="Description"> <field name="description"/> </page> <page string="About"> This is an example of notebooks </page> </notebook> </sheet> </form></field>
更新模块, 看效果.
More
还可以使用HTML为form view提供更加灵活的布局, 例如下面的例子.
<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 views
Search views用来自定义list views及其它统计/多条记录视图中的搜索字段. 根元素为<search>, 其子元素定义了在哪些字段上进行搜索.
<search> <field name="name"/> <field name="inventor_id"/></search>
如果一个模型没有定义对应的Search view, odoo自动创建一个仅搜索name字段的search view.
练习 #7
添加title以及description搜索, 在views/openacademy.xml中定义search view.
</field></record><record model="ir.ui.view" id="course_search_view"> <field name="name">course.search</field> <field name="model">openacademy.course</field> <field name="arch" type="xml"> <search> <field name="name"/> <field name="description"/> </search> </field></record><!-- window action --><!-- The following tag is an action definition for a "window action",
更新模块, 搜索框输入字符后可以看到下方能够选择搜索description字段.
模型中的关联
概述
一个模型中的记录可能关联到其他模型的记录, 例如销售订单记录会关联到一个包含客户信息的客户记录.
练习 #8
为了说明数据关联, 首先增加新的模型.
Open Academy模块中, 一个session是一个在特定时间针对特定听众讲授课程的过程. 需要为session创建相应的模型.
session具有name, 开始日期, 持续时间以及座位数量等. 此外还需要添加相应的action和menuitem显示模型数据.
首先在openacademy/models.py中创建Session类.
class Session(models.Model): _name = 'openacademy.session' name = fields.Char(required=True) start_date = fields.Date() duration = fields.Float(digits=(6, 2), help="Duration in days") seats = fields.Integer(string="Number of seats")
然后在openacademy/view/openacademy.xml中添加用于访问session模型的action和menuitem定义.
<!-- Full id location: action="openacademy.course_list_action" It is not required when it is the same module --> <!-- session form view --> <record model="ir.ui.view" id="session_form_view"> <field name="name">session.form</field> <field name="model">openacademy.session</field> <field name="arch" type="xml"> <form string="Session Form"> <sheet> <group> <field name="name"/> <field name="start_date"/> <field name="duration"/> <field name="seats"/> </group> </sheet> </form> </field> </record> <record model="ir.actions.act_window" id="session_list_action"> <field name="name">Sessions</field> <field name="res_model">openacademy.session</field> <field name="view_type">form</field> <field name="view_mode">tree,form</field> </record> <menuitem id="session_menu" name="Sessions" parent="openacademy_menu" action="session_list_action"/> </data></openerp>
digits=(6,2)确定浮点数的精度, 6表示总的数字位数(不包括小数点), 2表示小数点后的位数. 所以, digits=(6,2)小数点前最多4位.