转载:http://book.odoomommy.com/chapter2/
odoo开发的大多数场景都是基于它的ORM框架进行的, 一少部分要求性能的场景才会涉及到原生SQL的使用. 本章我们将带大家认识基本的ORM方法, 可以满足我们大部分场景的开发.
下面是一个方法的列表, 可以点击迅速浏览相应的介绍. 最基础的三大方法(create write unlink)已经在第一部分介绍过了,本章不再赘述.
read方法的作用是根据请求的字段返回相应的记录集中的值. read方法是一种低阶的RPC方法, 后端通常应该使用browse方法.
def read(self, fields=None, load='_classic_read'):
pass
该方法接收两个非必填的参数:
read方法的返回值是一个由字段名和值组成的字典的列表. 列表中的每一个字典都是根据当前记录集中的每一条记录生成的.
我们来举一个典型的例子, 在开发过程中,我们常会遇到点击一个按钮,打开一个包含若干记录的视图, 即form表单中的状态按钮(state button)的作用. 这种按钮的后端代码通常是一个方法,返回了一个既定的窗口动作, 而获取这个窗口动作就用到了read方法.
def button_open_wizard(self):
"""派单"""
action = self.env.ref('juhui_repairs.action_repair_wizard').read()[0]
return action
read方法在没有传入fields的情况下,将获取当前用户拥有访问权限的所有字段(对于超级管理员来说,就是所有字段)。如果用户没有对传入的fields的访问权限,那么将引发AccessDenied错误。
实际上,read方法内部是通过check_fields_access_rights方法对用户进行鉴权的,也是通过此方法将用户可以访问的字段返回的,这也就是为什么read方法本身并没有对fields进行None值判断,却允许fields为None时返回全部可访问的字段列表的原因。 不仅如此,read方法还将数据库中存储的字段进行了缓存,以加快访问速度。
_classic_read 模式,具体指的是针对Many2one字段(内部调用了convert_to_read方法,具体参考第十三章),read方法内部获取到字段的值的格式化方式,经典的格式化方法即使用name_get方法将值格式化可读的格式。与之相反的格式化方式,即只返回记录的ID。
read_group方法作用是根据groupby参数对查询的结果进行分组, 它返回一个分组后结果的列表.
先来看方法的定义:
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
pass
它接收如下几个参数:
该方法返回一个包含要求字段和值的字典的列表,eg: [{'field_name_1': value, ...] . 列表中的每一个元素都是根据记录集中的每一条记录生成的.
search方法是odoo中最常用的四大方法之一,用于检索符合条件的记录。
search方法的定义如下:
def search(self, args, offset=0, limit=None, order=None, count=False):
pass
args: 是domain,过滤条件 offset: 偏移量 limit: 返回结果的限定数量 order: 排序 count: 计数
比如我们希望搜索一个书名叫做《海底两万里》的书,那么我们可以这么写搜索语句:
books = self.env["book_store.book"].search([('name','=','海底两万里')])
这里我们搜索出来的是book对象集。
假如,我们的书店里有不止一个版本的《海底两万里》,我们希望按照出版日期倒序排列,那么搜索语句就可以这么写:
books = self.env["book_store.book"].search([("name",'=',"海底两万里")],order="date desc")
order默认是正序排列。
假设我们希望返回符合条件的搜索记录中的前两条记录,那么搜索条件应该这么写:
books = self.env["book_store.book"].search([("name",'=',"海底两万里")],order="date desc",limit=2)
首先要给大家介绍的就是name_get方法,这个方法在所有获取关联对象的名称时被调用,典型的场景就是Many2one字段的搜索框,当我们输入关键字后,下拉里列表中展示出来的名称就是通过name_get方法获取到的。
使用示例:
@api.multi
def name_get(self):
values = super(Demo, self).name_get()
_logger.info(f"name_get方法返回的结果:{values}")
return values
name_get方法的返回值是一个包含id和名称的元组组成的列表。
提到了name_get方法,就不得不提name_search方法,因为我们在Many2one上进行模糊搜索时,搜索部分的工作是由name_search方法完成的,然后name_search把搜索到的结果传递给name_get方法,从而返回我们上面讲到的返回值列表。
name search方法接受4个参数:
从结果上看,name_search方法并没有返回一个带文本的列表,而是一个延迟计算的函数,这是因为,在原生name_search方法中调用了lazy_name_get方法导致的,从这里也可以看出name_search和name_get方法的关系,即,由name_search方法限制条件过滤,然后再把结果传给name_get方法进行显示。
其实在更底层的层面上,还有一个_name_search方法,与name_search方法不同的是_name_search方法接受额外的一个参数:name_get_uid,这个参数的作用是指定一个调用_search和lazy_name_get方法的用户ID,用来解决当前用户权限不足的问题。
比如当前我有一个模型osc.person,没有任何一个组有权限访问,正常访问会试如下的界面:
当我们给_name_search方法传一个uid=1进去的时候:
@api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
_logger.info(f"私有name search方法被调用,参数:{name,args,operator}")
res = super(Person, self)._name_search(name, args, operator, limit, 1)
_logger.info(f"结果:{res}")
return res
odoo12中uid=1不再是超级用户的id,变成了机器人odoo_bot,超级管理员的ID变成了2
获取本模型的所有字段。
@api.model
def load_views(self, views, options=None):
pass
load_views用于加载视图,接收两个参数views和options。
返回值为包含fields_views,fields和filters的字典。
load_views方法在每次浏览器加载视图时都会被调用,一般不需要重载,除非你对返回的视图有特殊的需求。
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
接收的参数如下:
比如我们之前的book_store应用,在加载视图的过程中,调用fields_view_get方法返回的结果示例如下:
{'model': 'book_store.book', 'field_parent': False, 'arch':
'\n \n \n \n \n \n \n \n \n \n ',
'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就给了我们一个动态修改视图的机会,我们可以根据自己需要在拿到视图数据之后进行修改,然后再返回给前端。
fields_get方法返回每个字段的定义
@api.model
def fields_get(self, allfields=None, attributes=None):
pass
它接收两个参数:
前面我们知道,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': '签约出版商'}}
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方法要比传统写法方便了很多。
sorted方法提供了对数据集排序的一种快捷方式。比如,我们有一列采购单purchase.order.line(1192, 1193, 1194),出于某种特定需求,我们希望将其倒序排列,那么就可以使用sorted方法。
purchase_order_lines.sorted(reverse=True)
提供了一个先搜索再读取的快捷方法。
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
pass
参数:
返回一个字典的列表。
search_read方法很常见的一个使用场景就是在jsonrpc中,例如,我们希望在前端中获取某个省份的值,就可以这么写:
this._rpc({
model: "res.country.state",
method: "search_read",
domain: [['name','=','北京市']],
fields: ['name']
})
返回结果:
result: [{id: 747, name: "北京市"}]