【ODOO】来了解一下browse方法

本文分析了一次生产环境中定时任务的错误,发现错误源于使用`browse`方法查找不存在的记录。作者通过调试揭示了`browse`方法的工作原理,并提出将`browse`替换为`search`来修复问题,以确保数据准确获取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

错误抛出

前端时间在生产环境上线了一个定时任务,目的是采集客户信息,以用来分析客户数据。2022-05-30 系统告警出现了错误。下面是错误信息:

2022-05-30 14:40:46,332 76 ERROR momo_prod odoo.addons.base.models.ir_cron: Call from cron Partner信息采集定时任务 for server action #417 failed in Job #17
Traceback (most recent call last):
  File '/opt/momo/odoo/api.py', line 793, in get
    return field_cache[record._ids[0]]
KeyError: 12
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File '/opt/momo/odoo/fields.py', line 972, in __get__
    value = env.cache.get(record, self)
  File '/opt/momo/odoo/api.py', line 796, in get
    raise CacheMiss(record, field)
odoo.exceptions.CacheMiss: 'res.partner(75432,).group_id'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File '/opt/momo/odoo/addons/base/models/ir_cron.py', line 110, in _callback
    self.env['ir.actions.server'].browse(server_action_id).run()
  File '/opt/momo/odoo/addons/base/models/ir_actions.py', line 632, in run
    res = runner(run_self, eval_context=eval_context)
  File '/opt/momo/odoo/addons/base/models/ir_actions.py', line 501, in _run_action_code_multi
    safe_eval(self.code.strip(), eval_context, mode='exec', nocopy=True)  # nocopy allows to return 'action'
  File '/opt/momo/odoo/tools/safe_eval.py', line 330, in safe_eval
    return unsafe_eval(c, globals_dict, locals_dict)
  File '', line 1, in <module>
  File '/opt/momo/addons/momo_cron/models/cron_partner.py', line 199, in _action_cron_do_collection
    collection_item.handle_record()
  File '/opt/momo/addons/momo_cron/models/cron_partner.py', line 119, in handle_collection
    parent_name = partner_res.group_id.name
  File '/opt/momo/odoo/fields.py', line 2485, in __get__
    return super().__get__(records, owner)
  File '/opt/sps/odoo/fields.py', line 1004, in __get__
    _('(Record: %s, User: %s)') % (record, env.uid),
odoo.exceptions.MissingError: Record does not exist or has been deleted.
(Record: res.partner(75432,), User: 1)

简单分析

定位到报错代码位置(line 119):

parent_name = partner_res.group_id.name

再看最后的错误信息:

odoo.exceptions.MissingError: Record does not exist or has been deleted.
(Record: res.partner(75432,), User: 1)

结合可知,错误并不是因为字段  group_id 可能没有值导致的问题,而是因为记录 partner_res 不存在或已删除导致的。

但这不对呀,我是判断了 partner_res 是否存在的:

    partner_res = res_partner_obj.browse(self.partner_id)
    if partner_res:
        parent_name = partner_res.group_id.name
    else:
        parent_name = ""

如果 partner_res 不存在,就不应该走 partner_res.group_id.name ,更不应该报错。打个断点跟一下。

Debug

打上断点跟了一下不咋滴,这一跟简直跟出了一条惊天大案!

问题定位到了这句看起来非常普通的代码上:

partner_res = res_partner_obj.browse(self.partner_id)

断点过程发现 self.partner_id 是有值的,但数据库中确实不存在 id 为现 75432 的记录。怀疑问题出现在browse方法上,browse方法可能压根儿没走数据库。为了验证这个问题,我将问题代码简单抽出来复现一下。

问题复现

TIP: 已知在数据库中,表res_partner中不存在 id 为 999999 的记录,通过使用 browse 查找记录。

def match_record_by_browse(self):
    """
    演示 browse 查询记录集
    :return:
    """
    browse_result = self.env["res.partner"].browse(999999)
    print("browse_result", browse_result)
    if browse_result:
        print("browse_result yes")
    else:
        print("browse_result no")

终端输出

browse_result res.partner(999999,)
browse_result yes

可以看到数据库中,没有 id 为 999999 的记录,依然输出了 res.partner(999999,) 

browse() API

查看下browse源码:

    #
    # Instance creation
    #
    # An instance represents an ordered collection of records in a given
    # execution environment. The instance object refers to the environment, and
    # the records themselves are represented by their cache dictionary. The 'id'
    # of each record is found in its corresponding cache dictionary.
    #
    # This design has the following advantages:
    #  - cache access is direct and thus fast;
    #  - one can consider records without an 'id' (see new records);
    #  - the global cache is only an index to "resolve" a record 'id'.
    #
​
    @classmethod
    def _browse(cls, env, ids, prefetch_ids):
        """ Create a recordset instance.
​
        :param env: an environment
        :param ids: a tuple of record ids
        :param prefetch_ids: a collection of record ids (for prefetching)
        """
        records = object.__new__(cls)
        records.env = env
        records._ids = ids
        records._prefetch_ids = prefetch_ids
        return records
​
    def browse(self, ids=None):
        """ browse([ids]) -> records
​
        Returns a recordset for the ids provided as parameter in the current
        environment.
​
        .. code-block:: python
​
            self.browse([7, 18, 12])
            res.partner(7, 18, 12)
​
        :param ids: id(s)
        :type ids: int or list(int) or None
        :return: recordset
        """
        if not ids:
            ids = ()
        elif ids.__class__ in IdType:
            ids = (ids,)
        else:
            ids = tuple(ids)
        return self._browse(self.env, ids, ids)

王德发!browse方法真的没有访问数据库,只是将提供的 id 包装成了当前环境的实例。目的是为了快速读取数据。

实例表示给定执行环境中的有序记录集合。实例对象引用环境,而记录本身由它们的缓存字典表示。每条记录的“id”都可以在其对应的缓存字典中找到。

这种设计具有以下优点:

  • 缓存访问是直接的,因此速度很快。

  • 可以考虑没有“id”的记录。

  • 全局缓存只是“解析”记录“id”的索引。

这么来看,browse方法并不关心传入的 id 在相关表中是不是存在。它仅仅是为了将给定的 id 包装成ORM可用的实例。

问题解决

这是一个browse方法使用的问题。将browse方法替换为search方法,通过search方法访问数据库,查找是否真正存在即可解决此问题。

partner_res = res_partner_obj.search([("id", "=", self.partner_id)])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值