模块开发

简介

OpenERP 采用 三层架构。 应用层本身被设计成核心和多个附加的模块,这些模块可以安装,不用OpenERP创建一个特殊的配置。

OpenERP的核心和其他不同的模块都是用 Python 写的。 模块的功能通过XML-RPC(或是NET-RPC,这取决于服务器端的配置)显示出来。模块也采用OpenERP ORM在关系型数据库(PostgreSQL)中来持久化数据。当模块安装好后通过提供XML文件就可以在数据库中插入数据。

尽管使用模块是构建一个复杂应用的简单方式,在OpenERP中模块同样可以扩展系统。事实上,模块也被称为addons(它们有时也被称为plugins)。

在OpenERP的典型配置中,以下模块是必须的:

  • base: 最基本的模块。它无论何种情况下都必须安装,被认为是OpenERP核心的一部分。定义了 ir.property, res.company, res.request, res.currency, res.users, res.partner, 等等。
  • crm: 客户关系管理和供应商关系管理。
  • sale: 销售管理。
  • mrp: 制造资源管理。

通过使用Python, XML files,依赖OpenERP的ORM和它的延伸机制,新的模块可以很容易快速的写出。OpenERP的开源特性和它众多的模块也为新模块的开发提供很多的例子。

模块结构

模块

  1. 介绍
  2. 文件和文件夹结构
    1. __openerp__.py
    2. __init__.py
    3. XML 文件
      1. 操作
      2. 菜单
      3. 报表
      4. 向导
  3. 配置文件

模块 - 文件和文件夹结构

所有模块都应放到 server/addons 文件夹下。

按下面步骤创建新模块:

  • 在 server/addons 文件夹下面创建模块子文件夹
  • 创建模块定义文件: __openerp__.py
  • 创建定义 objectsPython 文件
  • 创建包括(初始数据, 视图, 菜单, 演示数据)的 .xml 文件
  • 创建包含 reports, wizardsworkflows 的 xml文件(可选)

模块 - 文件和目录 - XML 文件

模块目录里的XML文件用于修改数据库结构,他们有很多的用途,我们可以列出来:

  • 初始化,实例化(demonstration)数据声明(declaration)
  • 视图声明
  • 报表声明
  • 向导声明
  • 工作流声明

OpenERP XML文件主要结构的更多细节在 XML 数据序列化 部分,如果你想学习更多关于*初始化* 和 示例数据 XML文件,可以查看这里。 下面的部分只是关于特定的XML文件,如 following section are only related to XML specific to actions, menu entries, *操作、菜单、报表、向导工作流 定义。

Python 模块描述文件 __init__.py

** __init__.py 文件**

这个文件就像任何的Python模块中一样,在程序的开始运行。它负责导入程序所需的Python文件。

所以,如果你想创建一个“module.py”文件,它包括你的对象的描述,在这种情况下,你需要在__init__.py文件中写一行:
import module

OpenERP 模块描述文件 __openerp__.py

在已经创建的模块目录中,我们需要添加这样一个文件__openerp__.py。这个文件必须是Python的格式书写,用于:

1.确定所需的XML文件,server在进行初始化时将从语法上分析这些文件。 2.确定该创建模块的依赖模块。

这个文件包括下面的值:

name

(英文) 模块名称。

version

模块版本号, 2 位 (1.2 或 2.0).

description

模块的描述,包含模块的使用文档。

author

模块编者

website

模块的网址

license

模块的许可证(默认是GPL)

depends

init

List of .xml files to load when the server is launched with the “–init=module” argument. Filepaths must be relative to the directory where the module is. OpenERP XML File Format is detailed in this section.

data

List of .xml files to load when the server is launched with the “–update=module” launched. Filepaths must be relative to the directory where the module is. OpenERP XML File Format is detailed in this section.

demo

List of .xml files to provide demo data. Filepaths must be relative to the directory where the module is. OpenERP XML File Format is detailed in this section.

installable

True或是False,决定这个模块是否可安装。

images

List of .png files to provide screenshots, used on http://apps.openerp.com.

active

True或是False(默认是False),决定这个模块在数据库创建时是否安装。

test

List of .yml files to provide YAML tests.

Example

以product模块中的__openerp__.py为例:

{
    "name" : "Products & Pricelists",
    "version" : "1.1",
    "author" : "Open",
    "category" : "Generic Modules/Inventory Control",
    "depends" : ["base", "account"],
    "init_xml" : [],
    "demo_xml" : ["product_demo.xml"],
    "update_xml" : ["product_data.xml", "product_report.xml", "product_wizard.xml",
                    "product_view.xml", "pricelist_view.xml"],
    "installable": True,
    "active": True
}

