2.1 模块开发
模块-介绍
OpenERP采用三层架构(数据库、应用服务器、客户端)。应用层本身被设计成一个核心模块和许多附加模块。可以选择是否安装某些附加模块来创建一个openerp的特定配置。
OpenERP的核心和其他不同的模块都是用python写的。模块的功能通过XML-RPC(或是NET-RPC,这取决于服务器的配置)提供给客户端调用。模块也采用OpenERP对象关系映射在关系型数据库(PostgreSQL)中来保存数据。模块中可以包含XML(CSV 或者 YML)文件以便在模块安装过程中向数据库中插入数据。
模块化不仅是构建复杂系统的一个简洁的方式,在OpenERP中模块还用于扩展现有系统。模块也叫附加项(也可以叫做插件)。
在典型的OpenERP配置,通常有以下模块:
base:最基本的模块。它无论何种情况下都必须安装,被认为是OpenERP核心的一部分。定义了ir.property , res.company, res.request, res.currency, res.user, res.partner等等。
crm:客户/供应商关系管理
sale:销售管理
mrp:制造资源管理
通过使用Python, XML文件,依赖OpenERP的ORM和它的扩展机制,可以快速开发新的模块。OpenERP的开源特性和它众多的模块也为新模块的开发提供了很多的参考实例。
安装最小化必须有的模块 5.x base、base_setup 6.x base、base_setup board、hr、process、resource
模块结构
模块
- 介绍
- 文件和目录
- __openerp__.py
- __init__.py
- XML文件
- 操作
- 菜单
- 报表
- 向导
3.Profiles(配置文件)
模块 – 文件和目录
下面是创建一个新模块所必须的几个步骤:
1.在addons目录下新建一个子目录。
2.新建一个模块描述文件文件:__openerp__.py(__terp__.py),新建一个初始化文件:__init__.py
3.新建一个包含对象的Python文件
4.新建xml文件来加载数据(包括视图,菜单, 示例数据,…)
5.创建报表,向导,或是工作流。
模块 – 文件和目录 – XML 文件
模块目录里的XML文件用于帐套预配置,他们有很多的用途,我们可以列出来:
A.初始化数据,示例数据
B.视图声明
C.报表声明
D.向导声明
E.工作流声明
OpenERP XML文件主要结构的更多细节在XML数据配置部分,如果你想学习更多关于通过XML文件做初始化、示例数据,可以查看这里。下面的部分只是关于特定的XML文件,如动作,菜单, 报表, 向导 ,工作流的声明。
Python 模块描述文件 __init__.py
这个文件就像任何的Python模块中一样,在程序的启动时运行。它负责导入程序所需的Python包文件。所以,如果你创建了一个包含模块对象定义代码的“module.py”文件,您必须在_init_.py 文件里 添加:
Import module
OpenERP 模块描述文件 __openerp__.py
在已经创建的模块目录中,我们需要添加这样一个文件__openerp__.py。这个文件必须是Python的格式书写,用于:
1.确定服务器初始化期间,将解析的XML文件。
2.确定该创建模块的依赖模块。
这个文件包括下面的值:
name : 模块名称(短)
version : 模块版本号
这里的版本号比较有趣,界面上显示额版本号会加上openerp的大版本号,也就是说你这里写1.0,那5.0的界面上显示5.0.1.0。 如果你把这个模块copy到6.0系统里,他的版本号就是6.0.1.0。即使它实际上是不能用的(因为底层技术差异)。
description : 模块的描述(长)
author : 模块作者
website : 模块的网址
license : 模块的许可证(默认是GPL-2)
depends : 列出该模块所依赖的其他模块,因为base模块包括模块必须的视图,报表等数据,所以base模块应该在其他所有模块的依赖中。
init_xml :在模块安装时或启动openerp-server是用–init=module_name 参数强制重新初始化此模块时用于更新数据库的数据文件。
update_xml:在模块安装或升级时或启动openerp-server时用–update=module_name 参数强制升级此模块时用于更新数据库的数据文件。
统初始化时,后期不需要UPDATE的数据、或UPDATE会造成系统不可用时用INIT,比如公司信息; UPDATE是在更新模块时也执行导入,比如有新的业务伙伴、新的产品需要导入时,不过我觉得那样最好新的XML,不要在原有的XML文件上加; 如果在新建帐套是选择加载演示数据,那么新的模块在安装时,系统会自动导入,否则不导入.
以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中的文件包含视图,报表和向导的定义。
对象(object)
所有OpenERP的资源都是对象,如菜单,动作,报表,发票,业务伙伴……OpenERP通过数据库的对象关系映射(ORM,object relational mapping of a database)来控制信息存储。OpenERP的对象名是层次结构的,例如
* account.transfer : 付款单 * account.invoice : 发票 * account.invoice.line : 发票行
总之,第一个单词是模块的名字:account,stock,sale
ORM的其他优点有:
* 简化关系 : invoice.partner.address[0].city * 对象有属性和方法: invoice.pay(3400 EUR), * 继承, 应用服务层的约束检查 ...
用Python操作一个对象(如partner)比用SQL操作很多表(res_partner, res_partner_adress, res_partner_category, events)要容易很多。
对象关系图[OpenERP 版本 3.0.3]
PostgreSQL
OpenERP的ORM是在PostgreSQL之上构造的。在OpenERP上通过对象接口或是直接使用SQL语句查询对象都可以。
用SQL语句在在PostgreSQL数据库直接写入数据是非常危险的,因为可能会漏掉重要的步骤,如约束检查或是工作流的修改。
预先载入数据
PostgreSQL表中的数据,也就是OpenERP中的对象数据,可以使用XML文件来进行插入或更新。OpenERP XML文件的主要结构是:
XML中的字段内容是utf-8编码的字符串
让我们再看另一个例子(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>
最后一条记录定义了admin用户:
- login, password等字段很容易理解
- ref 属性用于指定 与其它对象间的相互引用
<field name="company_id" ref="main_company"/>
字段company_id是一个从user对象到company对象的many-to-one的关系型字段,main_company是相关联的XML_id(即record标签里包含的id属性值)
eval属性可以在XML文件中嵌入python代码:这里的groups_id字段是many2many的。 “[(6,0,[group_admin])]”的意思是:移除与当前用户相关的所有groups,使用list[group_admin]作为新的相关groups(并且group_admin是另一个record的XML_id)。
Search属性是当你不知道XML id时,用来查找相关记录。你要输入几个查找记录的条件。这个条件是一个tuple的list,和对象内置的search方法的参数一样。 如果找到了很多条记录,程序自动选择第一个:
<field name="partner_id" search="[]" model="res.partner"/>
这是个在demo数据中使用search的典型例子。在这里我们并不是真正想知道是哪个partner,所以我们给出了一个空的list。注意model属性是在一般情况下必须的。
Record 标签
新数据的添加是通过record标签实现的。这其中需要一个必需的属性:model。Model是做插入的对象的名称。record标签内还有一个可选择的属性:id。如果使用了这个属性,那么在相同文件中,这个id可以用于其他记录引用这条记录。
1.有这个属性, 同一模块中 可以直接使用 id 来进行 ref 的引用. 不同模块. 可以使用 模块.id 来进行ref 引用. 2.可以把原来的数据覆盖掉
record标签中可以包含field标签。指定record的字段值。未指定值的字段取默认值。
Field 标签
field标签包含的属性如下所示:
name :(必须有的)field name
eval :(可选)将指定值进行添加的python表达式
ref :这个文件中涉及到已定义的id
model :用于查找的model
search :查询
Function 标签
function标签可以嵌套其他function标签。
model :(必须有的)方法所在的对象
name :(必需)方法的名称
eval :方法的参数列表,不计cr和ui
function标签可以在unit test里用到,也可以在xml里用function标签模拟一些对象的动作
Getitem 标签
得到该标签最近子节点估值的子集
type :(必需)int or list
index :(必需)int or string(String 为 diction)
例子:
<getitem index="0" type="list"> <function model="ir.ui.menu" name="search" eval="[[('name','=','Operations')]]"/> </getitem>
i18n
改进翻译
翻译由Launchpad的网络界面管理。在这里你会找到可翻译条目的清单。
请在提问前阅读常见问题。
翻译你自己的模块
从openerp5.0开始,和之前4.2.x的版本不同,现在翻译都是逐个模块来做。所以和之前整个系统中只有一个i18n文件夹不同的是,现在每一个模块都有自己的i18n文件夹。此外,OpenERP可以处理.po文件作为导入导出格式。当我们安装或是更新一个模块时,安装语言的翻译文件可以自动装入系统中。OpenERP也可以产生一个tgz压缩文件,里面包括为每个选中模块组织好的.po文件。
http://www.gnu.org/software/autoconf/manual/gettext/PO-Files.html#PO-Files
工作流(Process)
定义工作流
在openerp界面上定义流程并用模块录制功能记录下来。然后把生成的xml文件放在你自己的模块内。
视图(Views)
Technical Specifications – Architecture – Views
视图是一种在客户端显示对象的方式。他们指示客户端如何在屏幕上显示对象数据。视图有两种表现形式:
- 表单视图(form views)
- 列表视图(tree views)
Lists是tree views中的特殊情形。
同一个对象有几种视图:首先定义的视图样式(tree,form,…)将会做为它默认的样式。那样的话,当你双击一个菜单项时,就有一个默认的tree view和一个特定的view显示差不多的信息。例如,products针对product变量有几种视图。
视图都是在XML文件中进行描述的。
如果一个对象没有定义视图,那么这个对象可以自己产生一个视图来显示它自己。这会限制开发者的工作,但是会导致较少的人们自己的视图设计(ergonomic views)。
用法示例
当我们打开一张发票时,接下来是在客户端上的操作:
- 要打开发票的动作(此动作上给出了对象名称(account.invoice),视图,过滤条件(例如只显示还未付款的发票))。
- 客户端(通过XML-RPC)询问服务器,发票对象有哪些视图,要显示哪些数据。
- 客户端通过视图显示表单
开发一个对象
定义新对象只需有限的工作量:创建对象,创建视图(可选)。PostgreSQL的表定义语句不用手写,因为对象会自动创建(或修改)这些表。
报表(report)
OpenERP使用一个非常灵活和强大的报表系统。报表以PDF或是HTML的形式生成,以数据层和表现层分开的原理进行设计的。
关于报表更多的细节在Reporting章节。
向导(wizard)
一个定义向导的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语句在菜单中添加向导。
<?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和它们间的交互。但是这还不够:你还得定义这些对象间的动态关系。
- 在某些情况下,一张已确定的销售单必须生成一张发票
- 在某些情况下,一张已付款的发票会开出发货单。
工作流使用图形来描述这些相互关联。一个对象可关联到一个或多个工作流,但不是必需的,某些对象没有工作流。
下面就是一个工作流的例子,用于销售订单。特定条件下,它必须生成发票和出库单。
在这个图中,节点表示动作完成后的状态
- 创建发票
- 取消销售订单
- 生成发货单
上面的箭头代表条件:
- 等待订单获得批准
- 发票支付
- 点击取消按钮,。。。
方格样式的节点代表其他的工作流:
- 发票
- 发货单
OpenERP 模块描述文件 : __openerp__.py
普通模块
在已经创建的模块目录中,我们需要添加这样一个文件__openerp__.py。这个文件必须是Python的格式书写,用于:
1.确定服务器初始化期间,将解析的XML文件。
2.确定该创建模块的依赖模块。
这个文件包括下面的值:
name : 模块名称(短)
version : 模块版本号
这里的版本号比较有趣,界面上显示额版本号会加上openerp的大版本号,也就是说你这里写1.0,那5.0的界面上显示5.0.1.0。 如果你把这个模块copy到6.0系统里,他的版本号就是6.0.1.0。即使它实际上是不能用的(因为底层技术差异)。
description : 模块的描述(长)
author : 模块作者
website : 模块的网址
license : 模块的许可证(默认是GPL-2)
depends : 列出该模块所依赖的其他模块,因为base模块包括模块必须的视图,报表等数据,所以base模块应该在其他所有模块的依赖中。
init_xml :在模块安装时或启动openerp-server是用–init=module_name 参数强制重新初始化此模块时用于更新数据库的数据文件。
update_xml:在模块安装或升级时或启动openerp-server时用–update=module_name 参数强制升级此模块时用于更新数据库的数据文件。
统初始化时,后期不需要UPDATE的数据、或UPDATE会造成系统不可用时用INIT,比如公司信息; UPDATE是在更新模块时也执行导入,比如有新的业务伙伴、新的产品需要导入时,不过我觉得那样最好新的XML,不要在原有的XML文件上加; 如果在新建帐套是选择加载演示数据,那么新的模块在安装时,系统会自动导入,否则不导入.
例子
以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 Module)
貌似在v6不再使用
一个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, }
模块的创建
获取模块框架
你可以从其他任意模块中复制文件__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
然后你再进到模块文件夹里去创建模块定义所需的代码或者数据文件…
更改默认的定义
为了更改模块“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。
修改主要的模块文件
现在你需要更新travel.py脚本以满足你自己的模块的需要。我们建议你按照Flash教程,或是从20minutes教程页面来下载travel agent模块。
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)。这就是你的对象的数据库结构,但是你真的不需要担心数据库端。当你安装模块时,这个文件会为你创建系统架构。
定义视图
接下来你可以编辑视图。编辑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>
如你所见,这个文件来自account模块。 定义视图就是定义访问你的模块时的用户界面。这里定义的这些字段已经是一个完整的界面。然而,由于做这个的复杂性,我们再次建议从链接http://www.openerp.com/download/modules/5.0/下载travel agent示例模块。接着你可以使用其他的文件来定义不同的视图,以此来把它们从basic/admin视图中分开。
创建动作
关联事件和动作
可用的事件类型有:
- client_print_multi(列表或表单上的报表)
- client_action_multi(列表或表单上的动作)
- tree_but_open(在树节点上双击,如菜单)
- tree_but_action(树的动作)
从事件到动作的映射是:
<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>
当你声明向导,报表或是菜单时,以下标签会自动创建ir.values记录:
<wizard… />
<menuitem… />
<report… />
所以一般不需要自己加映射。