创建模型
在模型上字段作为属性被定义
from openerp import models, fields
class AModel(models.Model):
_name = 'a.model.name'
field1 = fields.Char()
警告
意味着字段和方法不能有相同的名字,否则会冲突
默认情况下,显示给用户的是首字母大写的字段名,但是可以被参数"string"覆盖
field2 = fields.Integer(string="an other field")
字段的默认值通过参数定义,如下
a_field = fields.Char(default="a value")
或者调用一个函数,获取函数的返回值
a_field = fields.Char(default=compute_default_value)
def compute_default_value(self):
return self.get_value()
计算字段
字段的值可以通过compute参数来计算,代替直接从数据库中获取。必须将计算的值赋给字段,如果需要用到其它字段时,应该用depends()修饰符
from openerp import api
total = fields.Float(compute='_compute_total')
@api.depends('value', 'tax')
def _compute_total(self):
for record in self:
record.total = record.value + record.value * record.tax
当使用子字段时,依赖关系可以使点路径
@api.depends('line_ids.value')
def _compute_total(self):
for record in self:
record.total = sum(line.value for line in record.line_ids)
计算字段默认不存储,它们被计算并在请求时返回。设置"store = True"可以存储到数据库中并且启用自动搜索
搜索参数也可以被计算来启用计算字段上的搜索,这个方法返回一个domains
upper_name = field.Char(compute='_compute_upper', search='_search_upper')
def _search_upper(self, operator, value):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]
使用"inverse"参数可以在一个字段上设置值,它的值是一个函数名,反转计算并设置相关字段。
document = fields.Char(compute='_get_document', inverse='_set_document')
def _get_document(self):
for record in self:
with open(record.get_document_path) as f:
record.document = f.read()
def _set_document(self):
for record in self:
if not record.document: continue
with open(record.get_document_path()) as f:
f.write(record.document)
多个字段可以使用相同的方法进行设置,仅仅只用相同的方法进行设置即可
discount_value = fields.Float(compute='_apply_discount')
total = fields.Float(compute='_apply_discount')
@depends('value', 'discount')
def _apply_discount(self):
for record in self:
# compute actual discount from discount percentage
discount = record.value * record.discount
record.discount_value = discount
record.total = record.value - discount
related字段
计算字段的特殊字段是related字段,它提供当前记录上的子字段的值。它们是通过设置相关参数来定义的,像常规的计算字段一样,它们可以存储:
nickname = fields.Char(related='user_id.partner_id.name', store=True)
onchange:动态更新UI
当用户更新了字段的值(但并没有保存),它能够自动的更新其它字段的值
- 计算字段能够自动的检查和重新计算,不需要onchange
- 对于非计算字段,onchang()修饰符可以提供新的字段值
# 如果这些字段值发生改变,调用check_change方法
@api.onchange('field1', 'field2')
def check_change(self):
if self.field1 < self.field2:
self.field3 = True
方法执行过程中的更改对用户可见
- 客户端自动调用计算字段和onchange()修饰符,不需要在视图中添加
- 可以在视图中添加on_change="0"在特定字段中抑制触发器。
<field name="name" on_change="0"/>
当用户编辑时,即使字段拥有onchange属性,也不会触发更新。
Note
onchange只是对记录上的值进行计算,然后返回客户端,并不会保存到数据库中
Low-level SQL
环境上的cr属性是当前数据库事务的指针,允许直接执行SQL,对于用ORM难以表示的查询或者性能原因,则可以使用sql:
self.env.cr.execute("some_sql", param1, param2, param3)
由于模型使用相同的游标,并且环境中保存着各种缓存,所以当使用sql修改数据库时,这些缓存必须失效,否则模型的进一步使用可能会变得不连贯。在sql中使用创建、更新或者删除时,有必要清楚缓存,而不是SELECT(它只是读取数据库)
新旧API之间的兼容
odoo正在从一个老的(不太常规的)API过渡,需要手动连接到另一个API:
- RPC层(XML-RPC和JSON-RPC)是用旧API表示的,纯在新API中表示的方法在RPC上不可用
- 可重写的方法可以从仍然使用旧API样式编写的旧代码片段中调用
新旧API之间最大的区别是:
- 环境的值(游标、用户id和上下文)被显式地传递给方法
- 记录数据(ids)显式地传递给方法,可能根本就不传递
- 记录数据(ids)显式地传递给方法,可能根本就不传递
默认情况下,假定方法使用新的API样式,并且不能从旧的API样式调用
Tip
从新API到旧API的调用被桥接
当使用新的API样式时,对使用旧API定义的方法的调用将自动动态转换,不需要做任何特别的事情
>>> # 旧API定义的方法
>>> def old_method(self, cr, uid, ids, context=None):
... print ids
>>> # 新API定义的方法
>>> def new_method(self):
... # 系统自动推断如何调用旧的样式
... self.old_method()
>>> env[model].browse([1, 2, 3, 4]).new_method()
[1, 2, 3, 4]
两个decorator可以向旧API公开一个新风格的方法:
model()
该方法公开为不使用ids,其记录集通常为空。它的“旧API”签名是cr、uid、*arguments、context:
@api.model
def some_method(self, a_value):
pass
# 等于
old_style_model.some_method(cr, uid, a_value, context=context)
multi()
该方法公开为一个id列表(可能为空),其“旧API”签名为cr、uid、ids、*arguments、context:
@api.multi
def some_method(self, a_value):
pass
# 等于
old_style_model.some_method(cr, uid, [id1, id2], a_value, context=context)
因为新风格的api倾向于返回记录集,而旧风格的api倾向于返回id列表,所以还有一个装饰器来管理这一点:
returns()
假设函数返回一个记录集,第一个参数应该是记录集的模型或self(对于当前模型)的名称。
如果以新的API样式调用该方法,但从旧的API样式调用时将记录集转换为id列表,则不会产生任何影响:
>>> @api.multi
... @api.returns('self')
... def some_method(self):
... return self
>>> new_style_model = env['a.model'].browse(1, 2, 3)
>>> new_style_model.some_method()
a.model(1, 2, 3)
>>> old_style_model = pool['a.model']
>>> old_style_model.some_method(cr, uid, [1, 2, 3], context=context)
[1, 2, 3]
Model Reference
class openerp.models.Model(pool, cr)
常规数据库的主要超类——持久化OpenERP模型。
OpenERP模型是从这个类继承而来的:
class user(Model):
...
系统稍后将在每个数据库(安装了类模块的数据库)上实例化该类一次。
结构属性
_name
业务对象名称,用点符号表示(在模块名称空间中)
_rec_name
用作名称的替代字段,由osv的name_get()(默认:‘name’)使用
_inherit
- 如果设置了_name,则继承该模型以_name命名,并在其上进行扩展
- 如果不设置_name,则继承该模型,在原模型上进行扩展
_order
在没有指定排序的情况下进行搜索时的排序字段(默认值:‘id’)
_auto
是否应该创建数据库表(默认:True)如果设置为False,则重写init()以创建数据库表
_table
是否应该创建数据库表(默认:True)如果设置为False,则重写init()以创建数据库表
_inherits
字典将父业务对象的_name映射到要使用的相应外键字段的名称
_inherits = {
'a.model': 'a_field_id',
'b.model': 'b_field_id'
}
实现基于复合的继承:新模型公开了_inherits-ed模型的所有字段,但没有存储任何字段:值本身仍然存储在链接的记录上。
_constraints
定义python约束的列表(constraint_function, message, fields),字段列表是指示性的。
_sql_constraints
在生成备份表时,定义SQL约束以执行的元组(name,sql_definition,message)
_parent_store
在parent_left和parent_right旁边,设置一个嵌套集,以支持对当前模型记录进行快速分层查询(默认:False)
CRUD
create(vals)->record
在模型中创建一个新的记录
新记录使用vals中和default_get()中的值初始化
Parameters: vals(dict) –
模型字段的值,如字典
{'field_name': field_value, ...}
Returns:新创建的记录
Raises: AccessError –
- 如果用户对请求的对象没有创建权限
- 如果用户试图绕过在请求对象上创建的访问规则
- ValidateError——如果用户试图为未选择的字段输入无效值
- UserError——如果在对象的层次结构中创建了一个循环,则是操作的结果(例如将对象设置为其父对象)
browse([ids]) → records
返回当前环境中作为参数提供的id的记录集。
值可以为空,单个id或者一个列表
unlink()
删除当前集合的记录
Raises: AccessError –
- 如果用户在请求的对象上没有unlink权限
- 如果用户试图绕过请求对象上取消链接的访问规则
- UserError——如果记录是其他记录的默认属性
write(vals)
使用提供的值更新当前集合中的所有记录。
Parameters: vals(dict) –
要更新的字段和要设置的值,例如:
{'foo': 1, 'bar': "Qux"}
将字段foo设为1,字段bar设为“Qux”(否则会触发错误)
Raises: AccessError –
- 如果用户对请求的对象没有写权限
- 如果用户试图绕过对请求对象进行写操作的访问规则
- ValidateError——如果用户试图为未选择的字段输入无效值
- UserError——如果在对象的层次结构中创建了一个循环,则是操作的结果(例>- 如将对象设置为其父对象)
- 对于数值字段(整数、浮点数),值应该是相应的类型
- 对于布尔值,值应该是bool
- 对于选择,该值应该匹配选择值(通常为str,有时为int)
- 对于Many2one,该值应该是要设置的记录的数据库标识符
- 其它非关系字段使用字符串作为值
Danger
由于历史和兼容性的原因,日期和日期时间字段使用字符串作为值(写入和读> 取),而不是日期或日期时间。这些日期字符串是utc格式的,并且根据openerp.tools.misc.DEFAULT_SERVER_DATE_FORMAT和openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT进行格式化。
- One2many和Many2many使用一种特殊的“命令”格式来操作存储在/与字段关联的记录集。
这种格式是顺序执行的三元组列表,其中每个三元组是要在记录集上执行的命令。并不是所有命令都适用于所有情况。可能的命令是:
(0, _, values)
添加从提供的值字典创建的新记录。
(1, id, values)
使用值中的值更新现有的id id记录。无法在create()中使用。
(2, id, _)
从集合中删除id id记录,然后(从数据库中)删除它。无法在create()中使用。
(3, id, _)
从集合中删除id id记录,但不删除它。不能在One2many上使用。无法在create()中使用。
(4, id, _)
将现有的id id记录添加到集合中。不能在One2many上使用。
(5, _, _)
从集合中删除所有记录,相当于显式地对每个记录使用命令3。不能在One2many上使用。无法在create()中使用。
(6, _, ids)
替换ids列表中设置的所有现有记录,相当于对ids中的每个id使用命令5和命令4。不能在One2many上使用。
Note
上面列表中标记为_的值被忽略,可以是任何值,通常为0或False。
read([fields])
在self、low-level/RPC方法中为记录读取请求字段。在Python代码中,首选browse()。
Parameters: fields – 要返回的字段名称列表(默认为所有字段)
Returns: 将字段名称映射到其值的字典列表,每个记录有一个字典
Raises: AccessError – 如果用户对某些给定记录没有读权限