Creating Odoo Add-On Modules

Tổng quan

Ở những phần trước, chúng ta đã tìm hiểu cơ bản về hệ thống Odoo như cách thiết lập môi trường phát triển, cài đặt và cập nhật các module có sẵn của Odoo cũng như bên thứ ba. Phần này, chúng ta cùng tìm hiểu sâu hơn về module, module là gì và cách tạo ra một module cơ bản hoàn chỉnh trong Odoo.

Danh sách module trên hệ thống Viindoo

Ảnh 3.1 - Một số module trên hệ thống Viindoo

Các chủ đề sẽ được đề cập ở chương này

Module trong Odoo là gì?

Nếu bạn chưa bao giờ tiếp xúc với thư viện mã nguồn mở nói chung và Odoo nói riêng thì bạn có thể gặp khó khăn, mông lung khi tiếp cận với codebases hàng trăm triệu dòng của chúng. Về cơ bản code trong Odoo chia làm hai phần chính: code nền của thư viện nằm ở phần dưới gồm các hàm, lớp python; phần còn lại hoạt động dựa trên code nền được gói thành từng khối gọi là module. Các module có thể được cài đặt và gỡ bất bất cứ khi nào từ database và thực hiện hai mục đích chính:

  1. Tạo một ứng dụng để thực hiện một nghiệp vụ doanh nghiệp mới mà các module trước đó chưa có.

  2. Tùy chỉnh (bổ sung/sửa đổi) logic nghiệp vụ mà các module trước đó thực hiện.

Ví dụ

Module tạo ứng dụng quản lý giáo dục trên Odoo.

Odoo hiện đang được sử dụng bởi các doanh nghiệp thuộc tất cả các phân khúc khác nhau, mỗi doanh nghiệp đều có quy trình làm việc và yêu cầu riêng đối với phần mềm quản lý. Để đáp ứng điều đó, Odoo tách các tính năng của từng ứng dụng thành các module nhỏ khác nhau và chỉ cài khi cần thiết.

Ví dụ

Viết module quản lý dân tộc để bổ sung tính năng cho module quản lý giáo dục. Module chỉ cài đặt nếu có nhu cầu về quản lý dân tộc.

Hãy xem ảnh chụp một số module được sắp xếp dưới đây, module nằm đầu tiên trên từng cột là ứng dụng chính và những module phía dưới được thiết kế để bổ sung thêm các tính năng cho ứng dụng đó. Để xem được các module được nhóm theo loại ứng dụng như dưới hình, hãy vào menu Ứng dụng và bật nhóm theo Danh mục:

Một số module trên hệ thống Viindoo

Ảnh 3.2 - Danh sách module được nhóm theo Danh mục

Nếu bạn có kế hoạch phát triển một ứng dụng mới trên Odoo, bạn nên định hình các tính năng trên ứng dụng. Điều này rất có ích để chia ứng dụng của bạn thành nhiều module nhỏ. Đó là tất cả những gì bạn cần biết về mục đính của module trong Odoo, giờ chúng ta hãy bắt đầu bắt tay xây dựng thử một module.

Khởi tạo vào cài đặt một module mới

Ở phần này, chúng ta sẽ tạo một module, làm nó có sẵn và cài đặt trên Odoo instance.

Chuẩn bị

Để bắt đầu, chúng ta cần có một Odoo instance hoạt động. Nếu bạn đã làm theo phần Easy installation of Odoo from source trong Installing the odoo development environment, mã nguồn Odoo sẽ nằm tại ~/git/odoo14. Từ giờ trở đi chúng ta sẽ thống nhất sử dụng vị trí trên cho mục đính trình bày phía dưới,dù bạn có thể đặt mã nguồn Odoo ở bất cứ đâu bạn muốn. Chúng ta cũng sẽ cần vị trí để lưu những module sẽ phát triển, ở đây tôi sẽ sử dụng thư mục odoo-dev-14 với đường dẫn đầy đủ sẽ là ~/git/odoo-dev-14.

Các bước thực hiện

Chúng ta cùng bắt tay vào tạo một module nhỏ để quản lý danh sách học sinh, lớp học trong một trường học. Làm theo các bước dưới đây để khởi tạo module mới và cài đặt.

Lưu ý: Tất cả các bước dưới đây thực hiện thông qua câu lệnh Terminal, bạn hoàn toàn có thể thực hiện bằng giao diện hệ điều hành hỗ trợ.

  1. Đổi vị trí đến nơi chúng ta sẽ làm việc ~/git và tạo thư mục odoo-dev-14 chứa các module chúng ta sẽ phát triển. Tiếp tục di chuyển đến thư mục vừa tạo:

    $ cd ~/git
    $ mkdir /odoo-dev-14
    $ cd /odoo-dev-14
    
  2. Chọn tên kỹ thuật cho module mới và tạo thư mục với tên vừa chọn đặt vào trong thư mục odoo-dev-14. Với ví dụ này, chúng ta sẽ chọn tên viin_education:

    $ mkdir viin_education
    

    Tên kỹ thuật của mô-đun phải là một mã định danh Python hợp lệ. Tên phải bắt đầu bằng chữ cái và chỉ được chứa các chữ cái, số và ký tự gạch dưới. Tốt hơn là bạn chỉ nên sử dụng các chữ cái thường trong tên mô-đun.

  3. Làm cho module có thể import bằng cách thêm file __init__.py:

    $ touch viin_education/__init__.py
    
  4. Tiếp tục thêm file manifest vào trong thư mục viin_education để Odoo có thể xác định đó là module. Trước hết, chúng ta sẽ tạo file manifest một cách đơn giản nhất để làm ví dụ.

    $ touch viin_education/__manifest__.py
    

    Sửa file __manifest__.py vừa tạo với nội dung:

    {'name': 'Education Management'}
    
  5. Khởi động Odoo instance, chúng ta sẽ chỉ định đường dẫn thư mục odoo-dev-14 chúng ta mới tạo vào dòng lệnh cấu hình khởi động server, bên cạnh đường dẫn thư mục addon của Odoo gốc. Di chuyển đến vị trí mã nguồn odoo và khởi động server:

    $ cd ~/git/odoo14
    $ ./odoo-bin --addons-path=./addon,~/git/odoo-dev-14
    

    Nếu tùy chọn –save được thêm vào câu lệnh Odoo, các đường dẫn addon-path sẽ được lưu vào file cấu hình. Vào lần khởi động server tiếp theo, nếu bạn không chỉ định addons-path như trên thì addons-path trong file cấu hình sẽ được sử dụng.

  6. Làm cho module có sẵn trong mục Ứng dụng trên Odoo instance. Đăng nhập odoo sử dụng tài khoản admin, bật chế độ Nhà phát triển ở chân trang của menu Thiết lập. Quay lại menu Ứng dụng, chọn Cập nhật danh sách ứng dụng ở đầu trang. Ngay sau khi bạn ấn nút Cập nhật, Odoo sẽ quét lại danh sách module có trong hệ thống thông qua các đường dẫn add-on cấu hình ở bước 5.

  7. Quay về menu Ứng dụng, trên thanh tìm kiếm góc bên phải, xóa bộ lọc Ứng dụng mặc định và tìm kiếm module của chúng ta bằng từ khóa viin_education. Nhấn nút Cài đặt, việc cài đặt module sẽ được tiến hành.

