Odoo模块
服务器端的扩展和客户端的扩展都打包为模块,可以选择性的将其加载到数据库中。
模块可以向Odoo系统添加全新的业务逻辑,也可以更改和扩展现有的业务逻辑:可以创建一个模块以将您所在“国家/地区”的会计规则添加到Odoo的通用会计支持中,而下一个模块可以添加对实时数据的可视化支持。
因此,Odoo中的所有内容都以模块开始和结束。
模块组成
Odoo模块可以包含许多元素:
业务对象
声明为Python类,Odoo会根据其配置自动持久化。
对象视图
定义业务对象的界面显示。
数据文件
声明模型元数据的XML或CSV文件:
- 视图或报告,
- 配置数据(模块参数化,安全规则),
- 示范数据,
- 更多。
Web控制器
处理来自浏览器的请求。
静态网页数据
网页中使用的图像、css或javascript文件。
模块结构
每个模块都是模块目录中的一个文件夹。模块目录由--addons-path
选项配置。
(大多数命令行选项也可以使用配置文件进行设置。)
Odoo模块由其清单声明。
模块也是一个Python包,其中有一个__init__.py
文件,用于导入模块所需的Python文件。
例如,如果模块具有单个mymodule.py
文件,则__init__.py
可能包含:
from . import mymodule
Odoo提供了一种帮助建立新模块的机制,odoo-bin
有一个子命令scaffold
可以创建一个空模块:
odoo-bin scaffold <模块名称> <目录位置>
该命令为您的模块创建了一个子目录,并自动为模块创建了一些标准文件。这些文件大多数仅包含注释的代码或XML。本教程将说明这些文件的用法。
“创建模块”练习
使用上面的命令行创建一个空模块“Open Academy”(学院),并将其安装到Odoo中。
- 调用命令
odoo-bin scaffold openacademy addons
。 - 调整模块清单。
- 不必理会其他文件。
模块清单:openacademy/manifest.py
# -*- coding: utf-8 -*-
{
# 模块的名称
'name': "Open Academy",
# 模块简述/摘要
'summary': """Manage trainings""",
# 模块说明文本
'description': """
Open Academy module for managing trainings:
- training courses
- training sessions
- attendees registration
""",
# 模块的作者
'author': "My Company",
# 模块网站网址
'website': "http://www.yourcompany.com",
# 分类类别,模块列表中可以通过分类进行筛选。
# 可通过此链接查看完整的分类信息:
# https://github.com/odoo/odoo/blob/14.0/odoo/addons/base/data/ir_module_category_data.xml
'category': 'Test',
# 模块的版本
'version': '0.1',
# 依赖的其他模块
'depends': ['base'],
# 始终加载的数据文件
'data': [
# 'security/ir.model.access.csv',
'templates.xml',
],
# 仅在演示模式下加载的数据文件
'demo': [
'demo.xml',
],
}
Python包:openacademy/init.py
# -*- coding: utf-8 -*-
from . import controllers
from . import models
控制器:openacademy/controllers.py
# -*- coding: utf-8 -*-
from odoo import http
# class Openacademy(http.Controller):
# @http.route('/openacademy/openacademy/', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/openacademy/openacademy/objects/', auth='public')
# def list(self, **kw):
# return http.request.render('openacademy.listing', {
# 'root': '/openacademy/openacademy',
# 'objects': http.request.env['openacademy.openacademy'].search([]),
# })
# @http.route('/openacademy/openacademy/objects/<model("openacademy.openacademy"):obj>/', auth='public')
# def object(self, obj, **kw):
# return http.request.render('openacademy.object', {
# 'object': obj
# })
演示数据:openacademy/demo.xml
<odoo>
<!-- -->
<!-- <record id="object0" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 0</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object1" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 1</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object2" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 2</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object3" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 3</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object4" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 4</field> -->
<!-- </record> -->
<!-- -->
</odoo>
模型:openacademy/models.py
# -*- coding: utf-8 -*-
from odoo import models, fields, api
# class openacademy(models.Model):
# _name = 'openacademy.openacademy'
# name = fields.Char()
权限配置:openacademy/security/ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
视图:openacademy/templates.xml
<odoo>
<!-- <template id="listing"> -->
<!-- <ul> -->
<!-- <li t-foreach="objects" t-as="object"> -->
<!-- <a t-attf-href="{{ root }}/objects/{{ object.id }}"> -->
<!-- <t t-esc="object.display_name"/> -->
<!-- </a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </template> -->
<!-- <template id="object"> -->
<!-- <h1><t t-esc="object.display_name"/></h1> -->
<!-- <dl> -->
<!-- <t t-foreach="object._fields" t-as="field"> -->
<!-- <dt><t t-esc="field"/></dt> -->
<!-- <dd><t t-esc="object[field]"/></dd> -->
<!-- </t> -->
<!-- </dl> -->
<!-- </template> -->
</odoo>
对象关系映射
ORM层是Odoo的一个关键组件。该层避免了手工编写大多数SQL,并提供了可扩展的和安全的服务。
业务对象被声明为扩展自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 = field.Char(required=True)
这是一些常见的通用参数:
string
(unicode
字符串, 默认:字段的名称)
用户界面中字段的标签。required
(bool
布尔值, 默认:False)
如果为True,则该字段不能为空,它必须具有默认值或在创建时赋值。help
(unicode
字符串, 默认:”)
在用户界面中为用户提供帮助提示。index
(bool
布尔值, 默认:False)
在列上创建数据库索引。
简单字段
字段分为两大类:“简单”字段是直接存储在模型表中的值,而“关系”字段则链接记录(相同模型或不同模型的记录)。
简单字段如:Boolean、Date、Char。
保留字段
Odoo在所有模型中默认创建的几个字段。这些字段由系统管理,不允许写入。
- id(Id)
记录的唯一标识符。 - create_date(Datetime)
记录的创建日期。 - create_uid(Many2one)
创建记录的用户。 - write_date(Datetime)
记录的最后修改日期。 - write_uid(Many2one)
最后修改记录的用户。
特殊字段
默认情况下,Odoo还要求所有模型上都有一个name
字段,用于各种显示和搜索行为。可以通过设置_rec_name
覆盖用于这些目的的字段。
“定义模型”练习
在openacademy模块中定义一个新的数据模型“Course”(课程)。一门课程有标题和说明。课程必须有标题。
编辑文件openacademy/models/models.py
以包含课程类。
openacademy/models.py
from odoo import models, fields, api
class Course(models.Model):
_name = 'openacademy.course'
_description = "OpenAcademy Courses"
name = fields.Char(string="Title", required=True)
description = fields.Text()
数据文件
Odoo是高度数据驱动的系统。尽管模块中的一些数据是在加载时通过Python代码设置的。
存在一些仅用于向Odoo添加数据的模块。
通过数据文件(带有<record>
元素的XML文件 )声明模块数据。每个<record>
元素都会创建或更新数据库记录。
<odoo>
<record model="{模块名称}" id="{外部标识}">
<field name="{字段名称}">{字段值}</field>
</record>
</odoo>
model
是Odoo模型的名称id
是一个外部标识符,它允许引用记录(而不必知道其数据库内标识符)。<field>
元素具有name属性,这是模型中字段的名称(例如:description)。它的节点内容是字段的值。
必须在清单文件中声明要加载的数据文件,可以在data
列表中(始终加载)或在demo
列表中声明数据文件(仅在演示模式下加载)。
“定义演示数据”练习
创建演示数据,并用一些演示课程填充“课程”模型。
编辑文件openacademy/demo/demo.xml
以包含一些数据。
<odoo>
<record model="openacademy.course" id="course0">
<field name="name">Course 0</field>
<field name="description">Course 0's description
Can 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>
</odoo>
仅在安装或更新模块时才加载数据文件的内容。
进行一些更改后,请不要忘记使用odoo-bin -u openacademy
将更改保存到数据库中。
操作和菜单
操作和菜单是数据库中的常规记录,通常通过数据文件声明。可以通过三种方式触发动作:
- 通过单击菜单项(链接到特定操作)
- 通过单击视图中的按钮(如果这些按钮已连接到动作)
- 作为对对象的上下文动作
由于菜单的声明有些复杂,因此有一个<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创建菜单之前,动作必须存在于数据库中。
“定义新菜单项”练习
定义新的菜单项以访问OpenAcademy菜单项下的课程。用户应该能够:
– 显示所有课程的清单
– 创建/修改课程
- 创建
openacademy/views/openacademy.xml
文件,创建动作和触发该动作的菜单。 - 将它添加到
openacademy/__manifest__.py
清单文件的data
列表。
openacademy/manifest.py
'data': [
# 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
],
# only loaded in demonstration mode
'demo': [
openacademy/views/openacademy.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- 窗口操作
<!-- 下面的标签是一个“窗口操作”的操作定义,这是打开一个视图或一组视图的操作。 ->
<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_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">Create the first course
</p>
</field>
</record>
<!-- 顶级菜单:没有父级菜单 -->
<menuitem id="main_openacademy_menu" name="Open Academy"/>
<!-- 左侧菜单的第一级菜单项,必须在使用“action=”之前定义 -->
<menuitem id="openacademy_menu" name="Open Academy"
parent="main_openacademy_menu"/>
<!-- 下面的菜单项,必须在父菜单“openacademy_menu”和操作“course_list_action”之后出现-->
<menuitem id="courses_menu" name="Courses" parent="openacademy_menu"
action="course_list_action"/>
<!-- 完整的id位置:
action="openacademy.course_list_action"
如果是同一个模块,则它不是必须的。-->
</odoo>