Từ đầu loạt website tới giờ ta luôn đưa dữ liệu ra cho khách xem. Chiều ngược lại — cho khách gửi dữ liệu vào — mới là chỗ dễ vấp: một form công khai đặt trên Internet phải chống giả mạo request, phải cho phép một "Public user" không có quyền tạo bản ghi thực sự tạo được bản ghi, phải kiểm dữ liệu và chặn bot. Bài này dựng thật một form "Đăng ký thẻ thành viên", điền và gửi ngay trên Odoo 19, xác nhận bản ghi được tạo — và chỉ ra những chỗ chết người nếu làm sai.
Hai route: GET để hiện form, POST để nhận
Một form web luôn cần một cặp route: một GET render trang chứa <form>, một POST nhận dữ liệu khi khách bấm gửi.

Hình 1: Cặp route và form. GET (form) render template; POST (submit, methods=['POST']) nhận **post, kiểm tra, rồi create bản ghi. Trong template, dòng csrf_token là bắt buộc — mục sau giải thích. Sau khi tạo xong, redirect sang trang cảm ơn (mẫu PRG: Post-Redirect-Get, tránh gửi lại khi bấm F5).
Điền và gửi thật
Mở form, điền "Trần Thị Mai" và số điện thoại, bấm Đăng ký:

Hình 2: Form thật tại /quan_ca_phe/dang_ky — hai ô nhập và nút "Đăng ký", nằm trong khung website (t-call="website.layout").
Bấm gửi, server nhận POST, tạo bản ghi, rồi chuyển sang trang cảm ơn:

Hình 3: Sau khi POST thành công (server trả 303 redirect), khách thấy trang "Đăng ký thành công!". Kiểm tra CSDL: một bản ghi quan.the.thanh.vien mới đúng như đã nhập — name = "Trần Thị Mai", ghi_chu_noi_bo = "SĐT: 0912345678 (đăng ký online)", hạng "bac". Dữ liệu khách gửi đã vào hệ thống thật.
csrf_token: thiếu là 400 ngay
Đây là chỗ vấp phổ biến nhất. Với route type='http', Odoo mặc định bật bảo vệ CSRF: mọi request thay đổi (POST) phải kèm một csrf_token hợp lệ, nếu không server trả 400 "Session expired (invalid CSRF token)". Nhìn lõi http.py là rõ cơ chế:
# Trong HttpDispatcher: kiểm CSRF cho method không an toàn
if method not in SAFE_HTTP_METHODS and endpoint.routing.get('csrf', True):
token = request.params.pop('csrf_token', None)
if not request.validate_csrf(token):
raise werkzeug.exceptions.BadRequest('Session expired (invalid CSRF token)')
Token do request.csrf_token() sinh: một HMAC-SHA1 của session id + hạn dùng, ký bằng database.secret; validate_csrf so lại bằng consteq (so sánh thời-gian-hằng chống dò). Vì token gắn với session, form phải render token trong cùng phiên mà khách sẽ gửi — nên luôn đặt <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> trong form. (Khi thật cần một endpoint không session, ví dụ webhook máy ngoài, mới đặt csrf=False — và tự lo chống giả mạo cách khác.)
sudo(): Public user không có quyền create
Điểm chết người thứ hai: route auth='public' chạy bằng "Public user" — user này gần như không có quyền gì, chắc chắn không được create trên model của bạn. Gọi thẳng request.env['quan.the.thanh.vien'].create(...) sẽ ném AccessError. Vì vậy phải sudo():
request.env['quan.the.thanh.vien'].sudo().create({...})
Nhưng sudo() là con dao hai lưỡi: nó bỏ mọi kiểm quyền, nên bạn tự chịu trách nhiệm. Nguyên tắc: sudo() chỉ cho đúng thao tác cần (tạo một bản ghi từ dữ liệu đã kiểm), kiểm dữ liệu trước khi tạo, và không trả lại cho khách bất cứ dữ liệu nhạy cảm nào mà sudo() vô tình mở ra.
Validate và chống bot
Form công khai là mồi ngon cho bot. Hai lớp phòng thủ tối thiểu:
- Kiểm dữ liệu trước khi tạo. Thiếu họ tên hay số điện thoại thì
redirectlại form kèm cờ lỗi, không tạo bản ghi rác. Đừng tin đầu vào — kể cả khi HTML córequired, kẻ gửi POST thẳng bỏ qua được. - Honeypot. Thêm một ô ẩn (
display:none) tên vô hại; người thật không thấy nên để rỗng, bot điền tự động vào mọi ô. Nếu ô đó có giá trị, coi như bot và bỏ qua lặng lẽ. Rẻ và hiệu quả với bot đơn giản. (Lưu ý: đừng đặt tên ô ẩn làwebsite— Odoo dùng tên đó cho routing, sẽ nhiễu; tôi đặtemail_phu.)
Với form nhiều hơn, có thể thêm giới hạn tần suất theo IP hoặc captcha — nhưng honeypot + validate là mức nền cho mọi form.
Tự viết hay dùng Website Form builder
Odoo còn có sẵn snippet "Form" trong trình dựng web: kéo thả để tạo form ghi vào một model, không cần code. Vậy khi nào tự viết controller?
- Dùng builder khi form đơn giản (liên hệ, đăng ký nhận tin) và muốn người quản trị tự sửa field bằng chuột.
- Tự viết controller khi cần logic: kiểm tra phức tạp, tạo nhiều bản ghi liên quan, gọi API ngoài, xử lý sau khi lưu (gửi email, tạo mã), hay luồng nhiều bước. Controller cho bạn toàn quyền.
Ba ý mang về
- Form web cần cặp route GET (render form) + POST (
methods=['POST'], tạo bản ghi), kết thúc bằngredirectsang trang cảm ơn (PRG chống gửi lại khi F5). csrf_tokenlà bắt buộc với POSTtype='http'—<input name="csrf_token" t-att-value="request.csrf_token()"/>; thiếu là 400. Vàsudo()để Public user tạo được bản ghi, nhưng chỉ dùng có kiểm soát.- Luôn validate + chống bot: kiểm dữ liệu server-side (đừng tin
required), thêm honeypot (đừng đặt tên ô ẩn làwebsite); form phức tạp thì tự viết controller, form đơn giản dùng snippet Form của builder.
Form của ta mới nhận chữ. Nhiều lúc khách cần gửi cả tệp — ảnh CMND, file đính kèm. Phần sau làm controller nhận file upload — đọc file từ request, kiểm loại và kích thước, và lưu an toàn thành attachment trong Odoo.