Odoo 19: Lập trình chuyên sâu
Sê-ri ~200 bài lập trình Odoo 19 chuyên sâu: ORM, view/QWeb, OWL/JS, controller/website, tích hợp, test, deploy — có code và thao tác thật.
200/200 phần đã đăng
Odoo
1
Trước khi viết dòng code Odoo đầu tiên: dựng môi trường dev đúng để về sau khỏi khổ
Cài Odoo từ source, tách venv, viết odoo.conf và bật chế độ dev — nền tảng của mọi lập trình viên Odoo. Có cấu hình thật và kiểm chứng trên máy chủ Odoo 19.
17/09/2026
· 4 phút đọc
2
Vì sao Odoo của bạn chậm, ngốn RAM, hay treo? Câu trả lời nằm ở mấy dòng trong odoo.conf
Giải mã các tham số quan trọng nhất của odoo.conf: workers, giới hạn RAM/thời gian, dbfilter, proxy_mode, log. Kèm file cấu hình thật đọc từ máy chủ Odoo 19.
17/09/2026
· 4 phút đọc
3
Đặt module vào đúng chỗ mà Odoo vẫn 'không thấy'? Hiểu cách Odoo quét addons_path để hết bối rối
Odoo tìm module bằng cách quét thư mục addons lõi cộng addons_path, nhận diện qua __manifest__.py. Hiểu cơ chế này để cài module tự viết trơn tru. Có dữ liệu thật từ Odoo 19.
17/09/2026
· 4 phút đọc
4
Sửa một dòng Python rồi phải Ctrl+C khởi động lại Odoo mỗi lần? Cờ --dev=all xoá bỏ nghi thức đó
Chế độ --dev của Odoo 19 gồm 6 tính năng: reload tự khởi động lại khi sửa .py, xml đọc view thẳng từ file, qweb/access/werkzeug để debug. Mổ xẻ từng cái kèm bằng chứng thật từ mã nguồn.
17/09/2026
· 5 phút đọc
5
Muốn thử một câu ORM mà không phải viết cả module rồi khởi động lại? odoo shell cho bạn một dấu nhắc Python vào thẳng cơ sở dữ liệu
odoo shell là REPL Python có sẵn env, self, và toàn bộ ORM Odoo 19. Cách mở, search/read/create, và vì sao env.cr.commit() là bắt buộc khi ghi. Có output thật trên CSDL blog19.
17/09/2026
· 4 phút đọc
6
Log Odoo trôi hàng nghìn dòng mà bạn chỉ cần thấy câu SQL đang chậm? Bí quyết nằm ở --log-handler theo từng module
Mổ xẻ một dòng log Odoo 19 gồm 6 phần, năm cấp log, và cách bật DEBUG chỉ cho đúng module cần soi bằng --log-handler MODULE:LEVEL. Có log SQL thật kèm thời gian truy vấn.
17/09/2026
· 4 phút đọc
7
Muốn học Odoo nhanh nhất? Đừng đọc tài liệu — mở thẳng 693 module code core mà Odoo đã viết sẵn ngay trong máy bạn
Toàn bộ mã nguồn Odoo 19 nằm sẵn trong container. Cách điều hướng nó bằng grep để tìm model, người kế thừa, và học từ chính code core thay vì đoán mò. Có ví dụ thật từ sale_order.py.
17/09/2026
· 4 phút đọc
8
Lỡ commit cả thư mục __pycache__ và file odoo.conf chứa mật khẩu lên Git? Một file .gitignore đúng chặn được cả hai
Quy trình Git cho một module Odoo: cấu trúc repo, .gitignore chuẩn (bỏ pyc và bí mật), quy ước version manifest, và làm việc trên nhánh tính năng. Có phiên git thật với module quan_ca_phe.
17/09/2026
· 4 phút đọc
9
Đừng ngồi gõ tay 10 file rỗng cho module mới — odoo scaffold dựng sẵn cả khung chuẩn trong đúng một dòng lệnh
Lệnh odoo scaffold tạo sẵn toàn bộ khung thư mục một module Odoo: manifest, models, controllers, views, security, demo. Xem cấu trúc và nội dung thật nó sinh ra, và nên sửa gì trước tiên.
17/09/2026
· 3 phút đọc
10
Còn dùng print() để debug và để version manifest sai định dạng? pylint-odoo bắt hết những lỗi 'rất Odoo' mà pylint thường bỏ qua
pre-commit chạy kiểm tra tự động trước mỗi commit; pylint-odoo thêm hàng loạt checker riêng cho Odoo (sai version, thiếu tác giả, print thay logger). Có output lint thật trên một module lỗi cố ý.
17/09/2026
· 3 phút đọc
11
Rải print() khắp code rồi đọc log để tìm bug? Đặt một breakpoint() và bạn dừng ngay tại chỗ, soi được mọi biến ORM đang sống
pdb là trình gỡ lỗi tương tác có sẵn của Python. Cách đặt breakpoint trong code Odoo, chạy với workers 0, và các lệnh p/l/w/n/s/c. Có một phiên pdb thật soi dữ liệu ORM trên db blog19.
17/09/2026
· 3 phút đọc
12
Lỗi JavaScript báo ở dòng 1 của một file .min.js dài 1MB — làm sao debug? Bật assets dev là thấy đúng file gốc
Odoo gộp hàng trăm file JS/CSS thành bundle nén 1MB kèm hash. Chế độ --dev=assets tách chúng ra để debug; source map trỏ lỗi về mã gốc. Có URL bundle thật và ảnh giao diện Odoo 19 thật.
17/09/2026
· 4 phút đọc
13
Một máy chủ Odoo lỡ hiện danh sách 'blog19, shop19' cho mọi khách vào — vá bằng đúng một dòng --db-filter
Một tiến trình Odoo phục vụ được nhiều database PostgreSQL. --db-filter là regex chọn đúng database theo tên miền; list_db=False giấu danh sách. Có 2 database Odoo thật và ảnh trang chọn db.
17/09/2026
· 4 phút đọc
14
Cần chạy Odoo 17 của khách cũ và Odoo 19 của dự án mới trên cùng một máy? Bí quyết là mỗi bản một bộ cổng và data_dir riêng
Nhiều instance Odoo chạy song song trên một máy chỉ cần khác http_port, gevent_port, data_dir và database. Có bằng chứng thật hai tiến trình Odoo cùng chạy trên cổng 8069 và 8070.
17/09/2026
· 4 phút đọc
15
Mỗi lần sửa code lại docker cp rồi restart container? Một dòng bind-mount trong compose xoá sạch nghi thức đó
Docker Compose cho dev Odoo: bind-mount thư mục addons từ máy host để sửa code thấy ngay, kết hợp --dev=all. Có bằng chứng thật sửa file trên host và container đọc thấy tức thì.
17/09/2026
· 4 phút đọc
16
Trong Odoo, biến giữ 'một khách hàng' và biến giữ '500 khách hàng' là CÙNG một kiểu — hiểu recordset là hiểu nửa cái ORM
Recordset là khái niệm nền tảng nhất của ORM Odoo: mọi thứ là tập bản ghi, kể cả tập rỗng và tập một phần tử. Cách nó hành xử như tập hợp Python, qua output shell thật trên db blog19.
17/09/2026
· 4 phút đọc
17
'ValueError: Expected singleton' — lỗi Odoo ám ảnh nhất, và tại sao ensure_one() lại là bạn chứ không phải thù
Cách lấy đúng một bản ghi từ recordset: browse với id, duyệt bằng vòng for, và ensure_one để chốt tập một phần tử. Có lỗi Expected singleton thật chụp từ odoo shell trên db blog19.
17/09/2026
· 4 phút đọc
18
Tạo 200 bản ghi bằng vòng for create() mất gấp 3 lần một lệnh create() nhận cả danh sách — con số đo thật trong Odoo 19
create() tạo một bản ghi khi nhận dict, tạo hàng loạt khi nhận list dict. Vì sao create theo lô nhanh hơn hẳn create trong vòng lặp, với số đo thật trên db blog19.
17/09/2026
· 4 phút đọc
19
recs.write({...}) trên 100 bản ghi chỉ chạy MỘT câu UPDATE — và nhanh gấp 8 lần vòng lặp, đo thật trong Odoo 19
write() cập nhật cả recordset bằng một câu SQL. Gán trực tiếp thuộc tính là write ngầm; trường quan hệ dùng lệnh đặc biệt. Có số đo thật write cả tập vs vòng lặp trên db blog19.
17/09/2026
· 4 phút đọc
20
Viết len(search([])) để đếm bản ghi là nạp cả nghìn object chỉ để ra một con số — search_count có sẵn cho đúng việc đó
Ba cách truy vấn trong Odoo: search trả recordset, search_count trả số, search_read trả list dict. Khác nhau về hiệu năng và mục đích, đo thật trên 3030 bản ghi db blog19.
17/09/2026
· 3 phút đọc
21
Lọc 'thành phố chứa minh' mà like không ra kết quả còn ilike ra 3 — hiểu domain là hiểu cách Odoo hỏi cơ sở dữ liệu
Domain là ngôn ngữ lọc của Odoo: danh sách bộ ba (field, toán tử, value). Các toán tử =, in, like/ilike, =False, xuyên quan hệ bằng dấu chấm. Đếm thật trên db blog19.
17/09/2026
· 4 phút đọc
22
Vì sao dấu '|' trong domain Odoo lại đặt TRƯỚC hai điều kiện chứ không phải ở giữa? Hiểu ký pháp tiền tố là hết rối
Domain nâng cao trong Odoo dùng toán tử tiền tố &, |, ! (ký pháp Ba Lan). Cách đọc, cách lồng OR/NOT, và cách khớp phép tập hợp. Có đếm thật kiểm chứng logic trên db blog19.
17/09/2026
· 4 phút đọc
23
Muốn tổng doanh thu theo trạng thái đơn hàng mà nạp cả nghìn đơn về Python để tự cộng? read_group cộng ngay trong cơ sở dữ liệu
read_group là GROUP BY của Odoo: gom nhóm và tính sum/avg/count/min/max ngay ở tầng SQL, không nạp từng bản ghi. Nền tảng của mọi báo cáo và biểu đồ. Có output thật trên db blog19.
17/09/2026
· 4 phút đọc
24
Xóa một khách hàng mà Odoo báo 'cannot be deleted' — không phải lỗi, mà là khóa ngoại đang bảo vệ dữ liệu của bạn
unlink() xóa bản ghi, nhưng bị chặn khi bản ghi đang được tham chiếu — hai kiểu chặn: UserError của Odoo và ForeignKeyViolation của CSDL. Vì sao nên archive thay vì xóa. Có lỗi thật trên db blog19.
17/09/2026
· 4 phút đọc
25
Nhân bản một đơn hàng mà số đơn vẫn là S00015 thì loạn ngay — copy=False chính là thứ giữ mỗi bản một số riêng
copy() nhân bản một bản ghi trong Odoo: tự thêm (copy) vào tên, nhận default để ghi đè, và tôn trọng copy=False trên từng trường. Có output thật nhân bản partner và sale.order trên db blog19.
17/09/2026
· 4 phút đọc
26
Đổi số lượng một dòng hàng, tổng tiền đơn tự nhảy theo mà bạn không gọi hàm nào — phép màu đó tên là @api.depends
Trường computed tự tính giá trị từ trường khác; @api.depends báo Odoo biết khi nào tính lại. Ba quy tắc bắt buộc khi viết, và demo thật tổng đơn hàng tự cập nhật trên db blog19.
17/09/2026
· 4 phút đọc
27
Trường tính toán tìm kiếm và sắp xếp được, trường kia thì báo 'Cannot convert to SQL' — khác biệt chỉ nằm ở store=True
Computed store=True lưu xuống CSDL (tìm/sắp/báo cáo được), không store thì tính mỗi lần đọc. Đánh đổi hiệu năng đọc–ghi và khi nào chọn cái nào. Có bằng chứng cột SQL và lỗi thật trên db blog19.
17/09/2026
· 4 phút đọc
28
Gõ order_id.partner_id.name khắp nơi mỏi tay? Một dòng related='order_id.partner_id' biến nó thành một trường phẳng dùng thẳng
Trường related rút gọn một chuỗi quan hệ thành một trường phẳng, không cần viết hàm compute. Bản chất nó là computed rút gọn, store được để tìm/sắp. Có ví dụ thật từ sale.order.line trên db blog19.
17/09/2026
· 3 phút đọc
29
Muốn chặn điểm tích luỹ âm dù người dùng nhập kiểu gì? @api.constrains viết luật một lần, Odoo canh mọi lần lưu
@api.constrains cho phép viết luật kiểm tra hợp lệ bằng Python, chạy mỗi khi trường liên quan đổi. Sai thì raise ValidationError và rollback. Có constraint tự viết chạy thật trên module quan_ca_phe.
17/09/2026
· 4 phút đọc
30
Odoo 19 vừa khai tử _sql_constraints: ràng buộc CSDL giờ viết bằng models.Constraint — và tôi có bằng chứng nó tạo UNIQUE/CHECK thật
Ràng buộc ở tầng PostgreSQL (unique, check) nhanh và chắc hơn kiểm bằng Python. Odoo 19 thay _sql_constraints bằng models.Constraint. Có cảnh báo deprecate thật, ràng buộc sinh trong pg_constraint, và lỗi kích thật.
17/09/2026
· 4 phút đọc
31
Chọn khách hàng, ô mã thẻ tự điền và hiện cảnh báo 'khách chưa mua' — đó là @api.onchange, và nó KHÔNG phải hàng rào chặn dữ liệu
@api.onchange phản ứng ngay khi người dùng nhập trên form: tự điền ô khác, hiện cảnh báo. Nhưng nó không lưu, không chặn, không chạy từ API. Có onchange tự viết chạy thật trên module quan_ca_phe.
17/09/2026
· 4 phút đọc
32
Mở form tạo thẻ mới, ngày cấp đã điền sẵn hôm nay và hạng thẻ là 'Bạc' — ba cách đặt giá trị mặc định trong Odoo 19
default tĩnh cho hằng số, default callable cho giá trị động (ngày hôm nay, người dùng), default_get cho logic phức tạp. Thứ tự ưu tiên và cách chọn. Có output thật trên module quan_ca_phe.
17/09/2026
· 4 phút đọc
33
Odoo 19 lại khai tử name_get: muốn ô Many2one hiện 'TV-777 (Vàng) - Cà Phê Việt' thì giờ override _compute_display_name
display_name quyết định tên hiển thị của bản ghi trong ô Many2one, breadcrumb, danh sách. Odoo 19 bỏ name_get, chuyển hẳn sang _compute_display_name. Có bằng chứng name_get biến mất và display_name tuỳ biến chạy thật.
17/09/2026
· 3 phút đọc
34
Nhân viên gõ tên khách vào ô 'Thẻ' mà không ra thẻ nào? Một dòng _rec_names_search cho tìm khớp cả mã thẻ lẫn tên khách
_rec_name chọn trường định danh; _rec_names_search mở rộng các trường mà ô Many2one tìm khớp khi gõ. Odoo 19 override _search_display_name cho logic phức tạp. Có name_search chạy thật trên db blog19.
17/09/2026
· 4 phút đọc
35
Danh sách đơn hàng luôn hiện đơn mới nhất trên đầu mà không ai bấm sắp xếp — đó là _order, một dòng định đoạt thứ tự mặc định
_order đặt thứ tự sắp xếp mặc định cho mọi truy vấn của model, thành ORDER BY của SQL. Cú pháp, override tại chỗ, và bẫy hiệu năng khi sắp theo trường không index. Có thứ tự thật trên db blog19.
17/09/2026
· 4 phút đọc
36
Gán order.line = [1,2,3] để thêm dòng hàng thì Odoo báo lỗi khó hiểu — trường quan hệ nhiều bản ghi cần bộ lệnh Command (0..6)
One2many/Many2many không gán trực tiếp mà dùng Command: create, update, delete, unlink, link, clear, set. Bảy lệnh và khi nào dùng. Có tất cả Command chạy thật trên module quan_ca_phe.
17/09/2026
· 3 phút đọc
37
CSDL lưu 'dang_dung' nhưng người dùng thấy 'Đang dùng' — đó là trường Selection, và bạn còn sinh được lựa chọn động bằng một hàm
Trường Selection là danh sách lựa chọn: mã lưu CSDL, nhãn hiển thị. Khai tĩnh bằng list cặp, hoặc động bằng tên method sinh lúc chạy. Có cả hai kiểu chạy thật trên module quan_ca_phe.
17/09/2026
· 3 phút đọc
38
Lưu số tiền bằng Float thì 123456.789 VND vẫn còn phần lẻ vô nghĩa — trường Monetary tự làm tròn về đồng theo đúng tiền tệ
Trường Monetary xử lý số tiền kèm tiền tệ: bắt buộc có currency_field, tự làm tròn theo số chữ số thập phân của tiền, hiển thị đúng ký hiệu. Có làm tròn VND vs USD chạy thật trên db blog19.
17/09/2026
· 4 phút đọc
39
Nhập '<script>alert(1)</script>' vào ô ghi chú Odoo mà không hack được ai — trường Html tự làm sạch mọi mã độc trước khi lưu
Ba trường nội dung phong phú của Odoo: Html (văn bản định dạng, tự làm sạch XSS), Binary (tệp nhị phân), Image (ảnh tự resize). Cách dùng và bẫy. Có sanitize gỡ script chạy thật trên module quan_ca_phe.
17/09/2026
· 3 phút đọc
40
Đơn tạo lúc 5 giờ chiều nhưng báo cáo ghi 10 giờ sáng? Odoo lưu mọi Datetime bằng UTC — hiểu điều này là hết lệch giờ
Odoo lưu Datetime theo UTC, hiển thị theo múi giờ người dùng; Date thì không có giờ, không tz. Cách dùng đúng fields.Datetime.now, context_timestamp, và các bẫy lệch 7 giờ. Có múi giờ chạy thật trên db blog19.
17/09/2026
· 3 phút đọc
41
Một trường trỏ được lúc thì tới khách hàng, lúc thì tới đơn hàng, lúc thì tới người dùng — đó là Reference, 'Many2one động' của Odoo
Trường Reference trỏ tới bản ghi của model bất kỳ trong danh sách, lưu chuỗi model,id. Many2oneReference tách id và tên model để nhanh hơn. Có 1 field trỏ 3 model chạy thật trên module quan_ca_phe.
17/09/2026
· 3 phút đọc
42
Cho người dùng tự thêm trường vào bản ghi mà không cần lập trình viên và không ALTER TABLE — phép màu đó tên là Properties
Trường Properties cho mỗi bản ghi một schema linh hoạt do bản ghi cha định nghĩa, lưu trong một cột JSONB. Cách khai PropertiesDefinition + Properties, và output thật trên project.task db blog19.
17/09/2026
· 4 phút đọc
43
env.cr, env.uid, env.context — ba thứ mọi recordset Odoo mang theo, và hiểu chúng là hiểu 'ai đang làm gì với dữ liệu nào'
env là môi trường ORM: cr (con trỏ CSDL), uid/user (người dùng), context (ngôn ngữ, múi giờ), company (công ty). Mọi recordset mang theo env. Có giá trị env thật chụp từ odoo shell db blog19.
17/09/2026
· 4 phút đọc
44
Cùng một câu search cho 30 hay 33 bản ghi tùy một cờ trong context — with_context là cách đổi 'bối cảnh' mà không đụng dữ liệu
with_context tạo recordset mới với context thay đổi: đổi ngôn ngữ, thấy bản ghi đã lưu trữ (active_test), truyền cờ tùy biến. Context cộng dồn, không sửa env gốc. Có output thật đổi lang và active_test trên db blog19.
17/09/2026
· 3 phút đọc
45
sudo() trong Odoo không hoá bạn thành admin — nó chỉ tắt cái công tắc kiểm quyền
Nhiều người tưởng sudo() đổi user hiện tại thành quản trị viên. Không phải. Bài này dùng odoo shell thật chỉ ra sudo() giữ nguyên user, chỉ bật cờ su để bỏ qua access rights và record rules — và cách dùng nó cho an toàn.
17/09/2026
· 6 phút đọc
46
with_user và with_company: khi bạn cần chạy code 'dưới danh nghĩa người khác' — chứ không phải tắt kiểm quyền như sudo
sudo() bỏ qua quyền. with_user đổi hẳn sang một user và áp đúng quyền của họ. with_company đổi công ty đang hoạt động. Ba thứ hay bị nhầm, phân biệt bằng odoo shell thật trên Odoo 19.
17/09/2026
· 5 phút đọc
47
env.ref('base.group_user'): cách Odoo tìm đúng một bản ghi qua cái tên bất biến, thay vì id số dễ đổi
External id (XML id) là tên định danh ổn định của một bản ghi. env.ref biến cái tên đó thành recordset. Hiểu bảng ir.model.data đứng sau nó, tham số raise_if_not_found, và chiều ngược get_external_id — qua odoo shell thật.
17/09/2026
· 5 phút đọc
48
Vì sao tv.diem vẫn ra 120 dù bạn vừa UPDATE nó thành 999 trong database — cache ẩn của ORM Odoo
ORM Odoo giữ giá trị field trong một bộ đệm để đỡ chạm database. Rất nhanh, nhưng nếu bạn ghi thẳng bằng SQL thô, cache lệch và bạn đọc ra số sai. Hiểu cache và ba cách invalidate qua odoo shell thật.
17/09/2026
· 5 phút đọc
49
Gọi write() xong mà database vẫn chưa đổi: cơ chế 'ghi lười' của ORM Odoo và vai trò của flush
write() trong Odoo không UPDATE ngay xuống database — nó gom thay đổi vào cache rồi mới đẩy một lượt. flush là lúc thật sự chạm ổ đĩa. Hiểu khi nào ORM tự flush và khi nào bạn phải gọi tay, qua odoo shell thật.
17/09/2026
· 5 phút đọc
50
Lặp qua 20 bản ghi, đọc field mỗi vòng, mà chỉ tốn 1 câu SELECT: prefetch của ORM Odoo
Vòng lặp for trên recordset đọc field tưởng chừng gây N+1 query, nhưng đo thật chỉ ra ORM Odoo nạp field cho cả lô một lượt nhờ prefetch. Hiểu cơ chế này, cái gì phá nó, và cách chủ động điều khiển — qua phép đo query thật.
17/09/2026
· 4 phút đọc
51
Một dòng nối chuỗi vào cr.execute đủ để lộ cả bảng: gọi SQL thô an toàn trong Odoo 19
Đôi khi ORM không đủ và bạn phải viết SQL tay. cr.execute rất mạnh nhưng nối chuỗi input vào là mở toang cửa cho SQL injection. Hiểu tham số hoá %s, đối tượng SQL của Odoo 19, và cặp flush/invalidate — qua ví dụ injection thật.
17/09/2026
· 5 phút đọc
52
Cái hộp thoại 'cộng điểm hàng loạt' trong Odoo thật ra là một model — chỉ khác là dữ liệu tự bốc hơi sau 1 giờ
Mọi wizard trong Odoo đứng trên một TransientModel: model có bảng thật nhưng bản ghi tự dọn định kỳ. Tự tay dựng một wizard cộng điểm cho module cà phê và chạy thử, để hiểu vòng đời và cách nạp dữ liệu từ active_ids.
17/09/2026
· 4 phút đọc
53
Viết một lần, dùng cho mọi model: AbstractModel và mixin — model không có bảng nhưng cho đi field và method
AbstractModel là model không sinh bảng, dùng để gói field và logic dùng lại rồi trộn vào nhiều model bằng _inherit. Tự dựng một mixin ghi chú và gắn vào model thẻ thành viên, chạy thử trên hệ thống thật.
17/09/2026
· 4 phút đọc
54
Sao res.users.name lại nằm ở bảng res_partner? Bí mật của _inherits — kế thừa uỷ quyền trong Odoo
_inherits (có chữ s) khác hẳn _inherit: nó nối model của bạn vào một model khác qua Many2one, cho phép mượn toàn bộ field và tự tạo bản ghi liên kết. Mổ xẻ quan hệ res.users và res.partner qua dữ liệu thật.
17/09/2026
· 4 phút đọc
55
Quên @api.model_create_multi hay quên super() là dữ liệu sai âm thầm: ghi đè create/write/unlink đúng cách trong Odoo 19
Ghi đè create/write/unlink là cách chèn logic vào vòng đời bản ghi, nhưng đầy bẫy: create nhận danh sách vals, super() phải gọi đúng chỗ, hậu xử lý phải chạy trên bản ghi đã tạo. Tự dựng một model có ba override và chạy thử.
17/09/2026
· 4 phút đọc
56
sheet, group lồng group, notebook: ba khối XML dựng nên mọi form đẹp trong Odoo 19
Một form view gọn gàng của Odoo không phải nhờ CSS mà nhờ bố cục XML chuẩn: sheet làm khung, group lồng group chia hai cột, notebook gom tab. Dựng form thật cho thẻ thành viên và xem nó hiện ra trên giao diện.
17/09/2026
· 4 phút đọc
57
Danh sách thẻ tự sắp theo điểm, hàng VIP tô xanh, chân bảng cộng tổng: list view Odoo 19 làm được hết bằng thuộc tính XML
List view của Odoo không chỉ là bảng phẳng. Vài thuộc tính XML cho bạn sắp xếp mặc định, tô màu dòng theo điều kiện, cộng tổng cột số, ẩn cột và cột tuỳ chọn. Dựng list thật cho thẻ thành viên và xem kết quả.
17/09/2026
· 4 phút đọc
58
Mỗi thẻ thành viên một tấm card, badge tự đổi màu theo điểm: kanban view và QWeb trong Odoo 19
Kanban biến mỗi bản ghi thành một tấm thẻ HTML do bạn tự thiết kế bằng template QWeb. Hiểu t-name card, t-esc chèn giá trị, t-attf-class đổi màu theo điều kiện — qua một kanban thật cho thẻ thành viên.
17/09/2026
· 4 phút đọc
59
Mở màn hình là đã tự nhóm theo hạng thẻ: search view và bí mật của search_default trong Odoo 19
Search view khai báo các field tìm được, các bộ lọc bấm sẵn và cách nhóm dữ liệu. Còn search_default_ trong context của action là thứ bật sẵn một filter hay group ngay khi mở màn hình. Dựng search view thật và xem nó chạy.
17/09/2026
· 4 phút đọc
60
Khai ba dòng XML, người dùng tự xoay số liệu và vẽ biểu đồ: pivot và graph view trong Odoo 19
Pivot biến dữ liệu thành bảng chéo cộng tổng theo hàng và cột; graph vẽ biểu đồ cột, đường, tròn. Cả hai chỉ cần khai field nào là hàng, cột, thước đo — không viết code báo cáo. Dựng thật cho thẻ thành viên.
17/09/2026
· 4 phút đọc
61
Xem thẻ trên lịch theo ngày cấp, gắn việc 'gọi mời gia hạn' vào từng thẻ: calendar và activity view Odoo 19
Calendar view hiện bản ghi trên lịch theo một field ngày, tô màu theo phân loại. Activity view gom việc cần làm trên từng bản ghi — nhưng model phải kế thừa mail.activity.mixin. Dựng cả hai và lên lịch một việc thật.
17/09/2026
· 4 phút đọc
62
Thẻ Vàng thì hiện thêm ô ghi chú, thẻ từ 100 điểm thì bắt buộc chọn khách: form tự đổi theo dữ liệu với invisible/readonly/required
Odoo 19 bỏ cú pháp attrs cũ: giờ đặt biểu thức Python thẳng vào invisible, readonly, required trên field. Form tự ẩn/hiện, khoá/mở, bắt buộc theo giá trị người dùng nhập. Dựng thật và xem hai trạng thái.
17/09/2026
· 4 phút đọc
63
Thẻ khoá thì đỏ, thẻ hết hạn thì cam, thẻ VIP thì xanh đậm: tô màu từng dòng list bằng decoration
decoration-* cho phép tô màu và đổi kiểu chữ từng dòng list theo một biểu thức điều kiện. Bảy giá trị màu, ghép được nhiều loại, và một cạm bẫy: field trong biểu thức phải có trong list. Dựng thật với đủ màu.
17/09/2026
· 4 phút đọc
64
Cùng một trường điểm, hiện thành con số hay thanh tiến độ là do một chữ widget: chọn đúng widget trong Odoo 19
widget đổi cách một field hiển thị và cho nhập — badge, progressbar, image, monetary, many2many_tags, priority, boolean_toggle... Chọn đúng widget làm form vừa đẹp vừa đúng chức năng. Xem nhiều widget chạy thật trên một form.
17/09/2026
· 4 phút đọc
65
Nút 'Gia hạn thẻ' bấm một cái là +20 điểm: button type object gọi method Python, type action mở màn hình khác
Nút trên form Odoo có hai loại chính: type object gọi thẳng một method Python trên bản ghi, type action mở một action (wizard, màn hình khác). Hiểu chúng khác nhau ra sao, method trả về gì, và một cạm bẫy thứ tự nạp. Dựng nút thật.
17/09/2026
· 4 phút đọc
66
Dải 'Mới cấp → Đang dùng → Hết hạn' ở đầu form: biến một field Selection thành quy trình bấm được với statusbar
statusbar biến một field trạng thái thành dải các bước quy trình ngay đầu form. Hiểu statusbar_visible để ẩn bước ngoại lệ, options clickable để bấm chuyển, và cách kết hợp với nút quy trình. Dựng thật.
17/09/2026
· 4 phút đọc
67
Khung trao đổi bên phải form tự ghi lại '120 → 150 điểm' mà không cần code: chatter và mail.thread trong Odoo 19
Chatter là khung bên phải form: lịch sử thay đổi, ghi chú, tin nhắn, hoạt động — tất cả gắn vào bản ghi. Chỉ cần model kế thừa mail.thread, thêm tracking=True, và một thẻ chatter. Dựng thật với log thay đổi và ghi chú.
17/09/2026
· 4 phút đọc
68
Cùng một form, admin thấy 'Số dư' còn nhân viên thì không: ẩn field và nút theo nhóm với thuộc tính groups
Thuộc tính groups trên field, nút, tab hay group ẩn phần tử đó với ai không thuộc nhóm. Hiểu các nhóm hay dùng, và khác biệt quan trọng giữa groups ở view (ẩn giao diện) và ở model (bảo mật thật). Xem hai user thấy hai form khác nhau.
17/09/2026
· 4 phút đọc
69
Một template, bốn thẻ, tự tô màu VIP và cộng tổng 385 điểm: QWeb backend và các directive t-* trong Odoo 19
QWeb là ngôn ngữ template của Odoo, dùng cho kanban, báo cáo, email và website. Hiểu các directive cốt lõi t-foreach, t-if, t-esc, t-set, t-att, t-call — và render một template thật bằng ir.qweb để thấy chúng chạy.
17/09/2026
· 4 phút đọc
70
Thêm một tab 'Thẻ thành viên' vào form Liên hệ của Odoo mà không đụng một dòng code lõi: view inherit với xpath và position
View inheritance cho phép chèn, sửa, xoá phần tử trong view của module khác mà không chép lại. Hiểu inherit_id, xpath tìm phần tử, và các position (inside/after/before/replace/attributes). Xem một tab thật được thêm vào form Liên hệ.
17/09/2026
· 4 phút đọc
71
Cùng một view gốc, thêm mode='primary' là ra một form biến thể mới mà không đụng bản gốc: primary vs extension trong Odoo
Kế thừa view có hai chế độ: extension sửa view gốc tại chỗ (mọi nơi dùng nó đều đổi), primary tạo một view mới dựa trên gốc mà gốc không đổi. Hiểu khi nào dùng cái nào, qua một form biến thể thật.
17/09/2026
· 5 phút đọc
72
Chuyển field 'Hạng thẻ' từ cột trái sang cột phải mà không nhân đôi nó: position move và replace trong Odoo
Muốn dời một field sang chỗ khác trong form, hay thay nó bằng phiên bản có widget khác? position move di chuyển field có sẵn, replace thay hoặc xoá. Hiểu khác biệt và cạm bẫy field trùng tên, qua một field được dời thật.
17/09/2026
· 4 phút đọc
73
Gắn nút 'Thẻ TV: 1' lên form Liên hệ của Odoo để bấm là xem thẻ của khách: thêm nút vào form app khác
Kết hợp inherit model (thêm method + field) với inherit view (chèn nút qua xpath) để thêm một nút thống kê vào form của app khác. Nút bấm mở danh sách liên quan. Dựng nút Thẻ thành viên thật trên form Liên hệ.
17/09/2026
· 4 phút đọc
74
Một dải banner tự đếm '4 thẻ thành viên' chèn vào form lúc chạy: view động qua get_view trong Odoo 19
Khi XML tĩnh không đủ, bạn có thể ghi đè get_view (trước là fields_view_get) để sửa cấu trúc view bằng code lúc chạy — thêm field, cột, hay banner theo dữ liệu thật. Hiểu cách và khi nào nên dùng, qua một banner sinh động.
17/09/2026
· 4 phút đọc
75
Bấm 'Cấp thẻ Vàng' là form mở ra đã điền sẵn hạng Vàng, 100 điểm, đang dùng: context truyền từ action vào view
context của action là kênh nhẹ để đặt giá trị mặc định, bật filter, hay đổi hành vi form mà không đụng cấu trúc view. Hiểu default_, search_default_, và cách đọc cờ tuỳ biến — qua một form tự điền sẵn.
17/09/2026
· 4 phút đọc
76
Gom thẻ theo hạng rồi theo trạng thái, mỗi nhóm tự cộng tổng điểm: sắp xếp và nhóm nhiều cấp trên list Odoo
Ba tầng điều khiển thứ tự và nhóm: _order của model, default_order của list, và group_by của search view. Nhóm nhiều cấp tự tính subtotal. Hiểu cách phối hợp và vì sao field không store không sort được.
17/09/2026
· 4 phút đọc
77
One2many mà phải bấm mở form con cho từng dòng thì ai chịu nổi — editable list cho gõ thẳng trên bảng
editable list biến danh sách con trong form thành nơi nhập liệu trực tiếp: bấm vào ô là sửa được ngay, không mở form con. Mổ xẻ editable top/bottom, required/readonly/column_invisible theo cột, và onchange chạy khi gõ — trên form thật của Odoo 19.
17/09/2026
· 5 phút đọc
78
Form Odoo dài lê thê phải cuộn mỏi tay? Bọc nửa dưới vào một cặp thẻ và nó gập lại thành các tab gọn gàng
notebook và page là cách Odoo chia một form dài thành các tab. Mỗi <page> là một tab; đặt string, invisible, autofocus, groups để điều khiển. Thêm một tab thật vào form của module demo rồi xem kết quả trên Odoo 19.
17/09/2026
· 5 phút đọc
79
Cái nút hình hộp có con số to đùng ở đầu mọi form Odoo: đó là smart button, và tự làm một cái chỉ tốn 20 dòng
Smart button là nút thống kê ở đầu form: hiện một con số tổng hợp và bấm vào là mở danh sách bản ghi liên quan. Ghép một field compute, widget statinfo và một method trả act_window — dựng từ đầu trên module demo Odoo 19.
17/09/2026
· 4 phút đọc
80
Cùng một trường quan hệ nhiều-nhiều, đổi mỗi chữ 'widget' là giao diện nhảy từ bảng con sang badge màu hay ô tick
Many2many mặc định hiển thị thành bảng con, nhưng many2many_tags cho badge gọn có màu còn many2many_checkboxes cho ô tick. Chọn đúng widget theo bản chất dữ liệu — minh hoạ bằng cùng một field trên form Odoo 19 thật.
17/09/2026
· 4 phút đọc
81
Muốn cho người dùng kéo–thả sắp lại thứ tự các dòng? Odoo làm được chỉ với một field ẩn tên sequence và bốn chữ widget='handle'
Widget handle biến một cột số thứ tự thành cái tay nắm để kéo–thả sắp lại thứ tự các dòng. Cần đúng hai thứ: field sequence và _order theo nó. Xem nó hoạt động trên One2many thật của Odoo 19, kèm ảnh trước và sau khi kéo.
17/09/2026
· 4 phút đọc
82
Ngôi sao bấm một cái là xong, cái công tắc xanh, nhãn màu 'Vàng/Bạc': ba widget nhỏ khiến bảng dữ liệu Odoo bớt khô khan hẳn
badge biến Selection thành nhãn màu, priority thành dãy sao bấm nhanh, boolean_toggle thành công tắc. Ba widget hiển thị nhỏ mà đổi hẳn trải nghiệm đọc và sửa nhanh trên bảng — xem cả ba cùng lúc trên list thật của Odoo 19.
17/09/2026
· 4 phút đọc
83
Ô 'chọn khách hàng' đang cho chọn cả 6 công ty lẫn 25 cá nhân — thêm một dòng domain là nó chỉ còn hiện đúng người bạn muốn
domain trên field giới hạn những bản ghi được phép chọn trong Many2one/Many2many. Có domain tĩnh, domain động theo field khác, và domain đặt ngay trong định nghĩa field. Xem nó lọc thật trên Odoo 19 qua dropdown và odoo shell.
17/09/2026
· 4 phút đọc
84
Bấm 'Thêm một dòng' mà ngày, điểm, ghi chú đã điền sẵn giùm: bí mật là một khoá tên default_ trong context của field
context trên field truyền giá trị mặc định (default_<field>) xuống bản ghi con khi tạo nhanh. Khác domain: domain lọc chọn được, context điền sẵn khi tạo. Xem 'Thêm một dòng' tự điền ngày/điểm/ghi chú trên Odoo 19 thật.
17/09/2026
· 4 phút đọc
85
Người dùng cứ hỏi 'ô này điền gì?' — thay vì trả lời từng lần, gắn cho field một cái nhãn rõ hơn và một dòng tooltip là xong
string đổi nhãn hiển thị của field, help thêm tooltip khi rê chuột vào dấu hỏi. Đặt ở view cho một màn hình, hoặc ở định nghĩa field cho mọi nơi. Xem nhãn Chủ thẻ và tooltip thật trên form Odoo 19.
17/09/2026
· 4 phút đọc
86
Dòng chữ xám mờ trong ô trống không phải dữ liệu — và cái JSON bí ẩn sau chữ 'options' mới là nơi bạn tinh chỉnh widget
placeholder là gợi ý mờ hiện khi ô trống, biến mất khi gõ. options là JSON đổi cách một widget hoạt động: màu tag, cấm tạo mới, số chữ số thập phân... Xem placeholder thật và các options đang chạy trên Odoo 19.
17/09/2026
· 4 phút đọc
87
Vì sao form Odoo mặc định mỗi field một hàng, còn muốn xếp hai field cạnh nhau thì phải hiểu con số 'col' đằng sau mỗi group
group chia form thành cột; col đặt số cột logic, colspan cho field chiếm rộng, và group lồng group dựng bố cục nhiều cột. Xem bố cục 2 cột kinh điển và một nhóm col=4 chạy thật trên Odoo 19.
17/09/2026
· 4 phút đọc
88
Không cần lồng thêm group để chia đoạn cho form — một thẻ separator kẻ vạch kèm tiêu đề là gom được các field liên quan lại
separator kẻ một vạch ngang kèm tiêu đề để chia một group thành các đoạn có nhãn, gọn hơn lồng group con. Xem ba đoạn được separator gom lại trên form Odoo 19 thật.
17/09/2026
· 3 phút đọc
89
Dải nút với thanh trạng thái hình mũi tên trên đầu form, và cái tên bản ghi to đùng ngay dưới — đó là header và oe_title
header là dải trên cùng của form chứa nút hành động và statusbar; oe_title là khối tiêu đề lớn của bản ghi; oe_avatar đẩy ảnh về góc phải. Ba mảnh dựng nên phần đầu quen thuộc của mọi form Odoo 19.
17/09/2026
· 4 phút đọc
90
Bảng kanban chia cột theo trạng thái, mỗi cột có một thanh màu tóm tắt — và cái bẫy khiến nó nổ OwlError chỉ vì một dấu nháy
default_group_by chia kanban thành cột theo field; progressbar vẽ thanh phân bố màu trên đầu mỗi cột. Kèm một bẫy thật của Odoo 19: colors phải là JSON nháy kép, không phải dict Python. Xem kanban nhóm chạy thật.
17/09/2026
· 4 phút đọc
91
Cái bảng lọc dọc bên trái danh sách Odoo, bấm một cái là gọn còn đúng nhóm mình cần — dựng nó chỉ tốn một thẻ searchpanel
searchpanel là bảng lọc dọc bên trái list và kanban, chia dữ liệu theo danh mục để bấm một cái là thu hẹp. Phân biệt select one và multi, enable_counters, icon. Xem panel lọc theo hạng và trạng thái chạy thật trên Odoo 19.
17/09/2026
· 4 phút đọc
92
Cùng một dữ liệu, lúc là bảng thẻ kéo-thả, lúc là biểu đồ cột — chỉ nhờ một chuỗi phân tách bằng phẩy tên là view_mode
view_mode trong ir.actions.act_window liệt kê mọi kiểu view một action cho phép; thứ tự quyết định view mở đầu và thứ tự nút chuyển. Xem cùng một action hiện thành kanban và graph trên Odoo 19 thật.
17/09/2026
· 4 phút đọc
93
Người dùng mở app lần đầu thấy một cái bảng trắng trơn và không biết bấm gì — chữa bằng field help với cái mặt cười của Odoo
no-content help là đoạn HTML hiện giữa màn hình khi danh sách rỗng, hướng người dùng tạo bản ghi đầu tiên. Khai bằng field help trên action, class o_view_nocontent_smiling_face thêm mặt cười. Xem nó hiện thật trên Odoo 19.
17/09/2026
· 4 phút đọc
94
Muốn cái tên thẻ in màu nâu cà phê với vạch nhấn bên trái, mà không đụng một dòng code lõi Odoo? Một file SCSS của riêng module là đủ
Tuỳ biến giao diện backend Odoo bằng SCSS: thêm file .scss vào module, khai trong assets, nhắm class của Odoo hoặc class riêng để đổi màu, khoảng cách, nền. Xem form thẻ được nhuộm màu cà phê thật trên Odoo 19.
17/09/2026
· 4 phút đọc
95
File SCSS bạn viết cho module biến đi đâu? Odoo nuốt nó vào một 'bó' rồi nhả ra đúng một tệp .min.css khổng lồ — và tôi grep ra được nó nằm đó
assets_backend là cơ chế Odoo gom file CSS/JS/XML của module thành một bó, minify, phục vụ một URL. Hiểu các bó (backend/frontend/POS), glob, và bộ chỉ dẫn chèn tương đối. Kèm bằng chứng grep từ bó thật trên Odoo 19.
17/09/2026
· 4 phút đọc
96
Cùng một action mở model, nhưng đổi mỗi chữ 'new' ở field target là nó bật lên thành popup thay vì chuyển cả trang
ir.actions.act_window là loại action phổ biến nhất; các field target, res_id, domain, context, view_id, limit quyết định action mở ra cái gì và mở như thế nào. Xem wizard bật lên dạng dialog nhờ target=new trên Odoo 19.
17/09/2026
· 4 phút đọc
97
Không phải action nào cũng mở màn hình — có loại chỉ lặng lẽ chạy code, và tôi vấp ngay 'forbidden opcode' khi viết the.diem += 5
Server action (ir.actions.server) chạy logic mà không mở view; field state chọn loại việc: code, object_create, object_write, multi, mail_post... Gắn qua nút, menu Tác vụ, hoặc tự động hoá. Kèm một bẫy sandbox thật của Odoo 19.
17/09/2026
· 3 phút đọc
98
Khi list, kanban, form đều không vẽ nổi cái màn hình bạn muốn — viết một Component OWL rồi cắm vào Odoo bằng client action
Client action (ir.actions.client) là màn hình do JavaScript OWL tự vẽ ở trình duyệt, dùng khi các view có sẵn không đủ. Đăng ký Component vào registry actions, tag khớp action record. Dựng một dashboard OWL lấy dữ liệu thật trên Odoo 19.
17/09/2026
· 4 phút đọc
99
Loại action đơn giản nhất Odoo: không view, không JS, chỉ một dict ba khoá là mở được trang ngoài hay tải một file PDF
URL action (ir.actions.act_url) chỉ điều hướng trình duyệt tới một url; target new mở tab mới, self mở cùng tab. Khai bằng record XML hoặc method Python trả dict. Xem method trả act_url thật và nút mở website trên Odoo 19.
17/09/2026
· 3 phút đọc
100
Từ một bản ghi thẻ thành viên tới tờ PDF có logo, địa chỉ công ty và chân trang — tất cả nhờ report action trỏ vào một template QWeb
Report action (ir.actions.report) biến bản ghi thành tài liệu in được: trỏ tới một template QWeb, Odoo render HTML rồi xuất PDF. Hiểu docs, external_layout, t-field, report_name. Xem một thẻ thành viên in ra thật trên Odoo 19.
17/09/2026
· 4 phút đọc
101
Hộp thoại 'cộng điểm hàng loạt' cần một model để chứa dữ liệu người dùng gõ — nhưng lưu vĩnh viễn thì phí, nên Odoo có TransientModel
Wizard trong Odoo là một TransientModel: bản ghi tạm cho hộp thoại nhập liệu, tự dọn định kỳ, không để rác trong CSDL. Hiểu default_get lấy bản ghi được chọn và method đóng dialog. Xem một wizard cộng điểm chạy thật trên Odoo 19.
17/09/2026
· 4 phút đọc
102
Wizard 'chọn rồi xác nhận' hai màn hình mà vẫn là một hộp thoại: bí quyết là một field state và cái mẹo cho method mở lại chính nó
Wizard nhiều bước trong Odoo dùng một field state để ẩn/hiện từng nhóm và đổi nút; method đổi state rồi trả về action mở lại chính wizard (cùng res_id) nên dữ liệu vẫn còn. Xem hai bước Chọn và Xác nhận chạy thật trên Odoo 19.
17/09/2026
· 4 phút đọc
103
Wizard chạy xong đừng chỉ đóng lại bỏ mặc người dùng — cho method trả về một action để đưa họ thẳng tới danh sách kết quả
Method của wizard trả được bất kỳ action nào, không chỉ act_window_close. Trả act_window để mở danh sách bản ghi vừa xử lý, một form, hay act_url/report. Xem wizard cộng điểm dẫn thẳng tới danh sách thẻ vừa cập nhật trên Odoo 19.
17/09/2026
· 4 phút đọc
104
Năm cái directive bắt đầu bằng t- là đủ để biến một bản ghi thành trang in có bảng lịch sử và dòng tổng cộng tự tính
Viết template QWeb cho PDF: t-field tự format, t-esc in biểu thức, t-if/t-else rẽ nhánh, t-foreach lặp dòng con, t-set cộng dồn tổng. Dựng một bảng lịch sử điểm có tổng trên report Odoo 19 thật.
17/09/2026
· 3 phút đọc
105
Report của bạn đang tự động in trên A4 dọc — muốn nó ra khổ A5 nằm ngang với lề riêng thì thêm một report.paperformat rồi gán vào
paperformat khai khổ giấy, hướng dọc/ngang, lề và DPI cho bản in; gán vào report qua paperformat_id. Lưu ý paperformat chỉ áp cho PDF, không đổi bản HTML. Đo khổ trang PDF thật đổi từ A4 sang A5 ngang trên Odoo 19.
17/09/2026
· 3 phút đọc
106
Sửa một mẫu in của Odoo mà không đụng một dòng nào của bản gốc: inherit_id + xpath
Report trong Odoo cũng là QWeb template, nên kế thừa được y như view. Khai inherit_id trỏ tới mẫu gốc rồi dùng xpath position=after/before/attributes để chèn và sửa — bản gốc giữ nguyên. Xem trước/sau trên PDF thật của Odoo 19.
18/09/2026
· 6 phút đọc
107
Vì sao đổi số điện thoại công ty mà chân trang PDF vẫn không đổi? Header/footer report đọc từ đâu
Mọi report của Odoo dùng chung một header/footer qua web.external_layout, nội dung lấy từ res.company. Nhưng dòng chân trang KHÔNG đọc trường phone — nó đọc report_footer. Xem nguồn thật và một phép thử trước/sau trên PDF Odoo 19.
18/09/2026
· 5 phút đọc
108
Nhét mã QR quét được vào thẻ PDF của Odoo — và vì sao cách 'đúng sách' lại ra ô trống
Odoo sinh QR, Code128, EAN13... qua ir.actions.report.barcode(). Nhưng nhúng bằng URL /report/barcode trong PDF có thể ra ô rỗng khi workers=0. Cách chắc ăn: sinh ảnh thành Binary rồi image_data_uri. Đo thật trên Odoo 19.
18/09/2026
· 5 phút đọc
109
Hỏi người dùng trước khi in: dựng một wizard chọn tham số rồi mới xuất PDF trong Odoo
Một wizard TransientModel thu tham số rồi trả về report_action(records, data). Nhưng data KHÔNG vào QWeb dưới tên data — các khoá của nó trải thành biến rời. Dựng wizard In thẻ theo hạng thật trên Odoo 19 và xem data điều khiển nội dung PDF.
18/09/2026
· 5 phút đọc
110
In 4 thẻ một lần lại ra 5 trang: cách QWeb gộp nhiều bản ghi vào một file PDF
Chọn nhiều bản ghi rồi in là ra một file PDF gộp — nhờ t-foreach docs và class page trong QWeb. Nhưng số trang không bằng số bản ghi: bản ghi dài tự tràn trang. Đo bằng PyPDF2 trên Odoo 19.
18/09/2026
· 4 phút đọc
111
Bảng sao kê có cộng dồn theo tháng và một dòng TỔNG CỘNG: kỹ thuật t-set tích luỹ trong QWeb
QWeb không có hàm SUM. Muốn cộng dồn, gộp nhóm, ra subtotal và tổng cuối, bạn dùng một biến t-set tích luỹ qua vòng lặp. Dựng bảng sao kê điểm nhóm theo tháng, đo số khớp trên PDF thật Odoo 19.
18/09/2026
· 4 phút đọc
112
Một mẫu in, ba khuôn mặt: dùng t-if/t-elif/t-else để report tự đổi theo trạng thái bản ghi
QWeb chọn nhánh hiển thị bằng t-if, t-elif, t-else — mỗi trạng thái thẻ ra một banner khác. Và t-if đơn giúp bỏ hẳn phần rỗng thay vì in khoảng trắng. Xem ba thẻ thật in ra ba kiểu trên Odoo 19.
18/09/2026
· 4 phút đọc
113
Đưa SCSS vào bản in Odoo đúng chuẩn — rồi phát hiện style không ăn: chuyện bundle report và wkhtmltopdf
Odoo có bundle web.report_assets_common để nạp CSS/SCSS cho mọi report. Nhưng nó vào PDF qua link ngoài mà wkhtmltopdf phải fetch — với workers=0 thì style rơi mất. Cách chắc ăn và giới hạn WebKit, đo trên Odoo 19.
18/09/2026
· 4 phút đọc
114
Menu mở ra đã lọc sẵn, bấm Mới là điền sẵn: bí mật nằm ở domain và context của action
ir.actions.act_window có hai trường quyền lực: domain lọc trước danh sách, context với default_X điền sẵn field và search_default_X bật sẵn bộ lọc. Dựng một action lọc-và-điền thật rồi soi kết quả trên Odoo 19.
18/09/2026
· 4 phút đọc
115
Tick 4 thẻ, bấm một lần, cả 4 cùng cộng điểm: action chạy trên nhiều bản ghi qua active_ids
Chọn nhiều dòng rồi vào menu Tác vụ là chạy một action trên cả lô. Bí mật là active_ids biến lựa chọn thành một recordset, và code chỉ việc lặp records. Đo thật một server action cộng điểm hàng loạt trên Odoo 19.
18/09/2026
· 4 phút đọc
116
Vì sao lệnh của bạn hiện trong menu Tác vụ còn report thì nằm ở nút In: ba trường binding
binding_model_id, binding_type, binding_view_types quyết định action của bạn xuất hiện ở đâu trong menu ngữ cảnh. Server action vào Tác vụ, report vào In — cùng một cơ chế. Soi registry get_bindings thật trên Odoo 19.
18/09/2026
· 4 phút đọc
117
Để Odoo tự chuyển thẻ quá hạn mỗi đêm mà không ai bấm nút: khai một ir.cron
ir.cron là việc chạy nền theo lịch. Khai một record trỏ tới method của model, đặt chu kỳ, và scheduler tự gọi. Cách test không cần chờ, và những field Odoo 19 đã bỏ. Dựng cron rà soát hạn thẻ thật.
18/09/2026
· 4 phút đọc
118
Viết thẳng Python vào ô Mã của cron: những biến Odoo đưa sẵn cho bạn, và thứ sandbox chặn
state='code' cho phép cron chạy đoạn Python nhiều dòng. Odoo cấp sẵn env, model, log, datetime, Command, UserError... nhưng không có 'fields' và chặn import. Danh sách biến đo thật và một ví dụ chạy được trên Odoo 19.
18/09/2026
· 4 phút đọc
119
Cron chờ đồng hồ, Automated Action chờ sự kiện: hai cơ chế tự động của Odoo khác nhau chỗ nào
Cron chạy theo lịch định kỳ; Automated Action chạy ngay khi một bản ghi được tạo/sửa/xoá. Dựng một quy tắc on_create thật và đo nó bắn tức thì, so với cron phải chờ nextcall. Trên Odoo 19.
18/09/2026
· 4 phút đọc
120
Một mẫu email điền sẵn tên khách và số điểm: khai mail.template bằng XML, dùng {{ }} và t-out
mail.template là email có chỗ trống điền động. Subject và địa chỉ dùng {{ }}, còn body_html dùng QWeb t-out. Khai bằng XML trong module rồi render thật cho một thẻ trên Odoo 19 — chưa gửi, chỉ xem.
18/09/2026
· 4 phút đọc
121
send_mail xong rồi mà khách chưa nhận thư: email vào hàng đợi chứ chưa gửi ngay
Gọi send_mail không gửi email tức thì — nó tạo một mail.mail state outgoing chờ cron. force_send=True mới gửi ngay nhưng phụ thuộc SMTP. Đo thật trạng thái thư trên Odoo 19, cùng message_post và mail.mail.send.
18/09/2026
· 4 phút đọc
122
Giao việc 'gọi khách gia hạn' cho nhân viên ngay từ code: một dòng activity_schedule
Activity là việc-cần-làm gắn vào bản ghi, có hạn và người phụ trách. Model kế thừa mail.activity.mixin gọi activity_schedule là xong. Xem nó hiện trong chatter và cách đánh dấu hoàn tất, đo thật trên Odoo 19.
18/09/2026
· 4 phút đọc
123
Ghi một dòng vào khung trao đổi từ code: message_post, và vì sao 'ghi chú' khác 'bình luận'
message_post ghi vào chatter của bản ghi. Nhưng subtype quyết định ai được báo: mt_note chỉ lưu vết nội bộ, mt_comment gửi thông báo cho follower. Đo thật hai loại trên Odoo 19.
18/09/2026
· 4 phút đọc
124
Vì sao mã thẻ nhảy từ 0002 sang 0004: ir.sequence và cái bẫy khoảng trống
ir.sequence tự cấp mã tăng dần (TV-2026-0001...). Nhưng chế độ standard tiêu số ngay cả khi giao dịch rollback — nên có khoảng trống. Khi nào chấp nhận, khi nào phải dùng no_gap. Đo thật trên Odoo 19.
18/09/2026
· 4 phút đọc
125
Đính kèm hợp đồng vào một bản ghi từ code — và tệp đó thực ra nằm ở đâu
ir.attachment gắn file vào bất kỳ bản ghi nào qua res_model và res_id. Nhưng bytes không nằm trong CSDL — chúng ở filestore trên đĩa, DB chỉ giữ metadata và checksum. Đo thật trên Odoo 19.
18/09/2026
· 4 phút đọc
126
Một màn hình Odoo tự vẽ bằng JavaScript: giải phẫu component OWL đầu tiên
OWL là framework giao diện của Odoo 19. Một component gồm ba mảnh: lớp JS với setup và state, template QWeb-OWL, và đăng ký vào registry. Dựng một dashboard lấy số liệu thật qua ORM, chạy trong trình duyệt.
18/09/2026
· 3 phút đọc
127
t-out trong OWL không render HTML như bạn tưởng: chuyện escape, markup và XSS
Trong template OWL, t-esc luôn escape. Nhưng t-out với một chuỗi thường CŨNG escape — khác hẳn report QWeb. Muốn render HTML phải bọc markup(). Dựng demo thật trong dashboard OWL trên Odoo 19.
18/09/2026
· 4 phút đọc
128
Đổ một mảng thẻ ra bảng trong OWL: t-foreach, và vì sao t-key không phải trang trí
t-foreach lặp qua mảng để dựng danh sách trong OWL. Nhưng t-key là bắt buộc — và dùng id thay vì chỉ số quyết định OWL tái dùng DOM đúng khi danh sách đổi. Dựng bảng thẻ thật trên Odoo 19.
18/09/2026
· 3 phút đọc
129
Bấm nút 3 lần mà số không nhúc nhích: useState và bí mật của trạng thái phản ứng trong OWL
Trong OWL, gán this.state.x làm giao diện tự vẽ lại; gán this.x thì không. useState trả về một proxy theo dõi thay đổi. Đo thật bằng hai bộ đếm cạnh nhau trên Odoo 19.
18/09/2026
· 4 phút đọc
130
Tách một thẻ thành component riêng và truyền dữ liệu xuống: props trong OWL
Ứng dụng thật gồm nhiều component lồng nhau. Cha truyền dữ liệu xuống con qua props — một chiều, có kiểm kiểu. Con chỉ đọc, không sửa. Dựng component con TheCard nhận props thật trên Odoo 19.
18/09/2026
· 4 phút đọc
131
Vì sao chạm vào DOM trong setup luôn null: hooks vòng đời onWillStart và onMounted
Component OWL có vòng đời: setup, onWillStart, render, onMounted. Nạp dữ liệu ở onWillStart; chạm DOM ở onMounted vì trước đó node chưa tồn tại. Đo thật thứ tự hook qua Console trên Odoo 19.
18/09/2026
· 4 phút đọc
132
Bấm một thẻ để chọn nó: bắt sự kiện với t-on trong OWL, và cái bẫy khi lặp danh sách
t-on-click gắn handler cho sự kiện. Gọi thẳng method thì nhận event; muốn truyền tham số (item trong t-foreach) phải dùng arrow function. OWL không có modifier .stop/.prevent như Vue. Đo thật trên Odoo 19.
18/09/2026
· 3 phút đọc
133
Component OWL của bạn cần gọi server, hiện toast, mở một view — nó không tự làm, nó mượn 'service'
useService là cách một component OWL lấy về những mảnh chức năng dùng chung của web client: orm để nói chuyện với server, notification để hiện toast, action để mở view. Mổ xẻ cơ chế và thử thật trên Odoo 19.
18/09/2026
· 6 phút đọc
134
Gọi ORM của Odoo ngay từ JavaScript: searchRead, searchCount, và cái bẫy readGroup đã bị đổi tên ở Odoo 19
orm service là cầu nối để component OWL đọc/ghi dữ liệu qua đúng ORM của Odoo mà không tự dựng request. Xem searchRead/searchCount/call chạy thật, và vì sao readGroup của bạn ném lỗi trên Odoo 19.
18/09/2026
· 5 phút đọc
135
Khi orm không đủ: gọi thẳng một controller Python tự viết từ JavaScript — và hai cái tên Odoo 19 đã lặng lẽ đổi
rpc là cách component OWL gọi một route HTTP bất kỳ và nhận JSON, dùng khi orm không phủ được. Nhưng ở Odoo 19 rpc không còn là service, và type='json' đã thành 'jsonrpc'. Xem cả hai lỗi thật và cách sửa.
18/09/2026
· 5 phút đọc
136
Cái 'registry' mà mọi module OWL đều gọi tới: cách bạn nhét thêm một mảnh giao diện vào Odoo mà không sửa lõi
registry là hệ thống sổ đăng ký của web client Odoo — nơi client action, systray, field widget, view đăng ký tên để được nhận diện. Đăng ký thật một mục systray hiện điểm và xem nó xuất hiện trên thanh trên cùng.
18/09/2026
· 5 phút đọc
137
Không thích cách Odoo hiển thị một field? Viết widget của riêng bạn: biến ô 'điểm' thành huy hiệu đổi màu
Field widget quyết định một field hiển thị và cho nhập thế nào trong view. Tự viết một widget OWL: đọc giá trị qua props.record, đăng ký vào category fields, gắn vào form bằng widget=. Làm thật trên Odoo 19.
18/09/2026
· 5 phút đọc
138
1234567 hiện thành '1.234.567', và người dùng gõ 'abc' thì sao? Hai hàm formatter và parser đứng giữa dữ liệu và ô nhập
Formatter biến giá trị thô thành chuỗi đẹp theo locale; parser làm ngược lại và ném lỗi khi người dùng gõ sai. Đây là cặp hàm mọi field widget cho nhập đều cần. Gọi thật formatInteger, parseFloat trên Odoo 19.
18/09/2026
· 4 phút đọc
139
list, form, kanban đều chỉ là 'object' trong một sổ đăng ký — nên bạn viết được loại view thứ tư của riêng mình
Một view trong Odoo là một object JS với Controller, Renderer, Model, ArchParser. Kế thừa một view có sẵn và thay đúng mảnh bạn cần, rồi gắn qua js_class. Làm thật một view kanban có dải tổng kết trên Odoo 19.
18/09/2026
· 4 phút đọc
140
Thêm một icon có dropdown lên thanh trên cùng Odoo: ba số liệu và một nút mở màn hình, gói trong systray
Systray là dải icon bên phải thanh điều hướng Odoo. Viết một mục systray bài bản: icon FontAwesome, badge đếm, dropdown hiện số liệu và một mục mở view. Chú ý sequence quyết định vị trí. Làm thật trên Odoo 19.
18/09/2026
· 4 phút đọc
141
Khi form và list không đủ: tự vẽ nguyên một màn hình trong Odoo bằng client action OWL
Client action là một màn hình do component OWL toàn quyền vẽ, mở từ menu như một app. Không bị ràng buộc bởi view chuẩn. Dựng thật một bảng xếp hạng thẻ theo điểm và mở nó từ menu trên Odoo 19.
18/09/2026
· 4 phút đọc
142
Cần đổi hành vi một component lõi của Odoo mà không được chép cả file: patch() là câu trả lời
patch chèn thêm hoặc thay method của một class/component có sẵn ngay tại chỗ, super gọi lại bản gốc, và áp cho mọi instance. Vá thật component KanbanRenderer lõi và bắt bằng chứng ở console Odoo 19.
18/09/2026
· 4 phút đọc
143
Toast trong Odoo không chỉ có 'thành công' màu xanh: type, sticky, và cả nút hành động ngay trong thông báo
notification service bật toast góc phải: bốn loại màu, tùy chọn dính (sticky) để không tự tắt, và nút bấm ngay trong toast. Xem cả ba loại hiện thật cạnh nhau trên Odoo 19 và khi nào dùng thay cho dialog.
18/09/2026
· 3 phút đọc
144
Khi cần một câu trả lời trước khi đi tiếp: dialog service mở hộp thoại chặn ngay giữa màn hình Odoo
dialog service mở một hộp thoại chặn màn hình cho tới khi người dùng chọn xong. ConfirmationDialog là mẫu có sẵn với title, body và callback confirm/cancel. Dựng thật một hộp xác nhận xoá trên Odoo 19.
18/09/2026
· 4 phút đọc
145
Code JS của bạn im lặng không chạy, không một dòng lỗi? Rất có thể bạn khai nhầm asset bundle
Odoo gom JS/CSS/template thành các bundle, mỗi trang chỉ nạp bundle của nó. web.assets_backend cho web client, web.assets_frontend cho website. Đặt nhầm bundle là code chết lặng lẽ — chứng minh thật trên Odoo 19.
18/09/2026
· 4 phút đọc
146
Đổi một dòng SCSS, cả form đổi màu theo: biến Sass và cách ghi đè biến theme của Odoo
Odoo viết style bằng SCSS. Biến $ khai một lần dùng khắp nơi, nesting gom selector, và ghi đè biến theme để đổi màu toàn hệ thống. Xem một biến đổi màu cả form qua ảnh thật trước và sau trên Odoo 19.
18/09/2026
· 4 phút đọc
147
Ba loại QWeb dễ nhầm làm một: dựng một trang web công khai bằng template QWeb và controller website
QWeb website render HTML phía server cho trang công khai, khác QWeb của OWL (trình duyệt) và của report (PDF). Dựng thật một trang bảng điểm công khai với controller website=True và t-foreach trên Odoo 19.
18/09/2026
· 4 phút đọc
148
Viết một 'thẻ' một lần rồi dùng lại khắp nơi: t-call và cái khe bí ẩn t-out='0' trong QWeb
t-call chèn một template QWeb vào chỗ khác, template con thấy chung context. Nội dung con của lời gọi rơi vào khe số 0. Dùng để tách phần dùng chung. Làm thật một trang gọi lại template card qua ảnh trên Odoo 19.
18/09/2026
· 4 phút đọc
149
Cùng một field, hai kết quả: t-esc in '1234.5', t-field in '$ 1.234,50' — vì t-field hiểu kiểu dữ liệu
t-field đọc kiểu của field và tự định dạng: Monetary ra ký hiệu tiền, Selection ra nhãn, Date ra định dạng ngày. Còn cho sửa tại chỗ trên website. So sánh thật t-field với t-esc trên cùng một bản ghi Odoo 19.
18/09/2026
· 4 phút đọc
150
Một component 'khung thẻ' dùng cho ba nội dung khác nhau: slots của OWL cho phép nơi gọi quyết định ruột bên trong
Slots để component cha nhét nội dung tuỳ ý vào những chỗ định sẵn của component con — giống khe 0 của QWeb nhưng cho component OWL. Có slot mặc định và slot đặt tên. Làm thật ba thẻ dùng chung một khung trên Odoo 19.
18/09/2026
· 4 phút đọc
151
Một component 300 dòng làm mọi thứ là ác mộng bảo trì: cắt nó thành sub-component, dữ liệu chảy xuống props, sự kiện dội lên callback
Chia một component OWL lớn thành các con nhỏ: cha truyền dữ liệu xuống bằng props, con báo ngược lên bằng callback prop. Mỗi con tự lo phần của mình. Làm thật trên dashboard Odoo 19 với ảnh click và toast.
18/09/2026
· 4 phút đọc
152
Khi bạn thật sự cần chạm vào DOM: useRef để tự focus một ô, đo kích thước thật, vẽ lên canvas
OWL vẽ DOM cho bạn, nhưng đôi khi cần chạm thẳng vào node thật: focus, đo px, gọi thư viện ngoài. useRef + t-ref là cách an toàn — nhưng .el chỉ có sau khi mount. Làm thật một ô tự focus và đo bề rộng trên Odoo 19.
18/09/2026
· 4 phút đọc
153
Vue có 'computed', React có useMemo — OWL thì chỉ cần một getter JavaScript bình thường
Giá trị dẫn xuất từ state không nên lưu riêng (dễ lệch). OWL không có cơ chế computed riêng: chỉ dùng getter JS, đọc state phản ứng, tự cập nhật mỗi lần render. Xem tổng/trung bình/cao nhất tự đổi theo state trên Odoo 19.
18/09/2026
· 4 phút đọc
154
Một widget lỗi kéo sập trắng cả màn hình? onError bọc nó lại, phần còn lại vẫn sống
Một lỗi khi render một component OWL có thể làm hỏng cả cây giao diện. onError (error boundary) bắt lỗi từ component con, hiện một thông báo thay thế, và giữ phần còn lại của UI hoạt động. Làm thật trên Odoo 19.
18/09/2026
· 4 phút đọc
155
Kiểm thử JavaScript trong Odoo 19: QUnit đã lùi, Hoot lên thay — viết một test và xem nó xanh ở /web/tests
Odoo 19 chuyển bộ khung test frontend từ QUnit sang Hoot: cú pháp describe/test/expect gọn như Jest. Viết một test cho hàm thuần, khai vào bundle unit test, và chạy nó thật trên trình chạy /web/tests.
18/09/2026
· 4 phút đọc
156
Kiểm thử cả một luồng người dùng, không chỉ một hàm: tour của Odoo bấm nút và điền form giúp bạn
Tour là kịch bản mô phỏng người dùng đi qua nhiều bước trên UI thật — bấm, điền, chuyển màn hình. Dùng để kiểm thử end-to-end (và làm hướng dẫn onboarding). Viết một tour và chạy nó thật trên Odoo 19, thấy SUCCEEDED.
18/09/2026
· 4 phút đọc
157
Cái khối kéo–thả trong trình dựng website Odoo thật ra chỉ là một đoạn HTML: viết snippet đầu tiên của bạn
Snippet là khối nội dung người dùng kéo–thả vào trang bằng trình dựng website. Nó chỉ là một template <section> HTML, cộng một dòng đăng ký vào bảng chọn. Tạo một snippet thật và render nó trên Odoo 19.
18/09/2026
· 3 phút đọc
158
Thêm bảng tùy chỉnh cho snippet của bạn: Odoo 19 viết lại toàn bộ API options thành html_builder Plugin
Snippet options là bảng nút cho người dùng chỉnh khối trong trình dựng website. Odoo 19 bỏ we-select/SnippetOptionWidget cũ, chuyển sang html_builder Plugin với BuilderRow/BuilderSelect và classAction. Làm thật một option đổi tông màu.
18/09/2026
· 4 phút đọc
159
Thêm lệnh của riêng bạn vào trình biên tập website Odoo: gõ '/' là ra, một Plugin html_editor là đủ
Không chỉ tùy chỉnh một snippet — bạn mở rộng được chính trình biên tập: thêm lệnh vào powerbox (menu gõ /), nút vào thanh công cụ. Odoo 19 làm qua Plugin với user_commands và powerbox_items. Code thật, nạp sạch.
18/09/2026
· 4 phút đọc
160
Mỗi thẻ Kanban tự vẽ một thanh tiến độ: field widget OWL không chỉ dùng cho form, mà cho cả card
Field widget bạn viết cho form dùng lại được ngay trong thẻ Kanban. Gắn widget='ten' lên một field trong t-name='card' là mỗi thẻ tự render một mảnh JS. Làm thật một thanh tiến độ điểm trên Odoo 19.
18/09/2026
· 4 phút đọc
161
Cái avatar tròn cạnh tên người bạn thấy khắp Odoo: many2one_avatar — một chữ widget, không dòng JS nào
many2one_avatar hiển thị một field Many2one kèm ảnh đại diện của bản ghi liên quan. Có biến thể riêng cho user, nhân viên. Không ảnh thì tự vẽ avatar-chữ-cái. Thấy thật trên kanban Odoo 19.
18/09/2026
· 3 phút đọc
162
Vì sao một ô 'Ghi chú' trong Odoo lại có nút đậm, nghiêng, gạch đầu dòng — còn ô kia thì không? Bí mật nằm ở fields.Html
field Html không lưu chuỗi thô như Text hay Char — nó lưu HTML có định dạng và tự nhúng trình soạn thảo html_editor vào form. Mổ xẻ sanitize, widget='html', các options tùy biến qua field ghi_chu_html thật trên Odoo 19.
18/09/2026
· 6 phút đọc
163
_t('Save') ra 'Lưu', còn tên quán của bạn thì giữ nguyên: cách Odoo 19 dịch chuỗi ngay trong JavaScript
Hàm _t bọc mọi chuỗi hiển thị trong front-end Odoo để nó tự đổi theo ngôn ngữ người dùng. Mổ xẻ nguồn dữ liệu, cơ chế 'lười' mới của Odoo 19 (bỏ hẳn _lt), và các kiểu tham số — qua _t gõ thẳng trên hệ thống thật.
18/09/2026
· 7 phút đọc
164
Trình soạn web của Odoo nặng vài megabyte — nhưng nó chỉ tải về đúng lúc bạn bấm 'Sửa': kỹ thuật lazy load component
Nạp trễ (lazy load) hoãn tải một khối giao diện nặng đến đúng lúc cần, thay vì gánh hết ngay từ đầu. Mổ xẻ loadBundle, LazyComponent và dynamic import trong Odoo 19 — qua lõi thật và network fetch bundle chụp trực tiếp.
18/09/2026
· 6 phút đọc
165
Gõ một dòng vào console là thấy cả cây giao diện Odoo: WebClient, NavBar và những 'lưới an toàn' ErrorHandler quanh mỗi menu
OWL để lộ toàn bộ cây component đang chạy qua window.__OWL_DEVTOOLS__ và this.__owl__. Cách xem state/props của bất kỳ component nào, đọc cây từ gốc WebClient, và vì sao mỗi menu systray được bọc trong một error boundary — soi trực tiếp trên Odoo 19.
18/09/2026
· 5 phút đọc
166
Không cần view, không cần OWL: 12 dòng Python biến /quan_ca_phe/thong_ke thành một URL thật trả JSON
http.Controller và @route là cách Odoo biến một hàm Python thành một địa chỉ web. Mổ xẻ type http vs jsonrpc, auth, csrf và đối tượng request — qua controller thật dựng trên quan_ca_phe rồi curl lấy kết quả sống.
18/09/2026
· 5 phút đọc
167
Cùng một dòng code request.env.user, ba route trả ba câu trả lời khác nhau: bí mật nằm ở auth
auth trong @route quyết định ai được gọi và code chạy dưới danh nghĩa ai. Phân biệt user, public, none (và bearer mới của Odoo 19) qua ba route thật cùng thân hàm, curl lấy kết quả sống khi chưa và đã đăng nhập.
18/09/2026
· 5 phút đọc
168
Cùng trả về {'so_the': 4}, ba route cho ba dạng JSON khác nhau: khi nào bọc phong bì, khi nào trả thô
Controller trả JSON cho front-end OWL và cho API ngoài có nhiều đường: jsonrpc bọc phong bì result, http trả thô tự set header, và json2 mới của Odoo 19 trả REST-style. So sánh qua ba route thật, curl lấy kết quả sống.
18/09/2026
· 5 phút đọc
169
request.render một dòng, Odoo tự lắp header và footer quanh bảng của bạn thành trang web hoàn chỉnh
Controller không chỉ trả JSON — nó dựng được cả trang website. Mổ xẻ request.render, template QWeb kế thừa website.layout, và website=True qua một trang bảng vàng thành viên dựng thật rồi chụp trực tiếp trên Odoo 19.
18/09/2026
· 4 phút đọc
170
Một trang web thật với URL riêng, không một dòng Python: khai website.page bằng XML và để Odoo tự tạo route
Trang tĩnh không cần controller. Khai một template QWeb và một bản ghi website.page trỏ url + view_id, Odoo tự tạo route và cho quản lý trong Website > Pages. Dựng thật một trang thể lệ, chụp trực tiếp trên Odoo 19.
18/09/2026
· 4 phút đọc
171
Trang /my của Odoo: mỗi khách đăng nhập vào chỉ thấy đúng đơn, hoá đơn, thẻ của chính mình — cơ chế đằng sau là portal
Portal là khu 'Tài khoản của tôi' cho khách hàng đăng nhập. Mổ xẻ CustomerPortal, override _prepare_home_portal_values để thêm ô đếm, và portal.mixin (access_url/access_token) — qua một ô Thẻ thành viên gắn thật vào /my của Odoo 19.
18/09/2026
· 5 phút đọc
172
Trang /my/the với bảng thẻ, nút sắp xếp và phân trang 1-2-3: dựng bằng portal_pager và t-call portal.portal_table
Sau ô đếm ở /my, đây là trang danh sách đầy đủ của portal: liệt kê bản ghi của chính khách, phân trang bằng portal_pager, sắp xếp bằng searchbar_sortings. Dựng thật route /my/the và chụp bảng + pager trên Odoo 19.
18/09/2026
· 4 phút đọc
173
Quên một dòng csrf_token, cả biểu mẫu đăng ký trả về 400: cách nhận dữ liệu từ khách qua website form đúng cách
Dựng một form công khai để khách gửi dữ liệu vào Odoo: route GET render form, route POST tạo bản ghi. Mổ xẻ csrf_token bắt buộc, sudo cho public user, validate và honeypot chống bot — qua form đăng ký dựng thật rồi submit trên Odoo 19.
18/09/2026
· 5 phút đọc
174
File khách gửi lên không nằm trong request.params — nó ở httprequest.files, và đây là cách đọc rồi lưu an toàn
Nhận file upload trong controller Odoo: đọc từ request.httprequest.files, kiểm loại và kích thước, lưu thành ir.attachment. Demo thật một form tải ảnh, upload một PNG và xác nhận attachment trên Odoo 19.
18/09/2026
· 5 phút đọc
175
Trả một file cho khách tải về mà tên 'danh-sách-thẻ.csv' không bị vỡ dấu: bí mật ở content_disposition
Controller trả file để tải xuống: make_response với Content-Type và Content-Disposition đúng, content_disposition cho tên tiếng Việt có dấu, và Stream cho file lớn. Demo thật một route xuất CSV rồi curl xem header trên Odoo 19.
18/09/2026
· 5 phút đọc
176
Trang tùy biến của bạn Google không bao giờ thấy — cho tới khi bạn thêm đúng một chữ: sitemap=True
Route tự viết không tự vào sitemap.xml, không tự có thẻ meta. Cách khai sitemap=True (và callback cho URL động), đặt title/meta description/OG qua website.layout — kiểm chứng bằng sitemap.xml và thẻ head thật trên Odoo 19.
18/09/2026
· 4 phút đọc
177
Thêm một dòng XML, mục 'Ưu đãi' hiện ngay trên thanh menu website — và Odoo tự sao nó sang từng site
website.menu là cây điều hướng của website; website.page là trang. Cách khai một mục menu trỏ tới trang bằng XML, hiểu parent_id/sequence/page_id, và cơ chế Odoo sao menu sang từng website — qua một mục menu thật hiện trên header Odoo 19.
18/09/2026
· 5 phút đọc
178
Cùng một route POST, thêm token thì 200, thiếu token thì 400: mổ xẻ tấm khiên CSRF của Odoo
CSRF bảo vệ form POST khỏi giả mạo request. Cách Odoo sinh và kiểm token (HMAC + database.secret, consteq), khi nào csrf=False là đúng, và vì sao jsonrpc không cần — kiểm chứng bằng ba lần POST thật trên Odoo 19.
18/09/2026
· 4 phút đọc
179
so_lan_vao: 1, 2, 3 — controller không có bộ nhớ, nhưng session thì có: mổ xẻ request.env và request.session
request.env là cửa vào ORM (dựng theo auth), request.session giữ trạng thái giữa các request. Cách chúng hoạt động, khác nhau ra sao, và cách dùng đúng — kiểm chứng bằng một bộ đếm session thật tăng qua từng lần gọi trên Odoo 19.
18/09/2026
· 4 phút đọc
180
Đổi một con số trong CSDL, tải lại trang là bảng điểm tự sắp lại: snippet đọc dữ liệu sống từ model
Snippet tĩnh gõ cứng nội dung; snippet động đọc thẳng model, tự cập nhật mỗi lần tải. Cách dựng một khối QWeb truy vấn model bằng request.env + t-foreach, và website.snippet.filter của builder — chứng minh 'sống' bằng before/after thật trên Odoo 19.
18/09/2026
· 4 phút đọc
181
Cùng một sản phẩm, giá trên trang từ 185.000 thành 148.000 mà list_price không đổi: móc vào _get_combination_info
Muốn áp giá riêng trên trang sản phẩm eCommerce? Odoo tính giá qua nhiều tầng — bảng giá, _get_combination_info, dòng đơn. Cách override đúng chỗ để can thiệp giá, minh hoạ bằng giá thành viên -20% thật trên Odoo 19.
18/09/2026
· 4 phút đọc
182
Khách gõ một câu hỏi trên trang tài khoản, nhân viên thấy ngay trong chatter: cùng một mail.message, hai nơi hiển thị
Chatter cho phép khách và nhân viên trao đổi ngay trên bản ghi. Cách kế thừa mail.thread, message_post tạo mail.message, và cơ chế portal chatter (PortalChatter + token) cho khách gửi tin an toàn — qua một tin thật đăng lên Odoo 19.
18/09/2026
· 4 phút đọc
183
Vì sao nút 'Add to cart' và footer của cả website đổi màu cùng lúc chỉ với một dòng SCSS: cấu trúc một theme Odoo
Theme không phải bộ CSS rời — nó là một module đặc biệt khai màu/font qua hệ biến SCSS và color-combination. Mổ xẻ manifest theme, $o-color-palettes, o-cc, và cách theme áp lên toàn site — soi lõi thật trên Odoo 19.
18/09/2026
· 4 phút đọc
184
redirect('https://evil.com/x') mà trình duyệt lại đi tới /x cùng site: local=True âm thầm chặn open-redirect
Chuyển hướng, trả 404, đọc request thô — tất cả đi qua Werkzeug bên dưới controller Odoo. Cách dùng request.redirect (và bẫy local=True), raise NotFound, đọc request.httprequest — kiểm chứng bằng curl thật trên Odoo 19.
18/09/2026
· 4 phút đọc
185
?page=99 trên một danh sách 2 trang: một dòng clamp cứu bạn khỏi trang trắng và lỗi offset
Phân trang danh sách trong controller tự viết: đếm tổng, tính offset/limit, clamp số trang, giữ tham số sắp xếp qua các trang. Bốn bước và cạm bẫy hay gặp — kiểm chứng bằng curl thật trên Odoo 19.
18/09/2026
· 4 phút đọc
186
20 dòng Python bên ngoài Odoo, đăng nhập rồi đọc/ghi dữ liệu như một module nội bộ: XML-RPC
Một script bất kỳ ngôn ngữ nào cũng gọi được Odoo từ xa qua XML-RPC: authenticate lấy uid, rồi execute_kw để search/create/write/unlink. Luồng chuẩn, bảo mật, và một vòng CRUD thật trên Odoo 19.
18/09/2026
· 4 phút đọc
187
Cùng đọc 4 thẻ thành viên, nhưng lần này bằng fetch() từ JavaScript: gọi Odoo qua JSON-RPC
JSON-RPC cho cùng sức mạnh XML-RPC nhưng payload JSON nhẹ, gọi được ngay từ fetch/JavaScript. Cấu trúc /jsonrpc, cách gọi từ JS, và session-based cho front-end — với kết quả curl thật trên Odoo 19.
18/09/2026
· 4 phút đọc
188
Chiều ngược lại: để Odoo tự gọi ra một dịch vụ bên ngoài — và vì sao thiếu một chữ timeout=10 có thể treo cả worker
Hai bài trước bên ngoài gọi vào Odoo. Giờ Odoo là client gọi ra dịch vụ thứ ba bằng requests. timeout bắt buộc, bắt đúng exception, đừng chặn luồng — với kết quả requests chạy thật từ trong Odoo shell.
18/09/2026
· 5 phút đọc
189
Thay vì để Odoo hỏi 'xong chưa?' mỗi 30 giây, hãy để bên kia gõ cửa: viết một webhook nhận sự kiện, ký bằng HMAC
Webhook lật chiều tích hợp: hệ thống khác chủ động POST vào Odoo khi có sự kiện. Controller csrf=False, xác thực bằng chữ ký HMAC-SHA256 thay vì session — với một webhook chạy thật trên module quản cà phê.
18/09/2026
· 5 phút đọc
190
Viết 4 test cho module cà phê, chạy thật, rồi kiểm CSDL: không một dòng TEST-0001 nào sót lại — phép màu của TransactionCase
TransactionCase là lớp nền để viết test tự động trong Odoo. Mỗi test chạy trong một giao dịch riêng rồi tự cuộn ngược, nên không bao giờ bẩn CSDL. Cấu trúc setUp, self.env, cách chạy --test-enable — với 4 test chạy thật.
18/09/2026
· 4 phút đọc
191
Test của bạn chạy khi mới cài xong 94/175 module, hay đợi đủ 175? Một dòng @tagged quyết định điều đó — và cả việc nó xanh hay đỏ
at_install chạy ngay giữa lúc cài, post_install đợi mọi module xong. Chọn sai là test đỏ ngẫu nhiên. Cú pháp @tagged, dấu trừ để tắt tag, lọc bằng --test-tags — chứng minh bằng log thật cho thấy đúng thứ tự chạy.
18/09/2026
· 4 phút đọc
192
Một test không có dòng assert nào vẫn 'xanh' — và đó là loại test tệ nhất: viết dữ liệu và khẳng định cho đúng
Dựng dữ liệu nền gọn bằng setUpClass, so nhiều field nhiều bản ghi bằng assertRecordValues, lặp case bằng subTest, và những bẫy xanh giả khiến test vô dụng. Kèm log thật cho thấy assertEqual báo lỗi rõ ra sao.
18/09/2026
· 4 phút đọc
193
Một lệnh biến 183 chuỗi trong module thành file .pot để dịch — không sửa một dòng code: cơ chế i18n của Odoo
Odoo tự trích chuỗi từ field, view, nút ra file .pot mẫu; bạn dịch sang .po từng ngôn ngữ. Chuỗi Python thì bọc _(). Xem file .pot thật xuất từ module quản cà phê, với đủ msgid và chú thích vị trí.
18/09/2026
· 4 phút đọc
194
Cùng ra một con số 1750, nhưng một cách bắn 50 câu SQL còn cách kia chỉ 1 — bẫy N+1 đo bằng số query thật
N+1 là bẫy hiệu năng phổ biến nhất: lặp qua N bản ghi, mỗi vòng một truy vấn. Đo query thật cho thấy 50 câu SQL sụt còn 1 khi thay vòng lặp search bằng read_group. Cách nhận diện và né N+1 trong ORM Odoo.
18/09/2026
· 4 phút đọc
195
EXPLAIN ANALYZE nói thật: cùng một câu WHERE, thêm một dòng index=True biến Seq Scan cost 85 thành Index Scan cost 8
Index là B-tree giúp PostgreSQL nhảy thẳng tới dòng cần thay vì quét cả bảng. Odoo tự index gì, khi nào bạn phải thêm index=True, và cái giá của index — với plan EXPLAIN ANALYZE thật trên 3000 bản ghi.
18/09/2026
· 4 phút đọc
196
Bật một dòng workers=2, gõ ps, và một tiến trình Odoo hoá thành năm: vì sao production phải chạy đa tiến trình
Odoo dev chạy 1 tiến trình đa luồng, nhưng GIL của Python khiến nó không dùng nổi nhiều CPU. Chế độ multi-process (prefork) mới tận dụng được. Xem master sinh ra worker HTTP, cron, longpolling thật bằng ps, với đủ limit cấu hình.
18/09/2026
· 4 phút đọc
197
Cùng một trang, hai lần curl: Odoo trần trả 303 không nén, qua nginx thành 200 gzip có TLS — vai trò của reverse proxy
nginx đứng trước Odoo lo TLS, nén gzip, và định tuyến websocket/longpolling sang cổng gevent. Bốn header proxy bắt buộc để Odoo nhìn đúng khách. So header thật của Odoo trần với một trang qua nginx.
18/09/2026
· 4 phút đọc
198
Sao lưu CSDL 10.5MB xong tưởng an toàn — nhưng filestore 65MB nằm ngoài đó, và quên nó là ảnh vỡ hết khi phục hồi
Backup Odoo phải gồm CẢ HAI: CSDL (pg_dump) và filestore chứa attachment. Xem kích thước thật của cả hai trên blog19, cách restore đúng, và khung migration script migrate(cr, version) để đổi schema an toàn.
18/09/2026
· 4 phút đọc
199
Tăng version module lên một số, gõ -u, và log hiện đúng thứ tự: PRE chạy khi còn 4 thẻ, POST chạy sau khi schema đã đổi
pre-migrate cứu dữ liệu trước khi cột đổi, post-migrate điền dữ liệu sau khi schema mới sẵn sàng, end-migrate chạy cuối cùng. Xem thứ tự thật qua log của một lần nâng cấp module quản cà phê.
18/09/2026
· 5 phút đọc
200
Phần 200: biến một module 'chạy được' thành module chuyên nghiệp — chuẩn OCA, và lời tạm biệt sau 200 phần Odoo 19
OCA chuẩn hoá cấu trúc thư mục, manifest, và chất lượng code của module Odoo. Xem cấu trúc thật của module quản cà phê, 13 file py biên dịch sạch, và checklist OCA. Bài cuối cùng khép lại sê-ri 200 phần.
18/09/2026
· 5 phút đọc