Cơ chế hoạt động

Một module Odoo là một thư mục chứa các file code và các tài nguyên khác (ảnh, dữ liệu). Tên của thư mục chính là tên kỹ thuật của module. Từ khóa name trong file manifest quy định tên nghiệp vụ của module (hiển thị với người dùng ứng dụng). File manifest có nội dung là một Python dictionary để khai báo module metadata gồm các cặp khóa, giá trị khai báo các thông tin của module như tên hiển thị, loại module, phiên bản, những module cha mà module này phụ thuộc (những module cần có để module này hoạt động được), danh sách các file dữ liệu sẽ được nạp,… Ở phía trên chúng ta mới tạo file manifest cho module một cách đơn giản nhất tức chỉ có thông tin về tên module hiển thị. Trong thực tế, chúng ta cần nhiều các khóa khai báo các thông tin quan trọng khác cho module. Việc này sẽ được giới thiệu ở các phần kế tiếp Hoàn thiện module manifest.

Thư mục module có thể import vào trong hệ thống nhờ file __init__.py, do vậy mỗi module đều cần file này kể cả trong trường hợp file rỗng. Để nạp module, Odoo server sẽ import file __init__.py và code trong file sẽ được thực thi, tức file này chính là điểm bắt đầu của mọi code logic trong một module. Vậy nên, nội dung của file __init__.py thường là các câu lệnh import để nạp code từ các file, thư mục con cùng cấp trong module.

Các module có thể được cài đặt trực tiếp từ giao diện dòng lệnh command line bằng cách sử dụng tùy chọn –init hoặc -i. Khi bạn khởi động server bằng câu lệnh dưới đây, module viin_education sẽ được cài đặt ngay trong tiến trình khởi động server.

$ cd ~/git/odoo14
$ ./odoo-bin --addons-path=./addon,~/git/odoo-dev-14

Hoàn thiện module manifest

Manifest là một phần quan trọng của Odoo module. Nó chứa các thông tin quan trọng của một module cũng như chỉ định các file dữ liệu sẽ nạp khi cài module.

Chuẩn bị

Chúng ta cần một module có thể cài đặt (tức cần file __manifest__.py). Bạn cần theo dõi các phần hướng dẫn trước để tạo một module có thể cài đặt.

Các bước thực hiện

Chúng ta sẽ thêm file manifest và hình ảnh hiển thị cho module:

  1. Chúng ta sẽ sửa file __manifest__.py để bổ sung các cặp thông tin quan trọng cho module. Nhìn chung, một file module manifest cơ bản sẽ như sau:

    {
       'name': "Education Management",
       'summary': "Module education management",
       'description': """
       What it does
       ============
       The module provides management education features.
       Key Features
       ============
       * Students management
       * Parents management
       """,
       'author': "Your name",
       'website': "https://viindoo.com",
       'category': 'Uncategorized',
       'version': '0.1.0',
       'depends': ['base'],
       'data': ['views/views.xml'],
       'demo': ['demo.xml'],
    }
    
  2. Để thêm ảnh hiển thị cho module, chọn ảnh một ảnh PNG bạn muốn dùng và chép nó vào thư mục static/description/icon.png trong module.

Cơ chế hoạt động

