如何在 Odoo 中按 id 获取上一条或最新数据?
结论
问题在于 max(ids)。self.search() 返回的是 Odoo recordset,不是普通的整数 ID 列表。直接对 recordset 使用 Python 的 max(),无法可靠地找到 ID 最大的记录。
可以让数据库按 ID 排序,并且只取第一条:
last_bill = self.search(
[('meter_no', '=', self.meter_no.id)],
order='id desc',
limit=1,
)
再读取这条记录的 current_reading。
推荐写法
创建新账单时,当前记录尚未保存,search() 不会查到它。因此,可以直接查询该电表最后创建的账单:
@api.onchange('meter_no')
def onchange_meter_no(self):
self.ensure_one()
self.prev_reading = 0.0
if not self.meter_no:
return
last_bill = self.search(
[('meter_no', '=', self.meter_no.id)],
order='id desc',
limit=1,
)
if last_bill:
self.prev_reading = last_bill.current_reading
这段代码中:
order='id desc'按 ID 从大到小排序。limit=1只返回第一条,也就是 ID 最大的记录。last_bill仍然是 recordset,可以直接读取last_bill.current_reading。
为什么原来的 max() 不可靠
下面的变量虽然名为 ids,实际得到的却不是 ID 列表:
ids = self.search([('meter_no', '=', self.meter_no.id)])
它返回的是 billing 模型的 recordset,其中的元素是 Odoo 记录对象。因此,执行:
max(ids)
比较的是记录对象,并不等同于:
max(ids.ids)
即使改用 max(ids.ids),仍需通过 browse() 取得对应记录:
bills = self.search([('meter_no', '=', self.meter_no.id)])
last_bill_id = max(bills.ids) if bills else False
last_bill = self.browse(last_bill_id) if last_bill_id else False
这种写法可以工作,但它会先查询所有匹配记录,再由 Python 计算最大值,过程更绕,也没有必要。使用 order 和 limit 更直接,效率也更高。
编辑已有账单时需要排除当前记录
如果 onchange 也会在编辑已有账单时触发,搜索结果可能包含当前账单。此时需要排除当前记录,否则可能会把它自己的 current_reading 填入 prev_reading。
可以通过 _origin 获取数据库中原始记录的 ID:
@api.onchange('meter_no')
def onchange_meter_no(self):
self.ensure_one()
self.prev_reading = 0.0
if not self.meter_no:
return
domain = [
('meter_no', '=', self.meter_no.id),
]
if self._origin and self._origin.id:
domain.append(('id', '!=', self._origin.id))
last_bill = self.search(
domain,
order='id desc',
limit=1,
)
if last_bill:
self.prev_reading = last_bill.current_reading
新建且尚未保存的账单通常没有 self._origin.id,因此不会添加这项排除条件。
“最新数据”不一定等于”最大 ID”
如果业务中的”最新”是指最后创建的账单,按 id desc 排序通常可以满足要求。但如果账单允许补录或倒序录入,或者需要根据计费日期判断,就应该按业务日期排序。假设字段名是 billing_date:
last_bill = self.search(
[('meter_no', '=', self.meter_no.id)],
order='billing_date desc, id desc',
limit=1,
)
如果”最新”是指最后修改的记录,可以考虑:
order='write_date desc, id desc'
不过,任何字段发生修改都会更新 write_date,它不一定代表最后一期账单。因此,通常应优先使用明确的计费日期字段。
其他注意事项
不要这样赋值:
self.prev_reading = last_bill.current_reading or None
0.0 在 Python 中是假值,会被替换成 None。对于 Odoo 的数值字段,直接赋值即可:
self.prev_reading = last_bill.current_reading
没有历史账单时,可以明确设置为 0.0:
self.prev_reading = last_bill.current_reading if last_bill else 0.0
另外,onchange 只能查询已经写入数据库的数据。如果上一张账单的 current_reading 尚未保存,新账单就无法通过 search() 取得该值。
备注:内容仅供参考。
