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中。

  1. 调用命令odoo-bin scaffold openacademy addons
  2. 调整模块清单。
  3. 不必理会其他文件。

模块清单: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将更改保存到数据库中。

操作和菜单

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

  1. 通过单击菜单项(链接到特定操作)
  2. 通过单击视图中的按钮(如果这些按钮已连接到动作)
  3. 作为对对象的上下文动作

由于菜单的声明有些复杂,因此有一个<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菜单项下的课程。用户应该能够:
– 显示所有课程的清单
– 创建/修改课程

  1. 创建openacademy/views/openacademy.xml文件,创建动作和触发该动作的菜单。
  2. 将它添加到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>