以一个例子开头
* To-do 向导
# 配置文件 
__openerp_.py:

{ 'name': 'To-do Tasks Management Assistant',
'description': 'Mass edit your To-Do backlog.',
'author': 'Daniel Reis',
'depends': ['todo_user'],
'data': ['todo_wizard_view.xml'], }

__init__.py:

from . import todo_wizard_model

# 模型 todo_wizard_model.py
# -*- coding: utf-8 -*-
from openerp import models, fields, api
from openerp import exceptions # will be used in the code

import logging
_logger = logging.getLogger(__name__)

class TodoWizard(models.TransientModel):
_name = 'todo.wizard'
task_ids = fields.Many2many('todo.task', string='Tasks')
new_deadline = fields.Date('Deadline to Set')
new_user_id = fields.Many2one( 'res.users', string='Responsible to Set')


@api.multi
def do_mass_update(self):
self.ensure_one()
if not (self.new_deadline or self.new_user_id):
raise exceptions.ValidationError('No data to update!')
# else:
_logger.debug('Mass update on Todo Tasks %s', self.task_ids.ids)
if self.new_deadline:
self.task_ids.write({'date_deadline': self.new_deadline})
if self.new_user_id:
self.task_ids.write({'user_id': self.new_user_id.id})
return True

@说明几点:
@ 引入了日志功能,记录的级别如下4级
_logger.debug('A DEBUG message')
_logger.info('An INFO message')
_logger.warning('A WARNING message')
_logger.error('An ERROR message')

# 表单 todo_wizard_view.xml
<openerp>
<data>
<record id="To-do Task Wizard" model="ir.ui.view">
<field name="name">To-do Task Wizard</field>
<field name="model">todo.wizard</field>
<field name="arch" type="xml">
<form>
<div class="oe_right">
<button type="object" name="do_count_tasks"
string="Count"/>
<button type="object" name="do_populate_tasks"
string="Get All"/>
</div>
<field name="task_ids"/>
<group>
<group>
<field name="new_user_id"/>
</group>
<group>
<field name="new_deadline"/>
</group>
</group>
<footer>
<button type="object" name="do_mass_update"
string="Mass Update" class="oe_highlight"
attrs="{'invisible':[('new_deadline','=',False),('new_user_id', '=',False)]}"/>
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<!-- More button Action -->
<act_window id="todo_app.action_todo_wizard"
name="To-Do Tasks Wizard"
src_model="todo.task" res_model="todo.wizard"
view_mode="form" target="new" multi="True"/>
</data>
</openerp>

@说明:有一个批量更新的逻辑 do_mass_update

# 业务逻辑 todo_wizard_model.py
@api.multi
def do_mass_update(self):
self.ensure_one()
if not (self.new_deadline or self.new_user_id):
raise exceptions.ValidationError('No data to update!')
# else:
_logger.debug('Mass update on Todo Tasks %s', self.task_ids.ids)
if self.new_deadline:
self.task_ids.write({'date_deadline': self.new_deadline})
if self.new_user_id:
self.task_ids.write({'user_id': self.new_user_id.id})
return True

@说明:@api.one 这个是返回一个记录用的 这是主要用 task_ids,所以这里不用
@api.multi 这是是返回一记录列表用
self.ensure_one() 确保是单个,否则报异常

这里有异常处理 :
from openerp import exceptions
raise exceptions.Warning('Warning message')
raise exceptions.ValidationError('Not valid message')

# 自动加载代码变化,这是主要python代码变化,就要手动重启服务,有点麻烦
从而有了 ./odoo.py -d v8dev --auto_reload 自动加载
这个选项可以放到配置文件中 auto_reload = True 
有先安装一个依赖,才可以用 pip install pyinotify

# 向导窗口的动作
可以重新打开表单,从而一个表单上的多个按钮可以同时用
@api.multi
def do_reopen_form(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': self._name, # this model
'res_id': self.id, # the current wizard record
'view_type': 'form',
'view_mode': 'form',
'target': 'new'} 

全选:
@api.multi
def do_populate_tasks(self):
self.ensure_one()
Task = self.env['todo.task']
all_tasks = Task.search([])
self.task_ids = all_tasks
# reopen wizard form on same 

* 服务端的操作