放置在init_xml中的文件必须要么是和工作流定义相关,要么是安装软件时装载数据相关,或是和示例数据相关。

update_xml中的文件涉及到视图,报表和向导。

对象

所有OpenERP的资源都是对象,如menus,actions,reports,invoices,partners... OpenERP通过数据库的对象关系映射(ORM,object relational mapping of a database)来控制信息存储。OpenERP的对象名是层次结构的,例如:

  • account.transfer : a money transfer
  • account.invoice : an invoice
  • account.invoice.line : an invoice line

总之,第一个单词是模块的名字:account,stock,sale

ORM的其他优点有:

  • simpler relations : invoice.partner.address[0].city
  • objects have properties and methods: invoice.pay(3400 EUR),
  • inheritance, high level constraints, ...

操作一个对象比很多表要容易些。

../../_images/pom_3_0_3.png

The Physical Objects Model of [OpenERP version 3.0.3]

PostgreSQL

OpenERP的ORM是在PostgreSQL上构造的。在OpenERP上通过对象接口或是直接使用SQL语句查询一个对象是可行的。

在PostgreSQL数据库中直接进行读写是非常危险的,因为可能会漏掉重要的步骤如约束检查或是工作流的修改。

Note

The Physical Database Model of OpenERP

Pre-Installed Data

PostgreSQL表中的数据可以使用XML文件来进行插入或更新,使得于OpenERP对象数据一致。OpenERP XML文件的主要结构是:

<?xml version="1.0"?>
<openerp>
  <data>
    <record model="model.name_1" id="id_name_1">
      <field name="field1">
        "field1 content"
      </field>
      <field name="field2">
        "field2 content"
      </field>
      (...)
    </record>
    <record model="model.name_2" id="id_name_2">
        (...)
    </record>
    (...)
  </data>
</openerp>

Fields content are strings that must be encoded as UTF-8 in XML files.

让我们回顾一下另一个例子(base模块中的base_demo.xml):

<record model="res.company" id="main_company">
    <field name="name">Tiny sprl</field>
    <field name="partner_id" ref="main_partner"/>
    <field name="currency_id" ref="EUR"/>
</record>
<record model="res.users" id="user_admin">
    <field name="login">admin</field>
    <field name="password">admin</field>
    <field name="name">Administrator</field>
    <field name="signature">Administrator</field>
    <field name="action_id" ref="action_menu_admin"/>
    <field name="menu_id" ref="action_menu_admin"/>
    <field name="address_id" ref="main_address"/>
    <field name="groups_id" eval="[(6,0,[group_admin])]"/>
    <field name="company_id" ref="main_company"/>
</record>

上面的这个record定义了admin user:

  • 明确定义了login,password等 * ref属性用于在records之间建立关系
<field name="company_id" ref="main_company"/>

字段company_id是一个从user object到company object的many-to-one的关系,main_company是相关联的id。

  • eval字段使得XML中有很多python代码:这里的groups_id字段是many2many的。“[(6,0,[group_admin])]”的意思是:移除与当前用户相关的所有groups,使用list[group_admin]作为新的相关groups(并且group_admin is the id of another record)。
  • Search字段是当你不知道它的XML id时,用来查找相关记录(record)。当你查找所需记录时可以特别指定一个查找标准。这个标准相对于预定义的查找方法最好是一个相同形式元祖的列表(The criteria is a list of tuples of the same form than for the predefined search method.)。如果有很多查找记录,程序自动选择任意一个(第一个):
<field name="partner_id" search="[]" model="res.partner"/>

这是个在demo数据中使用search的典型例子。在这里我们并不是真正想知道是哪个partner,所以我们给出了一个空的list。注意model属性是在一般情况下必须要写的。

Record Tag

Description

T新数据的添加是通过record标签实现的。它利用一个必备的属性:model。Model是一个对象名称,可以用来实现插入数据。record标签内还有一个可选择的属性:id。如果使用了这个属性,那么在相同文件中,这个名字可以代替新创建的资源ID。

record标签中包含field标签。他们指出record的字段值(record’s fields value)。如果这个field没有详细说明,那么它会使用默认值。

Example

<record model="ir.actions.report.xml" id="l0">
     <field name="model">account.invoice</field>
     <field name="name">Invoices List</field>
     <field name="report_name">account.invoice.list</field>
     <field name="report_xsl">account/report/invoice.xsl</field>
     <field name="report_xml">account/report/invoice.xml</field>
