【转载】odoo技术开发白皮书 第二部分 第一章 ORM

转载:http://book.odoomommy.com/chapter2/

第一章 ORM

odoo开发的大多数场景都是基于它的ORM框架进行的, 一少部分要求性能的场景才会涉及到原生SQL的使用. 本章我们将带大家认识基本的ORM方法, 可以满足我们大部分场景的开发.

下面是一个方法的列表, 可以点击迅速浏览相应的介绍. 最基础的三大方法(create write unlink)已经在第一部分介绍过了,本章不再赘述.

read方法

read方法的作用是根据请求的字段返回相应的记录集中的值. read方法是一种低阶的RPC方法, 后端通常应该使用browse方法.

def read(self, fields=None, load='_classic_read'):
    pass

该方法接收两个非必填的参数:

  • fields: 要读取的字段列表 默认为None
  • load: 加载的机制, 默认值_classic_read

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方法

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的字段会生效,其他的则存在__context中. 如果是False, 所有的groupby将会在一次调用中完成.

该方法返回一个包含要求字段和值的字典的列表,eg: [{'field_name_1': value, ...] . 列表中的每一个元素都是根据记录集中的每一条记录生成的.

search方法

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默认是正序排列。

limit

假设我们希望返回符合条件的搜索记录中的前两条记录,那么搜索条件应该这么写:

books = self.env["book_store.book"].search([("name",'=',"海底两万里")],order="date desc",limit=2)

name_get方法

首先要给大家介绍的就是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_search方法

提到了name_get方法,就不得不提name_search方法,因为我们在Many2one上进行模糊搜索时,搜索部分的工作是由name_search方法完成的,然后name_search把搜索到的结果传递给name_get方法,从而返回我们上面讲到的返回值列表。

name search方法接受4个参数:

  • name: 被搜索的关键字
  • args: 限定条件domain
  • operator: 操作符,可选的参数有:=,!=,>,>=,<,<=,like,ilike,in,not in,child_of,parent_left,parent_right
  • limit: 搜索结果的条数。

从结果上看,name_search方法并没有返回一个带文本的列表,而是一个延迟计算的函数,这是因为,在原生name_search方法中调用了lazy_name_get方法导致的,从这里也可以看出name_search和name_get方法的关系,即,由name_search方法限制条件过滤,然后再把结果传给name_get方法进行显示。

私有_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

odoo12中uid=1不再是超级用户的id,变成了机器人odoo_bot,超级管理员的ID变成了2

fields_get_keys

获取本模型的所有字段。

load_views

@api.model
    def load_views(self, views, options=None):
        pass

load_views用于加载视图,接收两个参数views和options。

  • views: list类型[view_id,view_type]
  • options: 字典类型,options['toolbar']为真时加载上下文工具,options['load_filters']为真时返回模型的过滤器,options['action_id']为获取过滤器的动作ID

返回值为包含fields_views,fields和filters的字典。

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="{&quot;required&quot;: 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就给了我们一个动态修改视图的机会,我们可以根据自己需要在拿到视图数据之后进行修改,然后再返回给前端。

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': '签约出版商'}}

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方法要比传统写法方便了很多。

sorted方法

sorted方法提供了对数据集排序的一种快捷方式。比如,我们有一列采购单purchase.order.line(1192, 1193, 1194),出于某种特定需求,我们希望将其倒序排列,那么就可以使用sorted方法。

purchase_order_lines.sorted(reverse=True)

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: "北京市"}]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值