Odoo 8.0深入浅出开发教程

转自:http://blog.sunansheng.com/python/odoo/odoo.html

Table of Contents

  • 1. ERP简介
    • 1.1. Odoo历史
    • 1.2. ERPⅡ或商业智能化
      • 1.2.1. 什么是商业智能
  • 2. Odoo框架简介
    • 2.1. python模块分析
    • 2.2. python2还是python3
  • 3. Odoo的安装和配置
    • 3.1. PostgreSQL数据库
    • 3.2. Ubuntu14.04下可能缺失的软件包
    • 3.3. 网页显示node.js方面
    • 3.4. 其他问题
    • 3.5. 通过命令行运行时的配置
      • 3.5.1. –xmlrpc-port=8888
      • 3.5.2. –addons-path=addons
      • 3.5.3. 数据库的一些配置
      • 3.5.4. –save
    • 3.6. 将安装环境封装起来
    • 3.7. 文档编译
  • 4. 初入Odoo
    • 4.1. 管理数据库
    • 4.2. 登录界面
    • 4.3. Administrator首选项
    • 4.4. 导入一个翻译
    • 4.5. 新的Demo用户
    • 4.6. 模块管理
    • 4.7. 修改公司信息
    • 4.8. 打开技术特性支持之后
    • 4.9. 进销存和财务系统的抽象讨论
      • 4.9.1. 以采购部门为例
    • 4.10. 安装和配置模块
  • 5. 创建自己的模块
    • 5.1. 快速生成模块骨架
      • 5.1.1. python模块的init文件
      • 5.1.2. 作为Odoo模块的说明文件
        • 5.1.2.1. 属性值清单:
    • 5.2. 安装自定义模块
      • 5.2.1. 模块文件夹管理
    • 5.3. 一个简单的演示模块
      • 5.3.1. controllers
      • 5.3.2. views
      • 5.3.3. models
      • 5.3.4. security
      • 5.3.5. 美化网页
    • 5.4. 加分项:通过pgadmin3来查看数据库
      • 5.4.1. 安装
      • 5.4.2. 连接服务器
      • 5.4.3. 图形化查询
  • 6. Odoo开发基础: 请假模块第一谈
    • 6.1. 纯理论讨论
    • 6.2. 定义模型
    • 6.3. 加入菜单
      • 6.3.1. act_window 的属性
      • 6.3.2. menuitem 的属性
    • 6.4. 视图优化
      • 6.4.1. 修改tree视图
      • 6.4.2. 修改form视图
        • 6.4.2.1. 使用group布局
    • 6.5. 完整的views.xml
    • 6.6. 给模块加个图标
  • 7. Odoo开发基础: 工作计划模块第一谈
    • 7.1. 数据访问权限管理
      • 7.1.1. access rule
      • 7.1.2. record rule
  • 8. 扩展现有模块-继承机制
    • 8.1. 给模块增加field
    • 8.2. 修改已有的field
    • 8.3. 重载原模型的方法
      • 8.3.1. 什么是Recordset
      • 8.3.2. Odoo里面的domain语法
      • 8.3.3. recordset的search方法
    • 8.4. 视图xml文件的继承式修改
      • 8.4.1. 视图元素添加
      • 8.4.2. 原视图元素属性修改
    • 8.5. 多态继承
    • 8.6. 修改其他数据文件
      • 8.6.1. 删除记录
      • 8.6.2. 更新数据
    • 8.7. 委托继承
  • 9. 理解模型内的数据文件
    • 9.1. 理解外部id
    • 9.2. 使用外部id
    • 9.3. 导出或导入数据文件
    • 9.4. 快捷输入标签
    • 9.5. 用field标签设置值
      • 9.5.1. eval语法
      • 9.5.2. ref属性
      • 9.5.3. One2many和Many2many的eval赋值
  • 10. Odoo开发基础: 请假模块第二谈
    • 10.1. 本例涉及到的数据库表格简介
    • 10.2. 工作流概念入门
      • 10.2.1. 定义工作流对象
      • 10.2.2. 创建节点
      • 10.2.3. 创建连接
  • 11. Odoo模型层详解
    • 11.1. _name
    • 11.2. 各个表头属性
    • 11.3. name字段
    • 11.4. 具体模型的数据
    • 11.5. 模型间的关系
    • 11.6. 工作流
  • 12. Odoo视图层详解
  • 13. 附录
    • 13.1. Odoo里老的API
    • 13.2. PostgreSQL数据库命令行操作
      • 13.2.1. 命令行数据库备份
    • 13.3. 反向代理(reverse proxy)
      • 13.3.1. 安装ngnix软件
        • 13.3.1.1. 释放http默认端口号
      • 13.3.2. 强制https连接
      • 13.3.3. nginx优化
      • 13.3.4. 轮询机制
    • 13.4. 跟踪项目源码初始化进程
      • 13.4.1. base模块
      • 13.4.2. web模块
      • 13.4.3. web_kanban模块
    • 13.5. 配置会计科目
      • 13.5.1. 配置会计科目类型
      • 13.5.2. 配置会计科目
    • 13.6. 分录
    • 13.7. 新建业务伙伴
      • 13.7.1. 新建业务伙伴标签
      • 13.7.2. 新建客户
    • 13.8. 创建新的产品
    • 13.9. 设置会计年度
    • 13.10. 向供应商下单
    • 13.11. 会计学入门
      • 13.11.1. 财务报表
      • 13.11.2. 原始凭证
      • 13.11.3. 账户
        • 13.11.3.1. 资产类账户
        • 13.11.3.2. 负债类账户
        • 13.11.3.3. 所有者权益账户
      • 13.11.4. 分类帐
      • 13.11.5. 会计科目表
      • 13.11.6. 报告期间
    • 13.12. 参考资料

1 ERP简介

1.1 Odoo历史

Odoo8的前身是“Tiny ERP”,最初是由比利时的Fabien Pinckaers 创建的。

Figure 1: Fabien Pinckaers

到2009年的时候,发布第5版,公司获得风投,盈利增长迅速,软件更名为OpenERP。OpenERP这个名字最为人们熟知,当时软件已经包含几百个模块了,从财务管理、 采购/销售管理、库存管理到人力资源管理、销售点管理、项目管理等等都有。当时可能某些模块的功能已经开始超过传统意义上的ERP(Enterprise Resource Planning,企业资源规划)的定义了(不过最新的ERPⅡ定义则更广泛,下面会有详细的讨论。)。

而在2014年9月,软件发布第8版,在之前版本逐渐优化的web client这一块的基础上,进行了大范围的功能加强。比如有了Website builder模块,可以方便公司快速架构出自己的网站;e-commerce模块方便公司快速搭建销售平台;还有business intelligence这个模块,可以辅助生成高质量的说明演示用的图形等等等等。这使得OpenERP这个名字已经不能很好地说明这个软件的雄心壮志了,于是软件更名为 Odoo 这个名字了,目前最新的版本是Odoo8【预计2015年7月份出Odoo9】。

可以看得出来目前该软件的开发方向就是基于web client/server模型,将公司内部所涉及到的所有的信息流都整合起来,其不仅包括具体实施层面,也包括分析决策层面。可以预见不久的将来Odoo开发将快速为公司构建出这样一个生态圈:

1.2 ERPⅡ或商业智能化

随着信息时代的到来,商业也不可避免地走向信息化,智能化。最新的ERPⅡ的概念包含的内容如下所示:

Figure 3: ERPⅡ模型
  • Business Intelligence 商业智能,其主要关注于分析数据,并将数据变成知识这一过程。
  • e-Commerce 电子商务,关注于对外战略。
  • Enterprise asset management 企业资产管理,有效可持续地管理公司的资产生命周期,用强有力的分析工具来提高资产使用率和削减成本。
  • Procurement(SRM) 采购,最大化的节约成本和支持终端对终端的采购,还有物流过程。
  • Production(PLM) 生产,帮助管理和优化生产能力和物料资源。是MRP的升级版。1 这里谈论的PLM不仅要解决物料需求问题,而且要解决生产的时间问题,从而达到优化生产能力的目的。
  • Distribution(SCM) 配送,控制仓库流程,使其能够对补给需求或更改做出快速的反应。
  • Accounting 会计,自动化财务管理,同时要确保管理的便捷和对绩效做出实时反映。
  • Human Resource 人资,维护一个完整的雇员数据库,更好地使用所有雇员。
  • Corporate performance and governance 公司表现监管,对公司的各个部门更高的控制,目标让他们能够流水线作业。
  • Customer services(CRM) 客服,获取和维护和客户的关系,充分利用客户的体验来进行知识管理评估。(其和BI模块结合很紧密)
  • Sales 销售,具体的定单确认,下单,货运和开发票等。

1.2.1 什么是商业智能

商业智能(Business intelligence)的概念经由Howard Dresner(1989年)的通俗化而被人们广泛了解。其将商业智能定义为:一类由数据仓库(Data warehouses)、查询报表、数据分析、数据挖掘、数据备份和恢复等部分组成的、以帮助企业决策为目的技术及其应用。

