-
Openerp---Relational Types
ono2one:一个one2one表达式是两个对象之间是一对一关系。已经过时,现在使用many2one。
语法:
- fields.one2one('other.object.name', 'Field Name')
many2one:将这个对象与父对象借助这个字段关联起来。例如,员工与部分即为多对一关系。即许多员工属于通一个部门。
语法:
- fields.many2one(
- 'other.object.name',
- 'Field Name',
- optional parameters)
可选参数:
-
ondelete:当这个字段指向的资源删除时将发生。预定义值:cascade,set null,restrict,no action,set default。默认值:set null
-
required:True
-
readonly:True
-
select:True-(在外键字段上创建了一个索引)
例子:
- 'commercial': fields.many2one(
- 'res.users',
- 'Commercial',
- ondelete='cascade'),
one2many:两个对象之间是一对多关系
语法:
- fields.one2many(
- 'other.object.name',
- 'Field relation id',
- 'Fieldname',
- optional parameter)
可选参数:
-
invisible: True/False
-
states
-
readonly: True/False
例子:
- 'address': fields.one2many(
- 'res.partner.address',
- 'partner_id',
- 'Contacts'),
many2many:
语法:
- fields.many2many('other.object.name',
- 'relation object',
- 'actual.object.id',
- 'other.object.id',
- 'Field Name')
这里:
-
other.object.name :属于这个关系的其他对象
-
relation object:需要这种连接的表
-
actual.object.id 和other.object.id :关联表的字段名
例子:
- 'category_ids':
- fields.many2many(
- 'res.partner.category',
- 'res_partner_category_rel',
- 'partner_id',
- 'category_id',
- 'Categories'),
为了变成双向的(在另一个对象中创建一个字段)
- class other_object_name2(osv.osv):
- _inherit = 'other.object.name'
- _columns = {
- 'other_fields': fields.many2many(
- 'actual.object.name',
- 'relation object',
- 'actual.object.id',
- 'other.object.id',
- 'Other Field Name'),
- }
- other_object_name2()
例子:
- class res_partner_category2(osv.osv):
- _inherit = 'res.partner.category'
- _columns = {
- 'partner_ids': fields.many2many(
- 'res.partner',
- 'res_partner_category_rel',
- 'category_id',
- 'partner_id',
- 'Partners'),
- }
- res_partner_category2()
related:有时候需要涉及到关联的关联。例如:假设你有这些对象:City--->State--->Country,你需要从City关联到County。可以在City对象上定义一个字段。如下所示:
- 'country_id': fields.related(
- 'state_id',
- 'country_id',
- type="many2one",
- relation="res.country",
- string="Country",
- store=False)
这里:
-
type是字段类型
-
The first set of parameters are the chain of reference fields to follow, with the desired field at the end
-
如果期望的字段仍旧是某种引用就使用relation。 relation是查找关系引用的表
函数字段
函数字段是一个值由函数计算的字段(而不是保存在数据库中的)
参数:
fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type="float",
fnct_search=None, obj=None, method=False, store=False, multi=False
这里:
-
fnct 是一个计算字段值的方法或函数。必须在声明函数字段前声明它。
-
fnct_inv:是一个允许设置这个字段值的函数或方法。
-
type:由函数返回的字段类型名。其可以是任何字段类型名 除了函数
-
fnct_search:允许你在这个字段上定义搜索功能
-
method:这个字段是由一个方法计算还是由一个全局函数计算。
-
store:是否将这个字段存储在数据库中。默认false
-
multi:一个组名。所有的有相同multi参数的字段将在一个单一函数调用中计算
fnct 参数
如果这个方法是True,方法签名必须是:
def fnct(self, cr, uid, ids, field_name, arg, context):
否则,(如果是一个全局函数),方法签名如下:
def fnct(cr, table, ids, field_name, arg, context):
任何一种方式,它必须返回这个表单值的字典{id'_1_': value'_1_', id'_2_': value'_2_',...}.
返回字典的值必须是指定类型,由这个字段字典声明的type参数指定。
如果设置了multi,field_name由field_names替换:计算一列字段名。返回字典的每个值也是从名字到值的字典。例如,如果字段name和age都是基于vital_statistics函数的,那么这个函数的返回值可能像这样,当ids是[1,2,5]
- {
- 1: {'name': 'Bob', 'age': 23},
- 2: {'name': 'Sally', 'age', 19},
- 5: {'name': 'Ed', 'age': 62}
- }
fnct_inv 参数
如果method为true,方法签名如下:
def fnct(self, cr, uid, ids, field_name, field_value, arg, context):
否则,(全局函数),应该是:
def fnct(cr, table, ids, field_name, field_value, arg, context):
fnct_search参数
如果method为true,方法签名如下:
def fnct(self, cr, uid, ids, field_name, field_value, arg, context):
否则,(全局函数),应该是:
def fnct(cr, table, ids, field_name, field_value, arg, context):
返回值是包含3个元组的列表,用户查询函数。
- return [('id','in',[1,3,5])]
obj与self相似,并且name接受字段名。args是一列三元数组,包含了这个字段的查询条件,虽然查询函数可能分开由每个元组调用。
例子:
假如我们创建了一个合约对象:
- class hr_contract(osv.osv):
- _name = 'hr.contract'
- _description = 'Contract'
- _columns = {
- 'name' : fields.char('Contract Name', size=30, required=True),
- 'employee_id' : fields.many2one('hr.employee', 'Employee', required=True),
- 'function' : fields.many2one('res.partner.function', 'Function'),
- }
- hr_contract()
如果你想增加一个字段通过看其当前合约来重新获取一个员工的函数,我们使用functional字段。对象hr_employee用这种方式继承:
- class hr_employee(osv.osv):
- _name = "hr.employee"
- _description = "Employee"
- _inherit = "hr.employee"
- _columns = {
- 'contract_ids' : fields.one2many('hr.contract', 'employee_id', 'Contracts'),
- 'function' : fields.function(
- _get_cur_function_id,
- type='many2one',
- obj="res.partner.function",
- method=True,
- string='Contract Function'),
- }
- hr_employee()
注意
三点:
-
type=‘many2one’是因为函数字段必须创建一个many2one字段。函数在hr_contract also中声明为一个many2one。
-
obj=‘res.partner.function’ 用于指定这个对象使用many2one字段。
-
调用 _get_cur_function_id 方法,因为其角色是返回一个字典,其关键是员工ids,并且其对应值是那些员工的函数ids。这个方法代码如下:
- def _get_cur_function_id(self, cr, uid, ids, field_name, arg, context):
- for i in ids:
- #get the id of the current function of the employee of identifier "i"
- sql_req= """
- SELECT f.id AS func_id
- FROM hr_contract c
- LEFT JOIN res_partner_function f ON (f.id = c.function)
- WHERE
- (c.employee_id = %d)
- """ % (i,)
- cr.execute(sql_req)
- sql_res = cr.dictfetchone()
- if sql_res: #The employee has one associated contract
- res[i] = sql_res['func_id']
- else:
- #res[i] must be set to False and not to None because of XML:RPC
- # "cannot marshal None unless allow_none is enabled"
- res[i] = False
- return res
函数id通过使用一个SQL查询重新获取函数id。注意查询返回没有结果,sql_res['func_id']的值将返回为None。在此种情况下我们强制设定为False值,因为XML:RPC(服务器与u客户端之间的通信)不允许传递这样的值。
store Parameter(存储参数)
其将计算字段并在表中保存结果。当其他对象的几个字段变更了,将重新计算字段。其使用以下语法:
- store = {
- 'object_name': (
- function_name,
- ['field_name1', 'field_name2'],
- priority)
- }
当任何变化写入对象“'object_name'”的字段列表['field1','field2'],将调用function_name函数。这个函数有以下的标签:
- def function_name(self, cr, uid, ids, context=None):
这里ids是其他对象表的记录的ids。在关注的字段中改变了其值。这个函数将返回它的自己表中一列记录ids,其应该有要计算的字段。这个列表将作为字段的额主要函数的一个参数发送。
这里是来自membership 模块的例子
- 'membership_state':
- fields.function(
- _membership_state,
- method=True,
- string='Current membership state',
- type='selection',
- selection=STATE,
- store={
- 'account.invoice': (_get_invoice_partner, ['state'], 10),
- 'membership.membership_line': (_get_partner_id,['state'], 10),
- 'res.partner': (
- lambda self, cr, uid, ids, c={}: ids,
- ['free_member'],
- 10)
- }),
Property 字段
声明一个属性
属性是一个特殊的字段:fields.property
- class res_partner(osv.osv):
- _name = "res.partner"
- _inherit = "res.partner"
- _columns = {
- 'property_product_pricelist':
- fields.property(
- 'product.pricelist',
- type='many2one',
- relation='product.pricelist',
- string="Sale Pricelist",
- method=True,
- group_name="Pricelists Properties"),
- }
之后你得在一个XML文件中为这个属性创建默认的值
- <record model="ir.property" id="property_product_pricelist">
- <field name="name">property_product_pricelist</field>
- <field name="fields_id" search="[('model','=','res.partner'),
- ('name','=','property_product_pricelist')]"/>
- <field name="value" eval="'product.pricelist,'+str(list0)"/>
- </record>
注意
如果默认值指向另一个模块的资源,你可以使用ref函数:
<field name="value" eval="'product.pricelist,'+str(ref('module.data_id'))"/>
在表单中放入属性
为了在表单中添加属性,仅需要在你的表单中放入<properties>标签。这个将自动添加所有的与这个对象关联的属性字段。这个系统将添加由你权限决定的属性(一些人可以改变一些指定的属性,其他的不行)。
属性是一段段显示,取决于group_name属性(其在客户端中渲染,如同一个分隔符一样)
其是如何工作的?
fields.property类继承了fileds.funcation并且重写了读写方法。这类字段类型是many2one,所以表单中这个属性是many2one函数。
但是属性值存储于 ir.property 类/表中作为一个全记录。存储的值是一个类型关联的字段(不是many2one)因为每个属性可能指向一个不同的对象。如果你从管理员菜单编辑属性值,这些就像是类型关联的额字段。
当你读取一个属性时,程序给你这个属性来访问你正在读取的对象的实例。如果这个对象没有值,系统给返回你默认的属性。
属性的定义存储在 ir.model.fields类中,如同其他字段一样。在属性的定义中,你可以添加允许改变字段的分组。
使用属性或者一般的字段
当你想添加一个新的功能的时候,你得选择作为一个属性或者作为普通字段来实现它。当从一个对象中继承时使用一个普通属性并扩展这个对象。当新功能没有关联这个对象而是扩展的话使用属性
这里有几点帮助你选择使用属性还是普通字段。
普通字段继承对象,添加更多功能或者数据
一个属性是一个访问对象和有特殊功能的的概念。
-
取决于公司的相同属性的不同的值
-
每个字段的权限管理
-
不同资源的连接(many2one)
例子 1 : 账户应收款
指定合作者的账户应收款作为一个属性实现,因为:
-
这是一个关联账户图表而不是合作者的概念,所以这是一个账户属性,其对账户表单可见。对于会计管理这个字段权限,这些没有相同的权限应用在合作者对象。所以你对于合作者表单的这个字段仅有指定的权限:只有会计可能变更一个合作者的账户应收款。
-
这是一个multi-company字段:相同的合作者可能有不同的账户应收款值,取决于用户所属的公司。在一个多公司系统,对于每个公司有一个账户图表。一个合作者的账户应收款取决于公司的销售订单
-
默认的账户应收款对于所有合作者是相同的并且可以从一般的属性菜单中可配置(在管理员系统)
注意:
一件有意思的事情是属性避免了 "spaghetti"编码。应收账款模块取决于合作者(basis)模块。但是了你可以独立安装合作者模块。如果你在合作者对象上添加了一个指向应收款的字段,双方对象互相依赖。维护代码就更加困难(例如,当两张表指向彼此时尝试移除一张表)
例子 2 :产品时间(Product Times)
产品逾时模块实现了所有的与产品关联的逾时:removal data,product usetime,。。。这个模块适用于食品行业
这个模块继承了product.product object 并且添加了新的字段
- class product_product(osv.osv):
- _inherit = 'product.product'
- _name = 'product.product'
- _columns = {
- 'life_time': fields.integer('Product lifetime'),
- 'use_time': fields.integer('Product usetime'),
- 'removal_time': fields.integer('Product removal time'),
- 'alert_time': fields.integer('Product alert time'),
- }
- product_product()
这个模块给product.product 对象添加了简单的字段。我们不使用属性那是因为:
-
我们扩展了product,life_time 是一个关联产品的概念而不是另一个对象
-
我们不需要每个字段的权限管理,不同的延迟由相同的人管理,这些人管理着所有的产品
ORM 方法
在ORM方法中保持上下文
在OpenERP中,上下文持有很重要的数据,比如语言,其中文档必须要写,无论函数字段是否需要更新等等。
当调用一个ORM方法时,你将可能已经有了一个上下文-----例如,框架将提供你一个上下文作为每个方法的参数。如果你确实有一个上下文,每次在方法调用时传递这个参数。
这条规则也使用于写ORM方法。你应该期待接受一个上下文作为参数,总在调用每个其他方法时传递这个参数。