# 开启命令行交互操作
https://www.odoo.com/apps/modules/8.0/shell/. 
把那个shell模块,放到addons中去
$ ./odoo.py shell -d v8dev 
指定账套,采用命令交互模式启动

这样就可以测试代码中用到的一些对象,如:

>>> self 得到当前用户对象,这里是超级用户对象
res.users(1,)
>>> self.name 
u'Administrator'
>>> self._name
'res.users'
>>> self.env
<openerp.api.Environment object at 0xb3f4f52c>

# 用关系字段
>>> self.company_id
res.company(1,)
>>> self.company_id.name
u'YourCompany'
>>> self.company_id.currency_id
res.currency(1,)
>>> self.company_id.currency_id.name
u'EUR'

#模型中的查询
用self只能得到方法中的字段,但 self.env 可以得到任意其它模型
self.env['res.partner'] 得到合作伙伴对象
运用search() 或 browse() 得到对象的记录

search() 里面是domain表达式,若为空[]则搜索所有的记录
模型中有active字段,默认为True搜索,还有一些参数如下:
order 排序 limit 返回最大记录数 offset 记录数启点位置

search_count()得到记录总数
browse()主要根据记录id 返回记录 多个,采用列表形式。单个时可以直接传入

例子:
>>> self.env['res.partner'].search([('name','like','a')])
res.partner(36, 5, 30, 31, 20, 27, 10, 24, 29, 22, 18)

>>> self.env['res.partner'].browse([5,30])
res.partner(5, 30)

>>> self.env['res.partner'].browse(5)
res.partner(5,)

>>> self.env['res.partner'].browse([5])
res.partner(5,)

#记录上的操作
>>> admin = self.env['res.users'].browse(1)
>>> admin.name='Superuser'
>>> admin.name

@创建一个新记录,但还没有持久化到数据库
>>> Partner = self.env['res.partner']
>>> new = Partner.create({'name':'ACME','is_compnay':True})
>>> print new
res.partner(37,)

@删除记录
>>> rec = Partner.search([('name','=','ACME')])
>>> rec.unlink()
True

@把partner对象全部comment的值改为 'Hello!'
res.partner(37,)
>>> Partner.write({'comment':'Hello!'})

@复制一个记录
>>> demo = self.env.ref('base.user_demo') 得到demo这个用户对角
>>> new = demo.copy({'name': 'Daniel', 'login': 'dr', 'email':''})
>>> self.env.cr.commit()

注:命令效互时操作记录不会记录到数据库中,只有用了 self.env.cr.commit() 后才
会持久化到数据库中

#事务操作和原生sql
self.env.cr 数据库操作句柄
self.env.cr.commit() 事务提交
self.env.savepoit() 事务提交点,回滚时可以来指定回滚到什么位置
self.evn.rollbakc() 事务回滚

原生sql操作 self.env.cr.execute()
self.env.cr.execute("SELECT id, login FROM res_users WHERE login=%s OR id=%s",('demo',1))
self.env.cr.fetchall()

#时间和日期
%Y-%m-%d 和 %Y-%m-%d %H:%M:%S

>>> from openerp import fields
>>> fields.Datetime.now()
'2016-01-25 07:12:34'
>>> fields.Datetime.from_string('2016-12-08 22:23:22')
datetime.datetime(2016, 12, 8, 22, 23, 22)

fields.Date.today() 得到当前的日期是服务器端的
fields.Datetime.now()得到当前的时间是服务器端的
fields.Date.context_today(record,timestamp=None) 从上下文得到当前的日期
fields.Datetime.context_timestamp(record,timestamp) 从上下文得到当前的时间

from_string(value) 字符串转换为日期或时间对象
to_string(value) 日期或时间对象转换为字符串

#关系字段操作
当采用活动记录模式时,用 create() 或 write() 赋关联字段时,不要用对象,要用外键id
不要 self.write({'user_id':self.env.user})
而要改为 self.write({'user_id':self.evn.user.id})

#操作记录集 (集合操作)
rs1 | rs2 求并集 是指两个集合的所有元素构成的集合
rs1 + rs2 求两个集直接连起来,不管重复
rs1 & rs2 求交集 是指两个集合元素相同的部分构成的集合
rs1 - rs2 求差集 是指其中一个集合中除去另一个集合相同元素以后剩余的元素构成的集合

