Odoo10 开发者文档(3)--建立一个模块·

来源:互联网 发布:红十字会知乎 编辑:程序博客网 时间:2024/05/20 05:56

警告
该教程需要 已安装odoo

开启/停止Odoo服务
odoo使用客户端/服务器架构,客户端是通过RPC(远程过程调用协议)访问Odoo服务的web浏览器。
业务逻辑和扩展通常在服务器端执行,尽管支持客户端功能(如,想交互式地图的新数据表示)可以添加到客户端。
为了启动服务器,在shell内简单的调用命令Odoo,如果必要添加文件的完整路径

odoo-bin

服务器按Ctrl-C两次从终端停止,或杀死相应的OS进程。


建立一个Odoo模块
服务器和客户端扩展都打包为可选择的加载在数据库中的模块。
Odoo模块既可以添加新的业务逻辑到Odoo系统,也可以修改和扩展现有的业务逻辑:一个模块可以被创建为Odoo通用会计支持添加你的国家的会计准则,而下一个模块,增加了对公车的实时可视化支持。
因此,odoo的一切都随着模块的开始结束而开始结束。

1.模块的组成
一个Odoo模块可以包含多个元素:
业务对象:
声明为Python的类,这些资源是自动持续的,通过基于配置的Odoo。
数据文件
XML或CSV文件声明元数据(视图或工作流),配置数据(模块参数化),演示数据和更多
Web controllers
处理Web浏览器的请求
静态web数据
Web界面或网站使用的图片,CSS或JS文件

2.模块结构
每个模块是模块目录内的目录。模块目录通过–addons-path 选项被指定。

提示
大多数命令行选项也可以使用配置文件来设置

odoo模块是通过它的manifest声明的。一个模块也是一个带__init__.py的Python包,包含模块中各种Python文件的导入说明。
例如,如果模块有一个mymodule.py__init__.py应该包含文件:

from . import mymodule

Odoo提供了一种机制来帮助建立一个新的模块,odoo-bin有一子命令scaffold 创建一个空的模块:

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

该命令为模块创建一个子目录,并自动为模块创建一堆标准文件。其中大部分只包含注释代码或xml。这些文件的使用将在本教程中解释。

练习
模块创建
使用命令行上创建一个空的模块Open Academy,并安装在Odoo上。
1. 调用命令odoo-bin scaffold openacademy odoo/addons
2. 将manifest文件更新到模块。
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",    # 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 controllersfrom . 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>    <data>        <!--  -->        <!--   <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> -->        <!--  -->    </data></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_unlinkaccess_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
openacademy/templates.xml:<odoo>    <data>        <!-- <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> -->    </data></odoo>

3.对象关系映射

Odoo的一个关键组成部分是ORM层。这一层可以避免手工编写大多数SQL并提供了可扩展性和安全性的服务。
业务对象被声明为Python类扩展模型,该模型将它们集成到自动持久化系统中。
模型可以通过在它们的定义中设置一些属性进行配置。最重要的属性是_name,这是必要的,在Odoo中定义系统模型的名字。这里是一个模型的最小完整定义:

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

4.模型文件
字段用于定义模型可以存储和在哪里。字段被定义为模型类的属性:

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

(1) 共同属性
就像模型本身一样,它的字段可以通过将配置属性作为参数来配置:

name = field.Char(required=True)