Nội dung trong file manifest là một Python dictionary tiêu chuẩn, gồm các cặp khóa/giá trị. Ở ví dụ trên chúng ta đã sử dụng một vài cặp khóa/giá trị phổ biến:

  • name: Quy định tên hiển thị của module.

  • sumary: Mô tả gắn gọn module, thường chỉ có một dòng.

  • description: Mô tả tính năng module một cách đầy đủ có hỗ trợ định dạng văn bản ReStructuredText(RST). Mô tả này thường được bọc trong 3 nháy kép giúp định dạng chuỗi đa dòng. Bạn có thể tìm hiểu rõ hơn về RST tại đây.

  • author: Một chuỗi thể hiện tên của tác giả. Nếu có nhiều hơn một tác giả hãy sử dụng dấu ‘,’ giữa các tên, lưu ý sử dụng chuỗi thay vì list.

  • website: Chỉ định URL mà người dùng nên truy cập để biết thêm thông tin về module cũng như tác giả.

  • category: Trường này dùng để tổ chức các module thành các cụm theo chức năng, đặc tính để dễ quản lý và phát triển. Danh sách tên các loại module tiêu chuẩn của Odoo 14 tại đây. Tuy nhiên bạn hoàn toàn có thể khai báo một loại module mới.

  • version: Là chuỗi với định dạng x.x.x quy định phiên bản của module. Trường này được Ứng dụng Odoo sử dụng để phát hiện phiên bản mới đối với những module đã được cài đặt. Nếu chuỗi không bắt đầu bằng phiên bản của Odoo (ví dụ: 13 hoặc 14), thì phiên bản Odoo sẽ tự động được nối vào đầu. Như ví dụ trên, phiên bản hiển thị của module trên Odoo server sẽ là 13.0.1.0.

  • depends: Là một list tên kỹ thuật các module mà module hiện tại phụ thuộc vào. Đừng quên thêm những module khai báo dữ liệu mà module này cần sử dụng để tránh gặp vấn đề khi cài module và gây khó khăn trong việc tìm lỗi. Nếu thực sự module của bạn không phụ thuộc vào bất kỳ module nào khác, bạn nên điền vào ít nhất module ‘base’.

  • data: Là một list chỉ định các đường dẫn tương đối của các file dữ liệu, những file này sẽ được nạp khi cài hoặc nâng cấp module. Data trong module thường là những file định dạng XML hoặc CSV, nhưng đôi khi cũng có thể dùng YAML. Chúng ta sẽ thảo luận kỹ hơn về chúng ở Managing module data.

  • demo: Giống data, demo cũng là list các đường dẫn tương đối đến các file dữ liệu khai báo sẵn trong module, tuy nhiên những dữ liệu này chỉ phục vụ mục đích demo nên sẽ chỉ được nạp nếu database của bạn kích hoạt nạp dữ liệu demo (kích hoạt khi tạo database hoặc trong menu Thiết lập).

Ảnh được sử dụng làm ảnh hiển thị cho module là file PNG có vị trí tại static/description/icon.png.

Odoo mỗi năm sẽ ra mắt một phiên bản mới và có những thay đổi đáng kể giữa các phiên bản, vì vậy module có thể cài đặt và hoạt động ở phiên bản này nhưng chưa chắc đã tương thích ở phiên bản kế tiếp nếu không được nâng cấp logic code và migration. Vì vậy, bạn phải nắm rõ được phiên bản của module trước khi cài chúng.

Mở rộng

Thay vì viết tất cả mô tả module vào file manifest, bạn hoàn toàn có thể tách mô tả sang một file riêng biệt. Kể từ Odoo v8, bạn có thể thêm file README với đuôi mở rộng là .txt, .rst hoặc .md. Ngoài ra, bạn có thể thêm file description/index.html để mô tả module bằng trang HTML.

Sử dụng mô tả bằng HTML sẽ ghi đè mô tả (description) trong module manifest.

Có một vài cặp khóa/giá trị khác bạn cũng hay gặp trong module manifest:

  • license: giá trị mặc định là LGPL-3. Mã này được sử dụng để chỉ định bản quyền cho module. Các mã khác có thể điền như AGPL-3, Odoo Proprietary License v1.0 (thường được sử dụng cho những ứng dụng trả phí), và những Giấy phép được chấp thuận của OSI khác.

  • application: Nếu được đặt là True, module sẽ được đánh dấu là ứng dụng.

  • auto_install: Nếu được đặt là True sẽ chỉ ra rằng module này là module cầu nối và sẽ được tự động cài đặt khi tất cả các module nó phụ thuộc (khóa depends) được cài đặt.

  • installable: Nếu được đặt là True (giá trị mặc định nếu không khai báo khóa này), Odoo sẽ cho phép cài đặt module, ngược lại bạn sẽ không thể cài đặt module trong trong menu Ứng dụng.

  • external_dependencies: Một vài Odoo module sử dụng các thư viện Python/bin. Nếu module của bạn đang sử dụng các thư viện như vậy, bạn cần khai báo chúng vào khóa này. Điều này sẽ ngăn người dùng cài module nếu máy chủ chưa cài đặt các thư viện cần thiết vì sẽ gây ra lỗi trong quá trình module hoạt động.

  • {pre_init, post_init, uninstall}_hook: Đây là những hàm Python hook được gọi khi cài/gỡ module. Để chi tiết hơn bạn có thể đọc tại Advanced server-side development techniques.

Ngoài ra, còn có một số khóa đặc biệt được dùng cho việc đăng bán module trên cửa hàng ứng dụng:

  • price: Khóa này để đặt giá cho module của bạn. Giá trị của khóa nên là số nguyên. Nếu không có khóa này thì module của bạn đươc coi là miễn phí.

  • currency: Quy định tiền tệ cho giá, các loại tiền tệ được hỗ trợ là USD và EUR. Giá trị mặc định của trường này nếu không có là EUR.

  • live_test_url: Dùng khóa này nếu bạn muốn cung cấp một URL dùng thử trực tiếp ngay module này và có thể truy cập thông qua nút Live Preview hiển thị trên cửa hàng ứng dụng.

  • images: Khóa này cung cấp đường dẫn hình ảnh. Ảnh này sẽ được sử dụng làm ảnh nền module trên Chợ ứng dụng Odoo.

Cấu trúc thành phần của một module

Một module gồm các file code và các file dữ liệu khác như XML file hay ảnh. Hầu hết các file này bạn có thể đặt ở bất cứ đâu miễn là trong phạm vi thư mục module.

Tuy nhiên, Odoo sử dụng một vài quy ước khi xây dựng cấu trúc một module và bạn nên tuân theo chúng.

Chuẩn bị

Chúng ta cần chuẩn bị một thư mục module chỉ chứa file __init__.py và __manifest__.py. Ở phần này, chúng ta sẽ sử dụng module viin_education đã tạo ở phần trước.

Các bước thực hiện

Để tạo bộ khung cơ bản cho module, hãy làm theo các bước dưới đây:

