Odoo开发--Getting started with Odoo development

Odoo入门课程的学生资料, 参考书点击这里.

#1. 起步

##1.1 安装

##1.2 Odoo版本号说明

Odoo各个主版本号之间数据库不兼容;
Odoo主版本号保证”API stable”, 即同一个主版本中model的数据结构以及view的各个要素保持不变.

##1.3 Configuration

  • 查看更多server选项: ./odoo.py —help
  • 保存配置文件: ./odoo.py —save —stop-after-init
  • 使用特定配置文件: ./odoo.py —conf=
  • 改变服务器监听端口: ./odoo.py —xmlrpc-port=8080
  • log输出等级: ./odoo.py —log-level=[debug | debug_sql | debug_rpc]
  • log输出位置: ./odoo.py —logfile=, 默认是stdout
  • debug模式: ./odoo.py —debug, 当异常发生, 将启动Python调试器(pdb)

##1.4 启用技术特性和开发者模式

#2 开发一个Odoo应用

##2.1 概述

Odoo基础上的开发就是开发我们自己的模块, 本章及第三章开发一个TODO应用, 具有添加任务, 显示任务, 标记任务为完成, 清除已完成任务功能. 在此过程中可以学习到Odoo的MVC方面的知识:

  • model: 定义数据的结构. model层使用Python对象定义, 数据保存在PostgreSQL数据库中, 数据映射由Odoo自动管理.
  • view: 描述用户界面. views使用XML定义, 用于web client框架根据数据生成的HTML视图.
  • controller: 支持商业逻辑. web客户端视图与服务器ORM交互完成数据持久化操作, 这些操作可以是简单的读写删除, 也可以是调用ORM Python对象中定义的复杂的业务逻辑.

##2.2 应用(applications)和模块(modules)

一个模块可以为Odoo增加特性或修改Odoo的特性, 一个模块保存在一个包含模块描述文件(__openerp__.py)的目录中. 模块是Odoo应用的组成部分, 与模块不同, 应用提供某个功能, 应用中的某个模块提供一个核心特性, 应用中的其他模块为这个中心模块添加其他特性. 例如人力资源管理, 账号模块作为核心, 其他很多模块为其添加特性, 构成了HR应用

##2.3 修改和扩展模块
除了创建新的模块, 还可以在已有模块的基础上进行扩展, Odoo的”继承(inheritance)”机制允许自定义模块扩展已有的模块, 继承可以发生在几乎任何层次的数据模型, 业务逻辑以及用户界面层.

##2.4 创建一个新的模块
从Odoo 8.0开始, __openerp__.py的description内容可以利用一个放在模块根目录下的README.rst或README.md文件.

创建两个文件: __init__.py和__openerp__.py, 在__openerp__.py中添加以下内容:

{
   'name': 'To-Do Application',
   'description': """
   This module adds TODO to mail.
   """,
   'author': 'Jeff Zhang',
   'depends': ['mail'],
   'application': True,
}

其中’application’为true表示该模块设定为一个应用.

##2.5 增加模块路径

为了让Odoo能够找到我们新创建模块, 需要将该模块所在的路径添加到Odoo addons path中, 除了直接编辑.openerp_sever.rc文件外, 还可以使用以下的方法:

$ cd ~/odoo-dev
$ odoo/odoo.py -d v8dev —addons-path=“custom-addons, odoo/addons” —save
  • -d v8dev参数指定所使用的数据库
  • —save说明将这次设定的模块路径保存下来, 以后只需要odoo.py启动即可, 不再需要加—addons-path参数.

##2.6 安装新模块

选择setting=>Local Modules, 因为TODO设定为一个应用, 所以不需要删除应用的搜索项, 直接输入todo, 就可以搜索到.

Install new modules

##2.7 升级模块

###normal

模块开发是一个迭代的过程, 需要经常查看源码修改的效果. 这个工作可以通过升级来做到, 在Local Modules中找到相应的模块, 如果它已安装, 将会在该模块的详细信息中看到Upgrade按钮.

