1.构建odoo模块
True

1.1 构建一个Odoo模块

服务器和客户端扩展被打包成模块,可以选择加载到数据库中。
Odoo模块可以向Odoo系统添加全新的业务逻辑,或者更改和扩展现有的业务逻辑:例如可以创建一个模块,将您所在国家的会计规则添加到Odoo的通用会计支持,而另一个模块添加支持实时可视化的巴士车队。
因此,Odoo中的所有内容都以模块开头和结尾。

1.2 模块的组成

  • 业务对象
    声明为Python类,这些资源将自动保留由Odoo基于它们的配置
  • 数据文件
    XML或CSV文件声明元数据(视图或工作流),主要用于配置数据(模块参数化),演示数据等。
  • Web控制器
    处理来自Web浏览器的请求
  • 静态web数据
    图片,CSS或JavaScript文件由网络界面或网站使用

1.3 模块结构

一个Odoo模块也是一个Python模块, 存放在一个目录中, 包含一个__init__.py文件, 用于导入其他Python模块. 通过设置odoo.conf中的[addons-path]选项指定模块目录。

一个Odoo模块由它的清单文件声明。请查看关于它的清单文件文档。

模块同时也是一个包含了__init__.py文件的Python包,以及模块中多种Python文件的import指示。

例如,如果文件只包含一个mymodule.py文件,__init__.py文件就会包含:

from . import mymodule

Odoo提供了一个帮助设置一个新模块的机制,odoo-bin包含一个子命令scaffold来创建一个空的模块:

$ odoo-bin scaffold <module name> <where to put it>

该命令为您的模块创建了一个下级目录,并自动创建了一系列标准的文件。它们大多数包含注释性的代码或者XML。


练习 #1
执行 ./odoo-bin scaffold openacademy mymodules, 在 mymodules目录下创建一个名为openacademy的模块, 生成的目录文件结构如下.

各文件内容请查看文件或查看原文, 然后对__manifest__.py中的几种标识文本进行修改.

已复制

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",
   # Categories can be used to filter modules in modules listing
   # Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml
   # for the full list
   'category': 'Test',
   'version': '0.1',
   # any module necessary for this one to work correctly
   'depends': ['base'],
   # always loaded
   'data': [
       # 'security/ir.model.access.csv',
       'templates.xml',
   ],
   # only loaded in demonstration mode
   'demo': [
       'demo.xml',
   ],
}

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>

1.4 对象关系映射

ORM层是Odoo的一个关键组件,它可以避免大部分的SQL语句编写从而提高扩展性和安全性.
业务对象用派生自Model的Python类(模型)来编写,该类的_name属性定义了模型在Odoo系统中的名称.

模型可以通过在它们的定义中设置一定数量的属性来进行配置。最重要的属性是_name,它是必需的,并在Odoo系统中定义模型的名称。这里是一个模型的最小化的完整定义:

from odoo import models
class MinimalModel(models.Model):
    _name = 'test.model'

1.5 字段

字段定义模型能够存储什么以及在哪里存储, 字段在模型类中用属性来定义.

from odoo import models, fields
class LessMinimalModel(models.Model):
    _name = 'test.model2'

    name = fields.Char()
在/models下添加test.py文件
已复制
model属性详解:
_name:模型唯一标识,类非继承父类时必须指定。
_rec_name:数据显示名称,如设置则返回其指定的字段值,不设置默认显示字段为name的字段值,如无name字段则显示"模块名,id";详见BaseModel.name_get方法。
_log_access:是否自动增加日志字段(create_uid, create_date,write_uid, write_date)。默认为True。
_auto:是否创建数据库对象。默认为True,详见BaseModel._auto_init方法。
_table:数据库对象名称。缺省时数据库对象名称与_name指定值相同(.替换为下划线)。
_sequence:数据库id字段的序列。默认自动创建序列。
_order:数据显示排序。所指定值为模型字段,按指定字段和方式排序结果集。
例:_order = "create_date desc":根据创建时间降序排列。可指定多个字段。
不指定desc默认升序排列;不指定_order默认id升序排列。
_constraints:自定义约束条件。模型创建/编辑数据时触发,约束未通过弹出错误提示,拒绝创建/编辑。
格式:_constraints = [(method, 'error message', [field1, ...]), ...]
method:检查方法。返回True|False
error message:不符合检查条件时(method返回False)弹出的错误信息
[field1, ...]:字段名列表,这些字段的值会出现在error message中。
_sql_constraints:数据库约束。
例:_sql_constraints = [ ('number_uniq', 'unique(number, code)', 'error message') ]
会在数据库添加约束:
CONSTRAINT number_uniq UNIQUE(number, code)
_inherit:单一继承。值为所继承父类_name标识。如子类不定义_name属性,则在父类中增加该子类下的字段或方法,不创建新对象;如子类定义_name属性,则创建新对象,新对象拥有父类所有的字段或方法,父类不受影响。
格式:_inherit = '父类 _name'
_inherits:多重继承。子类通过关联字段与父类关联,子类不拥有父类的字段或方法,但是可以直接操作父类的字段或方法。
格式:_inherits = {'父类 _name': '关联字段'}