一些属性在所有领域都可用,这里是最常见的:
string (unicode, default: field's name) 用户界面中字段的标签(用户可见)。
required (bool, default: False) 如果为TRUE,字段不能为空,则必须具有默认值或在创建记录时始终给予值。
help (unicode, default: '') Long-form, 为用户提供了一个帮助提示。
index (bool, default: False) 要求 Odoo创建列上的数据库索引。

(2) 简单字段
有两种类型的字段:“simple”字段,它们直接存储在模型表中的原子值,以及连接记录(同一模型或不同模型)的“relational”字段。
简单字段的例子:Boolean, Date, Char.

(3) 保留字段
Odoo在所有模型创造了一些字段。这些字段由系统管理,不应写入。如果有用或必要,它们可以被读取:
id (Id) 模型中记录的唯一标识符。
create_date (Datetime) 记录的创建日期.
create_uid (Many2one) 创建记录的用户。
write_date (Datetime) 记录的最后修改日期。
write_uid (Many2one) 最后修改记录的用户。

(4) 特殊字段
默认情况下,Odoo所有的模型也需要一个name字段,以便各种显示和搜索行为。被用于这些目的的字段能通过设置_rec_name重写。

练习
定义一个模型
在openacademy 模块里 定义一个新的数据模型Course ,course 有标题(title)和描述(description) ,必须有标题
编辑openacademy/models/models.py文件,包含到Course 类中。

openacademy/models.py:from odoo import models, fields, apiclass Course(models.Model):    _name = 'openacademy.course'    name = fields.Char(string="Title", required=True)    description = fields.Text()

5.数据字段
虽然使用Python代码定制行为,但模块的值的一部分在加载时设置的数据中。

提示
一些模块的存在只为数据添加到Odoo。

模块数据通过数据文件,带有<record>元素的xml文件声明。每个元素创建或更新数据库记录。

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

· model是用于记录Odoo模型名称。
· id是一个外部标识符,它允许引用到记录(而不必知道它的内部数据库标识符)。
· <field> 元素的name是模型中字段的name(例如描述)。他们的body是字段的值。

必须在清单文件中声明要加载的数据文件。它们可以在“data”列表中(总是加载)或在“demo”列表中声明(仅在演示模式下加载)。

练习
定义演示数据 ,用演示课程创建演示数据,完善课程模式。
编辑openacademy/demo/demo.xml文件来包含一些数据

openacademy/demo.xml:<odoo>    <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></odoo>
  1. Actions和Menus
    动作和菜单是数据库中的常规记录,通常通过数据文件声明。动作可以用三种方式触发:
    (1) 通过点击菜单项(链接到特定的动作)
    (2) 通过点击视图的按钮 (如果被连接到动作)
    (3) 作为对象的上下文操作
    因为菜单的声明有点复杂,有一个 <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"/>

危险
必须在xml文件的相应菜单前声明该操作。
数据文件是按顺序执行的,在菜单能创建前,动作的id必须存在于数据库中

-

练习
定义新的菜单入口
在OpenAcademy 菜单入口定义新的菜单入口来访问课程,用户应该能够:
· 显示所有课程列表
· 创建/修改课程
(1) 创建openacademy/views/openacademy.xml,附带一个action和触发action的菜单。
(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>    <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>

基本视图
视图定义显示模型记录的方式。每种类型的视图表示一个可视化模式(一个记录列表,一个聚合的图形,…)。视图可以被要求通过他们的类型(例如类合作伙伴名单)或专门通过它们的ID的通用要求,具有正确的类型和优先级最低的视图将被使用(所以各类型优先级最低的视图是默认视图为型)。
视图继承允许改变在别处声明的视图(添加或删除内容)。

  1. 通用视图声明
    视图被声明为ir.ui.view模型的记录,视图类型由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>, <tree>, <graph>, ... -->    </field></record>

危险
视图的内容是XML.因此,arch字段必须声明为type="xml",以便正确解析。

2.树视图
树视图,也被称为列表视图。以表单形式显示记录。
根元素是<tree>,树视图的最简单形式只列出了表中显示的所有字段(每个字段列):

<tree string="Idea list">    <field name="name"/>    <field name="inventor_id"/></tree>

3.表单视图
表单被用于创建和编辑单个记录。
根元素是<form>,它们由高层次结构元素(groups、notebooks)和交互元素(按钮和字段)组成:

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

练习
使用XML制作form视图
给Course 对象创建form视图,数据应该显示:Course 的name和description。

openacademy/views/openacademy.xml:<?xml version="1.0" encoding="UTF-8"?><odoo>    <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",

练习
Notebooks
在Course 表单视图里,将“description”字段置于选项卡之下,以便稍后添加其他选项卡,包含附加信息。
如下修改Course 表单视图:

openacademy/views/openacademy.xml                    <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>

表单视图也可以使用纯HTML来进行更灵活的布局:

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

4.搜索视图
搜索视图自定义与列表视图关联的搜索字段(以及其他聚合视图)。它们的根元素是<search>,它们由定义哪些字段可以被搜索的字段组成:

<search>    <field name="name"/>    <field name="inventor_id"/></search>

如果模型没有搜索视图,Odoo产生一个只允许用名称字段检索的搜索视图。

练习
搜索视图
允许根据他们的标题或描述来搜索课程。

openacademy/views/openacademy.xml            </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",

模型之间的关系
例如,销售订单记录与包含客户数据的客户机记录有关,它还与销售订单线记录有关。

练习
创建一个session 模型
对于Open Academy 模块,我们考虑一个session 模型:一个session 是一个给定的时间给定的观众在一个给定的时间发生的课程。
创建session 模型。session 具有名称、开始日期、持续时间和若干个座位。添加一个动作和菜单项来显示它们。通过菜单项使新模型可见。
1、在openacademy/models/models.py中创建session 类
2、在openacademy/view/openacademy.xml中给session 对象添加入口

openacademy/models.py    name = fields.Char(string="Title", required=True)    description = fields.Text()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/views/openacademy.xml        <!-- 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></odoo>

注意
digits=(6, 2)指定浮点数的精度:6是数字的总数,而2是小数点后的位数。请注意,它的结果在小数点前是最大值4位数字。

1.关系字段
关系字段链接记录,包括相同的模型(层次结构)或不同模型之间的链接记录。
关系字段类型:
(1) Many2one(other_model, ondelete='set null')
到另一个对象的简单链接:

print foo.other_id.name

(2) One2many(other_model, related_field)
一个虚拟的关系,一个many2one的逆。一个one2many作为容器的记录,访问的结果是一个(可能为空)的记录集:

for other in foo.other_ids:    print other.name

危险
因为One2many 是一个虚拟的关系,在other_model里必须有一个Many2one字段,name必须是related_field 。

(3)Many2many(other_model)
双向多重关系,一方的任何记录都可以与另一方的任何记录有关。作为记录的容器,访问它也会导致一组可能空的记录:

for other in foo.other_ids:    print other.name

练习
Many2one 关系
使用Many2one ,修改Course 和Session 模型,反映他们与其他模型的关系:
· course有一个responsible 用户,该字段的值是内建模型res.users的一个记录。
· Session 有一个instructor,该字段的值是内建模型res.partner的一个记录。
· 一个session被连接到一个course,该字段的值是模型openacademy.course的一个记录,并且是必须的。
· 更新视图

添加Many2one字段到模型里, 并将它们添加到视图中。

openacademy/models.py    name = fields.Char(string="Title", required=True)    description = fields.Text()    responsible_id = fields.Many2one('res.users',        ondelete='set null', string="Responsible", index=True)class Session(models.Model):    _name = 'openacademy.session'----------    start_date = fields.Date()    duration = fields.Float(digits=(6, 2), help="Duration in days")    seats = fields.Integer(string="Number of seats")    instructor_id = fields.Many2one('res.partner', string="Instructor")    course_id = fields.Many2one('openacademy.course',        ondelete='cascade', string="Course", required=True)
openacademy/views/openacademy.xml                    <sheet>                        <group>                            <field name="name"/>                            <field name="responsible_id"/>                        </group>                        <notebook>                            <page string="Description">----------           </field>        </record>        <!-- override the automatically generated list view for courses -->        <record model="ir.ui.view" id="course_tree_view">            <field name="name">course.tree</field>            <field name="model">openacademy.course</field>            <field name="arch" type="xml">                <tree string="Course Tree">                    <field name="name"/>                    <field name="responsible_id"/>                </tree>            </field>        </record>        <!-- window action -->        <!--            The following tag is an action definition for a "window action",----------                <form string="Session Form">                    <sheet>                        <group>                            <group string="General">                                <field name="course_id"/>                                <field name="name"/>                                <field name="instructor_id"/>                            </group>                            <group string="Schedule">                                <field name="start_date"/>                                <field name="duration"/>                                <field name="seats"/>                            </group>                        </group>                    </sheet>                </form>            </field>        </record>        <!-- session tree/list view -->        <record model="ir.ui.view" id="session_tree_view">            <field name="name">session.tree</field>            <field name="model">openacademy.session</field>            <field name="arch" type="xml">                <tree string="Session Tree">                    <field name="name"/>                    <field name="course_id"/>                </tree>            </field>        </record>        <record model="ir.actions.act_window" id="session_list_action">            <field name="name">Sessions</field>            <field name="res_model">openacademy.session</field>

练习
one2many 逆关系
使用one2many逆关系字段,修改模型,以反映course和session之间的关系
修改course类 ,在course表单视图添加该字段。

openacademy/models.py    responsible_id = fields.Many2one('res.users',        ondelete='set null', string="Responsible", index=True)    session_ids = fields.One2many(        'openacademy.session', 'course_id', string="Sessions")class Session(models.Model):
openacademy/views/openacademy.xml                            <page string="Description">                                <field name="description"/>                            </page>                            <page string="Sessions">                                <field name="session_ids">                                    <tree string="Registered sessions">                                        <field name="name"/>                                        <field name="instructor_id"/>                                    </tree>                                </field>                            </page>                        </notebook>                    </sheet>

练习
多重many2many 关系
利用关系字段many2many,修改session模型 ,将一组人与每一个课程关联。出席者将通过伙伴的记录作为代表,所以我们将与内置的模型res.partner关联。相应地调整视图。
修改session类,并添加到表单视图

openacademy/models.py    instructor_id = fields.Many2one('res.partner', string="Instructor")    course_id = fields.Many2one('openacademy.course',        ondelete='cascade', string="Course", required=True)    attendee_ids = fields.Many2many('res.partner', string="Attendees")
openacademy/views/openacademy.xml                                <field name="seats"/>                            </group>                        </group>                        <label for="attendee_ids"/>                        <field name="attendee_ids"/>                    </sheet>                </form>            </field>

继承
1.模型继承
Odoo提供两继承机制,以模块化的方式扩展现有的模型。
第一继承机制允许模块修改在其他模块中定义的模型的行为:
· 添加字段到模块
· 重写模型上字段的定义
· 向模型添加约束,
· 向模型添加方法,
· 在模型上重写现有方法。
第二继承机制(委托)允许将模型的每个记录与父模型中的记录连接起来,并提供对父记录字段的透明访问。
这里写图片描述

2.视图继承
与其修改现有的视图(通过重写他们),Odoo提供的视图继承让子“拓展”视图应用在顶部的根视图,并可以从父内容添加或删除。
拓展视图使用inherit_id字段参考它的父视图。而不是单一的,arch字段是由任意数量的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 用于匹配元素的操作:

2 0
原创粉丝点击