Buidling a Odoo Module¶
Cảnh báo
Bạn nên sử dụng tài liệu này sau khi đã cài đặt thành công Odoo.
Khởi động/Dừng máy chủ Odoo¶
Odoo sử dụng kiến trúc Client/Server, trong đó Client là các trình duyệt web truy cập tới Odoo Server thông qua RPC (Tìm hiểu thêm về RPC).
Các phần mở rộng và logic nghiệp vụ thường được thực hiện ở phía Server mặc dù một số tính năng cũng có thể được thực hiện ở phía Client (ví dụ như việc trình diễn dữ liệu mới như là bản đồ tương tác).
Để khởi động máy chủ, chúng ta cần thực hiện lệnh odoo-bin
trong màn hình shell, odoo-bin
là một file thực thi, bạn phải thêm đầy đủ đường dẫn tới nó nếu cần:
odoo-bin
Dừng máy chủ bằng cách nhấn Ctrl-C hai lần hoặc bằng cách xóa tiến trình tương ứng trên hệ điều hành.
Xây dựng một Odoo Module¶
Các phần mở rộng của cả server và client được đóng gói dưới dạng các modules
được nạp sẵn trong cơ sở dữ liệu.
Các modules được tạo nhằm mục đích thay đổi hoặc bổ sung logic nghiệp vụ hiện có:
Ví dụ: Một module được tạo ra để thêm các quy tắc kế toán của Việt Nam vào hỗ trợ kế toán chung của Odoo, tương tự chúng ta cũng có thể tạo ra một module khác để tích hợp thanh toán với các ngân hàng nội địa tại Việt Nam vào hệ thống Odoo.
Mọi thứ trong Odoo đều được bắt đầu và kết thúc bằng các module.
Các thành phần của một module¶
- Business Objects
Được khai báo là các lớp Python, các tài nguyên này được Odoo tự động duy trì dựa trên cấu hình của chúng.
- Object views
Định nghĩa các đối tượng giao diện người dùng.
- Data files
Các file XML hoặc CSV với các chức năng:
Định nghĩa các view hiển thị hoặc báo cáo.
Cấu hình dữ liệu, cấu hình các quy tắc bảo mật.
v.v,...
- Web controllers
Xử lý các yêu cầu từ trình duyệt.
- Static web data
Các file Images, CSS or Javascript được sử dụng trên giao diện web hoặc trang web.
Cấu trúc của một Module¶
Mỗi module là một thư mục nằm trong thư mục (addons) chứa nhiều module. Các module được chỉ định bằng cách sử dụng tùy chọn --addons-path
.
Một module được tổ chức với các thư mục như sau:
controllers: chứa các controllers (http routes).
data: data xml.
demo: demo xml.
i18n: chứa các file dịch.
security: chứa các config phân quyền.
models: model definitions.
reports: chứa các model báo cáo (BI/analysis), webkit report templates.
static: bao gồm web assets, thư mục css/, js/, img/,.
templates: chứa các web templates.
views: bao gồm các views và các mẫu in QWeb report.
wizards: wizard model và views.
manifest: bao gồm các thông tin cấu hình module.
Để tạo nhanh một module với đầy đủ các thư mục cần thiết chúng ta có thể sử dụng lệnh scaffold
được Odoo cung cấp.
$ odoo-bin scaffold <module name> <where to put it>
Quan hệ giữa các đối tượng¶
Một thành phần quan trọng của Odoo là lớp ORM. Lớp này được Odoo cung cấp, và có đầy đủ các thao tác với cơ sở dữ liệu.
Chúng ta nên sử dụng ORM và hạn chế sử dụng query khi truy cập vào cơ sở dữ liệu.
Các đối tượng nghiệp vụ được khai báo dưới dạng các lớp Python kế thừa từ lớp Model
do Odoo định nghĩa.
Ví dụ về một class trong Odoo.
from odoo import models
class EducationClass(models.Model):
_name = 'education.class'
_description = 'Education Class'
_name
là một trong các thuộc tính cơ bản khi khai báo một lớp đối tượng trong Odoo. Đây là thuộc tính bắt buộc phải có
khi khai báo một class.
_description
là một thuộc tính cơ bản, thuộc tính này có chức năng mô tả cho model. Đây là thuộc tính không bắt buộc phải khai báo.
Ví dụ trên sẽ tạo ra một bảng có tên là education_class
trong cơ sở dữ liệu.
Các trường của Model¶
from odoo import models, fields
class EducationClass(models.Model):
_name = 'education.class'
_description = 'Education Class'
name = fields.Char(string='Name', required=True)
Các trường được định nghĩa là các thuộc tính của model class, được sử dụng để xác định những gì được lưu trữ và lưu trữ ở đâu trong cơ sở dữ liệu.
Các thuộc tính chung
Để khai báo các thuộc tính cho các trường của model chúng ta có thể làm như sau:
name = field.Char(required=True)
Các attributes cơ bản của một trường trong Odoo:
string
(unicode
, default: field’s name)Nhãn của trường.
required
(bool
, default:False
)Nếu
required=True
thì bắt buộc người dùng phải nhập dữ liệu vào trường này.help
(unicode
, default:''
)Giải thích, hướng dẫn cho trường dữ liệu này.
index
(bool
, default:False
)Thiết lập chế độ indexing trên một trường / cột (column) trong cơ sở dữ liệu. (Chỉ áp dụng với các trường được lưu trữ trong cơ sở dữ liệu).
Các trường cơ bản
Một số trường cơ bản trong Odoo như: Boolean
, Char
, Integer
, Float
.
Các trường dành riêng của Odoo
Odoo tự động tạo ra một số trường khi khai báo model, nó có thể được sử dụng khi cần thiết:
id
(Id
)Giá trị nhận dạng duy nhất của một bản ghi trong model.
create_date
(Datetime
)Ngày tạo của bản ghi.
create_uid
(Many2one
)Người tạo ra bản ghi.
write_date
(Datetime
)Ngày sửa bản ghi gần nhất.
write_uid
(Many2one
)Người sửa bản ghi gần nhất.
Các tệp dữ liệu¶
Mẹo
Một số module được tạo ra chỉ để thêm dữ liệu vào Odoo.
Dữ liệu của module được khai báo thông qua tệp dữ liệu (data files). Mỗĩ thẻ <record>
sẽ tạo hoặc cập nhật một bản ghi trong cơ sở dữ liệu.
<record id="education_class_view_tree" model="ir.ui.view">
<field name="name">education.class.tree</field>
<field name="model">education.class</field>
<field name="arch" type="xml">
<tree string="Class Tree">
<field name="name"/>
</tree>
</field>
</record>
model
là tên model của bản ghi.id
là một id định danh cho bản ghi, nó cho phép tham chiếu đến bản ghi.field
là tên của field trong model.Tệp dữ liệu (data files) thường được lưu trữ trong thư mục
data
và phải được khai báo trongmanifest
để tải dữ liệu.Các tệp dữ liệu lưu trữ trong thư mục
demo
và chỉ được tải dữ liệu trong chế độdemonstration
.
Mẹo
Nội dung của tệp dữ liệu chỉ được nạp khi mudule được cài đặt hoặc cập nhật.
Actions và Menus
Các actions và menu là các bản ghi thông thường của model ir.actions.act_window
trong cơ sở dữ liệu, thường được khai báo thông qua các tệp dữ liệu.
Các actions có thế được kích hoạt theo ba cách sau:
Bằng cách nhấn vào các mục menu.
Bằng cách nhấn vào các nút trong giao diện (nếu chúng được liên kết với các actions).
Dưới dạng các hành động theo ngữ cảnh trên đối tượng.
<record id="education_class_action" model="ir.actions.act_window">
<field name="name">Class</field>
<field name="res_model">education.class</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="education_class_menu" action="education_class_action" parent="education_configuration_menu_root" sequence="10"/>
Nguy hiểm
Trong Odoo tệp dữ liệu XML được thực thi tuần tự nên chúng ta phải khai báo action trước rồi mới khai báo menuitem tương ứng của nó.
View cơ bản¶
Các views xác định cách các bản ghi của một model được hiển thị.
Mỗi một loại view đại diện cho một giao diện trực quan riêng (danh sách bản ghi, biểu đồ tổng hợp).
Đối với các yêu cầu chung, giao diện với kiểu chính xác nhất và giá trị priority
thấp nhất sẽ được sử dụng (do đó giao diện có priority
thấp nhất của mỗi loại sẽ là giao diện mặc định cho loại đó).
Kế thừa views cho phép thay đổi các giao diện được khai báo ở những nơi khác (có thể thêm hoặc bớt nội dung trên các view đó).
Khai báo Generic view
Khai báo giao diện chung.
Mỗi view được khai báo như một bản ghi của model ir.ui.view
và nó được định nghĩa trong phần thân của field arch
.
<record model="ir.ui.view" id="view_id">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<!-- view content: <form>, <tree>, <graph>, ... -->
</field>
</record>
Nguy hiểm
Nội dung của view được viết bằng XML. Do đó chúng ta phải khai báo field arch
với type="xml"
.
Tree views
Tree views được gọi là giao diện dạng danh sách, hiển thị tất cả các bản ghi dưới dạng bảng (mỗi dòng tương ứng với một bản ghi, mỗi cột tương ứng là một trường).
Để khai báo tree views chúng ta bắt đầu với thẻ <tree>
.
<tree string="Class Tree">
<field name="name"/>
<field name="class_group_id"/>
<field name="next_class_id"/>
<field name="school_id"/>
</tree>
Form views
Form views được sử dụng để tạo và chỉnh sửa từng bản ghi đơn lẻ. Form view bao gồm các thẻ cấu trúc cấp cao (groups, notebooks) và các phần tử tương tác (các nút và trường).
Để khai báo form views chúng ta bắt đầu với thẻ <Form>
.
<form string="Class Form">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}" />
<field name="active" invisible="1" />
<group>
<group>
<field name="name" />
<field name="school_id" />
<field name="class_group_id" />
</group>
<group>
<field name="next_class_id" />
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
</sheet>
</form>
Form views cũng có thể sử dụng HTML thuần túy để có bố cục linh hoạt hơn:
<form string="Student Form">
<header>
<field name="state" widget="statusbar" />
</header>
<sheet>
<field name="image_1920" widget="image" class="oe_avatar" options='{"preview_image": "image_128"}' />
<div class="oe_title">
<h1>
<field name="name" placeholder="Full Name" required="True" />
</h1>
</div>
<group>
<group>
<field name="student_code" />
<field name="phone" widget="phone" />
<field name="mobile" widget="phone" />
<field name="email" widget="email" context="{'gravatar_image': True}" />
<field name="dob" />
<field name="gender" />
<field name="class_group_id" />
<field name="class_id" />
<field name="responsible_id" />
</group>
</group>
<notebook>
<page name="general" string="General Information">
<group>
<group>
<field name="student_level_id" />
</group>
<group>
<field name="school_id" />
<field name="company_id" groups="base.group_multi_company" />
</group>
</group>
</page>
<page name="documents" string="Attached Documents">
<group>
<field name="attachment_ids" widget="many2many_binary" />
</group>
</page>
</notebook>
</sheet>
</form>
Search views
Giao diện tìm kiếm tùy chỉnh trường tìm kiếm được liên kết với giao diện danh sách (và các giao diện tổng hợp khác).
Để khai báo Search views chúng ta bắt đầu với thẻ search
, search views giúp chúng ta xác định các trường nào có thể được tìm kiếm.
<search string="Class Search">
<field name="name" />
<field name="school_id" />
<field name="class_group_id" />
</search>
Nếu không khai báo giao diện tìm kiếm nào cho model thì Odoo sẽ tự động tạo ra một giao diện chỉ cho phép tìm kiếm các bản ghi với trường name
.
Mối quan hệ giữa các model¶
Một bản ghi từ model này có thể liên kết đến một bản ghi ở model khác.
Ví dụ: Model lớp học với model học sinh thì giữa chúng có mối quan hệ là một lớp học có nhiều học sinh.
Các trường quan hệ¶
Các trường quan hệ liên kết các bản ghi trên cùng một model (cấu trúc phân cấp) hoặc giữa các model khác nhau. Các loại trường quan hệ là:
Quan hệ tới một đối tượng khác (khóa ngoại).
Many2one(other_model, ondelete='set null')
other_model: tên (_name) của đối tượng liên kết (bắt buộc phải có).
ondelete: cho hệ thống biết sẽ làm gì khi xóa dữ liệu trong bảng dữ liệu của đối tượng mà trường này liên kết tới.
ondelete có thể nhận các giá trị :
cascade
,restrict
,set null
.
from odoo import fields, models
class EducationClass(models.Model):
_name = 'education.class'
_description = 'Education Class'
class_group_id = fields.Many2one('education.class.group', string='Class Group', ondelete='restrict')
next_class_id = fields.Many2one('education.class', string='Next Class')
Quan hệ một nhiều tới đối tượng khác (ngược lại với many2one).
One2many(other_model, related_field)
other_model: tên (_name) của đối tượng liên kết (bắt buộc phải có).
related_field: tên trường kiểu many2one liên kết đến đối tượng chứa trường đang khai báo này và bắt buộc phải có.
from odoo import fields, models
class EductionClassGroup(models.Model):
_name = 'education.class.group'
_description = 'Education Class Group'
_order = 'sequence'
class_ids = fields.One2many('education.class', 'class_group_id', string='Class of Groups')
Quan hệ nhiều nhiều giữa các đối tượng.
Many2many(other_model)
other_model: tên (_name) của đối tượng liên kết (bắt buộc phải có).
Nếu chỉ khai báo như trên thì khi đó odoo sẽ tự động tạo ra bảng dữ liệu quan hệ trung gian giữa 2 đối tượng với tên mặc định.
Đối với trường Many2many
có thể khai báo đầy đủ hơn với cấu trúc.
Many2many(other_model, rel, field1, field2)
rel: tên bảng dữ liệu quan hệ trung gian giữa 2 đối tượng.
field1: tên của cột trong bảng dữ liệu, chứa id của đối tượng hiện tại (bắt buộc phải có).
field2: tên của cột trong bảng dữ liệu, chứa id của đối tượng liên kết (bắt buộc phải có).
attachment_ids = fields.Many2many('ir.attachment', 'education_student_ir_attachment_rel',
'attachment_id', 'student_id', string='Attachments')
Kế thừa¶
Kế thừa Model¶
Odoo cung cấp hai cơ chế kế thừa để mở rộng một model hiện có.
Cơ chế kế thừa thứ nhất (Traditional Inheritance):
Class Inheritance: Kiểu kế thừa này cho phép chúng ta mở rộng thêm các trường mới vào model, ghi đè lại định nghĩa của các trường, chỉnh sửa các phương thức trong model đã có, và tổ chức database lưu trữ chung một bảng với model gốc.
Prototype Inheritance: Kiểu kế thừa này sẽ sao chép các trường, các phương thức trong model đã có và được lưu trữ trên bảng mới trong cơ sở dữ liệu.
Cơ chế kế thừa thứ hai (Delegation Inheritance):
Cho phép đa kế thừa, không kế thừa lại các phương thức. Kiểu kế thừa này sẽ tạo bảng mới khác với bảng của model gốc và model mới có field liên kết đến model gốc
Kế thừa View¶
Thay vì sửa đổi các giao diện hiện có bằng cách ghi đè, Odoo cung cấp tính năng cho phép kế thừa các giao diện hiện có và có thể thêm hoặc xóa nội dung khỏi giao diện gốc của chúng.
Các view kế thừa tham chiếu đến view gốc bằng cách sử dụng trường inherit_id
.
Khi sử dụng kế thừa view, các chỉnh sửa bổ sung sẽ nằm gọn trong module mới mà chúng ta đang phát triển, mà không phải sửa trực tiếp trên module gốc.
Khi gỡ bỏ module mới đồng thời sẽ gỡ bỏ các kế thừa, khi đó view sẽ trở về nguyên trạng.
<record id="education_student_view_form" model="ir.ui.view">
<field name="name">education.student.inherit</field>
<field name="model">education.student</field>
<field name="inherit_id" ref="viin_education.education_student_view_form" />
<field name="arch" type="xml">
<xpath expr="//field[@widget='image']" position="before">
<div class="oe_button_box" name="button_box">
<button name="action_view_opportunity_student" type="object" class="oe_stat_button" icon="fa-inbox">
<div class="o_stat_info">
<field name="opportunity_student_count" class="o_stat_value" />
<span class="o_stat_text">Opportunities</span>
</div>
</button>
</div>
</xpath>
</field>
</record>
XPath được sử dụng để tìm vị trí của bất kỳ phần tử nào trên view. Chúng ta sử dụng thẻ xpath
để thay đổi nội dung trên field gốc.
Tìm hiểu thêm về Xpath
Cú pháp: Xpath=//tagname[@attribute='value']
Biểu thức
xpath
được sử dụng linh hoạt với thuộc tínhposition
sau.
inside
: Thêm nội dung vào cuối phần tử xpath.
replace
: Thay thế nội dung của phần tử xpath, hạn chế dùng trừ trường hợp thật sự cần thiết.
before
: Thêm nội dung vào phía trước phần tử xpath.
after
: Thêm nội dung vào phía sau phần tử xpath.
attributes
: Thay đổi attribute của phần tử xpath bằng attribute trong phần thân của thẻ xpath.
Mẹo
Hai cách kế thừa sau sẽ cho cùng một kết quả:
<xpath expr="//field[@name='class_id']" position="after">
<field name="final_year" />
</xpath>
<field name="class_id" position="after">
<field name="final_year"/>
</field>
Domain¶
Trong Odoo, Search domains (miền tìm kiếm) là các giá trị mã hóa các điều kiện trên bản ghi. Domain là danh sách các tiêu chí được sử dụng để chọn một tập hợp con các bản ghi của model. Mỗi tiêu chí là một bộ ba với tên trường, toán tử và giá trị.
Ví dụ:
[('state', '=', 'confirmed')]
Theo mặc định, nếu các tiêu chí được ngăn cách với nhau bởi dấu phẩy thì chúng được ngầm định hiểu là biểu thức AND
. Các toán tử logic & (AND), | (Hoặc) và ! (KHÔNG) có thể được sử dụng để kết hợp các tiêu chí một cách rõ ràng.
Domain
có thể được thêm vào các trường quan hệ để lấy ra giới hạn các bản ghi hợp lệ cho mối quan hệ khi cố gắng chọn các bản ghi trên giao diện client.
Các trường được tính toán và giá trị mặc định¶
Các trường có thể được tính toán lại (thay vì đọc trực tiếp từ cơ sở dữ liệu) bằng cách sử dụng tham số tính toán. Khi đó, giá trị của trường không được truy xuất từ cơ sở dữ liệu mà được tính toán bằng cách gọi một phương thức compute của model.
Để tạo một trường được tính toán, chúng ta tạo một trường bình thường và đặt thuộc tính compute
của nó thành tên của một phương thức.
Phương thức compute sẽ tính toán lại giá của trường trên mọi bản ghi trong self
.
Nguy hiểm
self
là một tập bản ghi có thứ tự. Nó hỗ trợ các phép toán Python tiêu chuẩn trên collections, ví dụ như len(self)
, iter(self)
và các hoạt động tập hợp bổ sung như recs1 + resc2
.
Khi sử dụng vòng lặp (ví dụ dùng vòng lặp for) để lấy từng bản ghi trên self
, chúng ta có thể truy cập / gán các trường trên các bản ghi đơn lẻ bằng cách sử dụng ký hiệu dấu chấm, như record.name
.
from odoo import fields, models
class EducationClass(models.Model):
_name = 'education.class'
_description = 'Education Class'
name = fields.Char(string='Name', required=True, compute='_compute_name')
def _compute_name(self):
for r in self:
r.name = r.name.upper()
Sự phụ thuộc¶
Giá trị của một trường được tính toán thường phụ thuộc vào giá trị của các trường khác trên bản ghi được tính toán.
Nếu mong muốn trường compute đó được tính toán lại khi các trường khác (các trường được chỉ định) có sự thay đổi giá trị thì nó phải chỉ định các trường đó bằng cách sử dụng depend()
.
Các phần phụ thuộc đã cho được ORM sử dụng để kích hoạt tính toán lại trường bất cứ khi nào một số phần phụ thuộc của nó được sửa đổi:
Hàm compute
sẽ được gọi khi giá trị của các trường depends
có sự thay đổi.
from odoo import models, fields, api
class EducationAdmission(models.Model):
_inherit = 'education.admission'
lead_ids = fields.One2many('crm.lead', 'admission_id', string='Opportunities')
leads_count = fields.Integer(string='Number Of Leads', compute='_compute_leads_count')
@api.depends('lead_ids')
def _compute_leads_count(self):
for r in self:
r.leads_count = len(r.lead_ids)
Các phụ thuộc (dependencies) có thể là các đường dẫn chấm khi sử dụng các trường con:
@api.depends('student_ids.student_id.state')
def _compute_studying_student_count(self):
for r in self:
studying_student_count = 0
if r.student_ids:
students = r.student_ids.filtered(lambda s: s.student_id.state == 'studying')
studying_student_count = len(students)
r.studying_student_count = studying_student_count
Các trường tính toán không được lưu trữ mặc định trong cơ sở dữ liệu, chúng được tính toán và trả về giá trị khi được yêu cầu. Thêm store=True
sẽ lưu trữ chúng trong cơ sở dữ liệu và tự động cho phép tìm kiếm.
Các trường compute thì mặc định là trường readonly
. Để có thể sửa được giá trị của trường đó trên form thì chúng ta sử dụng thuộc tính readonly
và gán giá trị bằng False
.
Giá trị mặc định¶
Trong Odoo bất kì trường nào cũng có thể được cung cấp giá trị mặc định. Khi định nghĩa trường chúng ta thêm tùy chọn default = X
để gán giá trị mặc định, trong đó X
là một giá trị Python (Boolean, Integer, Float, Char) hoặc định nghĩa một hàm lấy một tập bản ghi và trả về một giá trị:
start_date = fields.Date(string='Start Date', default=fields.Date.today)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
Ghi chú
Đối tượng self.env
cấp quyền truy cập vào các tham số yêu cầu và những thứ hữu ích khác:
self.env.cr
hoặcself._cr
là đối tượng con trỏ cơ sở dữ liệu, nó được sử dụng để truy vấn cơ sở dữ liệu.self.env.uid
hoặcself._uid
là id cơ sở dữ liệu của người dùng hiện tại.self.env.user
là hồ sơ của người dùng hiện tại.self.env.context
hoặcself._context
là từ điển ngữ cảnh.self.env.ref(xml_id)
trả về bản ghi tương ứng với một id XML.self.env[model_name]
trả về một instance của model.
Onchange¶
Cơ chế onchange
cung cấp một cách để giao diện người dùng cập nhật dữ liệu trên view form bất cứ khi nào người dùng thay đổi dữ liệu của một trường mà không cần lưu bất kỳ thứ gì vào cơ sở dữ liệu.
<field name="ethnicity_id" placeholder="Ethnicity"/>
@api.onchange('ethnicity_id')
def _onchange_ethnicity_id(self):
if self.ethnicity_id:
self.country_id = self.ethnicity_id.country_id
Hàm _onchange_ethnicity_id
sẽ được gọi khi giá trị ethnicity_id
trên view thay đổi.
Model constraints¶
Odoo cung cấp hai cách để ràng buộc là: Python constraints
and SQL constraints
.
Để định nghĩa một ràng buộc Python constraints: chúng ta sử dụng một method decorated: @api.constrains()
,
decorated sẽ chỉ định trường nào có liên quan đến ràng buộc, do đó, ràng buộc được tự động đánh giá khi một trong số chúng có sự thay đổi về giá trị.
Phương thức constraints đã được định nghĩa sẽ tạo ra một ngoại lệ nếu điều kiện mong muốn của phương thức không được thỏa mãn.
Ví dụ về Python constraints:
from odoo.exceptions import ValidationError
@api.constrains('start_date', 'end_date')
def _check_date(self):
if self.filtered(lambda self: self.start_date and self.end_date and self.start_date > self.end_date):
raise ValidationError(_("The School year start date must be earlier than or equal to end date."))
Các ràng buộc SQL được xác định thông qua thuộc tính của model là _sql_constraints
.
Để định nghĩa một SQL constraints
chúng ta làm như sau.
_sql_constraints = [('class_name_unique', 'unique(name, company_id)', "The name of school year must be unique per company!")]
Advanced Views¶
Tree views¶
Là giao diện dạng danh sách, liệt kê tất cả các bản ghi trong cơ sở dữ liệu. Các cột trên view tương ứng là các fields trong cơ sở dữ liệu. Đây là giao diện cơ bản được Odoo hỗ trợ trong gần như toàn bộ các menu.
Giao diện dạng tree view có thể thêm các thuộc tính bổ sung để tùy chỉnh thêm hành vi của chúng:
decoration-{$name}
Cho phép thay đổi kiểu văn bản của hàng dựa trên các thuộc tính của bản ghi tương ứng.
Giá trị là biểu thức Python. Đối với mỗi bản ghi, biểu thức nếu có giá trị True sẽ được đánh giá với các thuộc tính của bản ghi dưới dạng giá trị ngữ cảnh, style tương ứng sẽ được áp dụng cho hàng.
Chúng ta có thể sử dụng một số giá trị ngữ cảnh sau:
uid
: id của người dùng hiện tại.today
: Ngày hiện tại dưới dạngYYYY-MM-DD
.now
: Ngày giờ hiện tại dưới dạngYYYY-MM-DD hh:mm:ss
.
{$name}
có thể là các giá trị như: danger
, info
, muted
, primary
, success
or warning
.
<tree string="Class Tree" decoration-info="active=='True'" editable="bottom">
<field name="name" />
<field name="class_group_id" />
</tree>
editable
editable
nhận giá trị là top
hoặc bottom
. Làm cho giao diện dạng tree view có thể được chỉnh sửa tại chỗ (thay vì phải đi qua giao diện form view để sửa).
Calendars¶
Hiển thị dạng danh sách các bản ghi theo thời gian. Để khai báo calendars view chúng ta bắt đầu với thẻ <calendar>
và các thuộc tính phổ biến nhất hay dùng là:
color
Thuộc tính này được sử dụng để tô màu các mục trên view.
date_start
Thuộc tính này là bắt buộc để chỉ định ngày bắt đầu cho sự kiện.
date_stop
Thuộc tính này là bắt buộc để chỉ định ngày kết thúc cho sự kiện. Nếu được đề cập trong bản ghi, bản ghi sẽ có thể di chuyển được trong view.
string
Tên của calendar view.
<calendar string="Education Class Calendar" date_start="start_date" color="company_id">
<field name="name"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="company_id"/>
</calendar>
Search views¶
Các phần tử <field>
của giao diện tìm kiếm có thể có @filter_domain
để tìm kiếm các bản ghi có giá trị thỏa mãn với domain.
Trong domain đã cho, self
đại diện cho giá trị được nhập vào bởi người dùng.
Giao diện search views cũng có thể chứa các phần tử <filter>
, hoạt động như nút chuyển đổi cho các tìm kiếm được xác định trước.
Bộ lọc phải có một trong các thuộc tính sau:
domain
Thêm domain đã cho vào tìm kiếm hiện tại.
context
Thêm một số ngữ cảnh vào tìm kiếm hiện tại, chúng ta sử dụng key group_by
để nhóm các kết quả theo tên trường đã cho.
<search string="School Search">
<field name="name"/>
<field name="code"/>
<filter string="Archived" name="ftr_inactive" domain="[('active', '=', False)]" />
<group expand="0" string="Group By">
<filter string="Company" name="grp_company" domain="[]" context="{'group_by' : 'company_id'}"/>
</group>
</search>
Chúng ta có thể tự đặt giao diện tìm kiếm mặc định trong action bằng các sử dụng trường search_view_id
khi khai báo action và chỉ định đến search view đã khai báo.
Gantt¶
Biểu đồ Gantt, thường được sử dụng trong quản lý dự án, là một trong những cách phổ biến và hữu ích nhất để hiển thị các hoạt động (nhiệm vụ hoặc sự kiện) được hiển thị theo thời gian. Ở bên trái của biểu đồ là danh sách các hoạt động và dọc theo trên cùng là thanh thời gian phù hợp. Mỗi hoạt động được thể hiện bằng một thanh, vị trí và độ dài của thanh phản ánh ngày bắt đầu, thời lượng và ngày kết thúc của hoạt động.
Để định nghĩa biểu đồ Gantt chúng ta sử dụng thẻ <gantt>
.
<gantt string="Ideas"
date_start="invent_date"
date_stop="date_finished"
progress="progress"
default_group_by="inventor_id" />
Graph views¶
Tự động hỗ trợ việc chuyển các số liệu về dạng đồ thị. Đưa chuột trực tiếp tới các vị trí trong biểu đồ để lấy số liệu. Có thể chọn các tiêu chí bằng cách sử dụng thước đo.
Các dạng hiển thị của Graph views:
Đồ thị cột:
Đồ thị dạng bánh:
Đồ thị dạng đường:
Kanban¶
Là giao diện dạng thẻ được Odoo tiếp thu từ Phương pháp Kanban có nguồn gốc từ Hệ thống sản xuất tinh gọn [Lean production system] do hãng Toyota phát triển. Kanban view thể hiện các hạng mục công việc ở từng công đoạn của quá trình phát triển. Mỗi bản ghi tương ứng với một thẻ. Tùy từng menu mà có thể kéo/ thả các thẻ từ cột này sang cột khác.
Để định nghĩa view kanban chúng ta sử dụng thẻ <kanban>
.
Giao diện Kanban xác định cấu trúc của mỗi thẻ là sự kết hợp của các phần tử biểu mẫu (bao gồm HTML cơ bản) và Mẫu QWeb.
Phân quyền¶
Trong Odoo, mỗi hệ thống đều có công cụ giúp doanh nghiệp có thể phân quyền người dùng cho từng nhân viên theo những cấp độ khác nhau. Việc phân quyền người dùng trong hệ thống giúp nhân viên có đủ công cụ để hoàn thành công việc hàng ngày của mình nhưng cũng giúp doanh nghiệp đảm bảo việc bảo mật thông tin.
Có ba cấp độ phân quyền trong Odoo:
Phân quyền cấp phân hệ (module): Chỉ những người được phân quyền sử dụng phân hệ nào mới thấy hình ảnh và icon để truy cập vào phân hệ đó.
Phân quyền cấp bản ghi: Có thể chỉ được thấy và thao tác với những menu mình được phân quyền và không thấy những menu cấp cao hơn nếu không được phân quyền.
Phân quyền theo cấp độ trường thông tin trong cùng một form: Có những trường thông tin chỉ được thấy nếu bạn được phân quyền.
Việc phân quyền người dùng đều được thực hiện thông qua các Nhóm người dùng (Group). Một người dùng (User) có thể thuộc về nhiều Group khác nhau. Khi chúng ta áp đặt việc phân quyền vào Group, thì những User thuộc về Group đó sẽ bị ảnh hưởng theo.
Access Control
Được quản lý trong bảng ir.model.access
. Xác định rõ ràng cho từng nhóm người dùng có được truy cập vào từng bảng dữ liệu hay không.
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_education_school_user,education.school user,model_education_school,viin_education_group_user,1,0,0,0
access_education_school_admin,education.school admin,model_education_school,viin_education_group_admin,1,1,1,1
Record rules
Được dùng để định nghĩa quyền người dùng cho từng hành động Đọc, Thêm, Sửa, Xóa trên từng dòng dữ liệu (record) dựa trên các điều kiện (Condition).
<record id="education_school_rule_multi_company" model="ir.rule">
<field name="name">School multi-company</field>
<field ref="model_education_school" name="model_id" />
<field eval="True" name="global" />
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
Field Access
Cho phép phân quyền trên từng field dựa vào thuộc tính groups.
class ResPartner(models.Model):
_inherit = 'res.partner'
is_parent = fields.Boolean(string='Is Parent')
student_ids = fields.One2many('student.relation', 'parent_id',
string='Student Relation', groups='viin_education.viin_education_group_user')
education_student_ids = fields.One2many('education.student', 'partner_id',
string='Students', groups='viin_education.viin_education_group_user')
Wizards¶
Wizards mô tả các phiên tương tác với người dùng (hoặc hộp thoại) thông qua các biểu mẫu động. Wizard chỉ đơn giản là một model mở rộng TransientModel
mà không phải là Model
và sử dụng lại tất cả các cơ chế của Model
.
Đặc điểm nổi bật của Wizards là: Dữ liệu điền vào form sẽ đổ vào một Transient model
(class odoo.models.TransientModel) và dữ liệu trong model này không được lưu bền vững, các dữ liệu sẽ được xóa khỏi cơ sở dữ liệu sau một thời gian nhất định, rất thích hợp để lưu trữ tạm thời.
Internationalization¶
Odoo là một hệ thống đa ngôn ngữ. Chúng ta hoàn toàn có thể chọn ngôn ngữ phù hợp với nhu cầu cho tài khoản truy cập (user) của mình.
Tính năng này đặc biệt hữu ích với các công ty đa quốc gia khi mà nhân viên thuộc nhiều quốc tịch khác nhau.
Các bản dịch nằm trong thư mục i18n
gồm một file .pot
và các file .po
Trong đó file .pot
gồm các msgid
có thể được dịch sang các ngôn ngữ khác, chúng ta không dịch trên file này. Với mỗi ngôn ngữ cần dịch sẽ cần một file .po
tương ứng với ngôn ngữ đó.
Reporting¶
Printed reports¶
Odoo sử dụng công cụ báo cáo dựa trên Mẫu QWeb, Twitter Bootstrap và Wkhtmltopdf.
Một báo cáo là sự kết hợp của hai yếu tố:
ir.actions.report
cấu hình các thông số cơ bản khác nhau cho báo cáo.Giao diện QWeb tiêu chuẩn cho báo cáo thực tế:
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="web.external_layout">
<div class="page">
<h2>Report title</h2>
</div>
</t>
</t>
</t>
Ngữ cảnh kết xuất tiêu chuẩn cung cấp một số yếu tố như:
docs
: các bản ghi báo cáo được in.
user
: người dùng in báo cáo.
WebServices¶
Web-services module cung cấp một giao diện chung cho tất cả web-services:
XML-RPC
JSON-RPC
Các đối tượng nghiệp vụ cũng có thể được truy cập thông qua cơ chế đối tượng phân tán. Tất cả chúng đều có thể được sửa đổi thông qua giao diện máy khách với các khung nhìn theo ngữ cảnh.
Odoo có thể truy cập được thông qua các XML-RPC/JSON-RPC interfaces, mà các thư viện tồn tại ở nhiều ngôn ngữ.
XML-RPC Library¶
Ví dụ sau là một chương trình Python 3 tương tác với máy chủ Odoo với thư viện xmlrpc.client:
import xmlrpc.client
root = 'http://%s:%d/xmlrpc/' % (HOST, PORT)
uid = xmlrpc.client.ServerProxy(root + 'common').login(DB, USER, PASS)
print("Logged in as %s (uid: %d)" % (USER, uid))
# Create a new viin_education
sock = xmlrpc.client.ServerProxy(root + 'object')
args = {
'color' : 8,
'memo' : 'This is a viin_education',
'create_uid': uid,
}
viin_education_id = sock.execute(DB, uid, PASS, 'education.class', 'create', args)
JSON-RPC Library¶
Ví dụ sau là một chương trình Python 3 tương tác với máy chủ Odoo với các thư viện Python tiêu chuẩn urllib.request
và json
. Ví dụ này giả định rằng ứng dụng Viin Education
đã được cài đặt.
import json
import random
import urllib.request
HOST = 'localhost'
PORT = 8069
DB = 'viin_education'
USER = 'admin'
PASS = 'admin'
def json_rpc(url, method, params):
data = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": random.randint(0, 1000000000),
}
req = urllib.request.Request(url=url, data=json.dumps(data).encode(), headers={
"Content-Type":"application/json",
})
reply = json.loads(urllib.request.urlopen(req).read().decode('UTF-8'))
if reply.get("error"):
raise Exception(reply["error"])
return reply["result"]
def call(url, service, method, *args):
return json_rpc(url, "call", {"service": service, "method": method, "args": args})
# log in the given database
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
uid = call(url, "common", "login", DB, USER, PASS)
# create a new viin_education
args = {
'color': 8,
'memo': 'This is another viin_education',
'create_uid': uid,
}
viin_education_id = call(url, "object", "execute", DB, uid, PASS, 'education.class', 'create', args)
Các ví dụ có thể dễ dàng điều chỉnh từ XML-RPC sang JSON-RPC.