Lưu ý: Tất cả các bước dưới đây thực hiện thông qua câu lệnh Terminal, bạn hoàn toàn có thể tạo thư mục, file bằng giao diện hệ điều hành hỗ trợ.

  1. Khởi tạo module mới: Odoo cung cấp một cơ chế giúp nhanh chóng khởi tạo một module mới đó là chạy câu lệnh odoo-bin cùng với scaffold để tạo một module trống

    $ odoo-bin scaffold <ten_module> <duong_dan_chua_module>
    
  2. Chỉnh sửa file __init__.py phía ngoài cùng (cùng cấp các thư mục con model, views…), thêm các dòng lệnh import để nạp các thư mục con:

    $ from . import models
    $ from . import controllers
    $ from . import wizard
    

Sau 2 bước trên, chúng ta sẽ có một bộ khung cấu trúc module tương đối hoàn chỉnh như sau:

viin_education
├── __init__.py
├── __manifest__.py
├── controllers
│     └── __init__.py
├── data
├── demo
├── i18n
├── models
│     └── __init__.py
├── security
├── static
│     ├── description
│     └── src
│         ├─ js
│         ├─ scss
│         ├─ css
│         └ xml
├── report
├── wizard
│     └── __init__.py
└──views
      └── __init__.py

Cơ chế hoạt động

Để hiểu đơn giản, code của module chia làm 3 phần:

  • Phần code Python được nạp bởi file __init__.py, tại đây các file code .py và các thư mục con sẽ được import. Các thư mục con cũng chứa các file code Python, do vậy bản thân chúng cũng cần có file __init__.py để import các file code đó.

  • Các file dữ liệu được khai báo thông qua khóa data và demo trong module manifest để được nạp, các file này thường có định dạng XML và CSV, chúng có những chứng năng như khai báo giao diện người dùng, dữ liệu cố định và dữ liệu demo. Đôi khi các file dữ liệu cũng có dạng đuôi YAML để tạo hoặc cập nhật dữ liệu theo chương trình logic thay vì tĩnh như file XML.

  • Tài nguyên Web như code JavaScript và các thư viện, CSS, SASS, và các mẫu QWeb/HTML. Các file này được sử dụng để xây dựng phần giao diện UI và quản lý hành động người dùng khi tương tác với các UI đó. Các file trên được khai báo thông qua file XML kế thừa mở rộng template gốc, chúng thêm các tài nguyên vào giao diện web.

Các file trong module được tổ chức chia thành các thư mục dưới đây:

  • models/ chứa các file code backend, các file này khai báo các model và logic doanh nghiệp của trong model. Mỗi file chỉ nên ứng với một model và tên file trùng với tên model tạo trong nó, ví dụ, file education_student.py sẽ chỉ khai báo model education.student. Điều này sẽ được đề cập sâu hơn trong Application models.

  • views/ chứa các file xml khai báo giao diện người dùng với các bản ghi action, form, list, menu. Giống với model, bạn nên khai báo một file cho một model, các file tạo website template nên đặt tên kết thúc bằng chuỗi _template. Các giao diện backend sẽ được giải thích kỹ hơn ở Backend views, và giao diện website được đề cập ở CMS website development, CMS Website Development.

  • data/ chứa các file dữ liệu cứng khi cài đặt module sẽ có mà không phải tạo thông qua giao diện người dùng, File dữ liệu sẽ được giải thích ở Managing module data.

  • demo/ chứa các file dữ liệu demo dùng trong mục đích kiểm thử, thực hành hay đánh giá module.

  • i18n/ chứa các file dịch có đuôi .pot và .po, chúng sẽ được giải thích kỹ hơn ở Internationalization. Các file này không cần phải khải báo trong file manifest như các file dữ liệu.

  • security/ chứa các file khai báo quyền truy cập model (ir.model.access.csv), và có thể có các file XML khai báo các nhóm người dùng và quyền cho nhóm người dùng ở mức quyền truy cập từng bản ghi. Hãy theo dõi Security access để hiểu rõ hơn.

  • controllers/ chứa các file code cho website controller. Chúng sẽ được đề cập chi tiết hơp tại Web server development.

  • static/ là nơi chứa tất cả các tài nguyên web. Không giống các thư mục khác, tên của thư mục này không bắt buộc phải theo quy tắc. Các file nằm trong thư mục này được công khai và có thể truy cập mà không cần người dùng đăng nhập hệ thống. Thư mục này hầu hết sẽ chứa các file như JavaScript, các file style(CSS, SASS) và ảnh. Chúng không cần phải khai báo trong module manifest nhưng cần phải được import vào web template thông qua các file XML. Điều này sẽ được đề cập chi tiết hơn tại CMS website development.

  • wizard/ chứa tất cả các file liên quan đến wizard. Trong Odoo, wizard được sử dụng làm cầu nối, lưu các giá trị tạm thời làm trung gian. Chúng ta sẽ tìm hiểu về wizard trong Advanced server-side development techniques.

  • report/ Odoo cung cấp các tính năng xuất tài liệu PDF như đơn bán, hóa đơn. Thư mục này chứa các file liên quan đến báo cáo PDF. Chúng ta sẽ tìm hiểu rõ hơn về báo cáo PDF tại Automation workflows emails and printing.

Khi thêm một file mới cho module, bạn đừng quên khai báo nó trong file __manifest__.py (đối với các file dữ liệu) hoặc __init__.py(đối với các file code), nếu không các file này sẽ bị bỏ qua và sẽ không được nạp.

Thêm model cho module

Model khai báo cấu trúc dữ liệu sẽ được sử dụng cho ứng dụng doanh nghiệp. Phần này sẽ hướng dẫn bạn cách thêm một model đơn giản cho module.

Ví dụ, chúng ta muốn quản lý học sinh cho từng lớp học. Để làm điều đó, chúng ta cần tạo model để đại diện cho học sinh. Mỗi học sinh sẽ bao gồm thông tin như mã học sinh, tên, lớp

Chuẩn bị

Chúng ta cần một module để thao tác. Nếu bạn đã theo dõi phần đầu của chương này, Tạo và cài đặt module mới, bạn sẽ có một module rỗng gọi là viin_education. Chúng ta sẽ sử dụng nó để thực hành.

Các bước thực hiện

