Odoo10开发教程一(构建模块)

警告
本教程需要已经安装odoo

启动/停止Odoo服务器

Odoo采用C/S架构,客户端通过Web浏览器访问服务端,遵从RPC协议。业务逻辑和扩展通常在服务端执行,而只有添加客户端支持的新特征才会在客户端添加代码(例如,交互过程中新数据的映射表示)。启动服务器,只需要在shell中调用命令odoo-bin,或者完整的路径名调用:

odoo-bin

通过Ctrl-c或杀死相应的系统进程来停止Odoo服务。

构建一个Odoo模块

服务端扩展和客户端扩展都被封装为模块,这些模块可选择性的被安装,安装完成后通过数据库来加载。模块即可以是全新的业务逻辑,也可以是更改和扩展已有的业务逻辑。比如创建一个中国会计模块,将中国的会计准则添加到Odoo的通用会计中,也可以创建一个全新的实时可视化管理车队的模块。Odoo中的所有功能都是包含在模块中。

模块的组成

Odoo模块包含多个部分:
业务对象
  Python类,这些类会被Odoo框架自动持久化,持久化的方式决定于类的定义。
数据文件
  包括视图、菜单、动作、工作流、权限、演示数据等,以XML或CSV文件定义。
Web控制器
  处理Web浏览器的请求
静态页面数据
  网站或界面使用的图片、CSS或JavaScript文件

模块结构

每个模块都是模块目录中的一个子目录。可以通过--addons-path选项指定模块目录的路径。

提示
大多数命令行选项可以通过配置文件进行设置

Odoo模块由清单文件进行声明。查看清单文件文档了解详细信息。模块是一个包含__init__.py文件的的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",

    # 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>
    <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_unlink
access_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>

对象关系映射

Odoo的关键组件是ORM层。这个层避免了大量手写SQL,并提供扩展性和安全性。业务对象被声明为Model类的扩展类,并自动将它们集成到持久层中。可以通过定义时设置属性来配置模型。最重要的属性是_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,default: field's name)
  用户界面中的字段标签(用户可见)
required(bool,default:False)
  如果为True,这个字段不能为空,它必须有一个默认值或者在创建记录时总是给定一个值。
help (unicode, default: '')
  长格式,在用户界面上显示的提示。
index (bool, default: False)
  请求Odoo在列上创建数据库索引。

简单字段
有两大类字段:简单字段和关联字段,简单字段的值是存储在模型表中的原子值,而关联字段是指向模型(相同模型或不同模型)的记录行。
简单字段的例子如:Boolean、Date、Char
关联字段的例子如:Many2One、One2Many、Many2Many

保留字段
Odoo在所有模型中都创建几个固定字段,这些字段由系统管理,用户程序不应写入。但是可以在用户程序中读取:
id(Id)
  模型中记录的唯一标识符
create_date(Datetime)
  记录的创建日期
create_uid(Many2one)
  创建记录的用户
write_date(Datetime)
  记录的最后修改时间
write_uid(Many2one)
  上次修改记录的用户

特殊字段
默认情况下,Odoo的name在所有模型上还需要一个字段,用于显示和搜索。用于这些目的的字段可以通过设置字段_rec_name来实现。

练习定义模型,在openacademy模块中定义新的数据模型课程,每门课程包含两个字段,标题和描述,其中标题是必填字段。编辑文件openacademy/models/models.py以包含Course类。

openacademy/models.py

from odoo import models, fields, api

class Course(models.Model):
    _name = 'openacademy.course'

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

数据文件

Odoo是一个高度数据驱动的系统,虽然行为是通过Python代码制定的,但一些模块的值是在加载时通过数据文件设置的。

提示:
一些模块的作用仅仅是为了将数据添加到Odoo中

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

<odoo>
    <data>
        <record model="{model name}" id="{record identifier}">
            <field name="{a field name}">{a value}</field>
        </record>
    </data>
</odoo>
  • model是在记录行中记录的Odoo模型名称
  • id是外部标识符,它允许引用记录(而不必知道其在数据库中的标识符)
  • <field>,每个<field>对应数据行中的一个字段,name属性是字段名(例如description),而body就是字段的值。
    数据文件通过在manifest文件声明而被载入,他们既可以在data列表声明(始终载入)也可以在demo列表声明(仅在演示模式下载入)

练习定义演示数据,添加演示数据以填充Course模型的数据,编辑文件openacademy/demo/demo.xml来添加演示数据

openacademy/demo/demo.xml

<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.点击菜单项(链接到特定操作)
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必须在对应的菜单建立之前就存在于数据库中。

练习定义新菜单项,在开放学院菜单项下定义新菜单项来访问课程。用户应该能够:

  • 显示所有课程的列表
  • 建立或编辑课程
    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>
    <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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容