Loạt bài qua ta chủ yếu thêm thứ mới. Nhưng nhu cầu hay gặp hơn cả là sửa thứ đã có: bạn muốn kanban của Odoo thêm một hành vi, muốn form controller làm thêm một việc khi lưu, muốn một widget lõi cư xử khác đi. Bạn không thể sửa file lõi (addons chỉ đọc, và sửa là mất khi nâng cấp), cũng không nên chép cả file ra rồi đăng ký đè (chép 500 dòng chỉ để đổi 3 dòng, rồi phải theo dõi mọi bản Odoo). Odoo cho một công cụ đúng cho việc này: patch.

patch làm gì

patch(objToPatch, extension) chèn các method trong extension vào objToPatch ngay tại chỗ, giữ nguyên mọi thứ khác. Vá Class.prototype là vá mọi instance của class đó — cả những cái do lõi hay module khác tạo ra. Trong method vá, super trỏ tới bản gốc, nên bạn gọi lại được logic cũ rồi thêm phần của mình:

Ảnh chụp đoạn mã nền tối file patch_kanban js, import patch từ web core utils patch và KanbanRenderer từ web views kanban kanban_renderer, chú thích patch nhận đối tượng cần vá và phần mở rộng, gọi patch trên KanbanRenderer chấm prototype với một object chứa method setup, trong setup gọi super chấm setup với spread arguments trước kèm chú thích gọi bản gốc trước đừng bỏ, rồi lấy số bản ghi từ this props list records length, và console log một dòng bắt đầu bằng PATCH KanbanRenderer setup chạy kèm số bản ghi, cuối cùng chú thích áp cho mọi kanban trong web client kể cả của module khác

Hình 1: Vá method setup của KanbanRenderer lõi. super.setup(...arguments) gọi bản gốc trước — bỏ dòng này là kanban mất toàn bộ khởi tạo và vỡ. Sau đó thêm phần của ta (đọc số bản ghi, log). patch nhận KanbanRenderer.prototype, nên bản vá áp cho mọi kanban trong hệ thống, không riêng module này.

Bằng chứng patch thật sự chạy

Khai file vào bundle, nâng cấp, rồi mở kanban thẻ thành viên (một view kanban có sẵn, ta không đụng tới arch của nó). Bắt console trình duyệt:

Ảnh chụp console trình duyệt nền tối khi mở kanban thẻ, hiển thị một dòng bắt đầu bằng PATCH KanbanRenderer setup chạy qua bản vá quan_ca_phe kèm 4 bản ghi, cùng các chú thích rằng setup của KanbanRenderer lõi đã chạy qua bản vá, 4 bản ghi là số thẻ đang hiển thị đọc từ this props list, và rằng ta không chép kanban_renderer js chỉ thêm một hàm setup mới còn super setup vẫn chạy toàn bộ logic gốc

Hình 2: Console thật khi mở kanban (bắt bằng Playwright). Dòng [PATCH] ... 4 bản ghi chứng minh: setup() của KanbanRenderer lõi đã chạy qua bản vá, và đọc được this.props.list (4 thẻ đang hiển thị). Ta không chép kanban_renderer.js — chỉ thêm một setup mới, super.setup() vẫn thực thi toàn bộ khởi tạo gốc.

Điểm mấu chốt: view kanban đó là của Odoo/của model, không phải thứ ta viết. Vậy mà setup của nó chạy thêm code của ta — vì patch vá vào chính prototype dùng chung.

Ba quy tắc khi patch

  • Luôn gọi super trong method vá (trừ khi cố ý thay hẳn, hiếm). Quên super.setup() là bạn thay thế bản gốc chứ không phải bổ sung, và component vỡ theo cách khó lần. ...arguments chuyển nguyên tham số xuống.
  • Patch prototype = áp cho mọi instance. Đây vừa là sức mạnh (đổi hành vi toàn hệ thống trong một chỗ) vừa là rủi ro (một lỗi trong bản vá làm hỏng mọi kanban, không riêng của bạn). Vá thứ dùng chung thì phải cực kỳ cẩn thận và phòng thủ (dùng ?., ?? như trong ví dụ).
  • Patch chỉ để đổi hành vi thứ đã có. Cần một thành phần mới thì đăng ký vào registry (client action, field widget, view...) như các bài trước — đừng patch để "thêm". Patch là dao mổ cho việc sửa, không phải búa để đóng mọi đinh.

patch vs kế thừa vs registry

Ba cách mở rộng, ba việc khác nhau:

  • Registry (category(...).add): thêm một thành phần mới (view, field, action, service).
  • Kế thừa + spread ({ ...kanbanView, Renderer }): tạo một biến thể dùng qua js_class, không đụng bản gốc — chỉ nơi nào khai js_class mới nhận.
  • patch: sửa tại chỗ bản gốc, áp cho tất cả ngay lập tức, kể cả code không biết tới bạn.

Chọn patch khi bạn thật sự muốn đổi hành vi mặc định ở khắp nơi; chọn kế thừa khi chỉ muốn một biến thể cục bộ.

Ba ý mang về

  1. patch(objToPatch, extension) sửa một class/component có sẵn ngay tại chỗ — không chép file, không mất khi nâng cấp. Vá Class.prototype áp cho mọi instance, kể cả của lõi và module khác.
  2. Trong method vá, super gọi bản gốc. Gần như luôn phải super.method(...arguments) để giữ hành vi cũ rồi mới thêm phần của mình; quên là component vỡ.
  3. Patch để sửa, registry để thêm, kế thừa js_class để tạo biến thể cục bộ. Vì patch áp toàn hệ thống, một lỗi trong bản vá làm hỏng mọi chỗ — vá phòng thủ và chỉ khi thật cần.

Ta đã dùng notification.add nhiều lần mà chưa mổ kỹ. Phần sau đào vào notification service: các loại thông báo (thành công, cảnh báo, lỗi), thông báo dính (sticky), nút hành động trong thông báo, và khi nào nên dùng nó thay cho dialog.