Để thêm một Model mới, chúng ta cần thêm một file Python để cấu trúc nó và sau đó nâng cấp module (hoặc cài đặt nếu nó chưa được cài).

Note: Vị trí thư mục chúng ta thao tác dưới đây nằm ngay trong module (ví dụ, ~/git/odoo-dev-14/viin_education/).

  1. Thêm file Python theo đường dẫn models/education_student.py của module với đoạn code sau:

from odoo import fields, models

class EducationStudent(models.Model):
   _name = 'education.student'
   _description = 'Education Student'

   name = fields.Char(string='Student Name')
      student_code = fields.Char(string='Student Code')
      class_id = fields.Many2one('education.class', string='Class')

2. Thêm file nạp(initialization) theo đường dẫn models/__init__.py, file này nằm cùng cấp với file education_student.py ở bước 1 để nạp file chứa model vừa tạo, thêm đoạn code dưới đây vào file:

  1. Sửa file nạp của module(viin_education/__init__.py) để nạp thư mục models vừa hoàn thiện ở hai bước trên:

4. Nâng cấp module bằng dòng lệnh hoặc từ giao diện Ứng dụng của người dùng. Nếu bạn để ý phần log khi server nâng cấp module, bạn sẽ dòng sau:

odoo.modules.registry: module viin_education: creating or updating database table

Sau khi nâng cấp, model education.student sẽ tồn tại trong hệ thống. Có 2 cách để kiểm tra model của chúng ta đã được thêm vào database.

Đầu tiên, bạn có thể kiểm tra trên giao diện người dùng của Odoo. Kích hoạt chế độ Nhà phát triển và mở menu Thiết lập | Kỹ thuật | Cấu trúc Cơ sở dữ liệu | Mẫu. Tìm kiếm model education.student tại đây.

Cách thứ 2 là kiểm tra trực tiếp trong database PostgreSQL. Bạn có thể tìm kiếm bảng education_student trong database. Ở ví dụ trên, chúng ta đang dùng database test-13.0, bạn hoàn toàn có thể đổi database theo server thực tế mà bạn đang chạy và kiểm tra theo câu lệnh dưới trong terminal:

$ psql test-13.0    // Truy cập database test-13.0
test-13.0# \d education_manager;    // Hoặc đổi sang database education_manager

Cơ chế hoạt động

Ở bước đầu tiên chúng ta đã tạo file Python khai báo model nằm trong thư mục models thuộc module.

Odoo sở hữu Object Relational Mapping(ORM) framework, chúng cung cấp cách thức tương tác ánh xạ với PostgresSQL database. Bằng cách kế thừa class Model của Odoo, chúng ta có thể tạo mới một model (tương đương với một bảng trong cơ sở dữ liệu). Khi một model được khai báo, chúng cũng được thêm vào trung tâm tổng hợp các model. Nó giúp các module khác tương tác dễ hơn với model.

Model có một vài thuộc tính có tiền tố là dấu gạch dưới. Thuộc tính quan trọng nhất là _name, nó cung cấp một tên định danh duy nhất cho model để sử dụng xuyên suốt trong hệ thống. ORM framework sẽ khởi tạo ra bảng trong database từ thuộc tính này. Ở ví dụ trên, chúng ta sử dụng _name = ‘education.student’, Dựa trên thuộc tính này, ORM framework sẽ tạo một bảng mới có tên là education_student. Chú ý rằng ORM framework sẽ tạo tên bảng bằng cách thay thế ký tự . bằng ký tự _ từ giá trị thuộc tính _name.

Các trường của model được khai báo như thuộc tính class. Chúng ta đã khai báo trường name với kiểu Char. Các model khi được khai báo nên có trường này vì mặc định trường này sẽ được sử dụng để hiển thị model trên giao diện khi được tham chiếu bởi các model khác.

Chúng ta cũng đã có ví dụ về trường quan hệ class_id. Trường này được khai báo có quan hệ many-to-one giữa học sinh và lớp học. Một lớp học có rất nhiều học sinh, nhưng một học sinh lại chỉ có thể thuộc về một lớp học.

Thế là đủ để bạn hiểu qua về model, chúng sẽ được nói đến nhiều hơn ở Application models.

Tiếp theo, chúng ta phải làm cho module biết chúng ta mới tạo ra file khai báo model bằng cách sử dụng file __init__.py. Vì file khai báo model ở trong thư mục con models/ nên ta cần import thư mục này vào file __init__ phía ngoài cùng cấp với thư mục, hơn thế nữa ngay trong chính thư mục này cũng cần một file __init__ khác để import các file code model (ở trường hợp của chúng ta chỉ có một file education_student.py).

Thay đổi cấu trúc model đã được tạo trước khi cài module bằng cách cập nhật module. Odoo server sẽ xử lý và ánh xạ các thay đổi vào cấu trúc bảng tương ứng trong database.

Mặc dù ví dụ ở trên không bao hàm code logic doanh nghiệp, tuy nhiên thông thường các class ngoài khai báo cấu trúc model còn cần khai báo các phương thức class mới hoặc kế thừa các phương thức gốc, ví dụ như create() hay write(). Điều này sẽ được nói rõ hơn ở Basic server-side development.

Thêm menu và views cho module

Khi chúng ta tạo được model với cấu trúc dữ liệu ta cần, chúng ta sẽ muốn thể hiện model thông qua giao diện người dùng để họ có thể tương tác với chúng (thêm/sửa/xóa các bản ghi của model). Phần này sẽ dựa trên model Education Student đã tạo ở phần trước, từ đó thêm menu truy cập giao diện người dùng dạng list views, form views để tương tác với model.

Chuẩn bị

Chúng ta sẽ tiếp tục sử dụng module viin_education đã tạo xong model education.student phía trên. Đường dẫn được sử dụng sẽ là vị trí nằm trong module n (ví dụ, ~/git/odoo-dev-14/viin_education/).

Các bước thực hiện