例子说明:
>>> Party=self.env['res.partner']
>>> Party.search([])
res.partner(36, 5, 7, 33, 32, 23, 30, 31, 19, 35, 20, 17, 21, 34, 27, 12, 8, 9, 15, 25, 14, 10, 24, 3, 1, 26, 29, 22, 6, 13, 11, 28, 16, 18)
>>> rs1=Party.browse([1,3,5,7])
>>> rs2=Party.browse([7,32,33])
>>> rs1 | rs2
res.partner(32, 1, 3, 5, 7, 33)
>>> rs1 + rs2
res.partner(1, 3, 5, 7, 7, 32, 33)
>>> rs1 & rs2
res.partner(7,)
>>> rs1 - rs2
res.partner(1, 3, 5)

记录切片(相当于序列切片操作)
>>> rs1
res.partner(1, 3, 5, 7)
>>> rs1[0]
res.partner(1,)
>>> rs1[-1]
res.partner(7,)
>>> rs1[1:]
res.partner(3, 5, 7)

记录集追求记录去除
self.task_ids |= task1 task1在task_ids中没有就增加 <=> self.write([(4, task1.id, False)])
self.task_ids -= task1 task1 在task_ids 减去 <=> self.write([(3, task1.id, False)])
self.task_ids = self.task_ids[:-1] task_ids 删除最后一个记录
<=> self.write([(3, self.task_ids[-1].id, False)])

其它记录集操作
record in recordset 检测一个记录是否在一个记录集中
record not in recordset 检测一个记录是否不在一个记录集中 

recordset.ids 记录ID集
recordset.ensure_one() 检测是一个记录
recordset.exists() 若存在返回一个备份
recordset.filtered(func) 过滤记录集
recordset.mapped(func)
recordset.sorted(func) 排序后

len(rs1) 得到记录数
>>> rs1
res.partner(1, 3, 5, 7)
>>> len(rs1)
4

rs1 = rs0.filtered(lambda r:r.name.startswith('H')) 通过name过滤
rs2 = rs0.filtered('is_company') 通过is_compay 为True 来过滤
rs2 = rs0.mapped('name') 得到只有name字段 组成记录集 列表
rs1.sorted(key=lambda r:r.id,reverse=True) 根据id 反向排序


* 执行环境
self.env 具有的属性:
env.cr 数据库操作句柄
env.uid session中的user的id
env.user session中的user对象
env.context session中的上下文,以字典方式组成
环境对象是不能改的只能换,重建
env.sudo(user) 给了用户记录就返回该用户记录,没有,就返回超级用户
env.with_context(dictionary) 换了原有的session
env.with_context(key=value,...) 换指定键的值

env.ref() 要一个 External ID 做为参数

>>> self.env.ref('base.user_root') 这是超级用户的external ID
res.users(1,)

* 客户端效互的模型方法

read([fields]) 像search() 但返回来,每一行是一个字典类型
search_read([domain],[fields],offset,limit,order=None) 根据 read()后的结果搜索
load([fields],[data]) 用于ssv数据导入
export_data([fields],raw_data=False) 用于csv数据导出

name_get() 得到(ID,name) 元组的列表 默认是计算 display_name
name_search(name='', args=None, operator='ilike', limit=100) 得到(ID,name) 元组的列表 ,按条件
name_create(name) 创建一个新记录,只用name字段
default_get([fields]) 返回创建新记录时,指定字佰的默认值
fields_get() 得到模型定义的哪些字段
fields_view_get() 得到指定视图类型下可用的字段, 视图类型,有tree ,form,等

#覆盖默认方法
create() 和 write() 这两个方法常扩展
例子如下:
@api.model
def create(self, vals):
# Code before create
# Can use the `vals` dict
new_record = super(TodoTask, self).create(vals)
# Code after create
# Can use the `new` record created
return new_record

@api.multi
def write(self, vals):
# Code before write
# Can use `self`, with the old values
super(TodoTask, self).write(vals)
# Code after write
# Can use `self`, with the new (updated) values
return True

#模型方法修饰器
@api.one 一次得到一条记录

@api.multi 一次得到一个记录集

@api.model 这是类级别静态方法

@api.return(model) 显性返回一个模型 


@api.depends(fld1,...) 
@api.constrains(fld1,...)
@api.onchange(fld1,...) 可用于警告

#调试

import pdb; pdb.set_trace()