</record>
Field tag

field标签包含的属性如下所示:

name : (必须有的)field name

eval : (可选)将指定值进行添加的python表达式

ref : 这个文件中涉及到已定义的id

model :用于查找的model

search :查询

Function tag

一个功能标签包含其他的功能标签。

model :(必须有的)要调用的model

name :(必需)function的名称

eval
eval :估值(evaluate)要调用的方法的参数列表,不计cr和uid

Example

<function model="ir.ui.menu" name="search" eval="[[('name','=','Operations')]]"/>
Getitem tag

得到该标签最近子节点估值的子集

type :(必需)int 或 list

index :(必需)int or string

Example

Evaluates to the first element of the list of ids returned by the function node

<getitem index="0" type="list">
    <function model="ir.ui.menu" name="search" eval="[[('name','=','Operations')]]"/>
</getitem>

i18n

改进翻译
Translating in launchpad

翻译由“Launchpad Web interface”管理。在这里你会找到可译项目的清单。

请在问问题前阅读 FAQ

Translating your own module

Changed in version 5.0.

和之前4.2.x的版本不同,现在翻译都是通过模块来做。所以和之前整个系统中有一个特殊i18n文件夹不同的是,现在每一个模块都有自己的i18n文件夹。此外,OpenERP可以处理.po文件作为导入导出格式。当我们安装或是更新一个模块时,安装语言的翻译文件可以自动装入系统中。OpenERP也可以产生一个.tgz文件归档,里面包括为每个选中模块组织很好的.po文件。

[1]http://www.gnu.org/software/autoconf/manual/gettext/PO-Files.html#PO-Files

Process

Defining the process

通过界面(interface)或是模块recorder来定义进程。然后放置生成的XML文件在自己的模块中。

Views

Technical Specifications - Architecture - Views

视图是一种在客户端显示对象的方式。他们指示客户端如何在屏幕上显示对象数据。

视图有两种表现形式:

  • 表单视图
  • 列表视图

Lists是tree views中的特殊情形。

同一个对象有几种视图:首先定义的视图样式(tree,form,…)将会做为它默认的样式。那样的话,当你双击一个菜单项时,就有一个默认的tree view和一个特定的view显示差不多的信息。例如,products针对product变量有几种视图。

视图都是在XML文件中进行描述的。

如果一个对象没有定义视图,那么这个对象可以自己产生一个视图来显示它自己。这会限制开发者的工作,但是会导致较少的人们自己的视图设计(ergonomic views)。

Usage example

当我们打开一张发票时,接下来是在客户端上的操作:

  • 一个动作请求打开发票(它给出了一个对象的数据(account.invoice),视图,域(例如仅仅是还未付款的发票))
  • 客户端请求server,什么样的视图由发票对象定义,哪些数据要显示。
  • 客户端通过视图显示表单
../../_images/arch_view_use.png
To develop new objects

对新对象的设计限制到最低限度:创建对象并且有选择的创建视图来显示他们。PostgreSQL的table数据不用手写,因为对象会自动创建它们(除非它们已经存在)。

Reports

OpenERP使用一个非常灵活和强大的报表系统。报表以PDF或是HTML的形式生成。报表是以数据层和表现层分开的原理进行设计的。

关于报表更多的细节在 Reporting 章节。

Wizards

这里有个描述向导的.xml文件的例子:

<?xml version="1.0"?>
<openerp>
    <data>
     <wizard string="Employee Info"
             model="hr.employee"
             name="employee.info.wizard"
             id="wizard_employee_info"/>
    </data>
</openerp>

向导的声明是通过使用wizard标签。想要知道更多关于向导XML文件的信息可以查看“Add A New Wizard”部分。

或者你可以在菜单中通过使用下面的XML entry添加向导。

<?xml version="1.0"?>
</openerp>
     <data>
     <wizard string="Employee Info"
             model="hr.employee"
             name="employee.info.wizard"
             id="wizard_employee_info"/>
     <menuitem
             name="Human Resource/Employee Info"
             action="wizard_employee_info"
             type="wizard"
             id="menu_wizard_employee_info"/>
     </data>
</openerp>

Workflow

通过对象和视图,我们可以很简单的定义新的表单,lists/trees和它们间的交互。但是这还不够:你还得定义这些对象间的动态关系。 .. i18n: A few examples: ..

举个例子:

  • 在一般的情况下,一个已确定的销售订单必须生成一张发货单。
  • 只是在确认发货单已付款的前提下,才会开出运送清单。