目前商业智能被理解为将企业中的现有数据转化为知识,帮助企业做出 明智的 的业务经营决策的工具。这里所谈的数据包括来自企业业务系统的订单、库存、交易账目、客户和供应商资料及来自企业所处行业和竞争对手的数据,以及来自企业所处的其他外部环境中的各种数据。而商业智能能够辅助的业务经营决策既可以是作业层的,也可以是管理层和策略层的决策。

商业智能(BI)的架构示意图如下2

Figure 4: 商业智能技术框架

典型的BI系统包括:

ETL过程
ETL过程是指对于数据的抽取(Extraction)、转换(Transformation)和装载(Load)。该部分从各业务系统中抽取、转换、装载数据到数据仓库。此部分通常提供一些配置手段,使得用户能够比较方便的从各种数据源取得数据,并设置规则,将数据变换成需要的形式。odoo带有ETL模块。
数据仓库
用于存放ETL抽取回来的数据,此部分通常是经过数据仓库优化的关系数据库。用于数据仓库的关系数据库特别适于处理大数据量及多维数据集。odoo的BI模块直接以PostgreSQL数据库作为数据仓库。
OLAP
通常是实现了MDX(多维数据查询,Multi-Dimensional eXpress)语言的多维数据集(Cube)查询器。MDX有些类似于SQL,但比SQL更简单,是数据分析语言的事实标准。OpenERP的BI模块支持MDX语言。
报表工具
用于展现MDX的查询结果,通常提供方便手段访问和格式化数据,提供丰富的数据呈现方式。odoo的BI模块以pyChart作为报表工具,报表开发方法和odoo中的Graph视图类似。

2 Odoo框架简介

下面一副图很好地说明了Odoo技术框架:

Figure 5: Odoo技术框架
  • PostgreSQL数据库
  • Object Relation Mapping 也就是大家熟知的SQL ORM包装层。Odoo除了使用的基本的psycopg2 作为接口之外,ORM层是Odoo自己写的。
  • Base Module Distribution 官方基本模块
  • Report Engine 负责生成各种报表。目前支持的报表格式有 PDF,OpenOffice,HTML 三种。
  • Workflow Engine 工作流引擎。支持任意复杂度的工作流。
  • WebService 提供网络调用接口。目前支持 Net-RPC、XML-RPC 两种。Odoo和flask一样使用Werkzeug作为WSGI层的包装,jinja2作为模板工具。然后剩下的框架部分是Odoo自己写的。

2.1 python模块分析

Odoo这个框架使用了很多模块,从这些模块的使用我们可以大致看出Odoo的工作原理。其中最主要的模块有:

  • psycopg2 PostgreSQL数据库接口,其ORM层Odoo是自己写的,python中有类似的模块SQLAlchemy或者peewee等。
  • Werkzeug和jinja2作为Odoo网络服务器框架的基础,同样flask也基于这两个模块。
  • babel为网页提供国际化方案,MarkupSafe可处理Markdown标记语言,lxml用于分析网页,mock和unittest2用于单元测试加强,pyserial用于串口通信,pyusb用于处理usb,requests用于处理网络协议,xlwt用来支持excel表格,pillow用于图像处理。这些模块都较好,目前也处于活跃开发中,已支持python3。
  • pyPDF模块用来处理PDF的,以后推荐使用pypdf2模块。pychart这个模块也快被废弃了,关于后台运算和绘图这一块推荐使用目前流行的ipython系(numpy和matplotlib等)来解决。

还有其他一些琐碎的模块十几个,这个以后再慢慢了解。

2.2 python2还是python3

Odoo框架严格意义上来说只支持python2.7,其他python版本都不支持,也因为这种局限性,也带来对其他一些python模块的版本号的选择性。

我做过试着编译Odoo的python3版本(经过2to3脚本处理之后),其依赖的python模块目前绝大部分都已经支持python3了,就是Odoo框架自身,要移植到python3问题还很大,最大的阻碍就是他自己写SQL ORM那一部分。

同时我们还要考虑官方自带的模块更新也是基于python2.7的,所以很长一段时间之内,都还是安心使用python2.7版本的Odoo框架,直到官方发布支持python3的版本或者我们写出了一个比他更好的框架。

python2.7和python3版本之间的差异在不断缩小,但还是有很多小细节上的差异,这块需要额外的留心。

3 Odoo的安装和配置

Odoo项目的github地址是 https://github.com/odoo/odoo 。我们可以看到这个项目非常活跃,下面的内容都基于Odoo8。推荐使用github的最新版本。

常规的安装就是到 Odoo的github 地址那里下载源码,然后运行:

sudo python setup.py install 

安装之,当然Odoo框架稍微复杂一点,这么简单处理一般是不会安装成功的,下面就一些额外的操作做出说明。

首先强调第一个问题: 注意 ,在Ubuntu下,下载下的源码不要放在有中文名字的目录下面了,目前还不支持中文目录,包括桌面文件夹。

接下来我们git clone 下Odoo的源码,然后python-dev build-essential 是一般都要安装的,推荐先用apt-get安装上。

然后是pip工具要安装上去:

sudo apt-get install python-pip

然后运行

sudo pip install -U pip

来将pip升级到最新的工具。

setuptools模块应该通过pip也安装上去了,不太确定,推荐用pip安装一下,顺便也确保升级到最新的版本。

sudo pip install -U setuptools

3.1 PostgreSQL数据库

PostgreSQL是很有名的一个开源数据库,最初由加州大学伯克利分校的计算机系开发,其和sqlite3最大的区别就是其采用了client/server模型,Odoo搭建在PostgreSQL基础之上了,也继承了这种client/server模型。Odoo对PostgreSQL数据库的版本号要求不是很严格,用最新的也是可以的。

PostgreSQL数据库在ubuntu下安装是很方便的,就用apt-get简单安装即可。

sudo  apt-get install postgresql

但是这样安装之后,你需要牢记一点的是,新安装的PostgreSQL数据库还只有 postgres 这个用户有新建role(或说用户)和新建数据库的权限。你需要通过postgres这个用户来执行createuser命令才能顺利创建一个新的用户。

sudo -u postgres createuser $USER

如果某个用户不存在,那么PostgreSQL将会报错:

createdb: could not connect to database template1: FATAL:  role "wanze" does not exist

你还可以通过postgres创建一个数据库:

sudo -u postgres createdb dbname

如果你的用户名已经创建了,然后这个dbname也已经创建了,那么你就可以用psql命令行登入这个数据库来进行相关操作了。不过Odoo框架要求你这个用户还具有可以创建数据库的权限。这需要你如下这样去做:

sudo -u postgres psql postgres
ALTER USER wanze CREATEDB;

首先是用postgres用户身份登入postgres这个数据库,这个数据库放着PostgreSQL的一些配置信息。然后使用SQL语句 ALTER USER wanze CREATEDB;

这样你的用户就有了创建数据库的权限了,这块内容参考了 这个网页 。

如果系统提示你没有 .psql_history 这个文件,那么简单的touch一下即可。

3.2 Ubuntu14.04下可能缺失的软件包

这些缺失的软件包主要和一些依赖的python模块有关。也就是如果使用whell技术封装的whl包可能是不需要安装下述的这些软件包了,不过最好还是都确保安装上吧。

关于python-ldap 模块的安装参考了 这个网页 ,请确保下面两个软件包都安装了(否则会提示找不到lber.h错误):

sudo apt-get install libldap2-dev 
sudo apt-get install libsasl2-dev

关于psycopg2模块请确保下面软件包安装了:

sudo apt-get install libpq-dev

还有这几个软件包确保安装了,其中libxml2和lxml模块有关。

sudo apt-get install libxml2-dev
sudo apt-get install libxslt1-dev

pillow模块需要安装下面这个软件包:

sudo apt-get install libjpeg-dev

随着系统的变动,你可能还缺少某些软件包,这个就要具体情况具体分析了。

3.3 网页显示node.js方面

按照官方文档的描述,ubuntu14.04 nodejs已经安装了,只需要通过npm安装 less 和 less-plugin-clean-css ,然后给nodejs创建一个别名链接 node 即可。

sudo apt-get install -y npm
sudo npm install -g less less-plugin-clean-css
sudo ln -s /usr/bin/nodejs /usr/bin/node

然后ubuntu小于14.04的版本还额外需要手工安装nodejs。

3.4 其他问题

  1. pydot因为googlecode不能用了,你需要用pip命令单独install pydot将其安装好。或者到 github这里下载之。
  2. 可能有其他模块因为网络问题或者其他问题下载失败,你可以考虑用pip命令来安装或者到github下载对一个模块的源码来安装之。
  3. PostgreSQL的连接配置可能会出问题,修改 /etc/postgresql/.../main/pg_hba.conf 文件,看到
# "local" is for Unix domain socket connections only
local	 all	 all	 peer

注意前面的postgres用户那一行绝对不要动,然后这里指本地连接,一般设置为peer,或者干脆设置为trust。

然后重启PostgreSQL服务器:

sudo service postgresql restart

3.5 通过命令行运行时的配置

在源码包目录下运行odoo.py文件即可启动Odoo,下面是一切其他参数的配置。

3.5.1 –xmlrpc-port=8888

设置网页显示的端口号,默认是8069,但可能会出于某些原因被占用了。

3.5.2 –addons-path=addons

设置插件addons的路径,默认会把源码addons文件夹加上去,但可能会出错。这里设置为源码的addons文件夹。

设置多个addons路径语法如下: --addons-path=addons, myaddons ,这可以用于加载你自己定义的某些模块。

上面两个参数配置是最常用的,我就简单设置这两个参数就可以运行Odoo了:

./odoo.py --addons-path=addons --xmlrpc-port=8888

3.5.3 数据库的一些配置

这一块推荐先不设置,就使用默认的 $USER ,然后创建数据库进入Odoo之后再配置。除非读者对PostgreSQL非常熟悉之后,再考虑更改这些配置(因为本地localhost登录还有用户权限还有连接方式上PostgreSQL里面还有很多内容,要小心啊。)。

  • –db_user=

这个参数用于指定Odoo框架数据库的user,又是你不想使用默认的 $USER ,那么可以加上这个参数。

  • –database=

这个参数用于指定具体Odoo框架使用的数据库,如上你不想使用默认的 $USER ,那么此时最好也为你想要的新用户名创建一个新的数据库。

  • –db_password=

这个参数用于指定登录某个数据库时的密码。

3.5.4 –save

保存目前你的运行命令行配置,下次就可以简单使用./odoo.py来运行了。具体配置文件是主文件夹的 .openerp_serverrc 。有兴趣的可以打开看一下。

3.6 将安装环境封装起来

我们在开发的时候,最好把所有依赖的python模块包版本号都固定起来,这可以通过pyhon的virtualenv模块来实现。安装virtualenv模块是:

sudo apt-get install python-virtualenv
sudo pip install -U virtualenv

最好如上将virtualenv升级到最新的版本。

然后我们将Odoo框架的源码加入进去,然后再安装一次。这里不赘述了。安装成功之后,我们使用pip freeeze命令来将目前的python模块包版本号环境输出出来:

pip freeze > requirements.txt

这样下次我们可以直接 pip install -r requirements.txt 来将依赖的python模块包刷一遍。但这还不够好。利用最新的wheel技术,我们可以将这些依赖的python模块包打包出来,从而可以实现不依赖网络的更快速的安装。

首先是安装wheel模块:

pip install wheel

然后:

pip wheel -r requirements.txt

注意将requirements.txt中的odoo版本删除,然后将pychart改成:

http://download.gna.org/pychart/PyChart-1.39.tar.gz#egg=PyChart

命令行执行完之后看到wheelhouse文件夹里面有一大堆whl文件,这就是所谓的pip模块安装包,其安装不依赖任何系统的工具,如果你有python环境,然后已经安装了pip工具,然后运行:

sudo pip install *.whl

就可以把所有依赖的python模块安装上去了,这个安装过程也不依赖于网络。

借助于virtualenv工具和wheel工具,你可以快速对你的开发环境进行再更新。比如你想升级你的开发环境的某个模块了,将其升级之后测试没有问题,就可以将其pip freeeze出来,然后pip wheel出来,这样就可以快速在其他机器上部署这个开发环境了。有时可能有些模块的某些版本号wheel不出来,最好将这个模块版本号倒退。

3.7 文档编译

这部分是可选的,就是编译文档。需要通过pip安装sphinx和sphinx-patchqueue,然后就可以make html 和latex了,通过latex就可以make出pdf文档了,这方便在本机上查阅资料。

pip install sphinx
sudo pip install sphinx
sudo pip install sphinx-patchqueue
make html
make latex

4 初入Odoo

如上安装配置好之后,在网页浏览器上输入 127.0.0.1:8069 (具体端口号读者要视自己的情况而定)之后我们就会看到如下界面:

4.1 管理数据库

Figure 6: 新建数据库

这里不仅新建了一个数据库,还指定了管理员的新密码,以后管理员要用那个新密码登录,用户名还是默认的 admin 。

左边有create(新建数据库),duplicate(复制数据库),drop(删除数据库),backup(备份数据库),restore(恢复数据库),password(修改管理员密码)。

然后这个管理数据库的url最好记下来,有的时候登入Odoo出问题了,这个管理数据库的界面你还是可以进入的,可以考虑还原数据库等操作。 http://127.0.0.1:8069/web/database/manager 。

读者可以如下用 psql -l 命令来查看一下,具体新建的那个数据库。

创建好数据库之后,我们就看到这个界面了,我们看到这里有很多模块。

Figure 7: 本地模块一览

4.2 登录界面

因为我们给Odoo框架创建数据库权限了,所以Odoo现在支持一切高级数据库操作了。我们注销Administor账户,然后就会看到一个登录界面。

Figure 8: 登录界面

然后下面就有一个管理数据库的链接,点击就进入之前看到的管理数据库界面了。

4.3 Administrator首选项

在右上角Administrator→首选项那里可以设置网站的语言,时区,还有管理员的头像,管理员的邮箱和个性签名。

Figure 9: Administrator首选项

4.4 导入一个翻译

看到左侧翻译一栏,点击导入一个翻译即可加载一个翻译。

4.5 新的Demo用户

看到左侧用户一栏,点击创建一个新用户,即进入创建一个新用户的界面。这里将新建一个演示用的Demo用户。

Figure 10: 新的Demo用户

然后我们使用这里设置的Demo用户名和密码登录,会看到一片空白,没有左侧的那些设置选项了。

4.6 模块管理

  • 安装模块,刚进入Odoo即看到那个模块一览的界面,点击相应的模块即安装对应的模块了。
  • 更新模块,然后看到左侧还有一个更新选项可用于更新本地模块。
  • 卸载模块,按照官方文档的描述,虽然点击对应模块的详细表单视图,里面有卸载按钮,但并不推荐。最好还是每次有模块更动之前先备份一下数据库,之后不行恢复一下数据库即可。

4.7 修改公司信息

在最左上角那里,鼠标划过会看到编辑公司数据的信息,然后点击进入即可以看到如下的修改公司信息界面:

Figure 11: 修改公司信息

比如设置公司的Logo,名字,地址,网站等等。然后在设置选单那里还可以设置币种,这个币种设置会影响后面会计模块的默认币种行为。还有一些信息设置能够填上的最好都填上。

4.8 打开技术特性支持之后

打开技术特性支持之后会多了很多参数设置,比如 工作流 配置那一栏,之前是没有的。

再比如你可以更新模块列表了,只有你更新模块列表之后,你新写的模块才能被搜索到和安装上去。

4.9 进销存和财务系统的抽象讨论

进销存和财务软件系统目前大多融为一体了,以前还是分开的。然后进销存那块以前最开始的软件是仓库管理软件,后面采购和销售是慢慢加上去的。理解这点很重要,我们可以把仓库管理看作最底层的模块,而采购和销售是于之上的模块,然后采购和销售又和财务存在着很多信息交流。具体如下图所示:

Figure 12: 进销存和财务

简单来说就是不管是采购还是销售其一笔成交的订单都必然产生两个信息流,一个是仓管那边;一个是财务那边。而这个信息流的过程最好是由系统自动化完成。下面以采购部门为例子来说明系统内部的这种信息流自动化过程。

4.9.1 以采购部门为例

采购部门一般职能是接受其他各部门的采购要求,定期汇总做成采购计划;然后根据采购计划会相应的供应商询价、议价;然后下采购单;然后跟踪供货商及时发货;货到后验货、入库。如果有问题则要求供应商换货、退货。

首先看采购计划那边,本公司各个部门根据实际需要应该都可以向采购部门发送采购需求,然后某些部门的模块会根据自己的实际情况有自动发送采购需求的功能,比如仓管的最小库存原则,再比如销售部门的某些紧急需求等等(原则上其他部门不紧急的需求应该发送给仓管,然后由仓管根据最小库存原则来发送采购需求,但某些部门具有特别紧急的需求是可以直接给采购部门发送采购需求的。当然还可能某些公司的某些部门的情况是种类繁多库存较小的情况,那么也是可以直接向采购部门发送需求的)。这个信息采购需求的发送机制需要根据不同的公司实际情况进行优化。

对于采购需求,系统应该具备一定的自动整理功能,比如按照供应商分类,紧急程度插队机制等等。采购人员处理经过初步整理出来的采购需求,将这些信息自动生成 询价单 ,此时系统可以提供电邮,打印询价单的功能,或者和供应商洽谈视频的功能等。询价结束之后对方同意之后采购员可以点击确定然后将这些信息发送给采购部门的经理,由经理确认之后, 采购单 就自动生成了。

Figure 13: 这是采购单

