read方法
read方法的作用是根据请求的字段返回相应的记录集中的值。read方法是一种低阶的RPC方法, 我们开发中通常用的到是browse方法。
def read(self, fields=None, load='_classic_read'):
...
该方法接收两个非必填的参数:
- fields: 要读取的字段列表,默认为None,代表读取全部字段。
- load: 数据加载模式,默认为经典模式(_classic_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方法还将数据库中存储的字段进行了缓存,以加快访问速度。
下面我们在我们的豆瓣图书应用中新增一个按钮来演示read方法的作用及其结果。
def button_read(self):
"""ORM Read"""
res = self.read()
print('---Read---')
print(res)
然后我们可以在页面中点击Read按钮来查看它的输出:
[
{
'id': 1,
'name': '冰与火之歌',
'title': '(1-5卷:权力的游戏、列王的纷争、冰雨的风暴、群鸦的盛宴、魔龙的狂舞)',
'publish_date': datetime.date(2013,10,1),
'price': 330.0,
'currency_id': False,
'rate': 9.5,
'author': 'res.partner,
3',
'publisher': [
1,
2,
3,
4
],
'publish_count': 4,
'__last_update': datetime.datetime(2022,4,1,13,26,2,993416),
'display_name': '冰与火之歌',
'create_uid': (2,
'Administrator'),
'create_date': datetime.datetime(2022,4,1,12,43,56,936257),
'write_uid': (2,
'Administrator'),
'write_date': datetime.datetime(2022,4,1,13,26,2,993416)
}
]
read方法的返回值是一个由字段名和值组成的字典的列表。列表中的每一个字典都是根据当前记录集中的每一条记录生成的,记录中包含了所有传入的字段的值,因此我们可以遍历该结果集然后取出自己想要的结果。
read方法的底层调用的是低阶的_read方法,关于read方法更深入的内容我们将在第五部分介绍有关模型更多的时候继续探索。
_classic_read 模式,具体指的是针对Many2one类型的字段(内部调用了convert_to_read方法,具体参考第十三章),read方法内部获取到字段的值的格式化方式,经典的格式化方法即使用name_get方法将值格式化可读的格式。与之相反的格式化方式,即只返回记录的ID。
read_group方法
read_group方法作用是根据groupby参数对查询的结果进行分组, 它返回一个分组后结果的列表.
先来看方法的定义:
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
pass
它接收如下几个参数:
- domain: 过滤数据的条件 留空则表示获取全部记录
- fields: 要获取的字段列表(只支持数值型,其他类型将被忽略)
- groupby: 分组字段 列表
- offset: 数据偏移
- limit: 返回结果的数量限制
- orderby: 对返回的结果进行排序
- lazy: 布尔值,如果是True,那么只有第一个groupby的字段会生效,且返回值的key会以字段名作为前缀,反之则不会,其他的则存在__context中。如果是False, 所有的groupby将会在一次调用中完成。默认情况下lazy=True.
该方法返回一个包含要求字段和值的字典的列表,eg: [{'field_name_1': value, ...] 。列表中的每一个元素都是根据记录集中的每一条记录生成的。
下面可看一个read_group方法返回的结果:
[{'vendor_id_count': 1, 'price': 5000.0, 'discount': 0.0, 'vendor_id': (9435, <odoo.tools.func.lazy object at 0x7f3efb236f40>), '__domain': ['&', ('vendor_id', '=', 9435), ('product_id', '=', 38751)]}]
如果lazy设置成False,那么返回的结果是:
[{'__count': 1, 'price': 5000.0, 'discount': 0.0, 'vendor_id': (9435, <odoo.tools.func.lazy object at 0x7ffa360dda80>), '__domain': ['&', ('vendor_id', '=', 9435), ('product_id', '=', 38751)]}]
read_group方法与groupby的区别在于read_group方法返回的是分组字段的信息,如果我们想要对结果集进行分组。关于grouby更多内容,参考工具一章
fields字段支持postgresql的聚合函数, 具体参考这个链接
search方法
search方法是odoo中最常用的ORM方法之一,用于检索符合条件的记录。
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对象的记录集(recordset)。
排序
假如,我们的书店里有不止一个版本的《海底两万里》,我们希望按照出版日期倒序排列,那么搜索语句就可以这么写:
books = self.env["book_store.book"].search([("name",'=',"海底两万里")],order="date desc")
order默认是正序排列。
limit
假设我们希望返回符合条件的搜索记录中的前两条记录,那么搜索条件应该这么写:
books = self.env["book_store.book"].search([("name",'=',"海底两万里")],order="date desc",limit=2)
name_get方法
name_get方法在所有获取关联对象的名称时被调用,典型的场景就是Many2one字段的搜索框,当我们输入关键字后,下拉里列表中展示出来的名称就是通过name_get方法获取到的。
name_get方法的返回值是一个包含id和名称的元组组成的列表。
使用示例:
def name_get(self):
values = super(Demo, self).name_get()
_logger.info(f"name_get方法返回的结果:{values}")
return values
-------------
[(1,'浓眉大眼的长腿叔叔')]
提到了name_get方法,就不得不提name_search方法,因为我们在Many2one上进行模糊搜索时,搜索部分的工作是由name_search方法完成的,然后name_search把搜索到的结果传递给name_get方法,从而返回我们上面讲到的返回值列表。
name_search方法
name_search方法接受4个参数:
- name: 被搜索的关键字
- args: 限定条件domain
- operator: 操作符,可选的参数有:=,!=,>,>=,<,<=,like,ilike,in,not in,child_of,parent_left,parent_right
- limit: 搜索结果的条数,默认100条。
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
pass
私有_name_search方法
其实在更底层的层面上,还有一个_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
name_search方法的底层实现在13.0->14.0时发生了变化,详情请参考附录一版本差异 odoo12中uid=1不再是超级用户的id,变成了机器人odoo_bot,超级管理员的ID变成了2
关于name_search方法的应用,最典型也是最常见的例子就是Many2one字段的查找搜索, 我们在输入关键字以后, 系统会触发onchange事件,然后调用name_search来搜索匹配的结果, 最后使用name_get方法将格式化后的结果组织成下拉列表的形式展示出来.
这是提一个有关于name_seach方法的扩展应用, 默认情况下, name_search只能针对于模型的name字段进行搜索, 而不能搜索本模型的其他字段, 这在某些条件下限制了我们的搜索可能行.因此, 笔者开发了name_search_options这个模块专门来处理这个问题.
fields_get_keys
获取本模型的所有字段,其原理即返回当前模型对对象的_fields属性列表。
@api.model
def fields_get_keys(self):
return list(self._fields)