Để thêm views, chúng ta sẽ thêm file XML khai báo chúng trong module. Bạn phải tạo một menu để truy cập và sử dụng được model education.student tạo ở phần trước.

Chú ý rằng bạn cần làm các bước dưới đây theo trình tự vì các bước phía sau sẽ tham chiếu đến ID bản ghi của các bước phía trước:

  1. Tạo file XML để khai báo dữ liệu các bản ghi giao diện người dùng views/education_student_views.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
       <!-- Dữ liệu các bản ghi sẽ nằm ở đây -->
    </odoo>
    
  2. Bổ sung dữ liệu cho file manifest để module biết và nạp file XML chúng ta mới thêm:

    {
       'name': "Viin Education",
       'summary': "Module education management",
       'depends': ['base'],
       'data': ['views/education_student_views.xml'],
    }
    
  3. Trong file education_student_views.xml. Thêm dữ liệu bản ghi action để mở view:

    <record id="education_student_action" model="ir.actions.act_window">
       <field name="name">Students</field>
       <field name="res_model">education.student</field>
       <field name="view_mode">tree,form</field>
    </record>
    
  4. Tiếp tục thêm menu vào file education_student_views.xml để truy cập model thông qua action vừa tạo:

    <menuitem name="Education Manager" id="education_student_menu_root" />
    <menuitem id="education_student_menu" action="education_student_action"
    parent="education_student_menu_root" name="Students" sequence="50" />
    
  5. Thêm form view vào file education_student_views.xml:

    <record id="education_student_view_form" model="ir.ui.view">
       <field name="name">education.student.form</field>
       <field name="model">education.student</field>
       <field name="arch" type="xml">
          <form string="Student Form">
             <group>
                <group>
                   <field name="name" />
                   <field name="student_code" />
                </group>
                <group>
                   <field name="class_id" />
                </group>
             </group>
          </form>
       </field>
    </record>
    
  6. Thêm tree(list) view vào file education_student_views.xml:

    <record id="education_student_view_tree" model="ir.ui.view">
       <field name="name">education.student.tree</field>
       <field name="model">education.student</field>
       <field name="arch" type="xml">
          <tree string="Student Tree">
             <field name="name" />
             <field name="student_code" />
             <field name="class_id" />
          </tree>
       </field>
    </record>
    
  7. Thêm các tùy chọn Search vào file education_student_views.xml:

    <record id="education_student_view_search" model="ir.ui.view">
       <field name="name">education.student.search</field>
       <field name="model">education.student</field>
       <field name="arch" type="xml">
          <search string="Students Search">
             <field name="name" filter_domain="['|', '|', ('name', 'ilike', self), ('student_code', '=', self)/>
             <field name="class_id" />
          </search>
       </field>
    </record>
    

Warning

Thứ tự view, action, menu phải được khai báo có thứ tự trong file XML, vì Odoo sử dụng cơ chế nạp tuần tự, mà menu lại dùng ID của action và action lại dùng dữ liệu view. Do đó file XML nên có cấu trúc như sau:

<odoo>

   <record model="ir.ui.view">

   <record model="ir.actions.act_window" />

   <menuitem />

</odoo>

Khi một model được thêm vào Odoo, người dùng mặc định sẽ không có bất kỳ quyền gì đối với model. Chúng ta cần khai báo access rights cho model mới để có thể truy cập. Ở ví dụ trên, chúng ta chưa khai báo bất kỳ access rights nào, vì vậy người dùng không có bất kỳ quyền nào đối với những model mới tạo. Nếu không có quyền, menu và view mới khai báo ở bước 5,6,7 cũng sẽ bị ẩn đối với người dùng. May mắn thay, chúng ta có cách khác để thử những views này. Bằng cách chuyển sang chế độ superuser, bạn có thể thấy menu, views mà bạn không cần quyền truy cập với model tương ứng.

Truy cập Odoo với quyền superuser

Bằng cách chuyển từ user admin thành superuser, bạn có thể bỏ qua mọi hạn chế về phân quyền, do đó bạn có thể truy cập menu và views mà không cần khai báo phân quyền cho model. Để chuyển từ user admin sang superuser, kích hoạt chế độ Nhà phát triển. Trên menu công cụ nhà phát triển, nhấn tùy chọn Trở thành Superuser như hình dưới đây:

Cách kích hoạt Superuser

Ảnh 3.3 - Cách kích hoạt Superuser

Sau khi trở thành superuser, menu top của bạn sẽ có một background sọc như dưới hình:

Top menu Superuser

Ảnh 3.4 - Top menu Superuser

Nếu bạn thử cập nhật module, bạn sẽ thấy các tùy chọn menu. Nhấn vào menu Students sẽ mở ra list view cho model student như hình dưới:

Hiển thị menu Education sau khi kích hoạt Superuser

Ảnh 3.5 - Hiển thị menu Education sau khi kích hoạt Superuser

Cơ chế hoạt động

Bản chất các views khai báo phía trên đều là bản ghi của những model đặc biệt và cũng đều được lưu vào trong database. Hai bước đầu chúng ta đã tạo file XML và khai báo các bản ghi views, sau đó thêm chúng vào danh sách dữ liệu module trong manifest để cài đặt.

File dữ liệu có thể được đặt ở bất kỳ đâu trong thư mục module, nhưng theo quy ước, chúng ta nên đặt dữ liệu về giao diện người dùng tại thư mục con views/. Thông thường, tên của file view sẽ dựa trên tên model mà chúng hướng tới. Trong trường hợp trên, chúng ta đang tạo giao diện người dùng cho model education.student, vì vậy chúng ta đã tạo file views/education_student.

Bước tiếp theo để khai báo window action để hiển thị giao diện tương tác cho người dùng ở phần chính của trang web. Trường res_model chỉ định model mục tiêu mà action hướng tới, trường name được dùng để hiển thị tiêu đề khi người dùng ấn vào menu mở action. Đây chỉ là những trường cơ bản của model ir.actions.act_window. Một window action còn có các trường khác cung cấp cho chúng ta nhiều tùy chọn để xuất ra view, ví dụ như kiểu view nào sẽ được hiển thị, lọc các bản ghi có sẵn khi vào views hay thiết lập một giá trị mặc định. Tất cả những điều trên sẽ được thảo luạn chi tiết hơn tại Backend views.