但是, 如果仅仅改变了Python代码, 那么点击Upgrade按钮可能不会有任何效果, 这种情况下, 应当重启应用服务器. 如果Python代码和数据文件都有了改变, 应该两步都做, 也即更新模块并重启应用服务器. (数据文件的变化说明界面, 权限, 菜单, 动作等有变化, 需要更新DB中相应的数据)

###better way

更方便的方法: 关闭应用服务器, 并在重启服务进程时通过参数指明更新数据库.

为了在启动服务进程时为jeff数据库更新todo_app模块, 使用以下命令启动:

$ ./odoo.py -d jeff -u todo_app

启动时, 在终端可以看到以下信息:

2015-06-27 02:45:01,364 2367 INFO jeff openerp.modules.module: module todo_app: creating or updating database tables

-u 选项要求-d 选项, 并且接受用逗号分隔的欲更新模块列表, 例如:

-u todo_app, mail

###更新模块列表和卸载模块

更新模块列表及卸载模块操作无法在命令行完成, 必须通过web界面.

##2.8 创建模型

模型用一个派生自Odoo模板类的Python类实现, 它们被转换为一个数据库对象, Odoo在安装和升级模块时自动处理这些事情.

将所有模型的Python模块放在”models”子目录中是一个好的实践.

Step 1 创建一个models子目录, 并在该目录下添加一个__init__.py文件, 内容如下:

# -*- coding: utf-8 -*-
#!/usr/bin/env python

import todo_model

Step 2 在models目录下创建一个todo_model.py文件, 内容如下:

# -*- coding: utf-8 -*-
#!/usr/bin/env python

from openerp import models, fields

class TodoTask(models.Model):
  _name = 'todo.task'
  name = fields.Char('Description', required=True)
  is_done = fields.Boolean('Done?')
  active = fields.Boolean('Active?', default=True)

Step 3 在模块根目录(todo_app)下的__init__.py中, 添加以下内容:

from models import todo_model

模型编写完毕, Ctrl+C结束服务进程然后使用以下命令重新启动:

$ ./odoo.py -d jeff -u todo_app

然后在Local Modules中找到todo app, 并点击升级按钮. 在技术=>数据库结构=>模型中找到todo.task模型, 点击它查看详细信息.

Model

从上图可以看到, 除了模型中设定的几个字段外, 还有几个Odoo自动添加的字段.

注意: 其他Odoo模块用_name(例如本例的todo.task)来区分不同模型, 而不是Python类名.

##2.9 添加菜单

上一节的模型用于在数据库中保存数据, 现在将它显示在用户界面上. 只需要加一个菜单, 打开todo.task即可, 其他工作Odoo自动完成.

添加菜单需要在一个XML格式的data file中完成, 与模型文件类似, 所有视图定义相关的文件放在”views”子目录中是一个好的实践.

Step 1 在模块根目录下新建一个views目录, 在其中新建一个名为”todo_view.xml”的文件, 内容如下:

<?xml version="1.0"?>
<openerp>
    <data>
        <!-- Action to open todo task list -->
        <act_window id="action_todo_task"
                    name="To-do Task"
                    res_model="todo.task"
                    view_mode="tree,form" />

        <!-- Menu item to open todo task list -->
        <menuitem id="menu_todo_task"
                  name="To-Do Tasks"
                  parent="mail.mail_feeds"
                  sequence="20"
                  action="action_todo_task" />
    </data>
</openerp>

Step 2 在模块根目录下的__openerp__.py中的字典中增加视图的data files:

'data': ['views/todo_view.xml'],

然后, 在Local Modules中找到todo模块并升级它, 然后就可以在”消息”下看到”To-Do Tasks”菜单了.

Menu

在本节, 只是定义了菜单入口, 并没有定义如何显示数据, 但是Odoo自动给出了默认的添加编辑等视图.

###错误处理

