首先,让我们看一个简单的模型方法,假设我们有一个名为Product的模型,其中包含一个字段price表示产品的价格。我们可以定义一个计算折扣后价格的方法,如下所示:
复制代码
class Product(models.Model):
_name = 'product.product'
name = fields.Char(string='Product Name')
price = fields.Float(string='Price')
def apply_discount(self, discount):
discounted_price = self.price * (1 - (discount / 100))
return discounted_price
在这个例子中,apply_discount方法接受一个折扣值作为参数,并返回折扣后的价格。这个方法可以在Odoo中的视图或其他地方调用,以获取产品的折扣价格。
另一个例子是关于创建记录的方法。假设我们有一个名为SaleOrder的模型,我们可以定义一个方法来创建新的销售订单,如下所示:
复制代码
class SaleOrder(models.Model):
_name = 'sale.order'
name = fields.Char(string='Order Name')
customer_id = fields.Many2one('res.partner', string='Customer')
def create_order(self, customer_id, order_lines):
new_order = self.create({
'customer_id': customer_id,
'order_lines': order_lines
})
return new_order
在这个例子中,create_order方法接受客户ID和订单行信息作为参数,并使用self.create方法创建一个新的销售订单记录。这个方法可以在Odoo中的业务逻辑中调用,以创建新的销售订单。
load_views
load_views方法用来加载当前模型指定的视图,并且可以选择性的指定动作的过滤器。
@api.model
def load_views(self, views, options=None):
pass
它接收两个参数views和options。
- views: 由视图ID和视图类型组成的列表,[(view_id, view_type)]
- options: 参数字典
- toolbar: 为真时加载上下文工具
- load_filters: 为真时返回模型的过滤器
- action_id: 为获取过滤器的动作ID
返回值为包含fields_views,fields和filters的字典。load_views方法在每次浏览器加载视图时都会被调用,一般不需要重载,除非你对返回的视图有特殊的需求。
下面是load_views返回值的一个示例:
fields_view_get
fields_view_get方法是用于获取视图的详细组成的方法。它跟load_views的关系是,load_view方法内部调用了本方法获取到详细的视图类型和视图布局。
fields_view_get方法的定义如下:
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
pass
接收的参数如下:
- view_id: 视图的ID或None
- view_type: 视图的类型(form,tree, ...)
- toolbar: 是否包含上下文动作
- submenu: 该参数已过时弃用
比如我们之前的book_store应用,在加载视图的过程中,调用fields_view_get方法返回的结果示例如下:
{'model': 'book_store.book', 'field_parent': False, 'arch':
'<search string="图书搜索">\n <field name="author" can_create="true" can_write="true" modifiers="{"required": true}"/>\n <field name="date" modifiers="{}"/>\n <field name="price" modifiers="{}"/>\n <separator/>\n <filter name="liu_book" string="大刘小说" domain="[(\'author\',\'=\',\'刘慈欣\')]"/>\n <separator/>\n <group expand="0" string="Group By">\n <filter name="author" string="按作者分组" domain="[]" context="{\'group_by\':\'author\'}"/>\n </group>\n </search>',
'name': '图书搜索', 'type': 'search', 'view_id': 813, 'base_model': 'book_store.book',
'fields': {'author': {'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'help': '作者', 'manual': False, 'readonly': False, 'relation': 'book_store.author', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': '作者', 'views': {}},
'date': {'type': 'date', 'change_default': False, 'company_dependent': False, 'depends': (), 'help': '日期', 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': '出版日期', 'views': {}},
'price': {'type': 'float', 'change_default': False, 'company_dependent': False, 'depends': (), 'group_operator': 'sum', 'help': '定价', 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': '定价', 'views': {}}}}
很容易看出,这个加载的是搜索的视图,同样的,form和tree视图加载时也会调用同样的方法。我们知道,odoo的页面布局都是写在XML中然后静态存储在数据库中的,而fields_view_get就给了我们一个动态修改视图的机会,我们可以根据自己需要在拿到视图数据之后进行修改,然后再返回给前端。
动态修改XML我们常用到的库是lxml, 通常的修改步骤是:
- 使用fromstring方法加载xml文本
- 使用xpath定位到要修改的节点
- 修改视图结构
- 将修改后的节点重新导出成文本供fields_view_get方法使用
fields_view_get方法只在第一次加载的时候被调用, 之后的操作不会再次触发此操作.
下面给出一个修改视图的示例:
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
result = super(AssetModify, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=submenu)
asset_id = self.env.context.get('active_id')
active_model = self.env.context.get('active_model')
if active_model == 'account.asset.asset' and asset_id:
asset = self.env['account.asset.asset'].browse(asset_id)
doc = etree.XML(result['arch'])
if asset.method_time == 'number' and doc.xpath("//field[@name='method_end']"):
node = doc.xpath("//field[@name='method_end']")[0]
node.set('invisible', '1')
setup_modifiers(node, result['fields']['method_end'])
elif asset.method_time == 'end' and doc.xpath("//field[@name='method_number']"):
node = doc.xpath("//field[@name='method_number']")[0]
node.set('invisible', '1')
setup_modifiers(node, result['fields']['method_number'])
result['arch'] = etree.tostring(doc, encoding='unicode')
return result
16.0起本方法被废除, 使用get_view方法替代.
fields_get方法
fields_get方法返回每个字段的定义
@api.model
def fields_get(self, allfields=None, attributes=None):
pass
它接收两个参数:
- allfields: 字段列表,如果为空则返回全部字段
- attributes: 每个字段的属性描述,如果为空则返回全部属性
前面我们知道,fields_view_get是被load_views调用的,同样的,fields_get也是被load_views调用的。他们一个返回视图的结构,一个返回字段的描述,最终形成一个完整的视图。我们可以看一下fields_get返回的结果示例:
{'name':
{'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': (), 'help': '书名', 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': '名称', 'translate': False, 'trim': True},
'author': {'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'help': '作者', 'manual': False, 'readonly': False, 'relation': 'book_store.author', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': '作者'}, 'date': {'type': 'date', 'change_default': False, 'company_dependent': False, 'depends': (), 'help': '日期', 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': '出版日期'},
'price': {'type': 'float', 'change_default': False, 'company_dependent': False, 'depends': (), 'group_operator': 'sum', 'help': '定价', 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': '定价'}, 'ref': {'type': 'reference', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'selection': [('book_store.author', '作者'), ('book_store.publisher', '出版商')], 'sortable': True, 'store': True, 'string': 'Ref'}, 'age': {'type': 'integer', 'change_default': False, 'company_dependent': False, 'depends': ('date',),
'group_operator': 'sum', 'manual': False, 'readonly': True, 'required': False, 'searchable': True, 'sortable': False, 'store': False, 'string': '书龄'}, 'category': {'type': 'char', 'change_default': False, 'company_dependent': False,
'depends': (), 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': '分类', 'translate': False, 'trim': True}, 'id': {'type': 'integer', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': True, 'required': False, 'searchable': True, 'sortable': True,
'store': True, 'string': 'ID'}, 'display_name': {'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': True, 'required': False, 'searchable': False, 'sortable': False, 'store': False, 'string': 'Display Name', 'translate': False, 'trim': True}, 'create_uid': {'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': True, 'relation': 'res.users', 'required': False,
'searchable': True, 'sortable': True, 'store': True, 'string': 'Created by'},
'create_date': {'type': 'datetime', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': True, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Created on'}, 'write_uid': {'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': True, 'relation': 'res.users', 'required': False, 'searchable': True, 'sortable': True,
'store': True, 'string': 'Last Updated by'}, 'write_date': {'type': 'datetime', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': True, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Last Updated on'}, '__last_update': {'type': 'datetime', 'change_default': False, 'company_dependent': False, 'depends': ('create_date', 'write_date'), 'manual': False, 'readonly': True, 'required': False, 'searchable': False,
'sortable': False, 'store': False, 'string': 'Last Modified on'}, 'publisher_id': {'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {},
'depends': ('author.publisher_id',), 'domain': [], 'help': '', 'manual': False,
'readonly': False, 'related': ('author', 'publisher_id'), 'relation': 'book_store.publisher',
'required': True, 'searchable': True, 'sortable': True, 'store': False, 'string': '签约出版商'}}
fields_get方法默认返回所有的字段和属性, 但这里有个隐藏的因素, 如果方法的调用者没有该字段的访问权限, 那么返回的结果中将不包含该字段.
mapped方法
mapped方法提供了一种简洁地获取数据集(recordset)的方法,官方定义:
mapped(): applies the provided function to each record in the recordset, returns a recordset if the results are recordsets。The provided function can be a string to get field values.
说人话呢,就是mapped方法返回一个记录集合,需要传入的参数是对象的一个字段。
比如,我们都知道,销售订单(sale.order)对象有一个明细的one2many的字段order_line,假设我想获取order_line中单价大于1的记录的prouduct_id的列表
传统的写法:
lines = order.order_line.filtered(lambda l:l.price_unit > 1)._ids)
products = [line.product_id for line in lines]
使用mapped方法:
products = order.order_line.filtered(lambda l:l.price_unit > 1).mapped( "product_id" )
显然,使用mapped方法要比传统写法方便了很多。
关于mapped方法更多的内容,请参考第五部分模型字段一章。
sorted方法
sorted方法提供了对数据集排序的一种快捷方式。比如,我们有一列采购单purchase.order.line(1192, 1193, 1194),出于某种特定需求,我们希望将其倒序排列,那么就可以使用sorted方法。
purchase_order_lines.sorted(reverse=True)
sorted方法接收两个参数:
- key: 用来进行比较的字段,也可以是一个接收一个参数的函数,它返回每条记录的用来比较的关键字。
- reverse: 是否进行倒序排列。
例子:
records.sorted(key=lambda r: r.name)
search_read方法
search_read方法提供了一个先搜索再读取的快捷方法。
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
pass
参数:
- domain:search方法运行时的domain
- fields:read方法运行时的字段列表
- offset:搜索结果的的偏移值
- limit:返回结果的限制
- order:排序结果,默认不排序
返回一个字典的列表。
search_read方法很常见的一个使用场景就是在jsonrpc中,例如,我们希望在前端中获取某个省份的值,就可以这么写:
this._rpc({
model: "res.country.state",
method: "search_read",
domain: [['name','=','北京市']],
fields: ['name']
})
返回结果:
result: [{id: 747, name: "北京市"}]
concat方法
concat方法用来连结相同对象的多个记录, 例如现在两条记录 res.users(1,) 和 res.users(2,), 利用concat方法,我们就可以得到一个包含1,2两条记录的集合
r1 = res.users(1,)
r2 = res.users(2,)
r3 = r1.concat(r2)
=======
res.users(1,2)
default_get方法
default_get方法用来获取传入的字段的默认值,它的使用场景是需要同时设置多个字段时使用。如果针对单个字段的设置,可以使用字段属性的default属性进行设置。
它接收一个参数fields_list, 返回一个由相应字段及其默认值组成的字典.
res = self.default_get()
=======================
{
"field_1": a,
"feild_2": b,
...
}
当字段传入到default_get方法后, odoo会依次从以下几个地方寻找默认值:
- 环境变量的上下文中
- ir_defaults, 也就是模型定义时指定的默认值.
- 字段定义时的默认值
- 继承的父类的默认值
找到即返回,不会再继续寻找。
对于X2many类型的字段,我们在进行默认值设置的时候,使用的直接的ids赋值,而不是使用命令字(4,6,...)进行赋值。
default_get方法的运行时机是在create方法内,通过_prepare_create_values方法内的_add_missing_default_values调用完成的。也就是说,我们在使用create方法的vals内是获取不到默认值的,因为这个时候,default_get方法尚未被调用。
对于company_dependent类型的字段,其默认值的获取过程还有些曲折,详情参考第五部分默认值一章。
copy方法
copy方法用来复制当前记录的值,如果某些字段不想要被复制,那么设置其copy属性为False
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
""" copy(default=None)
Duplicate record ``self`` updating it with default values
:param dict default: dictionary of field values to override in the
original values of the copied record, e.g: ``{'field_name': overridden_value, ...}``
:returns: new record
"""
self.ensure_one()
vals = self.with_context(active_test=False).copy_data(default)[0]
# To avoid to create a translation in the lang of the user, copy_translation will do it
new = self.with_context(lang=None).create(vals).with_env(self.env)
self.with_context(from_copy_translation=True).copy_translations(new, excluded=default or ())
return new
从copy方法的定义上,我们可以看得出
- copy方法只能在单个记录上使用
- copy方法先是使用无语言环境的记录复制,然后再将原有的翻译值复制过来。