Thông thường, dữ liệu bản ghi được khai báo bằng tag <record>, như giải thích phía trên, chúng ta đã tạo một bản ghi cho model ir.actions.act_window. Điều này sẽ tạo ra một window actions.

Tương tự, menu cũng được lưu trong database dưới dạng bản ghi của model ir.ui.view, chúng ta cũng có thể tạo menu bằng tag <record>. Tuy nhiên, có một tag thuận tiện hơn mà Odoo hỗ trợ để khai báo menu là <menuitem>, vì vậy chúng ta đã sử dụng nó ở ví dụ trên.

Dưới đây là những thuộc tính chính của một menu (là các field trong model ir.ui.view):

  • name: Menu sẽ hiển thị bằng chuỗi này.

  • action: Tên định danh của action sẽ được thực thi khi ấn vào menu. Chúng ta sử dung ID của window action tạo ở bước trước đó.

  • sequence: Trường này được sử dụng để chỉ định thứ tự của menu so với các menu khác cùng cấp.

  • parent: Trường này chỉ định menu cha. Ở menu đầu tiên chúng ta khai báo không có menu cha, có nghĩa rằng menu này sẽ được hiển thị ở phía trên cùng của menu (cấp ứng dụng).

  • web_icon: Trường này được sử dụng để hiển thị ảnh cho menu. Ảnh này chỉ hỗ trợ hiển thị ở phiên bản Odoo Enterprise Edition (bản trả phí), tuy nhiên Viindoo cũng có module hỗ trợ chức năng trên.

Nếu chỉ khai báo menu và window actions, Odoo cũng sẽ tự động tạo cho chúng ta các view mặc định cho model. Tuy nhiên, các view này quá đơn giản, vậy nên chúng ta đã khai báo form view và tree view ở hai bước tiếp theo để chắc chắn rằng giao diện sẽ được hiển thị như chúng ta mong muốn.

Cả form view và tree view khai báo phía trên đều là bản ghi của model ir.ui.view, một view sẽ có các thuộc tính chính, hay nói cách khác model ir.ui.view có các trường thường dùng dưới đây:

  • name: Đây là tiêu đề của view. Ở trong mã nguồn của Odoo, bạn sẽ thấy ID bản ghi được lặp lại thường được lặp lại ở trường name, tuy nhiên nếu muốn bạn hoàn toàn có thể sửa trường name dễ hiểu hơn. Nếu trường này bị bỏ qua không khai báo, Odoo sẽ tự động sinh trường này dựa trên tên model và kiểu view. Tuy nhiên một model có thể có nhiều view hay các view kế thừa, do vậy bạn nên khai báo trường này rõ ràng để tiện cho việc theo dõi sau này.

  • model: tên định danh của model được khai báo thông qua thuộc tính _name chúng ta đã tìm hiểu ở phần trước.

  • arch: trường này định nghĩa cấu trúc view, cách hiển thị của view sẽ được định hình thông qua trường này. Đây là trường khác nhau nhất khai khai báo các kiểu view.

Form view được khai báo với thẻ <form> phía trên cùng, các trường trên form được tổ chức thành hai cột nhờ thẻ <group>. Thẻ này để nhóm các trường theo chiều dọc, thẻ <group> đầu khai báo cột to nhất bao trọn form, hai thẻ <group> nằm trong chia form thành hai cột. Các trường trên view sử dụng một widget mặc định theo kiểu dữ liệu của trường, tuy nhiên các widget đặc biệt có thể được sử dụng nhờ thuộc tính widget.

Tree view thì đơn giản hơn, chúng được khai báo với thẻ <tree> trên cùng và chứa các thẻ <field>, mỗi thẻ <field> này sẽ tương ứng với một cột.

Cuối cùng, chúng ta đã thêm Search view để mở rộng các tùy chọn tìm kiếm bản ghi model ở hộp phía trên bên phải tree view. Bên trong thẻ <search> phía trên cùng, chúng ta có thể có các thẻ <field> hay <filter>. Thẻ <field> sẽ thêm các trường cho phép tìm kiếm thông qua chuỗi đầu vào mà người dùng nhập. Thẻ <filter> sẽ cho phép lọc theo điều kiện khi bạn kích hoạt chúng. Điều này sẽ được thảo luận kỹ hơn tại Backend views.

Khai báo quyền truy cập cho model

Khi thêm một cấu trúc model (bảng) mới, bạn cần phải chỉ định ai là người có thể tạo, đọc, sửa hay xóa bản ghi. Khi tạo một ứng dụng mới, bạn cần xác định các nhóm người liên quan đến ứng dụng để cấu hình quyền cho phù hợp. Tất nhiên, nếu người dùng không có quyền access rights đối với model, Odoo sẽ không hiển thị menu và view cho họ. Như đã đề cập ở phần trước, chúng ta đã truy cập menu của model chưa cấu hình quyền bằng cách chuyển user admin thành superuser. Tuy nhiên qua phần hướng dẫn này, bạn có thể truy cập menu, view của model bằng chính user admin.

Phần này sẽ xây dựng dựa trên model Education Student ở phần trước và khai báo các nhóm quyền người dùng để phân quyền, kiểm soát ai sẽ có thể truy cập và sửa đổi thông tin học sinh (model education.student).

Chuẩn bị

Module đã khai báo model education.student, nếu chưa có, bạn có thể đọc các phần trước để xây dựng module. Tiếp theo đây chúng ta sẽ thêm security rules (các luật bảo mật) cho model. Đường dẫn chúng ta sử dụng cho các bước bên dưới là vị trí module (ví dụ, ~/git/odoo-dev-14/viin_education/).

Các bước thực hiện