1.5.1 通用属性

与模型类似, 字段也可以通过参数传递对其进行设定:

name = field.Char(required=True)

字段的常用属性有:

Char:字符型,使用size参数定义字符串长度。
Text:文本型,无长度限制。
Boolean:布尔型(True,False)
Interger:整型
Float:浮点型,使用digits参数定义整数部分和小数部分位数。如digits=(10,6)
Datetime:日期时间型
Date:日期型
Binary:二进制型
selection:下拉框字段。

例:state = fields.Selection([('draft', 'Draft'),('confirm', 'Confirmed'),('cancel', 'Cancelled')], string='Status')

Html:可设置字体格式、样式,可添加图片、链接等内容。效果如下:

截于odoo自带项目管理模块

1.5.2 简单字段

字段可以分为两类: 简单字段和关系字段. 前者为原子值, 直接保存在模型对应的数据库表中; 后者连接到其他的记录上(可以是相同的模型也可以是不同的模型).

Boolean, Date, Char这些都是简单字段.

1.5.3 保留字段

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

复杂类型

参数

readonly:是否只读,缺省值False。
required:是否必填,缺省值Falsse。
string:字段显示名,任意字符串。
default:字段默认值
domain:域条件,缺省值[]。在关系型字段中,domain用于过滤关联表中数据。
help:字段描述,鼠标滑过时提示。
store:是否存储于数据库。结合compute和related使用。

例:sale_order = fields.One2many("sale.order", "contract_id",string="销售订单", domain=[('state','=','sale')])

compute:字段值由函数计算,该字段可不储存于数据库。

例:amount = fields.Float(string="金额总计", compute=‘_compute_amount’,store=True)
_compute_amount为计算函数。

related:字段值引用关联表中某字段。

以下代码表示:company_id引用hr.payroll.advicecompany_id

advice_id = fields.Many2one('hr.payroll.advice', string='Bank Advice')
company_id = fields.Many2one('res.company', related='advice_id.company_id', string='Company', store=True)

1.5.4 特殊字段

默认情况下, Odoo要求模型中有一个name字段, 用于显示和搜索, 通过设置_rec_name也可以达到这样的目的.

练习 #2

在openacademy模块中定义一个新的模型Course, openacademy/models.py内容如下:

# -*- coding: utf-8 -*-
from odoo import models, fields, api
class Course(models.Model):
    _name = 'openacademy.course'

    name = fields.Char(string="Title", required=True)
    description = fields.Text()

1.6 数据文件

Odoo是一个高度数据驱动的系统, 虽然使用Python代码来定制模块行为, 但很多模块数据是在其载入时setup的, 并且有些模块仅仅为Odoo添加数据.

通过数据文件来定义模块数据, 例如可以使用XML文件中的元素定义数据, 每一个元素创建或者更新数据库中的一条记录, 形式如下:

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

Odoo模型名.

  • id

外部ID(External Identifier), 通过它可以引用到记录(并且不需要知道记录所在的数据库ID).

  • field元素

name属性用于确定字段名称(例如description), 该元素的body给出字段的值.

数据文件必须在模块载入清单文件列表中, 也就是__openerp__.py的’data’列表(全部载入)或’demo’列表(只有设定为载入演示数据才会载入)中.

练习 #3

创建一个数据文件来向Course中添加数据, 编辑openacademy/demo.xml, 并确认__openerp__.py的’demo’列表中有该文件.

<odoo>
    <data>
        <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>
    </data>
</odoo>

1.7 动作和菜单

在Odoo中, 动作和菜单都是定义在数据库中的数据记录, 一般通过数据文件来定义.

动作可以由三种方式触发:

  1. 点击菜单项(菜单项链接到特定动作)
  2. 点击视图上的按钮(如果按钮连接到动作)
  3. 作为对象的上下文动作
使用

<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"/>

因为菜单的声明有一些复杂,通过快捷键<menuitem>来声明一个ir.ui.menu并把它连接到相应的动作会更容易一些。

注意: action必须先于menu的连接使用定义, 数据文件在载入时顺序地执行, 所以动作的ID必须首先存在于数据库中才能使用.

练习 #4

定义一个新的菜单项访问OpenAcademy课程.

  1. 创建openacademy/views/openacademy.xml文件, 并在其中添加动作和菜单.


openacademy/views/openacademy.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
    <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>
</odoo>
    2.在__openerp__.py中添加这个数据文件名到’data’.

'data': [
        # 'security/ir.model.access.csv',
        'views/templates.xml',
        'views/openacademy.xml',
    ],
'security/ir.model.access.csv',别注释


odoo优势介绍
True