采购单确认之后,前面讲的信息流分成仓管和财务两边。首先我们看仓管那边,在货物还没送到之前,仓管那边就可以看到将要到的货物了,这样他们可以预先对仓库进行整理,方便后面的快速存放工作。货物送到之后首先是采购员验收,验收确认采购单上单击 收货 ,然后是点击 入库 操作,就表示这个货物正式入库了,然后仓管那边对于是什么产品,单价多少,重量多少甚至体积多少都可能有所说明自动存放在软件系统仓管那一栏了。

然后是财务那边,在采购单确定之后,财务那边应该 接受发票 ,然后点击 确认生效 ,这是相应的信息应该送到财务那边去了。财务那边还有一些付款事宜。财务付款完了采购员就会看到这个采购单已经完成了。

这里谈论的以采购部门为例的一般流程,这个流程不是死的,也不一定是最优的。具体要根据公司的实际情况的不同和功能需求不同对这些底层的信息流程做出调整和优化。

4.10 安装和配置模块

我们现在就安装进销存加财务这四个经典模块:

  • 财务与会计 ( account 模块),
  • 仓库管理( stock 模块),
  • 采购管理 ( purchase 模块),
  • 销售管理 ( sale 模块).

安装完这些模块之后需要再导入一次翻译(否则可能会有某些词条没有正确翻译),然后重启Odoo框架好让这些新的模块的翻译生效。

在安装过程中会弹出这个会计窗口,默认值就可以了。

5 创建自己的模块

Odoo开发的一条黄金准则就是我们最好不要修改现有的模块,特别是官方内置的那些模块。这样做会让原始模块代码和我们的修改混为一谈,使得很难对软件进行升级管理。我们应该创建新的模块(在原有的模块基础之上)来达到修改和扩展功能的目的。Odoo提供了一种继承机制,允许第三方模块扩展现有模块,或者官方的或者来自社区的,这种继承修改可从任意层次来开展,从数据模型到业务逻辑到用户界面。

5.1 快速生成模块骨架

可以如下快速生成一个模块骨架:

./odoo.py scaffold  mymodule myaddons

这将在当前位置新建一个myaddons文件夹,然后在myaddons文件夹下创建一个名字叫mymodule的模块骨架。创建好了之后读者可以进入翻一下。值得一提的是,这个模块骨架并不怎么实用,在学完本章稍微对Odoo的模块结构有所熟悉之后,自己新建文件或者复制一些代码过来可能反而会更加灵活便捷一些。其中有些原因是这个命令生成的模块结构有点老旧了,官方文档的推荐结构如下所示:

addons//
|-- __init__.py
|-- __openerp__.py
|-- controllers/
|    |-- __init__.py
|    `-- main.py
|-- data/
|    |-- _data.xml
|    `-- _demo.xml
|-- models/
|    |-- __init__.py
|    |-- .py
|    `-- .py
|-- security/
|    |-- ir.model.access.csv
|    `-- _security.xml
|-- static/
|    |-- img/
|    |-- lib/
|    `-- src/
|        |-- js/
|        |-- css/
|        |-- less/
|        `-- xml/
`-- views/
    |-- _templates.xml
    |-- _views.xml

这里的文件夹权限推荐是755,文件权限推荐是644。

上面只是一个泛泛而论的情况,具体有些文件夹或文件可能是不需要的。下面对这些内容进一步说明之。

5.1.1 python模块的init文件

Odoo模板本质上就是一个python模块,所以首先它应该有一个 __init__.py 文件。刚开始里面不放任何内容都是可以的。

而scaffold自动创建有下面的内容:

import controllers
import models

推荐改成这样的形式(有更好的python2和python3兼容性):

from .models import main_model

这种相对路径语法python2和python3都是通用的,具体对应的就是本目录下的models目录下的main_model.py文件。推荐将所有的模型定义python文件都放入models文件夹中,models文件夹下也应该有一个 __init__.py 文件。然后这里还新建了一个main_model.py文件,这个文件的名字倒是随意的了。

5.1.2 作为Odoo模块的说明文件

然后本模块作为Odoo框架的模块还必须新建一个 __openerp__.py 文件,最小型的什么都不做的Odoo模块就需要这两个文件,一个是这个 __openerp__.py 文件,一个是 __init__.py 文件。

scaffold自动创建的 __openerp__.py 文件大致内容如下:

# -*- coding: utf-8 -*-
{
    'name': "mymodule",

    'summary': """
        我的第一个模块
        """,

    'description': """
        我的第一个模块,用于学习自定义模块。
    """,

    'author': "wanze",
    'website': "http://www.yourcompany.com",

    # Categories can be used to filter modules in modules listing
    # Check https://github.com/odoo/odoo/blob/master/openerp/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': ['website'],

    # always loaded
    'data': [
        'security/ir.model.access.csv',
        'views/mymodule_templates.xml',

        'demo.xml',
    ],
    # only loaded in demonstration mode
    'demo': [
        'demo.xml',
    ],
}

以后我们要创建一个新的模块,这个文件的格式可以复制粘贴过去。其中一些基本的比如name就是本模块的名字,然后description还有category分类等等就不多说了,这些含义都是很明显的,就是本模块的一些描述信息。然后提得一提的是这三个属性:

depends
本模块的依赖关系,这里主要是指本模块对于Odoo框架内其他的模块的依赖。如果本模块实在没什么依赖,就把  base 模块填上去。
data
本模块要加载的数据文件,别看是数据文件,似乎不怎么重要,其实Odoo里面视图,动作,工作流,模型具体对象等等几乎大部分内容都是通过数据文件定义的。具体这些xml或csv文件如何放置后面再讲。
demo
这里定义的数据文件正常情况下不会加载,只有在demonstration模式下才会加载,具体就是你新建某个数据库是勾选上了加载演示数据那个选项。如下图所示:

Figure 14: 加载演示数据

这可能并不是你想要的效果,因为其他官方内置模块也附加很多演示信息进来了。其实读者一定也想到了,我们完全可以将demo.xml放入 "data" 那里,然后实际运作的时候不加载就是了,我更喜欢这种处理方案。

5.1.2.1 属性值清单:
name
模块名字
summary
可以看作简短介绍吧
description
可以看作详细介绍
author
模块作者
website
模块网站
category
模块分类
version
模块版本号
license
模块版权信息,默认是AGPL-3
depends
模块依赖
data
模块必加载的数据文件
demo
demonstration下才加载的数据文件
installabel
默认True,可设为False禁用该模块
auto_install
默认False,如果设为True,则根据其依赖模块,如果依赖模块都安装了,那么这个模块将自动安装,这种模块通常作为胶合(glue)模块。
application
默认False,如果设为True,则这个模块成为一个应用了。你的主要模块建议设置为True,这样进入Odoo后点击本地模块,然后默认的搜索过滤就是  应用 ,这样你的主模块会显示出来。

5.2 安装自定义模块

就设置好这样两个文件,虽然里面什么内容都没有,实际上也就可以开始安装这个模块而来。

前面说了设置 --addons-path=addons, myaddons ,就可以加载自定义的模块了。具体安装就和安装其他模块没有两样,除了你需要清除搜索栏然后输入搜索关键词。然后注意如果不是新建的数据库,那么需要打开技术特性执行, 更新模块列表 之后,才能搜索到你自己定义的新的模块。

自定义模块如果修改之后,(不需要重新编译Odoo), 肯定是需要重启Odoo的 ,然后进去之后如果你的模块增减了额外的文件,则还需要升级(update)相应的模块。

5.2.1 模块文件夹管理

  • data文件夹 ,放着demo和data xml
  • models文件夹,放着模型定义
  • controllers文件夹,http路径控制
  • views文件夹,网页视图和模板
  • static文件夹,网页的一些资源,里面还有子文件夹:css,js,img,lib等等。

5.3 一个简单的演示模块

5.3.1 controllers

现在我们在controllers文件夹里新建一个 __init__.py ,然后新建一个main.py文件。在main.py文件中添加如下内容:

class Mymodule(http.Controller):@http.route('/mymodule/mymodule/', auth='public')def index(self, **kw):return "Hello, world"

现在请读者如下所示安装和更新自定义模块之后,进入 127.0.0.1:8089/mymoduel/mymodule 来看一下效果。这样我们就明白了这里的 @http.route 装饰器有根据具体某个函数的返回信息进行路径分发和控制访问权限的功能。

如果没有什么问题,你应该能看到一行hello world文字,祝你好运。

5.3.2 views

views文件夹用于视图控制,里面通常放着一些模板文件等。我们首先修改 __openerp__.py 文件中data属性为这个样子。

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

然后按照这个在views文件夹里面创建一个mymodule_templates.xml文件。

 id="index"></span>MyModule<span class="nt" style="color:rgb(0,119,0)"> t-foreach="fruits" t-as="fruit">

t-esc="fruit"/>

这里使用了Qweb模板语言,就这里提及的我们可以简单了解下:


其输出是:

1

2

3

如果对应python语言的话,可以理解为:

for i in [1, 2, 3]:print('

{i}

'
.format(i = i))

这里的  是先计算 i 的值,然后将其打印出来。

然后在之前controllers那里的main.py文件那里,我们使用这个模板文件。

from openerp import httpclass Mymodule(http.Controller):@http.route('/mymodule/mymodule/', auth='public')def index(self, **kw):return http.request.render("mymodule.index",{'fruits':['apple','banana','pear']})

如果不出意外的话,你应该看到这样的画面了:

这里调用http.request.render函数,可以猜到这是一个网页模板渲染输出函数。然后注意看第一个参数, mymodule.index ,这里还是有一些讲究的,mymodule就是你正在编写的这个模块的名字,然后这个index对应的就是那个网页模板文件的id属性: 

这里引用了fruit的id属性和name属性。我们可以看到这样的输出结果:

刚开始id是从1,2,3开始的,因为我运行过几次,在这里成7,8,9了。

哦,最后我们还需要修改一个东西:

5.3.4 security

security文件夹里面已经有一个 ir.model.access.csv 文件了,其对模型的访问权限进行管理,大致修改内容如下:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mymodule_fruits,mymodule.fruits,model_mymodule_fruits,,1,0,0,0

目前还不太懂,看到perm_read那一列,要设置为1,这样所有用户可读。然后一些名字是根据你定义的模型的名字变化的。

5.3.5 美化网页

Odoo框架里面已经内置了一个帮助你设计网页的website builder模块,然后这里我们可以通过调用这个模块的结构来简单看一下。

首先是 __openerp__.py 的"depends" 属性修改一下:

'depends': ['website'],

然后是controllers的main.py那里加上 website=True 这个设置。

@http.route('/mymodule/mymodule/', auth='public', website=True)

最后是模块文件那里修改为:



    


请读者自己试着运行一下,看看效果,然后里面还有很多其他的设置功能等等。这里的细节先略过了。

5.4 加分项:通过pgadmin3来查看数据库

pgadmin3是针对PostgreSQL数据库很有名的一个管理员工具,里面的经很多,这里只是简单谈论一下。

5.4.1 安装

在ubuntu下可以用apt-get简单安装之。

sudo apt-get install pgadmin3

5.4.2 连接服务器

刚进入软件需要连接服务器,如下图所示:

Figure 17: pgadmin3连接服务器

目前我已经确认的就是名称随意填,然后主机不能填127.0.0.1,而只能填"localhost"这个字符串。然后后面的应该不用更改什么了,如果你给你的postgres数据库设置密码了,那么这个密码也需要填上。

连接好服务器了,我们就可以双击或点击查看你的PostgreSQL数据库的信息了。

5.4.3 图形化查询

在工具那里有很多有用的工具,比如查看服务器状态工具等。然后我们点击查询工具,会弹出一个窗口,看到图形化查询那个子选单,如下图所示:

Figure 18: pgadmin3图形化查询

这里我找到了前面我们新建的那个fruits模型,可以看到所有的fruits模型是放在一个名字叫做mymodule_fruits 的SQL表格里的。然后它的表头有:id, create_uid, create_date, name等。

然后我们切换到SQL编辑器子选单。这个工具会根据你的图形化查询选择结果自动生成对应的SQL查询语句:

SELECT 
  * 
FROM 
  public.mymodule_fruits;

所以在我们这个odoo数据库里面,我们新建的模型fruits的各个对象,具体存入的table名是public.mymodule_fruits 。

然后我们点击 查询→执行 ,来具体执行这个SQL语句,输出结果如下所示:

6 Odoo开发基础: 请假模块第一谈

6.1 纯理论讨论

在实际编写前先谈谈理论,这部分理论讨论非常有用,对于具体编写模块的时候你清楚自己在感谢什么很有帮助。感谢老肖的《OpenERP 应用和开发基础》一书,该书第六章对我帮助很大。

首先我们需要一个菜单,那么这个菜单在Odoo框架中是如何生成的呢?前面谈到Odoo的模型具体的对象实际上就是SQL表格的一条记录,而Odoo框架具体显示的菜单也是一个Odoo中的一个对象,其对应的表格是 ir_ui_menu ,其在xml中的声明是通过menuitem标签来完成的,具体细节等下再讲。然后菜单需要连接一个动作,这样用户点击这个菜单的时候,这个动作将会触发。

这些动作对象(和窗口操作相关的)是存放在 ir_act_window 表格中的。动作触发之后接下来是要处理视图问题,首先根据 ir_act_window_view 表格来找到具体关联的某个视图对象,具体某个视图对象是存放在 ir_ui_view 表格中的。然后根据具体关联的某个模型的某个对象的具体的值来构建出显示画面。

具体研究对象的模型,视图,菜单,动作等,这些实际上都是Odoo里面的模型,也就是具体对象的值是存放在某个具体的SQL表格里的,然后程序完成一系列的索引,取值等操作,并最终生成显示结果,这大概就是Odoo框架里面发生的故事概貌了。

按照上面的讨论,等下我们的工作有:

  1. 具体研究对象的模型,这里是请假单模型,然后请假单模型里面应该有的field有: 申请人,请假天数,开始休假日期,请假事由。
  2. 构建菜单对象。
  3. 构建动作对象,并与具体的某个菜单关联起来。
  4. 构建视图对象。

__init__.py 文件内容如下:

# -*- coding: utf-8 -*-from .models import main_model

__openerp__.py 文件内容如下:

# -*- coding: utf-8 -*-
{
    'name': "qingjia",

    'summary': """
        请假模块,提供请假功能
        """,

    'description': """
        请假模块,提供请假功能。
    """,

    'author': "wanze",
    'website': "http://www.yourcompany.com",

    # Categories can be used to filter modules in modules listing
    # Check https://github.com/odoo/odoo/blob/master/openerp/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',
        'views/views.xml',
    ],
    # only loaded in demonstration mode
    'demo': [
        'demo.xml',
    ],
    'application' : True,
}

首先我们来看下main_model.py文件里面定义的模型是怎样的:

6.2 定义模型

from openerp import models, fields, apiclass Qingjd(models.Model):_name = 'qingjia.qingjd'name = fields.Many2one('res.users', string="申请人", required=True)days = fields.Float(string="天数", required=True)startdate = fields.Date(string="开始日期", required=True)reason = fields.Text(string="请假事由")def send_qingjd(self):self.sended = Truereturn self.sendeddef confirm_qingjd(self):self.state = 'confirmed'return self.state

这种模型定义语法结构我们大体是熟悉的了,下面定义的两个方法等下会用到的,等下再谈。然后Float是浮点类型,Date是日期类型这个是一目了然的,然后Text是大段文本对象,string是这个field的用户界面显示的文字,required设置为True则该值为必填项。然后这个 Many2one 还有 res.users是什么?

首先让我们看看 public.res.users 这个表格的值:

Figure 20: res.users表格

这里Many2one的意思像是我从很多(Many)相同模型的对象中取一个(one)的意思。等下我们会看到一个下拉选单。更多细节需要深入学习Odoo ORM的API细节。

6.3 加入菜单

接下来的工作就是在views/views.xml文件里面定义具体的菜单对象。

代码第一版如下所示:




    
        请假单
        qingjia.qingjd
        tree,form
    

    
    
    



这种record语法我们已经有所熟悉了,然后model是 ir.actions.act_window ,我们可以在源码openerp→addons→base→ir中找到 ir_actions.py 文件,然后有下面的代码:

class ir_actions_act_window(osv.osv):
    _name = 'ir.actions.act_window'
    _table = 'ir_act_window'

我们可以看到其对应的正是表格 ir_act_window 。

field是用来填充具体表格的某个列值的,我们还可以使用如下简便的语法:


这里的标签 act_widow 还不清楚是在那里规定的,然后下面具体的菜单对象也简便使用了menuitem 标签。

我们可以在openerp→addons→base→ir中找到 ir_ui_menu.py 文件,有如下代码:

class ir_ui_menu(osv.osv):
    _name = 'ir.ui.menu'

可以看到菜单对象对应的是 ir_ui_menu 表格——和数据模型一样的映射法则,_name的点号变成下划线。至于菜单对象为何对应 menuitem 标签还不清楚那里固定的。

这两个对象具体的一些属性说明一下:

6.3.1 act_window 的属性

name
具体act_window动作在UI中显示的名字(类似于QT中动作作为菜单中的项目的情况)。
res_model
act_window动作对应的某个数据模型(这里动作和数据模型关联在一起了)
view_mode
act_window动作打开后支持的视图模式。

6.3.2 menuitem 的属性

name
具体这个菜单在视图中显示的名字。
sequence
是显示排序(还不太懂)。
parent
是本菜单的父菜单。如果是子菜单则需要指定,只有顶级菜单不需要指定。
action
指定本菜单连接的动作。如果连接动作了那么name属性可以不用指定了,系统会直接引用动作的name属性的。这里菜单和某个动作关联起来了。和前面联系起来,那么就是具体某个子菜单和某个数据模型关联起来了。

这样现在你应该看到类似如下的视图了:

6.4 视图优化

最主要的三个视图是list或者tree视图;表单form视图和search搜索视图。视图都属于ir.ui.view模型

6.4.1 修改tree视图

下面定义了一个自己的tree视图:


qing jia dan tree
qingjia.qingjd

    
        
        
        
    


这里的name属性是本视图的名字,似乎没什么意义。然后 model 属性很重要,前面子菜单关联到了某个动作,然后某个动作关联到了某个数据模型了,这里就是将这个视图和某个模型关联起来了。

下面的 arch 这个格式还不清楚有什么用,但必须这么写:


    ...

然后接下来定义tree视图,又叫列表视图。其中的field就是对应的具体那个数据模型的field,加入谁就要显示谁。

6.4.2 修改form视图

下面定义一个自己的form视图:


qing jia dan form
qingjia.qingjd

    

其他都类似上面的,不赘述了。

重点在form标签里面。这里引入了sheet布局,然后label加入标签 field引入具体属性。

然后我们还可以使用 header 标签引入表单视图的头部分。头部分里面一般放着一些按钮动作。比如:

这里string是这个按钮具体显示的字符,然后name是这个按钮具体应该执行的动作(对应本模型的该名字的方法),class="oe_highlight" 让按钮变为红色突出显示。

6.4.2.1 使用group布局

此外还可以使用group布局,group布局还不太懂,其中可以引入 string 属性,等下可以作为group的标题显示出来。

这是我的这个简单的请假单布局


    
        
        
        
        
    

6.5 完整的views.xml

至此完整的views.xml文件如下所示:

 id="action_qingjia_qingjd"name="请假单"res_model="qingjia.qingjd"view_mode="tree,form" /> id="qingjia_qingjd_form" model="ir.ui.view"> name="name">qing jia dan form name="model">qingjia.qingjd name="arch" type="xml">
name="send_qingjd" type="object"string="发送" class="oe_highlight" /> name="confirm_qingjd" type="object"string="确认" />
name="group_top" string="请假单"> name="name"/> name="days"/> name="startdate"/> name="reason"/> id="qingjia_qingjd_tree" model="ir.ui.view"> name="name">qing jia dan tree name="model">qingjia.qingjd name="arch" type="xml"> name="name"/> name="startdate"/> name="days"/> id="menu_qingjia" name="请假" sequence="0"> id="menu_qingjia_qingjiadan" name="请假单" parent="menu_qingjia"> id="menu_qingjia_qingjiadan_qingjiadan" parent="menu_qingjia_qingjiadan" action="action_qingjia_qingjd">

至此显示效果如下所示:

6.6 给模块加个图标

模块 static→description 的 icon.png 文件就对应模块的图标。把它设置好你的模块就有一个图标了。

7 Odoo开发基础: 工作计划模块第一谈

这个例子来自Daniel Reis的《Odoo Development Essentials》一书,之所以也加进来是因为我觉得学习框架是例子越多越好,然后这本书我是在 这个网站 限时观看的,也是为了保留有价值的信息吧。

我们这次要创建的模块的功能是进行工作计划管理,也就是常说的"to do task",

首先是 __init__.py 文件:

# -*- coding: utf-8 -*-

from .models import main_model

然后是 __openerp__.py 文件:

# -*- coding: utf-8 -*-
{
    'name': "todo task app",

    'summary': """
        工作计划管理系统
        """,

    'description': """
        工作计划管理系统: 安排你的工作计划。
    """,

    'author': "wanze",
    'website': "http://www.yourcompany.com",

    # Categories can be used to filter modules in modules listing
    # Check https://github.com/odoo/odoo/blob/master/openerp/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': ['mail'],

    # always loaded
    'data': [
        'security/ir.model.access.csv',
        'views/views.xml',
    ],
    # only loaded in demonstration mode
    'demo': [
    #    'demo.xml',
    ],
    'application' : True,
}

注意到depends设置为了mail模块,因为等下to do task子菜单要在消息子菜单中显示的,其是由mail模块设置的。然后同样加载了views.xml文件,还有设置访问权限的ir.model.access.csv文件等下再说。

然后我们再来看 main_model.py 文件

from openerp import models, fields, apiclass TodoTask(models.Model):_name = 'todo.task'name = fields.Char('Description',required=True)is_done = fields.Boolean('Done?')active = fields.Boolean('Active?', default=True)@api.onedef do_toggle_done(self):self.is_done = not self.is_donereturn True@api.multidef do_clear_done(self):done_recs = self.search([('is_done', '=', True)])done_recs.write({'active': False})return True

读者现在应该很清楚这些代码在做什么了,其中fields.Boolean定义了一个布尔值字段,然后我们看到第一个可选参数默认就是 string ,就可以直接写上。然后 default 是来设置该字段的默认值的。

下面定义的两个方法和等下form视图下的两个按钮相关,这里用到了Odoo新的ORM API,使用了@api.one @api.multi 这样的装饰器。 值得一提的是,可被用户调用的方法,比如这里的按钮是被用户点击才调用的, 都需要一个返回值 ,否则XMLRPC协议无法正常工作,实在没啥好返回的,就return True 。

@api.multi
默认的装饰器是这个,没有自动迭代recordset,因为它默认接受的self就是recordset对象(所有recordset就是指相同模型下的所有对象,或者说同一SQL表格下的所有记录。)。
@api.one
@api.one装饰器将会自动产生一个迭代动作,具体是指迭代某一recordset,然后其内的self就是一个record也就是该模型下SQL表格的一条记录。然后@api.one返回的是一个列表值,某些网络客户端可能并不支持这点。所以还是尽量少用@api.one。

具体 views.xml 文件的内容如下所示:

 id="action_todo_task"name="To-do Task"res_model="todo.task"view_mode="tree,form" /> id="view_form_todo_task" model="ir.ui.view"> name="name">To-do Task Form name="model">todo.task name="arch" type="xml">
name="do_toggle_done" type="object"string="Toggle Done" class="oe_highlight" /> name="do_clear_done" type="object"string="Clear All Done" />
name="group_top"> name="group_left"> name="name"/> name="group_right"> name="is_done"/> name="active" readonly="1" /> id="view_tree_todo_task" model="ir.ui.view"> name="name">To-do Task Tree name="model">todo.task name="arch" type="xml"> colors="gray:is_done==True"> name="name"/> name="is_done"/> id="view_filter_todo_task" model="ir.ui.view"> name="name">To-do Task Filter name="model">todo.task name="arch" type="xml"> name="name"/> string="Not Done"domain="[('is_done','=',False)]"/> string="Done"domain="[('is_done','!=',False)]"/> id="menu_todo_task"name="To-Do Tasks"parent="mail.mail_feeds"sequence="20"action="action_todo_task" />

有了前面的基础,这个文件虽然看上去有点复杂,但我们应该是已经能够看出点端倪出来了的。首先我们不管视图那块,看到act_window和munuitem,我们看到这个菜单项设置的父菜单是mail.mail_feeds ,

parent="mail.mail_feeds"

因为对Odoo官方模块还不是很熟悉,但我们根据后面的显示效果,如下图所示:

我们可以推断这个父菜单 mail.mail_feeds 就是上图箭头所指的那个菜单。

然后我们看到tree视图,其中有这么一行:


这种语法还不太熟悉,但大体意思就是如果该模型下的某个对象 is_done 的值为True,则颜色设置为灰色。

然后值得一提的是,这里的search视图,并不需要在前面的动作对象的view_mode属性上加上,其更像是tree视图的增强功能,在搜索框那里,你点击才能看到。代码中唯一的难点就是domain过滤器的语法,这里先略过,后面再讨论吧。

然后我们再看到form视图,具体显示效果如下所示:


    
        
        
        
        
        
        
        
    

这里代码如何利用group来布局要好好体会一下,然后field标签这里又出现了一个新的属性readonly ,这样这个字段的值就不能更改了。

7.1 数据访问权限管理

Odoo对于其内的数据访问权限管理有两种机制: 一种是模型访问权限管理(access rule);第二种是记录规则管理(record rule)。其中record rule可以看作在access rule之上的更进一步细化。如果什么访问规则都不设定的话,那么默认只有Administrator才能访问这个模型的数据,其他的用户都不能访问。Odoo的安全机制是通过群组(group)来管理的,然后record rule对Administator用户是无效的,而access rule还有效。

https://www.odoo.com/forum/help-1/question/is-it-possible-to-manage-record-based-access-control-2597

7.1.1 access rule

访问权限控制一般是用security文件夹下的 ir.model.access.csv 文件来管理的。这个csv文件的表头是:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink

我们这里定义的模型名字是 todo.task ,其对应的 model_id 就是 model_todo_task 。这个是规定死了的,然后其他的id, name名字可随意。一般id就是在模型名字前加个access即 access_todo_task ,然后name更随意了,似乎我看到空格都可以的,但一般就简单把对应的模型的名字放进去即可,即todo.task 。然后第四个是群组id,接下来就是读权限,写权限,创建权限和删除权限,用0表示无权限,1表示有权限,具体要根据需要来设置权限。

下面是todo_app模块的 ir.model.access.csv 详情:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_todo_task,todo.task,model_todo_task,base.group_user,1,1,1,1

The default policy is DENY

这里的base.group_user群组参考资料4说雇员群组属于base.group_user群组,这里不太清楚,如果实在不确定这个值省略不填也是可以的。

7.1.2 record rule

这样设置之后每一个登录Odoo用户写的todo task其他人都可以看到,这显然不太好。进一步我们可以用record rule来对具体的record记录进行筛选。

在security文件夹里新建一个 todo_record_rules.xml 文件,文件内容如下:

 noupdate="1"> id="todo_task_user_rule" model="ir.rule"> name="name">ToDo Tasks only for owner name="model_id" ref="model_todo_task"/> name="domain_force">[('create_uid','=',user.id)]
         name="groups" eval="[(4,ref('base.group_user'))]"/>

这些record rule记录是 ir.rule 模型,存储在 public.ir_rule 表格里面的。name属性就不多说了,应该很熟悉了。然后 model_id 属性对应的就是某个模型,而 domain_force 就是对该模型中所有记录进行某种过滤操作,

The default policy is ALLOW,so by default any operation will be refused if the user does not explicitly have the right to perform it via one of her groups' access rights.

The default policy is ALLOW, so if no rule exists for a given model, all documents of that model may be accessed by users who have the necessary access rights. 然后 groups 字段还看不太懂。我看了一下 ir_rule 表格,似乎没有groups这个属性,这里的细节先略过吧。

data标签这里有个 noupdate 属性,如果设置为"1",则意思是本模块升级不更新本数据,如果设置为"0"则更新本数据。

然后我还发现一个问题: 那就是我中途试验过一个不加载这个xml文件的版本,数据库里面还是保存着这条记录,然后我手动删除SQL数据库的这条记录才行,后来又试验加载这个xml文件的版本,数据库这个权限记录加载不上去。然后我发现如果删除模块,则数据库里面的这条记录才会被删除了。然后再重新安装本模块,则这条权限记录才会被更新上去。也就是说目前的Odoo框架升级对于record rule支持并不是很好,在这方面要小心。

经过这样的配置之后,除了Administrator用户可以看到所有人的todo task之外,其他人都只能看到和编辑自己的todo task了。

8 扩展现有模块-继承机制

即使是对于现有的模块,推荐的做法也是通过新建一个模块来达到扩展和修改现有模块的目的。具体方法就是在python中的类里面使用 _inherit 属性。这标识了将要扩展的模块。新的模型继承了父模型的所有特性,我们只需要声明一些我们想要的修改就行了。通过这种继承机制的修改可从模型到视图到业务逻辑等对原模块进行全方位的修改。

实际上,Odoo模型在我们定义的模型之外,它们都在注册中心注册了的,所谓全局环境的一部分,可以用 self.env[model name] 来引用之。比如要引用 res.partner 模型,我们就可以写作self.env['res.partner'] 。

8.1 给模块增加field

如下代码就是首先通过 _inherit 继承原模块,然后再增加一些field:

from openerp import models, fields, apiclass TodoTask(models.Model):_inherit = 'todo.task'user_id = fields.Many2one('res.users',string='Responsible')date_deadline = fields.Date('Deadline')

关于 res.users 和 res.partner 具体是雇员还是合作伙伴什么的,这个以后再摸清楚,这里先简单将其看作一个SQL表格,然后Many2one前面讲过了就是根据某个给定的SQL表格来生成一个下拉选单,具体是引用的该SQL表格的那个表头属性,这里应该还有一个细节讨论。

不管怎么说,现在我们通过新建一个模块 todo_user ,如前面描述的将模块设置配置好之后,原模块 todo_app 的todo.task模型就增加了新的两个field了,也就是两个新的表头了。

8.2 修改已有的field

按照上面的继承机制,我们可以如上类似处理,只修改你希望更改的某个field的某个属性即可。如下:

name = fields.Char(help="can I help you")

这样原模型的namefield额外增加了help帮助信息了。

Figure 26: help帮助信息

8.3 重载原模型的方法

读者一定已经想到了,类似的在这种继承机制下,可以通过重写原模型的方法来重载该方法。事实上确实可以这样做,而这里要讲的是还有一种更加优雅的继承原模型的方法,那就是通过 super()来调用父类的方法3

首先我们看到下面这个例子:

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

这里涉及到Odoo新API的一些东西,这里先浅尝辄止讲一下。

8.3.1 什么是Recordset

Odoo8引入了一种新的ORM API,老的API也兼容,但推荐使用新的API,因为新的API更加简洁和方便。

首先是模型(model),其对应的就是python的类,具体类的实例就是对应现实世界的某个对象。然后老式的简单ORM封装就是将这些类的具体某些数据对应到SQL的数据库的一条记录(record)中去。新的API引入一个核心的概念就是 Recordset ,Recordset是个什么东西呢?就是前面讲的某一个模型(类)的所有对象(具体的实例)的集合就是一个Recordset对象。——这是recordset最大的情况,一个重要的限定条件就是其内元素必定是相同模型的,由这个最大的集合情况然后删除过滤掉一些元素(记录)之后仍然是recordset对象。

按照官方文档的描述是,一个Recordset对象应该是已经排序了的同一模型的对象的集合。他还指出虽然现在还可以存放重复的元素,这个以后可能会变的。同时你从名字可能猜到这个Recordset对象应该支持集合的一些操作,事实确实如此。

比如Recordset支持如下运算:

record in recset1     # include
record not in recset1 # not include
recset1 + recset2     # extend 
recset1 | recset2     # union
recset1 & recset2     # intersect
recset1 - recset2     # difference
recset.copy()         # copy the recordset (not a deep copy)

上面的操作只有 + 还保留了次序,不过recordset是可以排序的,关于次序比如使用:

for record in recordset:
    print(record)

具体的次序是否像集合set一样是不一定的还是如何呢?这里需要进一步的讨论。

8.3.2 Odoo里面的domain语法

本小节主要参考了 这个网页 。

Odoo里面的domain语法使用比较广泛,其就好像一个过滤器,应该对应的是SQL的SELECT语句。最基本的语句形式是 [('field_name', 'operator', value)]

field_name
必须是目标模型的有效field名字。
operator
比如是一个字符串,可用的值有:  = != > >= < <= like ilike , 此外还有"in", "not in", "parent_left", "child_of", "parent_right"。这里的parent和chind似乎是某种记录的关系,先暂时略过。其他的意义都是很明显的。
value
必须是和前面的field_name类型相同的某个值。

然后这些圆括号包围的基本语句可以用以下几个逻辑运算符连接: & | ! ,其中 & 是默认的逻辑运算连接符,也就是你看到两个圆括号表达式中间没有逻辑运算连接符,则要视作其间加入了 & 。具体形式大概类似这样:

[('field_name1', 'operator', value), '!',  
    ('field_name2', 'operator', value), '|', 
    ('field_name3', 'operator', value),('field_name4', 'operator', value)]

多个逻辑运算符的情况有点复杂,具体是 ! 先解析,其只作用于后面的第一个元素;然后 & 和 |作用于后面的两个元素。一个简单的解析步骤是先将 ! 解析进去,比如是解析为不是等,然后再将 |解析进去,相当于一个并联电路接进来,然后所有的过滤条件组成一个大的串联过滤线路。这样上面的表达式就解析为:

1表达式 and 2表达式否 and 3表达式或4表达式

然后前面的那个domain:

domain = [('is_done', '=', True),
'|', ('user_id', '=', 'self.env.uid'),
('user_id','=',False)]

应该解析为:

is_done是True and user_id 是self.env.uid 或 user_id是False

8.3.3 recordset的search方法

一个recordset对象调用其search方法还是返回一个recordset对象。

search方法接受一个参数,这个参数就是前面谈论的基于Odoo domain语法的过滤器表达式。

所以下面这个表达式:

self.env[’res.users’].search([(’login’, ’=’, ’admin’)])

的含义就是调用 res.users 这个表格或者说recordset,然后执行search方法,具体选中的record是login这个字段等于admin的。

好了前面那个 do_clear_done 函数我们应该完全理解了,首先 @api.multi 告诉我们这个函数里面的self是一个recordset,然后domain的语法是: is_done是True或说被勾选了,然后要某该记录的user_id等于当前用户的id self.env.uid ,要某 user_id 值为False(不清楚什么情况)。

接下来执行search方法,返回的done_recs也是一个recordset对象,对于这些recordset对象执行了 write 方法,其接受一个字典值,就是直接更改SQL表格里面的某个表头(属性),将其改为某个值。值得一提的是,recordset调用write方法会将本recordset内所有的record都进行修改操作的。

前面讲到通过 super() 来继承修改原模型的某个方法,请看下面的例子:

@api.one
def do_toggle_done(self):
    if self.user_id != self.env.user:
        raise Exception('Only the responsible can do this!')
    else:
        return super(TodoTask, self).do_toggle_done()

这里 @api.one 自动遍历目标recordset,然后方法里面的self就是一个record。这里程序的逻辑很简单,就是如果用户名不是当前登录用户(因为todo task管理只是自己管理自己的任务计划),那么将会报错。如果是那么就调用之前的方法。

8.4 视图xml文件的继承式修改

一个初步的继承式修改视图xml文件如下所示:

 id="view_form_todo_task_inherited" model="ir.ui.view"> name="name">Todo Task form – User extension name="model">todo.task name="inherit_id" ref="todo_app.view_form_todo_task"/> name="arch" type="xml"> name="name" position="after"> name="user_id" /> name="is_done" position="before"> name="date_deadline" /> name="name" position="attributes"> name="string">I have to...

我们可以看到其通过这样的语句:


对xml视图进行了继承。这里是要对from视图进行修改,就继承的原form视图的id。

8.4.1 视图元素添加

首先我们来看视图元素的添加问题。Odoo提供了这样的定位语法:


    

其具体对应的是所谓的XPath语法,比如  对应的是:

//field[@name]='is_done'

除了field,其他的tag如sheet、group等等都是可以用的,属性name最常使用,其他的属性也是可以用的。定位到具体的标签之后,需要使用 position 来指明插入点。

inside
默认的就是inside,也就是插入定位标签之内。
before
插入定位标签之前。
after
插入定位标签之后。
replace
替换掉定位标签的元素,如果使用空内容,则就是删除原标签元素。

比如这个例子


    


    

的意思就是找到field name="name"的那个标签,然后在它的后面插入 

然后找到field name="is_done"的那个标签,在它的前面插入  。

8.4.2 原视图元素属性修改

position如果设置为 attributes ,则可以具体对原标签元素的某个属性进行修改。

attributes
修改定位标签元素的某个属性。

比如这样:


    I am going to

再如:


    1

之前的active field没必要显示出来了,可以将这个字段的 invisible 属性设置为1,让这个字段在视图上不显示即可。前面讲到replace说到可以删除某个标签元素,但一般不建议这样做,因为可能其他扩展模块又依赖这个标签元素。最好就是将它的 invisible 属性修改一下即可。

读者可以看到 之前那个form视图 。

经过如上的修改,现在成了这个样子了:

8.5 多态继承

_inherit 继承也可以继承多个模型,如下所示写成一个列表值即可,然后 _name 比如指明了,因为有多个继承模型,不指明Odoo是不清楚要继承谁的 _name 的。

_name = 'todo.task'
_inherit = ['todo.task', 'mail.thread']

mail.thread是一个抽象模型,抽象模型没有数据库表达,没有实际创立SQL表格。抽象模型最适合被混合继承使用。要创建一个抽象模型就是继承自 models.AbstractModel 而不是 models.Model 。

8.6 修改其他数据文件

不像视图文件的 arch 结构下的xml可以用XPath表达式,其他xml数据文件则要采取不同的方法来修改之。

8.6.1 删除记录

这是删除记录的语法


使用的是delete标签,然后模型对应某个recordset,然后使用search方法,这里的ref语句还不太清楚。

8.6.2 更新数据

其他记录若不像删除,则使用 这样的语法,若该记录不存在,则会创建,若存在则会修改其中的某些值。

下面这个例子是用来修改record rule权限文件的,将其改成本人和follower都可以看你的todo task。

 noupdate="0"> model="ir.rule" search="[('id', '=',     ref('todo_app.todo_task_user_rule'))]" /> id="todo_task_per_user_rule" model="ir.rule"> name="name">ToDo Tasks only for owner name="model_id" ref="model_todo_task"/> name="groups" eval="[(4, ref('base.group_user'))]"/> name="domain_force">
          ['|',('user_id','in', [user.id,False]),
            ('message_follower_ids','in',[user.partner_id.id])]
        

这里的一些细节我们可以先暂时略过,记住这种记录数据删除和更新的方法就是了。然后看到data标签的 noupdate 属性,如果设置为"0"的话就更新数据,这通常是在开发期这样设置,如果在运行期则设置为"1",也就是接下来模块升级也不会更新本data数据,通常为了运行期稳定会这样设置。

8.7 委托继承

除了前面谈论的 _inherit 继承外,Odoo还提供了一种继承机制,叫做什么委托继承(delegation inheritance)。委托继承特别适合继承官方内置的现有模型。按照官方文档的说法,委托继承一些值是存放于不同的SQL表格中的,所以其似乎是通过一种SQL连接机制来达到继承效果的。然后委托继承只有fields被继承了,而方法没有被继承(因为那些方法又不是存放在SQL表格里面的。)。

具体委托继承的详情分析还需要进一步讨论。

9 理解模型内的数据文件

9.1 理解外部id

所有的记录在Odoo数据库中都有一个独一无二的标识码id,Odoo是通过 ir.model.data 模型来管理这些外部id的。ir.model.data模型对应的SQL表格是 ir_model_data 。这个表格里面存储着各个模型外部名字ID(通过record标签的id属性指定)和具体数据库某个表格ID的映射关系。这个表格有四个字段值得引起我们的注意:

我们执行:

SELECT 
  id, name, module, model, res_id
FROM 
  public.ir_model_data
 WHERE
MODULE = 'qingjia'
;

注意WHERE字句后面的字段要大写。则有:

  id  |                name                | module  |         model         | res_id 
------+------------------------------------+---------+-----------------------+--------
 3707 | model_qingjia_qingjd               | qingjia | ir.model              |    153
 3708 | field_qingjia_qingjd_startdate     | qingjia | ir.model.fields       |   1703
 3709 | field_qingjia_qingjd_create_date   | qingjia | ir.model.fields       |   1704
 3710 | field_qingjia_qingjd_name          | qingjia | ir.model.fields       |   1705
 3711 | field_qingjia_qingjd_create_uid    | qingjia | ir.model.fields       |   1706
 3712 | field_qingjia_qingjd_state         | qingjia | ir.model.fields       |   1707
 3713 | field_qingjia_qingjd_days          | qingjia | ir.model.fields       |   1708
 3714 | field_qingjia_qingjd_reason        | qingjia | ir.model.fields       |   1709
 3715 | field_qingjia_qingjd_write_date    | qingjia | ir.model.fields       |   1710
 3716 | field_qingjia_qingjd_write_uid     | qingjia | ir.model.fields       |   1711
 3717 | field_qingjia_qingjd_id            | qingjia | ir.model.fields       |   1712
 3718 | access_qingjia_qingjd              | qingjia | ir.model.access       |    189
 3719 | action_qingjia_qingjd              | qingjia | ir.actions.act_window |    130
 3720 | qingjia_qingjd_form                | qingjia | ir.ui.view            |    298
 3721 | qingjia_qingjd_tree                | qingjia | ir.ui.view            |    299
 3722 | menu_qingjia                       | qingjia | ir.ui.menu            |    133
 3723 | menu_qingjia_qingjiadan            | qingjia | ir.ui.menu            |    134
 3724 | menu_qingjia_qingjiadan_qingjiadan | qingjia | ir.ui.menu            |    135
 3725 | wkf_qingjia                        | qingjia | workflow              |      1
 3726 | act_draft                          | qingjia | workflow.activity     |      1
 3727 | act_confirm                        | qingjia | workflow.activity     |      2
 3728 | act_accept                         | qingjia | workflow.activity     |      3
 3729 | act_reject                         | qingjia | workflow.activity     |      4
 3731 | qingjia_draft2confirm              | qingjia | workflow.transition   |      1
 3732 | qingjia_confirm2accept             | qingjia | workflow.transition   |      2
 3733 | qingjia_confirm2reject             | qingjia | workflow.transition   |      3

然后我们看到

Figure 28: 记录的外部id

这里的完整ID就对应具体的那条记录,其是由module和name这两个字段的值组合而成的,比如说 qingjia.menu_qingjia ,具体格式就是 .name 。然后具体的内部引用对应的是 ir_ui_menu这个SQL表格(根据上面的model ir.ui.menu 而来)中的133号记录(根据 res_id )而来。

9.2 使用外部id

在Odoo新的API下,你可以通过这样 self.env.ref('external id') 的简介语法来通过外部id来引用具体的某个record。

9.3 导出或导入数据文件

在tree列表视图下,有具体的导入或到处数据文件功能。导入需要csv格式,导出可以是csv格式或excel格式。

值得一提的是 security 文件夹下的 ir.model.access.csv 文件名字是固定的,然后其他一些访问权限规则最好是单独用文件编写。

然后视图的xml文件讲起来也是官方内置模块的对象数据文件,不过这里是不能在网页下点击操作的,必须手工编写xml文件来完成。

workflow的xml文件推荐放在workflow文件夹下。

在一定要手工编写XML文件的情况下,前面已经有所讨论了,这里进一步进行一些补充说明。

9.4 快捷输入标签

一般的记录声明就是使用的record标签,然后加上id属性和model属性。如下所示:


    User
    
    

同时前面提到了 menuitem 这样的快捷输入标签可以这样使用。


这些快捷标签的使用很方便的,下面是一些可用的快捷输入标签清单:

对应模型  ir.actions.act_window ,视窗动作对象。
对应模型  ir.ui.menu ,菜单对象。
对应模型  ir.actions.report.xml 打印动作对象。