Bạn viết một module và cần tham chiếu tới nhóm quyền "Nhân viên nội bộ". Cách tệ nhất là gõ id số: env['res.groups'].browse(1). Vì id số không ổn định — cài trên máy khác, phục hồi từ backup khác, cùng một nhóm có thể mang id khác. Cách đúng của Odoo là gọi nó bằng tên bất biến: env.ref('base.group_user'). Bài này mổ xẻ env.ref và cái tên đó — external id — thật ra là gì.
External id là gì
Mỗi bản ghi được tạo từ file dữ liệu XML/CSV của một module đều có thể mang một external id (còn gọi là XML id) dạng module.tên_kỹ_thuật. Đó là một bí danh do con người đặt trỏ tới một bản ghi cụ thể. env.ref(xmlid) tra bí danh đó và trả về recordset tương ứng:
admin = env.ref('base.user_admin') # res.users(2,)
nhom = env.ref('base.group_user') # res.groups(1,)

Hình 1: Các cách dùng env.ref. Truyền external id dạng module.tên, nhận về recordset. Không tìm thấy thì mặc định ném ValueError; thêm raise_if_not_found=False để nhận None. Trong module, ta tham chiếu chính action của mình bằng external id thay vì id số.
Chạy thật trên hệ thống:

Hình 2: env.ref biến external id thành record. base.user_admin ra res.users(2,) (Administrator), base.group_user ra res.groups(1,). Để ý: cùng là "admin" nhưng đây là user id 2, còn SUPERUSER_ID là user id 1 (OdooBot) — một lý do nữa để không đoán id số mà dùng external id.
Đằng sau nó: bảng ir.model.data
env.ref không có phép thuật gì. Mỗi external id là một dòng trong bảng ir.model.data, lưu bốn thứ: module, name, model, res_id. Khi bạn gọi env.ref('base.group_user'), Odoo tách chuỗi thành module='base', name='group_user', tra bảng đó ra model='res.groups' và res_id=1, rồi trả về res.groups(1,).
env['ir.model.data'].search([('module','=','base'), ('name','=','group_user')])
# module=base name=group_user model=res.groups res_id=1
Hiểu điều này giải thích luôn vì sao external id ổn định qua các lần cài: khi module cài lại hay nâng cấp, Odoo dùng chính bảng này để biết "bản ghi tên group_user của module base là dòng nào" và cập nhật đúng dòng đó thay vì tạo trùng. Cái tên là mỏ neo; id số bên dưới có thể khác nhau giữa các hệ thống.
Module của bạn cũng sinh external id. Module demo quan_ca_phe có 63 external id — cho model, action, view, menu... Ví dụ quan_ca_phe.action_the_thanh_vien trỏ tới một ir.actions.act_window (res_id 918). Nhờ vậy trong code hay trong file XML khác, bạn tham chiếu tới action đó bằng tên, không bao giờ bằng số.
Khi external id không tồn tại
Mặc định, env.ref ném ValueError nếu không tìm thấy:
env.ref('base.khong_co_dau') # ValueError: External ID not found in the system: base.khong_co_dau
Đây thường là hành vi bạn muốn: nếu code phụ thuộc một bản ghi mà nó biến mất, hỏng to ngay còn hơn chạy tiếp với None rồi nổ ở chỗ khác khó lần. Nhưng khi external id có thể vắng mặt một cách hợp lệ (ví dụ nó thuộc một module tuỳ chọn có thể chưa cài), truyền raise_if_not_found=False để nhận None:
rec = env.ref('mo_dun_tuy_chon.co_the_thieu', raise_if_not_found=False)
if rec:
... # chỉ dùng khi thật sự có
Chiều ngược: từ record ra external id
Có sẵn recordset, muốn biết external id của nó (hữu ích khi viết migration, hay debug), dùng get_external_id():
admin.get_external_id() # {2: 'base.user_admin'}
Nó trả về một dict {id: 'module.name'}. Bản ghi không có external id nào (ví dụ dữ liệu người dùng tạo tay) sẽ cho chuỗi rỗng ở id đó.
Dùng env.ref cho đúng
Vài lưu ý rút ra từ cơ chế trên:
def mo_danh_sach_the(self):
action = self.env.ref('quan_ca_phe.action_the_thanh_vien')
return action._get_action_dict()
- Dùng external id thay cho id số ở mọi tham chiếu cứng. Nhóm quyền, action, view, sequence, template email — tất cả nên gọi bằng
env.ref('module.name'), khôngbrowse(số). - Đặt module prefix cho đúng.
env.ref('group_user')thiếu prefix sẽ lỗi; luôn ghibase.group_user. Prefix cho biết bản ghi thuộc module nào. env.reftrả về đúng một record (không phải nhiều). Nếu cần một tập, dùngsearch;env.reflà cho những bản ghi định danh duy nhất.- Cẩn thận vòng phụ thuộc: gọi
env.reftới external id của module khác nghĩa là module bạn phụ thuộc module đó — phải khai trongdependscủa manifest, nếu không thứ tự cài có thể khiến ref chưa tồn tại.
Ba ý mang về
- External id (
module.name) là tên bất biến của một bản ghi, ổn định qua các lần cài/nâng cấp — dùng nó thay cho id số vốn khác nhau giữa các hệ thống.env.ref(xmlid)biến tên đó thành recordset. - Mỗi external id là một dòng trong
ir.model.data(module,name,model,res_id); đó là cách Odoo neo tên vào bản ghi và cập nhật đúng chỗ khi cài lại. env.refmặc định némValueErrorkhi không thấy; truyềnraise_if_not_found=Falseđể nhậnNonekhi external id có thể vắng mặt hợp lệ. Chiều ngược dùngget_external_id().
Lần sau ta bước vào một chủ đề hiệu năng ít người để ý nhưng gây bug rất kỳ quặc: Phần sau nói về cache của ORM — Odoo nhớ giá trị field trong bộ đệm ra sao, và khi nào bạn phải chủ động làm mới nó.