如果升级是发生错误, 屏蔽上一次的修改, 例如本节内容如果出错, 注释掉__openerp__.py中’data’的todo_veiw部分设定, 确认错误发生位置, 然后一步步进行检查处理.

##2.10 创建视图 - form, tree及search

前一节没有定义任何视图, 这种情况下Odoo为数据自动生成基本的视图, 为了或得更合适的视图, 就需要定制模块的视图.

Odoo支持多种类型的视图, 其中三种最主要的是: list(也称为tree), form和search视图.

所有的视图保存在数据库中的ir.model.view模型中. 为了在模块中添加视图, 需要XML格式的在data file中定义元素, 在其中给出描述视图的定义, 当模型安装时, data file中的视图定义会载入到数据库中.

###创建form视图

编辑todo_view.xml文件, 在节内增加节, 内容如下:

<?xml version="1.0"?>
<openerp>
    <data>
      <record id="view_form_todo_task" model="ir.ui.view">
          <field name="name">To-do task Form</field>
          <field name="model">todo.task</field>
          <field name="arch" type="xml">

              <form string="To-do Task">
                  <field name="name"/>
                  <field name="is_done"/>
                  <field name="active" readonly="1"/>
              </form>

          </field>
      </record>

      <!-- actions and menus ...... -->

    </data>
</openerp>

这段代码在ir.ui.view中添加一条标识为view_form_todo_task的记录. 这段代码定义了以下信息:

  • 这个视图对应的模型是todo.task;
  • 这个视图被命名为To-do Task Form, 这个名称不需要唯一, 仅是一个信息, 但是该名称应当是从字面容易理解其功能的.

最重要的属性是arch, 其中给出了视图的定义, 上面的一段xml代码首先说明这个视图是一个form, 包括三个字段, 并且设定active字段为只读.

View1

odoo 8.0版这样定以后, view中没有字段的说明, 但可以看到三个字段中最后一个active字段已是只读.

###格式化视图

上面创建了一个基本的视图, 与odoo自动生成的相比没什么区别.

下面一步步的开始定制, 首先增加header区域并将各个字段分组显示. 将form部分修改为以下内容:

<form string="To-do Task">
    <header>
        <!-- buttons go here -->
    </header>
    <sheet>
        <group name="group_top">
            <group name="group_left">
                <field name="name"/>
            </group>
            <group name="group_right">
                <field name="is_done"/>
                <field name="active" readonly="1"/>
            </group>
        </group>
    </sheet>
</form>

View2

###添加动作按钮

form中可以定义按钮用于执行一些动作, 例如触发工作流动作, 执行窗口动作(例如打开另一个form, 运行model中的Python函数等).

按钮可以放在form中的任何地方, 但是放在

节中更好. 在header节中增加以下内容:

<form string="To-do Task">
    <header>
        <button name="do_toggle_done" type="object"
                string="Toggle Done" class="oe_highlight"/>
        <button name="do_clear_done" type="object"
                string="Clear All Done"/>
    </header>
    <sheet>
      <!-- ...... -->
    </sheet>
</form>

按钮基本的属性有:

  • name: 动作的标识, 对应一个定义在model中的Python函数;
  • string: 按钮上的文本;
  • class: 制定一个CSS类, 类似HTML中应用CSS.

View3

###完成后的form视图

完成form试图定义后的todo_view.xml如下:

<?xml version="1.0"?>
<openerp>
    <data>
        <record id="view_form_todo_task" model="ir.ui.view">
            <field name="name">To-do task Form</field>
            <field name="model">todo.task</field>
            <field name="arch" type="xml">

                <form string="To-do Task">
                    <header>
                        <button name="do_toggle_done" type="object"
                                string="Toggle Done" class="oe_highlight"/>
                        <button name="do_clear_done" type="object"
                                string="Clear All Done"/>
                    </header>
                    <sheet>
                        <group name="group_top">
                            <group name="group_left">
                                <field name="name"/>
                            </group>
                            <group name="group_right">
                                <field name="is_done"/>
                                <field name="active" readonly="1"/>
                            </group>
                        </group>
                    </sheet>
                </form>

            </field>
        </record>

        <!-- Action to open todo task list -->
        <act_window id="action_todo_task"
                    name="To-do Task"
                    res_model="todo.task"
                    view_mode="tree,form" />

        <!-- Menu item to open todo task list -->
        <menuitem id="menu_todo_task"
                  name="To-Do Tasks"
                  parent="mail.mail_feeds"
                  sequence="20"
                  action="action_todo_task" />
    </data>