工作流使用图表描述这些交互,一个或几个工作流相关到对象。工作流是非必须的;一些对象就没有工作流。

下面的工作流用于销售订单的例子。在一定的条件下,它必须产生发货单和出货。

../../_images/arch_workflow_sale.png

在这张图表中节点代表着要做的动作。

  • 创建发票
  • 取消销售订单
  • 生成装货单, ...

上面的箭头代表条件:

1.等待订单获得批准 2.发票支付 3.点击取消按钮,。。。

方格样式的节点代表其他的工作流:

  • 发票
  • 发货

OpenERP 模块描述文件 : __openerp__.py

一般模块

在已创建模块的目录下,你必须添加一个__openerp__.py文件。这个文件必须在Python的格式下,负责:

  1. 确定所需的XML文件,server在进行初始化时将从语法上分析这些文件。
  2. 1.确定已创建模块的依赖。

这个文件包括下面的值:

name

(英文)名称.

version

版本

description

描述

author

模块的作者

website

模块的网站

license

模块的授权协议(默认AGPL).

depends

列出该模块所依赖的其他模块,因为base模块包括模块必须的视图,报表等数据,所以base模块应该在其他所有模块的依赖中。

init_xml

List of .xml files to load when the server is launched with the “–init=module” argument. Filepaths must be relative to the directory where the module is. OpenERP XML File Format is detailed in this section.

update_xml

List of .xml files to load when the server is launched with the “–update=module” launched. Filepaths must be relative to the directory where the module is. OpenERP XML File Format is detailed in this section.

installable

True或是False,决定这个模块是否可安装。

active

True或是False(默认是False),决定这个模块在数据库创建时是否安装。

示例

以product模块中的__openerp__.py为例:

{
    "name" : "Products & Pricelists",
    "version" : "1.1",
    "author" : "Open",
    "category" : "Generic Modules/Inventory Control",
    "depends" : ["base", "account"],
    "init_xml" : [],
    "demo_xml" : ["product_demo.xml"],
    "update_xml" : ["product_data.xml","product_report.xml", "product_wizard.xml","product_view.xml", "pricelist_view.xml"],
    "installable": True,
    "active": True
}

放置在init_xml中的文件必须要么是和工作流相关,要么是安装软件时装载数据相关,或是和示例数据相关。

update_xml中的文件涉及到视图,报表和向导。

Profile 模块

一个profile的目的是在数据库创建后直接使用一组模块来初始化OpenERP。这个profile是一种特殊的模块,它不包含代码,只是 依赖于其他的模块

为了创建一个新的profile,你需要在server/addons里建一个新目录(可以给它取名为profile_modulename)。在新目录里放一个空的__init__.py文件和__openerp__.py。这个文件的结构是:

{
     "name":"''Name of the Profile'',
     "version":"''Version String''",
     "author":"''Author Name''",
     "category":"Profile",
     "depends":[''List of the modules to install with the profile''],
     "demo_xml":[],
     "update_xml":[],
     "active":False,
     "installable":True,
}

示例

我们以文件server/bin/addons/profile_manufacturing/__openerp__.py中的代码为例,它对应着OpenERP中的manufacturing industry profile。

{
     "name":"Manufacturing industry profile",
     "version":"1.1",
     "author":"Open",
     "category":"Profile",
     "depends":["mrp", "crm", "sale", "delivery"],
     "demo_xml":[],
     "update_xml":[],
     "active":False,
     "installable":True,
}

创建模块

Getting the skeleton directory

你可以从其他任意模块中复制文件__openerp__.py和__init__.py到一个新目录来创建一个新模块。

Ubuntu中一个例子:

$ cd ~/workspace/stable/stable_addons_5.0/
$ mkdir travel
$ sudo cp ~/workspace/stable/stable_addons_5.0/hr/__openerp__.py ~/workspace/stable/stable_addons_5.0/travel
sudo cp ~/workspace/stable/stable_addons_5.0/hr/__init__.py ~/workspace/stable/stable_addons_5.0/travel

你如果想修改这个目录,你需要设置自己的权限在这个目录上:

$ sudo chown -R `whoami` travel

进入新模块的目录,里面有个框架结构,你仍需要去更改模块定义里面的东西。

Changing the default definition

为了更改模块“travel”里面的默认设置,我们需要进入“travel”目录,编辑__openerp__.py文件。

$ cd travel
$ gedit __openerp__.py

文件里面类似下面:

{
  "name" : "Human Resources",
  "version" : "1.1",
  "author" : "Tiny",
  "category" : "Generic Modules/Human Resources",
  "website" : "http://www.openerp.com",
  "description": """
  Module for human resource management. You can manage:
  * Employees and hierarchies
  * Work hours sheets
  * Attendances and sign in/out system

  Different reports are also provided, mainly for attendance statistics.
  """,
  'author': 'Tiny',
  'website': 'http://www.openerp.com',
  'depends': ['base', 'process'],
  'init_xml': [],
  'update_xml': [
      'security/hr_security.xml',
      'security/ir.model.access.csv',
      'hr_view.xml',
      'hr_department_view.xml',
      'process/hr_process.xml'
  ],
  'demo_xml': ['hr_demo.xml', 'hr_department_demo.xml'],
  'installable': True,
  'active': False,
  'certificate': '0086710558965',
}

你可能会更改任意你觉得正确的东西,像下面这样:

{
    "name" : "Travel agency module",
    "version" : "1.1",
    "author" : "Tiny",
    "category" : "Generic Modules/Others",
    "website" : "http://www.openerp.com",
    "description": "A module to manage hotel bookings and a few other useful features.",
    "depends" : ["base"],
    "init_xml" : [],
    "update_xml" : ["travel_view.xml"],
    "active": True,
    "installable": True
}

注意“active”字段变成了true。

Changing the main module file

现在你需要更新travel.py脚本以满足你自己的模块的需要。我们建议你遵循Flash教程,或是从20minutes教程页面来下载travel agent模块。

The documentation below is overlapping the two next step in this wiki tutorial,
so just consider them as a help and head towards the next two pages first...

travel.py文件应该看起来是这样:

from osv import osv, fields

class travel_hostel(osv.osv):
       _name = 'travel.hostel'
       _inherit = 'res.partner'
       _columns = {
       'rooms_id': fields.one2many('travel.room', 'hostel_id', 'Rooms'),
       'quality': fields.char('Quality', size=16),
       }
       _defaults = {
       }
travel_hostel()

理想情况下,你会拷贝那些代码几次来创建你所需要的实体(travel_airport, travel_room, travel_flight)。这就是你的对象的数据库结构,但是你真的不需要担心数据库端。当你安装模块时,这个文件会为你创建系统架构。

Customizing the view

接下来你可以编辑视图。编辑custom_view.xml文件,像这样:

<openerp>
<data>
    <record model="res.groups" id="group_compta_user">
            <field name="name">grcompta</field>
    </record>
    <record model="res.groups" id="group_compta_admin">
            <field name="name">grcomptaadmin</field>
    </record>
    <menuitem name="Administration" groups="admin,grcomptaadmin"
                    icon="terp-stock" id="menu_admin_compta"/>
</data>
</openerp>

就像你看到的,这是个accounting系统的例子。

定义视图就是定义访问你的模块时的用户界面。这里定义的这些字段已经是一个完整的界面。然而,由于做这个的复杂性,我们建议,再一次,从链接http://www.openerp.com/download/modules/5.0/下载travel agent模块。

下次你可以使用其他的文件来定义不同的视图,并且在你的basic/admin视图中分开它们。

Action creation

Linking events to action

可用类型的事件是:

  • client_print_multi (print from a list or form)
  • client_action_multi (action from a list or form)
  • tree_but_open (double click on the item of a tree, like the menu)
  • tree_but_action (action on the items of a tree)

从事件到动作的映射是:

<record model="ir.values" id="ir_open_journal_period">
    <field name="key2">tree_but_open</field>
    <field name="model">account.journal.period</field>
    <field name="name">Open Journal</field>
    <field name="value" eval="'ir.actions.wizard,%d'%action_move_journal_line_form_select"/>
    <field name="object" eval="True"/>
</record>

如果你双击journal/period (object: account.journal.period),将会打开一个选中的向导(id=”action_move_journal_line_form_select”).

只是当用户点击特定的对象时,你可以使用res_id字段来允许这个动作。

<record model="ir.values" id="ir_open_journal_period">
    <field name="key2">tree_but_open</field>
    <field name="model">account.journal.period</field>
    <field name="name">Open Journal</field>
    <field name="value" eval="'ir.actions.wizard,%d'%action_move_journal_line_form_select"/>
    <field name="res_id" eval="3"/>
    <field name="object" eval="True"/>
</record>

当用户点击account.journal.period n°3时,这个动作将会触发。

当你声明向导,报表或是菜单时,ir.values的创建会自动由下面的标签完成:

  • <wizard... />
  • <menuitem... />
  • <report... />

所以一般不需要自己加映射。