Chúng ta sẽ thêm các security rules theo nguyên tắc dưới đây:

  • Tất cả mọi người đều có quyền đọc thông tin bản ghi học sinh.

  • Một nhóm người dùng mới gọi là Giáo viên sẽ có quyền tạo, đọc, cập nhật hay xóa bản ghi học sinh.

Chúng ta sẽ tiến hành thực hiện theo hướng dẫn dưới đây:

  1. Tạo file security/groups.xml với nội dung sau:

    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
       <record id="group_teacher" model="res.groups">
          <field name="name">Teacher Group</field>
          <field name="users" eval="[(4, ref('base.user_admin'))]"/>
       </record>
    </odoo>
    
  2. Thêm file security/ir.model.access.csv với nội dung dưới đây:

    id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
    ace_student,education.student user,model_education_student,,1,0,0,0
    ace_student_teacher,education.student teacher,model_education_student,group_teacher,1,1,1,1
    
  3. Thêm cả hai file vừa tạo vào khóa data trong manifest:

    # ...
    'data': [
       'security/groups.xml',
       'security/ir.model.access.csv',
       'views/education_student_views.xml'
       ],
    # ...
    

Những khai báo quyền phía trên sẽ được thêm vào hệ thống khi bạn cập nhật module.

Chú ý: Trong khóa data, bạn phải đặt file groups.xml nằm trước ir.model.access.csv vì file groups.xml khai báo nhóm người dùng mà file ir.model.access.csv sử dụng.

Cơ chế hoạt động

Chúng ta đã tạo hai file dữ liệu mới và thêm chúng vào module manifest để nạp vào database hệ thống thông qua việc cài đặt/cập nhật module:

  • File security/groups.xml khai báo nhóm quyền người dùng mới bằng việc tạo bản ghi cho model res.groups. Chúng ta cũng đã thêm luôn nhóm quyền người dùng mới tạo cho user admin bằng cách sử dụng ID của user, base.user_admin, vì thuộc nhóm quyền Giáo viên cộng thêm việc nhóm này được phân quyền ở file ir.model.access.csv, user admin đã có toàn quyền với model education.student.

  • File ir.model.access.csv phân quyền model cho từng nhóm quyền. Dòng phân quyền đầu tiên giá trị cột group_id:id được bỏ trống, điều đó có nghĩa dòng phân quyền này áp dụng cho tất cả người dùng, tuy nhiên dòng này chỉ cho phép đọc mà không được tạo, sửa, xóa. Dòng tiếp theo chỉ định nhóm người dùng vừa mới tạo group_teacher và cấp mọi quyền, do vậy người dùng nhóm quyền Giáo viên có mọi quyền với model.

Xem thêm

Chúng ta có nguyên một chương nói về phân quyền trong Odoo, chi tiết hơn bạn có thể đọc tại đây Security access.

Sử dụng câu lệnh scaffold để tạo cấu trúc module

Khi tạo mới một module trong Odoo, có một số công đoạn phải lặp lại giống nhau với mỗi module. Để giúp việc tạo module nhanh chóng hơn, Odoo cung cấp câu lệnh scaffold.

Phần này sẽ hướng dẫn bạn cách tạo module mới bằng câu lệnh scaffold, nó sẽ giúp bạn tạo cấu trúc file, thư mục của một module rỗng có đầy đủ các thành phần.

Chuẩn bị

Chúng ta sẽ tạo một module mớ, do đó bạn cần mã nguồn Odoo và môt thư mục chứa các module tự tạo. Giả sử chúng ta có mã nguồn Odoo tại ~/git/odoo14 và những module mới tạo sẽ đặt tại thư mục ~/git/odoo-dev-14.

Các bước thực hiện

Chúng ta sẽ sử dụng câu lệnh scaffold để tạo khung mẫu code. Thực hiện các bước dưới đây để tạo mới module dùng câu lệnh scaffold:

  1. Thay đổi thư mục làm việc đến vị trí bạn muốn tạo module. Bạn có thể chọn bất kỳ thư mục nào khác nhưng chúng phải được khai báo trong khóa add-on path khi khởi chạy server. Ở đây để đơn giản tôi sẽ chọn vị trí sử dụng ở các bài viết trước:

    $ cd ~/git/odoo-dev-14
    
  2. Chọn tên kỹ thuật cho module, sau đó sử dụng câu lệnh scaffold để tạo chúng. Ví dụ chúng ta sẽ sử dụng tên education_module:

    $ ~/git/odoo14/odoo-bin scaffold education_module
    
  3. Sửa file __manifest__.py mới được sinh từ câu lệnh scaffold sao cho phù hợp module bạn chuẩn bị tạo.

Dưới đây là cấu trúc thư mục được tạo từ câu lệnh:

$ tree education_module
education_module/
├── __init__.py
├── __manifest__.py
├── controllers
│ ├── __init__.py
│ └── controllers.py
├── demo
│ └── demo.xml
├── models
│ ├── __init__.py
│ └── models.py
├── security
│ └── ir.model.access.csv
└── views
├── templates.xml
└── views.xml

5 directories, 10 files

Bạn sẽ cần chỉnh sửa các file, thư mục mới sinh sao cho đáp ứng nhu cầu module bạn sẽ phát triển.

Cơ chế hoạt động

Câu lệnh scaffold tạo khung cấu trúc module dựa trên template.

Mặc định, module mới được tạo tại thư mục làm việc bạn đang đứng, tuy nhiên chúng ta có thể chỉ định vị trí sẽ tạo module băng cách thêm tham số bổ sung.

Xem xét câu lệnh dưới đây:

$ ~/git/odoo14/odoo-bin scaffold education_module ~/git/odoo-dev-14

Template mặc định sẽ nằm tại vị trí ~/git/odoo14/cli/templates/. Bạn có thể tạo các template khác tương tự và chỉ định chúng bằng tham số -t như câu lệnh dưới đây:

$ ~/git/odoo14/odoo-bin scaffold -t path/to/template education_module