</openerp>

##2.11 创建list和search视图

当以列表(list)形式查看model时, 将会用到视图. 树视图可以显示具有层级关系的各行记录, 但大多情况下显示单个的行记录.

向todo_veiw.xlm中增加以下内容:

<record id="view_tree_todo_task" model="ir.ui.view">
    <field name="name">To-do Task Tree</field>
    <field name="model">todo.task</field>
    <field name="arch" type="xml">
        <tree colors="gray:is_done==True">
            <field name="name"/>
            <field name="is_done"/>
        </tree>
    </field>
</record>

以上代码定义了具有两列(name和is_done)的列表, 并且使用is_done==True来使完成的任务行显示为灰色.

List view

在列表的右上方显示了一个搜索框, 在节可以定义默认搜索字段及预定义的过滤器.

向todo_veiw.xml中添加如下内容:

<record id="view_filter_todo_task" model="ir.ui.view">
    <field name="name">To-do Task Filter</field>
    <field name="model">todo.task</field>
    <field name="arch" type="xml">
        <search>
            <field name="name"/>
            <filter string="Not Done"
                    domain="[('is_done','=',False)]"/>
            <filter string="Done"
                    domain="[('is_done','!=',False)]"/>
        </search>
    </field>
</record>

节中的定义在搜索框中输入时搜索的字段, 定义一些特定的预定义搜索方法, 例如上面预定义了两个检索条件, 一个是is_done字段为False(Not Done), 另一个表示is_done字段不为False(Done), 在搜索框中输入内容”Not Done”或”Done”的部分字符后, 将会在下拉的选项内容中看到这两个filter, 如下图所示.

Search view

##2.12 增加业务逻辑

前面在form视图中定义了两个按钮, 现在为这两个按钮增加按下时触发的动作逻辑. 通过编辑todo_model.py, 向其中定义的类中添加相应的函数即可. 以下的内容使用odoo 8.0中新增的API.

首先导入新API:

from openerp import models, fields, api

Toggle Done按钮的动作很简单, 仅仅将Is Done?字段的值取反. 对于处理一条记录的业务逻辑, 可以通过在函数上增加@api.one装饰, @api.one将会使传入函数的self参数代表一条记录. 代码如下:

@api.one
def do_toggle_done(self):
    self.is_done = not self.is_done
    return True

该方法简单地将is_done字段取反, 这种方法被客户端调用, 并且必须有返回值. 如果返回的是None, 客户端使用XMLRPC协议的调用将不起作用, 所以没有任何可返回对象, 一般的处理方法是返回True.

对于Clear All Done按钮, 点击它之后应该找到所有active的并且is_done的记录, 把它们标记为inactive. form的按钮一般假定仅处理选中的当前记录, 但是这里可以做一个简单的处理, 处理多条记录而不是当前的那一条. 代码如下:

@api.multi
def do_clear_done(self):
    done_recs = self.search([('is_done', '=', True)])
    done_recs.write({'active': False})
    return True

@api.multi装饰的方法中, self代表一个记录集(recordset), 记录集可以是一条记录(用于form时)或多条记录(tree). 在上面的代码中, 并没有使用self记录集, 而是构造了done_recs*这个自定义的记录集, 它之中包括所有被标记为已完成的记录, 最后将该记录中所有记录的active字段设置为False.

Odoo中, active字段是一个特殊的字段, 如果active=False, 那么这条记录自动不显示.

