Ở bài trước, display_name quyết định bản ghi hiện ra thế nào. Bài này là mặt còn lại: khi người dùng gõ để tìm trong ô Many2one, Odoo khớp theo trường nào? Mặc định chỉ trường định danh. Nhưng thực tế bạn thường muốn tìm được cả bằng thông tin liên quan — ví dụ gõ tên khách để ra thẻ của họ. Đó là việc của _rec_name và _rec_names_search.
Hai thuộc tính, hai vai trò
_rec_name: trường định danh của model (mặc định làname).display_namevà tìm kiếm cơ bản lấy từ đây. Nếu trường định danh của bạn tên khác (ví dụma_so), phải khai_rec_name = 'ma_so'._rec_names_search: danh sách các trường mà tìm kiếm gợi ý sẽ khớp — mở rộng ra ngoài trường định danh, kể cả xuyên quan hệ.

Hình 1: _rec_name chọn trường định danh; _rec_names_search = ['name', 'partner_id.name'] bảo Odoo khi gõ tìm thì khớp cả mã thẻ lẫn tên khách. Cần logic phức tạp thì override _search_display_name(operator, value) để tự dựng domain. (name_search API cũ vẫn còn nhưng _rec_names_search gọn hơn hẳn.)
Chạy thật — tìm bằng tên khách
Tôi thêm _rec_names_search = ['name', 'partner_id.name'] vào model quan.the.thanh.vien, nâng cấp, rồi tìm thử. Output thật trên db blog19:

Hình 2: Output thật. Thẻ ABC-123 gắn khách "Nguyễn Minh Anh" — mã thẻ không chứa tên khách. Gõ 'ABC' (mã thẻ) → ra thẻ đó. Và điểm mấu chốt: gõ 'Nguyễn' (tên khách) cũng ra các thẻ — VIP-0001 và ABC-123 — nhờ partner_id.name có trong _rec_names_search. Không khai nó thì gõ tên khách sẽ không ra thẻ nào.
Vì sao hữu ích?
Đây là trải nghiệm người dùng thực tế. Nhân viên quầy nhớ tên khách, không nhớ mã thẻ. Khi chọn thẻ trong một form (ví dụ tạo giao dịch điểm), họ gõ tên khách và muốn ra ngay thẻ đúng. _rec_names_search biến điều đó thành hiện thực chỉ với một dòng.
Nhìn rộng ra, cùng cơ chế này giải thích vì sao trong Odoo bạn gõ email vào ô "Khách hàng" vẫn ra đúng người, hay gõ mã sản phẩm vào ô "Sản phẩm" vẫn khớp — các model đó khai thêm trường vào tìm kiếm.
_search_display_name — khi cần logic
Nếu muốn kiểm soát hoàn toàn (ưu tiên khớp mã trước, hay ghép điều kiện phức tạp), override _search_display_name để tự trả về domain:
def _search_display_name(self, operator, value):
# Tìm theo mã thẻ HOẶC tên khách
return ['|', ('name', operator, value), ('partner_id.name', operator, value)]
Method này nhận operator (ilike, =...) và value (chuỗi người dùng gõ), trả về domain. Đây là cách linh hoạt nhất, tương đương override name_search kiểu cũ nhưng đúng chuẩn Odoo 19.
Lưu ý
- Xuyên quan hệ tốn hơn:
partner_id.namesinhJOIN; nhiều trường xuyên sâu làm tìm kiếm nặng. Chỉ thêm trường thật cần. - Trường phải tồn tại: khai sai tên trường trong
_rec_names_searchsẽ lỗi lúc tìm. - Khác
display_name:_rec_names_searchlà trường để tìm (input),display_namelà chuỗi để hiện (output) — hai thứ độc lập, thường khai cùng nhau cho nhất quán.
Nhớ ba ý
_rec_namechọn trường định danh (mặc địnhname; đổi tên trường thì phải khai);_rec_names_searchmở rộng các trường mà tìm kiếm gợi ý khớp — kể cả xuyên quan hệ.- Đo thật:
_rec_names_search = ['name', 'partner_id.name']cho gõ'Nguyễn'(tên khách) ra đúng thẻ; không khai thì gõ tên khách không ra gì. - Cần logic phức tạp → override
_search_display_name(operator, value)trả về domain (chuẩn Odoo 19, thayname_searchcũ).
Tìm và hiển thị bản ghi xong, một câu hỏi cơ bản còn lại: danh sách bản ghi mặc định sắp xếp thế nào? Phần sau đi vào _order — đặt thứ tự sắp xếp mặc định cho model, và ảnh hưởng của nó tới hiệu năng truy vấn.