搜索这个API方法返回满足条件的记录, 搜索的条件使用domain(多个元组的列表)来给定.

write方法一次设置记录集中所有元素的值, 修改的值用字典来给定. 这里使用write的方法比迭代记录集中各条记录进行修改更有效.

do_clear_done没有使用self, 也就是说其实用@api.one也可以, 但为什么用@api.multi而不是@api.one:

@api.one修饰的方法将会为每个选中记录运行一次, 而@api.multi确保其修饰的方法即使有多条选中的记录也仅会执行一次.

##2.13 设定访问控制

现在载入模块时, 服务器控制台会有一条关于访问权限的警告日志, 如下:

AC Log

这条警告说明我们的新模型没有访问控制, 所以它只能被admin超级用户使用, 超级用户忽略数据访问规则. 为了让非超级用户能够使用todo, 需要做一些修改.

先来看看向一个model添加访问规则需要哪些信息, 设置=>技术=>安全=>访问控制列表, 打开访问控制列表并设置搜索条件为mail.mail, 如下图所示.

ACL mail

图中是mail.mail模型的ACL, 从中可以看出各个组在这个模型记录上允许哪些动作(读写创建删除)

这些信息应当由模块来提供, 利用一个data file来将其数据载入到ir.model.access模型中. 在这里, 为所有的employee组添加完整的todo访问, employee组是最基本的访问组, 几乎所有用户都属于该组.

创建一个CSV文件(security/ir.model.access.csv), 内容如下:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_todo_task_group_user,todo.task.user,model_todo_task,base.group_user,1,1,1,1
  • 模型id自动产生, 模型todo.task对应的id是model_todo_task;
  • employee组由base模块创建, id为base.group_user;
  • name仅用于信息作用, 但最好保持唯一, 核心模块中name一般使用点分隔的模型字符串和组字符串.

最后, 向__openerp__.py中添加这个data file:

'data': [
       'views/todo_view.xml',
       'security/ir.model.access.csv',
],

现在升级模块, 然后用另一个用户登录, 之前看不到的To-Do Tasks菜单可以在消息下看到了, 并且可以进行操作.

##2.14 行级访问规则

Odoo是一个多用户系统, 所以todo应用中的任务应当是属于某个用户的, Odoo提供了行级的访问控制来支持这个需求.

在技术=>安全=>记录规则中可以看到各个规则, 记录规则定义在ir.rule模型中, 创建一个security/todo_access_rules.xml数据文件来为todo定义行级访问规则, 该文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<openerp>
    <data noupdate="1">
        <record id="todo_task_user_rule" model="ir.rule">
            <field name="name">ToDo Tasks only for owner</field>
            <field name="model_id" ref="model_todo_task"/>
            <field name="domain_force">
                [('create_uid','=',user.id)]
            </field>
            <field name="groups" eval="[(4, ref('base.group_user'))]"/>
        </record>
    </data>
</openerp>

注意noupdate=”1”, 它说明此项数据在升级模块是不会被更新, 也即这项数据随后在webclient中的修改可以保存下来, 而不会因为应用下一次开发迭代的内容更新时将所做修改重新初始化.
当然, 如果希望每次都重新初始化该规则, 可以将其设置为noupdate=”0”.

groups字段有一个特殊的表达式([(4, ref(‘base.group_user’))]), 这是一个一对多关系字段. (4, x)元组表示将x附加到记录上, 其中这里的x是用base.group_user表示的employee组.

最后, 向__openerp__.py添加将数据文件载入模块的配置:

'data': [
              'views/todo_view.xml',
              'security/ir.model.access.csv',
              'security/todo_access_rules.xml',
       ],

##2.15 模块图标

模块目录下的”static/description/icon.png”文件.

Custom Icon

##2.16 删除模块

Step 1 首先点击”升级”按钮旁的”卸载”按钮卸载模块;
Step 2 然后点击header部分的”更多=>删除”删除模块;

Remove Module

Step 3 最后在文件系统删除插件模块目录.