Odoo ORM Basic¶
Giới thiệu chung¶
ORM (Object Relational Mapping) là một kỹ thuật / cơ chế lập trình thực hiện ánh xạ cơ sở dữ liệu sang các đối tượng trong các ngôn ngữ lập trình hướng đối tượng như Python, ….
Cùng nhìn vào ví dụ đơn giản dưới đây:
from odoo import models, fields
class EducationStudent(models.Model):
_name = 'education.student'
name = fields.Char(string="Name")
student_code = fields.Char(string='Student Code')
class_id = fields.Many2one('education.class', string='Class')
def get_student_has_class(self):
return self.env['education.student'].search([('class_id', '!=', False)])
Khi bạn tạo một đối tượng student với code Python, ORM giúp thực hiện luôn việc tạo bảng dữ liệu với từng cột tương ứng. Bên cạnh đó, ORM cũng cung cấp một số hàm / phương thức khác để đơn giản hóa việc truy vấn dữ liệu bằng SQL query cho lập trình viên.
Việc này giúp đơn giản hóa công việc của lập trình viên đi rất nhiều, từ việc thiết kế đến quản lý và công tác bảo trì. Qua đó giúp lập trình có nhiều thời gian hơn để tập trung xử lý Logic nghiệp vụ.
Model¶
Model được khởi tạo bằng cách kế thừa đến 1 trong 3 kiểu class:
Model
TransientModel
AbstractModel
Hệ thống tự động khởi tạo Instance, mỗi model một lần, cho mỗi cơ sở dữ liệu.
Tip
Yêu cầu tối thiểu để tạo một model là phải khai báo thuộc tính _name
Mỗi model instance này là một “recordset”, hay còn gọi là tập hợp có thứ tự các record.
Giả sử, sau khi tạo model education.student
xong, chúng ta sử dụng model này tạo dữ liệu của 2 sinh viên:
{
id: 1,
name: 'Hank Aaron',
student_code: 'HA001',
gender: 'male',
class_id: 5,
school_id: 1,
state: 'new'
},
{
id: 7,
name: 'Agatha Christie',
student_code: 'AC001',
gender: 'female',
class_id: 3,
school_id: 1,
state: 'new'
},
Như vậy, record 1 chứa thông tin của sinh viên Hank Aaron
và tương tự record 2 chứa thông tin của sinh viên Agatha Christie
. Tập hợp của 2 records này gọi chung là một recordset.
Tip
Một record cũng được hiểu là một recordset của một record duy nhất.
See also
Tham khảo thêm Recordsets: truy cập đến các trường
Thuộc tính chung của Model:
_name
= NoneTên của model. Giá trị là string được viết cách nhau bởi dấu chấm. Ví dụ:
_name = 'education.student'
_description
= NoneTên không theo form quy chuẩn của model. Ví dụ:
_description = 'student'
_inherit
= NoneTham số truyền vào là string hoặc list(string)
Thuộc tính cho phép một model kế thừa đến một hoặc nhiều model khác. Ví dụ:
_inherit = 'education.student'
(tham khảo thêm Kế thừa trong ORM)._inherits
= {}Thuộc tính cho phép một model kế thừa đến một hoặc nhiều model khác. Thuộc tính này khác với thuộc tính
_inherit
là đây là kiểu kế thừa Delegation (tham khảo thêm Kế thừa trong ORM)._rec_name
= NoneTrường để gán nhãn tên cho một record, được lấy giá trị mặc định từ trường name. Cụ thể, record của sinh viên Hank Aaron được hiển thị với tên là
Hank Aaron
. Nếu gán giá trị_rec_name = 'student_code'
thì tên record sẽ làHA001
thay vìHank Aaron
(Xem ví dụ ở phần giới thiệu chung)._auto
Chỉ định có tạo bảng dữ liệu ở database hay không. Giá trị mặc định của thuộc tính này là:
True
vớiModel
vàTransientModel
vàFalse
vớiAbstractModel
._registry
Khi tạo class mới: gán giá trị
True
để tạo instance cho class này và ngược lại._log_access
Chỉ định ORM có hay không việc tự động tạo và cập nhật các trường thông tin tự động (tham khảo thêm ORM advanced). Giá trị của thuộc tính sẽ được gán theo giá trị của thuộc tính
_auto
._table
= NoneTên bảng dữ liệu SQL được sử dụng bởi model nếu
_auto = True
._sequence
= NoneTrình tự SQL để sử dụng cho trường ID, ví dụ 1,2 3,4, …
_sql_constraints
= []SQL constraints
[(name, sql_def, message)]
. Được sử dụng để chặn tất cả những record lưu vào database không thỏa mãn điều kiện đề ra. Ví dụ, check không cho lưu vào database những record của student cóstudent_code
trùng lặp nhau._sql_constraints = [('student_code_unique', 'unique(student_code)', "The student code must be unique!")]
_abstract
Chỉ định model là abstract hay không (tham khảo thêm Abstract Model).
_transient
Chỉ định model là transient hay không (tham khảo thêm Transient Model).
_order
= ‘id’Trường mặc định để sắp xếp theo thứ tự đối với kết quả tìm kiếm được trả về từ database.
_check_company_auto
= FalseTrong lúc cập nhật hoặc khởi tạo, gọi hàm
check_company
để đảm bảo sự đồng nhất company trên các trường quan hệ mà có đặt thuộc tínhcheck_company=True
_parent_name
= ‘parent_id’Trường quan hệ
Many2one
của một model được sử dụng như là parent field (quan hệ phả hệ)_parent_store
= FalseGán giá trị bằng
True
để tính toán và gán lại giá trị choparent_path
field.Qua đó đồng thời thiết lập hệ thống
index
cho cấu trúc cây phả hệ của records. Điều này sẽ giúp tăng tốc độ query trên cây phả hệ sử dụng domain operator làchild_of
vàparent_of
_date_name
= ‘date’Chọn trường sử dụng cho
Calendar
view mặc định._fold_name
= ‘fold’Chọn trường cho việc thu gọn nhóm trên
Kanban
views.
Model¶
Super-class chính cho tất cả các model Odoo duy trì cơ sở dữ liệu thông thường.
Một model cơ bản được khởi tạo thông qua việc kế thừa tới class này:
from odoo import models
class EducationStudent(models.Model):
# a table will be created in the database with name 'education_student'
_name = 'education.student'
Hệ thống sẽ tạo 1 instance cho class EducationStudent và đồng thời tạo một bảng dữ liệu dưới database với tên tương ứng education_student
_auto
= True_transient
= False_abstract
= False
Transient Model¶
Giống như class Model nêu trên, class kế thừa TransientModel cũng sẽ khởi tạo một bảng dữ liệu tương ứng dưới database.
Tuy nhiên, Model kế thừa tới class TransientModel chỉ chứa những records ngắn hạn. Những records này có đặc điểm là chỉ duy trì tạm thời, không mang tính lâu dài, và thường xuyên được dọn dẹp hoặc xóa hẳn khỏi database. Ví dụ, những records chỉ được tạo duy nhất 1 lần với mục đích sử dụng đúng một lần duy nhất đấy thôi.
from odoo import models
class EducationStudent(models.TransientModel):
# a table will be created in the database with name 'education_student_transient'
_name = 'education.student.transient'
TransientModel áp dụng quy tắc quản lý quyền truy cập đơn giản như sau: Tất cả người dùng đều có quyền tạo record mới và đồng thời chỉ có quyền truy cập đến những records đã được tạo bởi chính họ. Mặt khác, người dùng với quyền cao nhất (superuser) thì lại có toàn quyền truy cập tới tất cả các records của tất cả các TransientModels.
_auto
= True_transient
= True_abstract
= False
Abstract Model¶
Giống như abstract class ở Python, abstract model có mục đích duy nhất là sẽ được kế thừa lại bởi các model khác.
from odoo import models
class EducationStudent(models.AbstractModel):
# No table will be created in the database
_name = 'education.student.abstract'
Khởi tạo một AbstractModel sẽ không khởi tạo 1 bảng dữ liệu dưới database.
_auto
= False_abstract
= True
Kế thừa trong ORM¶
Có 3 kiểu kế thừa trong Odoo:
Kế thừa truyền thống: Tạo một model mới từ model có sẵn, thêm thông tin / dữ liệu / thuộc tính mới cho model mới và giữ nguyên model có sẵn kia.
Kế thừa mở rộng: Mở rộng model có sẵn và thay thế hoặc mở rộng các thông tin / dữ liệu / thuộc tính cũ bằng các thông tin / dữ liệu / thuộc tính mới.
Kế thừa ủy thác: Tạo ủy quyền các trường các model có sẵn tới model mới. Model mới sẽ có toàn bộ các trường của model có sẵn.
Note
Odoo dựa vào thuộc tính _name
của model (Model
hoăc TransientModel
) để tạo ra một bảng dữ liệu mới dưới database.
Một model với một tên _name
không trùng lặp sẽ tạo một bảng dữ liệu mới.
Kế thừa truyền thống¶
Sử dụng đồng thời cả 2 thuộc tính của model:
_name
(tên model mới không trùng với tên của model cơ sở)_inherit
(kế thừa đến model cơ sở)
để tạo một model mới lấy model có sẵn làm cơ sở, hay nói cách khác model mới kế thừa model cơ sở.
Model mới lấy tất cả các trường, phương thức và các thông tin meta từ model mà nó kế thừa đến.
Tip
Khi nào nên sử dụng kế thừa truyền thống:
Khi tạo một model mới với mong muốn tận dụng toàn bộ tính năng của một model có sẵn mà không phải code lại từ đầu.
Dữ liệu của model mới và model có sẵn là độc lập, không liên quan ràng buộc gì đến nhau.
Kế thừa mở rộng¶
Hoặc chỉ sử dụng
_inherit
và bỏ qua_name
Hoặc sử dụng cả 2:
_name
(tên model mới trùng với tên của model cơ sở)_inherit
(kế thừa đến model cơ sở)
Để tạo model mới và vẫn sử dụng chung bảng dữ liệu có sẵn.
Model mới này thay thế model cơ sở, hay nói cách khác về cơ bản là mở rộng model cơ sở.
Tip
Khi nào nên sử dụng kế thừa mở rộng:
Khi muốn bổ sung thêm tính năng mới cho model có sẵn (được tạo ở các module khác).
Khi muốn tùy biến hoặc điều chỉnh lại luồng logic của luồng nghiệp vụ có sẵn theo nhu cầu phát sinh thực tế.
Note
Kiểu truyền thống:
Kế thừa toàn bộ tính năng (trường, phương thức, dữ liệu meta, …)
Model mới và model cơ sở là 2 model độc lập, không cái nào thay thế cái nào.
Model mới lưu dữ liệu vào bảng dữ liệu mới ở Database. Model cơ sở vẫn lưu dữ liệu mới vào bảng cũ của chính nó. Hay nói cách khác, dữ liệu cũng độc lập
Kiểu mở rộng:
Thêm và mở rộng tính năng dựa trên cái có sẵn. Cái mới thay thế cái cũ.
Model mới chỉ là một phiên bản mở rộng của model cơ sở.
Model mới và model cơ sở vẫn sử dụng chung bảng dữ liệu ở Database
Kế thừa ủy thác¶
Sử dụng đồng thời cả 2 thuộc tính của model để tạo một model mới:
_name
(tên model mới không trùng với tên của model cơ sở)_inherits
(kế thừa ủy thác đến model cơ sở)
Toàn bộ các trường của model cơ sở sẽ được “ủy thác” đến model kế thừa đến nó. Qua đó model mới sẽ có toàn bộ các trường tương ứng.
Sự ủy thác này được thực hiện qua trường fields.Reference
.
Điểm khác chính của kế thừa ủy thác là ý nghĩa của nó. Với kiểu này, model “có” được trường (field) chứ không phải “là” trường đó. Điều này biến mối quan hệ giữa model mới và model ủy thác thành mối quan hệ thành phần thay vì là mối quan hệ kế thừa.
Tip
Khi nào nên sử dụng kế thừa ủy thác:
Khi muốn tạo biến thể từ model cơ sở. Ví dụ, xét điểm thi đại học. Học sinh nói chung phải thi 6 môn. Học sinh nghèo vượt khó ngoài thi 6 môn thì được cộng 2 điểm. Học sinh nghèo vượt khó vùng sâu vùng xa được cộng 4 điểm. Hai kiểu học sinh này được gọi là biến thể của kiểu học sinh nói chung.
Model cơ sở là một phần / bộ phận trong model mới. Ví dụ, team đi thi học sinh giỏi thành phố môn Toán bao gồm: 1 giáo viên hướng dẫn, 1 giáo viên hỗ trợ, 1 học sinh thi chính, 1 học sinh thi phụ. Đây là 1 tổ hợp, trong đó mỗi một cá nhân “ủy thác” đến team qua
id
của mình
Note
Khác với 2 kiểu trước, kiểu ủy thác:
Kế thừa tất cả các trường, nhưng không kế thừa phương thức nào cả.
Có sự đồng bộ hóa giữa các trường ở 2 model. Dữ liệu trên những trường được ủy thác mà bị thay đổi ở model này thì sẽ được đồng bộ với các model còn lại.
Cũng tạo một bảng dữ liệu mới dưới database
Mở rộng các định nghĩa có sẵn¶
Một trường thông tin được định nghĩa là một thuộc tính của một model class. Nếu model này được mở rộng, thì một trường bất kỳ trong model này cũng có thể được mở rộng. Việc định nghĩa lại đó được thực hiện bằng cách viết trùng tên và trùng kiểu trên class kế thừa. Qua đó, trường được mở rộng này sẽ có tất cả các thuộc tính cũ ở class có sẵn và:
các thuộc tính mới bổ sung ở class kế thừa. Hoặc
một hoặc nhiều thuộc tính cũ bị ghi đè bởi các thuộc tính tương ứng khai báo trên class kế thừa.
def EducationStudent(models.Model):
_name = 'education.student'
name = field.Char(string='Name')
def EducationStudentExtend(models.Model):
_inherit = 'education.student'
name = field.Char(string='Name Of Student', copy=False, help="This is the name of a student")
Fields: Các trường cơ bản¶
Trong ORM, một field sẽ có những thuộc tính chung như sau:
string
(str)trường này được hiển thị nhãn tên của trường cho người dùng xem với giá trị string tương ứng.
Note
Lấy ví dụ trên trường
student_code
:student_code = fields.Char(string='Code of Student')
Trên giao diện Form của model
education.student
, trườngstudent_code
sẽ có label bên cạnh input form là'Code of Student'
.Nếu không sử dụng thuộc tính
string
, label sẽ lấy giá trị theo string của tên trường và sẽ là'Student Code'
.
help
(str)help string được hiển thị dưới dạng tooltip
invisible
(bool):default = False
Ẩn hoặc không ẩn trường trên View giao diện
readonly
(bool):default = False
Nếu readonly =
True
, user trên giao diện Form của model không thể thay đổi giá trị của trường một cách thủ công và ngược lại. thuộc tính này chỉ có tác dụng trên UI. Mặc dù thế, nếu dùng code thì vẫn thay đổi được. (Với điều kiện đây là trường được lưu trữ hoặc có thể thay đổi ngược (inversable field - tham khảo thêm ORM advanced))require
(bool):default = False
Bắt buộc phải điền giá trị cho trường khi tạo một record mới.
index
(bool):default = False
Thiết lập chế độ indexing trên một trường / cột (column) trong database. (Chỉ áp dụng với trường được lưu trữ ở database)
default
(value hoặc hàm - callable)Gán giá trị mặc định của trường. Giá trị có thể là:
Giá trị bất biến: một giá trị Boolean, một string, một số, …
Một hàm, ví dụ một hàm nhận vào 1 recordset và trả về một giá trị tương ứng.
Sử dụng
default=None
nếu muốn bỏ giá trị mặc định của field.states
(dict)thuộc tính states nhận giá trị là 1 dictionary có từ 1 đến 3 keys:
invisible
,require
,readonly
Ví dụ:from odoo import models, fields class EducationStudent(models.Model): _name = 'education.student' state = fields.Selection(string='Status', selection=[('new', 'New'), ('studying', 'Studying'), ('off', 'Off')], default='new') student_level_id = fields.Many2one('student.level', string='Level', states={'new': [('invisible', True),('readonly', True)], 'studying': [('invisible', False),('readonly', True)], 'off': [('invisible', true),('readonly', True)]})
Note
state is a specicial field called reserved field (tham khảo thêm ORM advanced)
groups
(str)Thiết lập quyền truy cập đến một trường cho từng nhóm người dùng cụ thể. Nếu thuộc tính này được khai báo, chỉ có nhóm người dùng được chỉ định mới có quyền truy cập (ví dụ xem hoặc sửa) đến trường này.
# only members of group 'viin_education_group_user' may access this fields student_ids = fields.One2many('student.relation', 'parent_id', string='Student Relation', groups='viin_education.viin_education_group_user')
company_dependent
(bool)Thiết lập giá trị của trường, tạm gọi (A), này có hay không phụ thuộc vào công ty hiện tại. Chủ yếu sử dụng trong môi trường đa công ty.
Giá trị này không được lưu vào bảng dữ liệu được tạo ra từ model đó mà được đăng ký là 1 property trong bảng
ir.property
. Khi giá trị của trường (A) được truy cập đến thì hệ thống sẽ tìm kiếm trong bảngir.property
, sau đó liên kết đến công ty hiện tại (và đến record hiện tại nếu một property được tìm thấy)Nếu giá trị trường (A) bị thay đổi trên record, thì hoặc là giá trị của property tương ứng bị thay đổi, hoặc là tạo mới property cho công ty hiện tại.
Nếu giá trị trường (A) bị thay đổi trên công ty thì sẽ cập nhật lại toàn bộ tất cả các records khác liên quan tới giá trị này mà chưa được thay đổi.
copy
(bool)Nếu
copy=True
, khi record này được nhân bản thì giá trị của trường này cũng sẽ được copy sang record mới. (Tham khảo thêm phương thức copy())giá trị mặc định là
True
với các trường bình thường.giá trị mặc định là
False
với One2Many và compute fields, bao gồm cả related fields
store
(str)Thiết lập có lưu giá trị của trường này vào database (tạo 1 column trong bảng dữ liệu) hay không. (giá trị mặc định là True. Chỉ False với compute fields)
giá trị mặc định là
True
.giá trị mặc định chỉ
False
với compute fields.
group_operator
(bool)Hàm tổng hợp được sử dụng bởi phương thức read_group() của ORM khi nhóm các giá trị lại trên 1 trường.
Hàm tổng hợp này hỗ trợ:
- thuộc tính
array_agg
: các giá trị, kể cả Null, được ghép với nhau thành một mảng (array)count
: số hàng trong bảng dữ liệu, nói cách khác là số records tương ứng.count_distinct
: số records không trùng lặpbool_and
: True nếu tất cả giá trị làTrue
, nếu không sẽFalse
bool_or
: True nếu một trong các giá trị làTrue
, nếu tất cảFalse
thì sẽFalse
max
: giá trị tối đa trong số các giá trị trả vềmin
: giá trị tối thiểu trong số các giá trị trả vềavg
: giá trị trung bình (arithmetic mean) của tất cả các giá trị trả vềsum
: tổng giá trị của tất cả các giá trị trả về
group_expand
(str)Sử dụng để mở rộng kết quả của phương thức read_group() khi nhóm các giá trị trên trường.
Computed Fields¶
Và đặc biệt là thuộc tính compute:
compute
(str)Thuộc tính nhận giá trị là một phương thức / hàm.
See also
Tham khảo thêm ORM advanced
Trường có compute thường có (hoặc không) đi kèm với các thuộc tính sau đây:
- thuộc tính
compute_sudo
(bool): Xác định trường này sẽ được tính toán lại với quyền của người dùng cao nhất (superuser) để không bị ngăn cản bởi các thiết lập quyền truy cập nói chung. (thuộc tính này có giá trị mặc định làTrue
đối với tất cả các trường lưu giá trị vào database và ngược lại)inverse
(str): nhận giá trị là tên của phương thức mà thay đổi ngược giá trị của trường (tùy chọn)search
(str): nhận giá trị là tên của phương thức tìm kiếm giá trị cụ thể cho trường này (tùy chọn)related
(str): chuỗi các tên trường
See also
Tham khảo thêm ORM advanced
Trường cơ bản¶
odoo.fields.Boolean
Nhận giá trị boolean True/False
odoo.fields.Char
Nhận giá trị là string, có thể giới hạn về độ dài, thường được hiển thị là một dòng string cho người dùng.
- thuộc tính hỗ trợ
size
(int): Độ dài tối đa của string lưu vào database.trim
(bool): Tự động bỏ hết dấu cách trống ở đầu và cuối của string nhận vào. (default =True
). Lưu ý hoạt động Trim sẽ được thực hiện bởi web client.translate
(bool hoặc callable): Thiết lập phiên dịch ngôn ngữ cho giá trị string của trường. Gán giá trịTrue
nếu toàn bộ string sẽ được phiên dịch. Nếu chỉ phiên dịch một bộ phận string thì truyền vào một hàm callable, ví dụ,translate(callable, value)
.
odoo.fields.Integer
Nhận giá trị là số nguyên tự nhiên
odoo.fields.Float
Nhận giá trị số thập phân Float, ví dụ: 0.0 hoặc 100.95.
Phần chữ số thập phân được xác định bởi thuộc tính
digits
:digits
(tuple(int,int) hoặc str): cặp (tổng chữ số, chữ số phần thập phân) hoặc string tham chiếu đến record
DecimalPrecision
Khi một số thập phân có liên quan đến đơn vị đo lường cần đến độ chính xác cho phần thập phân, Odoo ORM cung cấp các phương thức sau:
round()
: làm tròn một số thập phân tới chữ số phần thập phân được chỉ định.fields.Float.round(self.student_score, precision_rounding=self.student_score_id.rounding)
is_zero()
: so sánh một số thập phân với giá trị 0 tại chữ số phần thập phân được chỉ định.fields.Float.is_zero(self.student_score, precision_rounding=self.student_score_id.rounding)
compare()
: so sánh 2 số thập phân với chữ số phần thập phân được chỉ định.res = field.Float.compare(self.ha001_score, self.ac001_score, precision_rounding=self.student_score_id.rounding) if res < 0: print('ha001_score < ac001_score') # nhỏ hơn elif res > 0: print('ha001_score > ac001_score') # lớn hơn else: print('ha001_score = ac001_score') # bằng
Recordsets: truy cập đến các trường¶
Recordset là cầu nối giúp chúng ta tương tác với model và các records. Như đã nói nhanh ở phần Mở đầu Model, recordset là tập hợp có thứ tự của tất cả các records có chung một model.
Warning
Mặc dù có tên gọi là set nhưng recordset vẫn có thể bao gồm 2 records trùng lặp. Việc này có thể được thay đổi ở những bản cập nhật của Odoo trong tương lai.
Hàm / phương thức được định nghĩa trong model thì đều áp dụng được lên recordset, và bản thân self
chính là một recordset:
from odoo import models, fields
class EducationStudent(models.Model):
_name = 'education.student'
...
@api.onchange('state_id')
def _onchange_state(self):
if self.state_id.country_id:
self.country_id = self.state_id.country_id
if self.state_id and self.state_id != self.district_id.state_id:
self.district_id = False
Truy cập đến trường thông tin Field¶
Trường thông tin trên model được đọc hoặc cập nhật thông qua interface của recordset.
Với trường cơ bản¶
Với những trường không phải trường quan hệ, ví dụ Char()
, Boolean()
, Integer()
, …:
- Đọc record:
Nếu recordset có 1 record, truy cập dữ liệu của 1 trường bằng cách chấm trực tiếp tới tên của trường đó.
Nếu recordset có nhiều records, truy cập dữ liệu của tất cả các trường bằng cách sử dụng phương thức
mapped(tên_trường)
.>>> self[0].name 'Hank Aaron' >>> self[0]['name'] 'Hank Aaron' >>> self[1].name 'Agatha Christie' >>> self.mapped('name') ['Hank Aaron', 'Agatha Christie'] >>> self.name ValueError: Expected singleton: education.student(1, 7)
- Cập nhật record:
Hoặc gán trực tiếp giá trị mới cho trường của record. Việc này sẽ kích hoạt phương thức update cho database.
Hoặc sử dụng phương thức
write()
của ORM.>>> self[0].name = 'Micheal Admin' >>> self[0].name 'Micheal Admin' >>> self[0].write({'name': 'Micheal Phell Admin'}) >>> self[0].name 'Micheal Phell Admin'
See also
tham khảo thêm Các hàm ORM cơ bản
Với trường quan hệ¶
Với những trường quan hệ, Many2one
, One2many
và Many2many
:
- Đọc record:
Chỉ cần truy cập dữ liệu bằng cách chấm trực tiếp tới tên của trường.
Note
Đọc các trường quan hệ luôn trả về một recordset: hoặc recordset rỗng hoặc recordset có 1 hay nhiều records.
>>> student_ha001.class_id education.class(5) >>> student_ha001.team_id.name 'Class 10A1' >>> self.class_id education.class(3, 5) >>> self.team_id.mapped('name') ['Class 10A1', 'Class 11A1'] >>> self.team_id.name ValueError: Expected singleton: education.class(3, 5)
- Cập nhật record:
Many2one
: sử dụng phương thức write() hoặc gán trực tiếp 1 giá trịid
(id
có tồn tại trong database)One2many
vàMany2many
: sử dụng phương thức write() với cú pháp đặc biệt.>>> student_ha001.team_id education.class(5) >>> student_ha001.team_id = student_ac001.team_id >>> student_ha001.team_id education.class(3)
See also
tham khảo thêm Các hàm ORM cơ bản
Method decorator¶
odoo.api.autovacuum
(method)api này đáp ứng những nhu cầu của những nhiệm vụ như dọn dẹp dữ liệu rác hàng ngày (lưu ở bảng
ir.autovacuum
).Sử dụng
api.autovacuum
khi không cần thiết phải thiết lập hẳn 1 cron job.odoo.api.constrains
(*args)Tạo ràng buộc có điều kiện cho một hoặc nhiều trường trong model.
Những trường có liên quan và có ảnh hưởng đến điều kiện ràng buộc đều phải truyền vào
@api.constrains
.@api.constrains('district_id', 'country_id') def _check_country(self): for r in self: if r.country_id and r.district_id and r.district_id.country_id != r.country_id: raise ValidationError(_("The district '%s' does not belong to country '%s'. Please select another!")\ % (r.district_id.name, r.country_id.name))
Hàm constrains sẽ được kích hoạt khi một trong các trường truyền vào được gán giá trị mới.
Mong muốn sẽ tạo
ValidationError
nếu điều kiện check không thỏa mãn.
Warning
@api.constrains
giá trị được truyền vào phải là tên của trường cụ thể.không nhận tên trường liên kết (“chấm” trường quan hệ, ví dụ
class_id.name
).
@api.constrains
chỉ được kích hoạt khi và chỉ khi các trường truyền vào đều phải được sử dụng trong phương thứccreate
hoặcwrite
trong model tương ứng.Nếu 1 trường mà không được ghi nhận ở View thì khi bị thay đổi sẽ không kích hoạt hàm constrains ứng với nó. Với trường hợp này, cần ghi đè phương thức
create
.
Tip
So sánh
@api.constrains
và_sql_constraints
?Giống:
Cả 2 phương thức ràng buộc đều tạo nút chặn với điều kiện tương ứng trên một hoặc nhiều trường trong model. Ví dụ, tạo ràng buộc chặn không cho phép tạo nhiều records student có trùng
student_code
Khác:
@api.constrains
:Chặn ở Server.
Python constrains
Có thể thiết lập tính toán, logic và điều kiện phức tạp phục vụ cho mục đích kiểm tra ràng buộc.
_sql_constraints
:Chặn ở Database.
SQL constrains
Chỉ thiết lập với logic và điều kiện đơn giản mà không có những tính toán phức tạp.
odoo.api.depends
(*args)Thiết lập các trường phụ thuộc của phương thức
compute
. Tham số truyền vào là tên (str) các trường phụ thuộc:Tên các trường cơ bản trong model này, ví dụ ‘name’
Tên các trường cơ bản thông qua các trường quan hệ trong model này, ví dụ ‘school_id.name’
yob = fields.Integer(string='Year of Birth', help='Student's year of birth') age = fields.Integer(compute='_compute_age') @api.depends('yob', 'school_id.age_privacy') def _compute_age(self): for record in self: if record.school_id.age_privacy: record.age = 0 else: record.age = fields.Date.today().year - record.yob
Trong ví dụ trên, chỉ cần một trong hai tham số
yob
hoặcschool_id.age_privacy
bị thay đổi thì đều kích hoạt hàm_compute_age
tính toán lại số tuổi tương ứng của sinh viên đó.Warning
Lưu ý khi truyền tham số là một trường quan hệ, ví dụ chỉ truyền
school_id.age_privacy
mà không phảischool_id.age_privacy
.Nếu chỉ truyền
school_id
, mọi thay đổi trên bất kỳ một trường nào trên record school tương ứng đều kích hoạt hàm_compute
.Nếu truyền
school_id.age_privacy
, thì chỉ khi thay đổi tên của school tương ứng thì mới kích hoạt hàm_compute
.
Note
Nếu không truyền một tham số nào vào
@api.depends
, hàm_compute
sẽ phụ thuộc vào toàn bộ các trường trên model này. Điều đó có nghĩa là thay đổi giá trị một trường bất kỳ trong model này đều kích hoạt hàm_compute
Một hàm
_compute
không sử dụng kết hợp@api.depends
tương đương với không truyền tham số nào vào@api.depends
# dependencies are all the fields on the model @api.depends() def _compute_age(self): ... # dependencies are all the fields on the model def _compute_age(self): ...
Ngoài ra cũng có thể truyền một hàm là tham số. Lúc này, các trường phụ thuộc sẽ được chỉ định bằng việc thực hiện gọi hàm truyền vào và thông thường hàm này sẽ phải trả về một list các tên trường.
odoo.api.onchange
(*args)Trên giao diện Form với trường onchange này được hiển thị, phương thức onchange sẽ được kích hoạt khi một trong các trường phụ thuộc thay đổi giá trị. Phương thức này được gọi trên pseudo-record (bản ghi chưa chính thức) chứa các giá trị có trên Form.
@api.onchange('class_id') def _onchange_class_id(self): if self.class_id: self.class_group_id = self.class_id.class_group_id
Warning
Không thể dùng phương thức onchange lên trường quan hệ
one2many
vàmany2many
để thay đổi giá trị của chính nó.Danger
@api.onchange
chỉ trả về pseudo-records, nên nếu thực hiện một trong các phương thức CRUD (create()
,write
,read
,unlink
) trên các recordset đang được xử lý đó thì sẽ gây lỗi. Vì có khả năng cao là những dữ liệu này chưa tồn tại ở database và do đó không thể truy cập đến dữ liệu không tồn tại.Tip
So sánh: @api.onchange (onchange) và @api.depends (compute)
- Giống
cả 2 phương thức dựa vào trường phụ thuộc đều kích hoạt tính toán lại giá trị của một trường nhất định. Ví dụ:
age_compute = fields.Integer(string='Student age', default=0, compute='_compute_age') age_onchange = fields.Integer(string='Student age', default=0) @api.depends('yob') def _compute_age(self): for record in self: record.age_compute = fields.Date.today().year - record.yob @api.onchange('yob') def _onchange_age(self): for record in self: record.age_onchange = fields.Date.today().year - record.yob
Trên giao diện Form của student, thay đổi giá trị trường năm sinh sẽ đồng thời kích hoạt việc tính toán lại và thay đổi giá trị của cả 2 trường
age_compute
vàage_onchange
.Cả 2 decorators đều nhận giá trị là tên trường làm tham số truyền vào.
- Khác
onchange
@api.onchange
chỉ hỗ trợ tên trường đơn giản, ví dụname
,code_name
. Không hỗ trợ trường “chấm” tên. Điều đó có nghĩa là trường phụ thuộc chỉ là những trường cơ bản trong scope của model hiện tại mà thôi.Chỉ bắt sự kiện trên View. Ví dụ trên giao diện Form, lúc đầu trường
age_onchange
luôn có giá trị bằng 0. Chỉ sau khi thay đổi năm sinh thìage_onchange
mới có giá trị mới.Trường onchange có mặc định store =
True
.Không cần khai báo trên Field.
compute
@api.depends
hỗ trợ “chấm” tên, ví dụproject_id.name
,company_id.age_privacy
. Điều đó có nghĩa là trường phụ thuộc có thể nằm ở model khác và cũng có thể tác động đến giá trị của model hiện tại.Trên giao diện Form, lúc đầu trường
age_compute
luôn có giá trị được tính toán bởi bởi hàmcompute
. Và sau đó cứ mỗi lần thay đổi năm sinh sẽ tính toán lại giá trịage_compute
Trường compute có mặc định store =
False
.Cần khai báo hàm compute trên Field qua thuộc tính
compute
.
odoo.api.depends_context
(*args)Tương tự như
@api.depends
, nhưng phương thứccompute
(store=``False``) sẽ phụ thuộc vào giá trị trong objectcontext
(type dictionary)total_score = fields.Float(compute='_compute_student_score') @api.depends_context('suddent_sick') def _compute_student_score(self): for student in self: if student.env.context.get('suddent_sick'): score = 3 else: score = student.get_latest_score_examination() student.total_score += score
Tất cả các giá trị phụ thuộc đều phải mang tính bất biến, ví dụ một string, hay một set. Những
keys
sau đều hỗ trợ đặc biệt:company
: giá trị từ context hay giá trị là id của công ty hiện tạiuid
: giá trị từ người dùng hiện tại và dấu hiệu sử dụng cấp người dùng cao nhất (superuser)active_test
: giá trị trong env.context hoặc giá trị trong field.context. (Tham khảo chi tiết tại …)
odoo.api.model
(method)Sử dụng
@api.model
khi:self
là recordsetNội dung của
self
(thông tin các trường) thì không liên quan hoặc không cần sử dụng trong hàm.
Ví dụ điển hình nhất là sử dụng
@api.model
với phương thứccreate
của ORM:from odoo import models, fields, api class EducationCompanyStudent(models.Model): _inherit = 'education.student' ... @api.model def create(self, vals): if 'company_id' in vals: vals['name'] = vals['name'].upper() return super(EducationCompanyStudent, self).create(vals)
>>> student = self.env['education.student'].create({'name': 'Micheal Bubble', 'code_name': 'MB001',...})
Như ví dụ trên, phương thức
create
không quan tâm / không sử dụng truy cập đến nội dung trongself
mà chỉ đơn thuần sử dụng model này.See also
Tham khảo thêm Các hàm ORM cơ bản
odoo.api.model_create_multi
(method)Tương tự như
@api.model
nhưng với decorator@api.model_create_multi
, phương thứccreate
có thể nhận:Một giá trị dict (như ví dụ trên ở
@api.model
).Một list các giá trị dict.
Để có thể tạo nhiều records cùng lúc.
from odoo import models, fields, api class EducationCompanyStudent(models.Model): _inherit = 'education.student' ... @api.model_create_multi def create(self, val_list): for vals in val_list: if 'company_id' in vals: vals['name'] = vals['name'].upper() return super(EducationCompanyStudent, self).create(val_list)
>>> students = self.env['education.student'].create([{'name': 'Micheal Bubble', ...}, {'name': 'Christine Ha', ...}])
Các hàm ORM cơ bản¶
Create / Update¶
Model
.create
(val_list) -> recordsTạo một hoặc nhiều records mới.
- Tham số
val_list
: list của một hoặc nhiều dictionary, trong đó mỗi một dict bao gốm tên các trường của model và giá trị khởi tạo tương ứng.
new_students = self.env['education.student'].create([ {'name': 'Hank Aaron', 'student_code': 'HA001', 'gender': 'male', ...}, {'name': 'Agatha Christie', 'student_code': 'AC001', 'gender': 'female', ...} ])
val_list
cũng có thể chỉ là một dictionary. Trường hợp này, tham số sẽ được coi nhu là một list đơn[val]
, và chỉ có 1 record được tạo mới.
new_student = self.env['education.student'].create( {'name': 'Hank Aaron', 'student_code': 'HA001', 'gender': 'male', ...}, )
- Trả về
Recordset (một hoặc nhiều records) mới tạo.
- Báo lỗi
AccessError xảy ra nếu:
người dùng không có quyền khởi tạo đối tượng được yêu cầu
người dùng bỏ qua các quy tắc truy cập để tạo trên đối tượng được yêu cầu
ValidationError: xảy ra nếu người dùng nhập giá trị không hợp lệ.
UserError: xảy ra nếu một vòng lặp sẽ được tạo liên quan tới cây phả hệ (chẳng hạn như đặt một đối tượng làm cha của chính nó).
Warning
Cần lưu ý sử dụng
@api.model
hoặc@api.model_create_multi
kết hợp với hàmcreate
. Lý do:Khi tạo mới,
self
là record rỗng.Chỉ quan tâm đến giá trị của
val_list
chứ không quan tâm đến nội dung củaself
Model
.copy
(default=None) -> new recordTạo một bản ghi mới từ một bản ghi cũ. Bản ghi cũ sẽ có:
Giá trị các trường giống hệt giá trị của bản ghi cũ
Ngoài ra có thể cập nhật đồng thời giá trị mới của một hoặc nhiều trường (theo nhu cầu)
- Tham số
default
(dict): dictionary của {trường - giá trị} để ghi đè lên sau khi đã sao chép giá trị gốc của record gốc.
- Trả về
Record mới
>>> student_ha001.read() {name: 'Hank Aaron', student_code: 'HA001', gender: 'male', class_id: 5, school_id: 1, state: 'new', ...} >>> new_student = student_ha001.copy({'name': 'Micheal Admin', 'student_code': MA002}) >>> new_student.read() {name: 'Micheal Admin', student_code: 'MA001', gender: 'male', class_id: 5, school_id: 1, state: 'new', ...}
Warning
Cần lưu ý các ràng buộc của Python và ràng buộc của SQL khi nhân bản một record.
Example
Nếu ràng buộc SQL là tên của student là duy nhất không trùng lặp thì phải truyền giá trị mới khi nhân bản.
>>> student_ha001.copy() ERROR: duplicate key value violates unique constraint "student_code_unique" >>> student_ha001.copy({'student_code': 'MJ222'}) education.student(11) # new record of student whose student code is MJ222
Model
.default_get
(fields_list) -> default_valuesPhương thức này trả về tất cả các giá trị mặc định của tất cả các trường của model. Những giá trị này được xác định bởi context, mặc định bởi người dùng và chính model đó.
- Tham số
fields_list
(list): tên của các trường cần lấy về giá trị mặc định
- Trả về
Dictionary có dạng {‘tên_trường’: ‘giá trị mặc định’, …}, nếu những trường này có thiết lập giá trị mặc định.
Note
Những trường không có trong
fields_list
thì hệ thống sẽ tự động bỏ qua.Note
Phải truyền hết những trường yêu cầu cần trả về giá trị mặc định. Nếu tham số truyền vào là một list rỗng
[]
thì kết quả trả về là một dictionary rỗng{}
.Model
.name_create
(name) -> recordPhương thức này cho phép tạo nhanh 1 record mới bằng việc sử dụng phương thức
create
với tham sô có duy nhất là giá trị của trườngname
.Ngoài tên mới, record mới được tạo dựa trên các giá trị mặc định thiết lập trên model, hoặc dựa trên giá trị truyền trong context.
- Tham số
name
: tên hiển thị của record sẽ được tạo mới.
- Trả về
Tuple định dạng của phương thức
name_get()
>>> self.env['education.class'].name_create({'name': 'Project leader class'}) (27, "{'name': 'Project leader class'}")
Example
Trên giao diện Form, trường
Many2one
sẽ hiển thị các lựa chọn. Người dùng nhập tên để tìm kiếm nhanh record tương ứng (tham khảo thêm name_search). Nếu không tìm thấy kết quả nào phù hợp, phương thứcname_create
đưa ra lựa chọn cho phép tạo nhanh một bản ghi sử dụng chính giá trị tìm kiếm đó.
Model
write
(vals) -> BoolCập nhật tất cả các records trong recordset hiện tại với giá trị mới mong muốn.
- Tham số
val
(dict): trường sẽ cập nhật và các giá trị mới tương ứng. Ví dụ:
# You can update some of the fields or all fields student_ha001.write({'is_teacher': True, 'class_id': 7})
Sẽ chỉ cập nhật trường
is_teacher
giá trị True và trườngclass_id
giá trị 7, nếu những trường và giá trị này là hợp lệ (nếu không thì sẽ báo lỗi)- Trả về
Boolean (
True
nếu update thành công)
- Báo lỗi
AccessError xảy ra nếu:
người dùng không có quyền khởi tạo đối tượng được yêu cầu
người dùng bỏ qua các quy tắc truy cập để tạo trên đối tượng được yêu cầu
ValidationError: xảy ra nếu người dùng nhập giá trị không hợp lệ.
UserError: xảy ra nếu một vòng lặp sẽ được tạo liên quan tới cây phả hệ (chẳng hạn như đặt một đối tượng làm cha của chính nó).
Bên cạnh đó có một số chú ý về giá trị mới như sau:
Với trường giá trị là số (
Integer
,Float
), giá trị truyền vào cần giữ đúng kiểu (Integer
hoặcFloat
)Với trường
Boolean
, giá trị truyền vào phải làbool
Với trường
Selection
, giá trị truyền vào phải đồng nhất với giá trị sẵn có trong các lựa chọn (Thường là typestr
, thi thoảng làint
)Với các trường không phải trường quan hệ thì giá trị là
str
.Lưu ý, từ Odoo v14 trở lên thì trường Date và Datetime hỗ trợ nhận Date / Datetime object với timezone UTC
Với trường
Many2one
, giá trị truyền vào phải là ID của record tương ứng (theo database).Với trường
One2many
,Many2many
sử dụng cú pháp đặc biệt để thao tác với các bản ghi được lưu với trường liên quan.- cú pháp
(0, 0, values): Thêm 1 record được tạo mới với giá trị mong muốn
values
.# Eg. only update field 'class_id' and add a new record to field 'parent_ids' student_ha001.write({ 'class_id': 7, 'parent_ids': [(0, 0, {'parent_id': 1, 'student_id': 18, 'relationship_id': 8, 'is_contact_person': True} )] })
(1, id, values): Cập nhật record đã tồn tại có ID là
id
với giá trị sẽ cập nhật làvalues
. Không thể sử dụng vớicreate()
.(2, id, 0): Xóa bản ghi đã tồn tại có ID là
id
khỏi recordset hiện tại, sau đó xóa luôn record đó dưới Database. Không thể sử dụng vớicreate()
.(3, id, 0): Xóa bản ghi đã tồn tại có ID là
id
khỏi recordset hiện tại, nhưng không xóa record đó dưới Database. Không thể sử dụng vớicreate()
.(4, id, 0): Thêm record đã tồn tại có ID là
id
vào recordset hiện tại. Gần giống với lệnh 0, nhưng lệnh 0 là thêm và tạo mới. Còn lệnh 4 là chỉ thêm 1 record đã có sẵn(5, 0, 0): Xóa tất cả các records khỏi recordset hiện tại, tương đương với việc sử dụng lệnh 3 trên từng record một. Không thể sử dụng với
create()
.(6, 0, ids): Thay thế toàn bộ các records đã có sẵn trong recordset hiện tại bằng các records được chỉ định theo danh sách
ids
. Điều này tương đương với việc sử dụng lệnh 5 và sau đó là lệnh 4 cho từng record cóid
trong danh sáchids
.
Model
flush
(fnames=None, records=None)Xử lý tất cả các tính toán (công việc) đang chờ xử lý (trên tất cả các model) và đẩy tất cả các bản cập nhật đang chờ xử lý vào Database.
- Tham số
fnames
(list<str>): danh sách tên các trường sẽ được flush. Chỉ giới hạn flush với những trường được truyền vào của model hiện tại. Nếu không truyền vào tên trường nào thì sẽ flush cả model.Model
(records): nếu được truyền vào (cùng vớifnames
), thì chỉ giới hạn flush với những records được truyền vào
Search / Read¶
Model
browse
([ids]) -> recordsTrả về một recordset các records dựa theo danh sách
ids
>>> self.env['education.student'].browse(1) education.student(1) >>> self.env['education.student'].browse([1, 7]) education.student(1, 7)
Warning
Phương thức
browse
luôn trả về 1 recordset, kể cả với trường hợp record với ID truyền vào không tồn tại trong Database. Lỗi chỉ xảy ra khi truy cập đến các trường của record kết quả được trả về đó. Cho nên cần lưu ý check record được trả về có tồn tại hay không.- Tham số
ids
(int hoặc list(int) hoặc None) - ids
- Trả về
recordset
Model
search
(args[, offset=0][, limit=None][, order=None][, count=False]) -> recordsTìm kiếm records phù hợp dưới database thỏa mãn Search Domain được chỉ định
- Tham số
args
(list(tuple)): search domain. Truyền vào một list rỗng[]
để tìm tất cả các records.offset
(int): số lượng records sẽ bị bỏ qua (mặc định làNone
)limit
(int): Giới hạn số lượng records được trả về (mặc định là trả về tất cả)order
(str): tên trường sẽ sắp xếp thứ tự theocount
(bool): Nếu count=True
, thay vì trả về recordset thì trả về số lượng records được tìm thấy (mặc định:False
)
- Trả về
recordset với số lượng records thỏa mãn điều kiện tìm kiếm. Số lượng này nhiều nhất bằng với giá trị tham số
limit
.
- Báo lỗi
AccessError: Nếu người dùng bỏ qua các quy tắc truy cập để tạo trên đối tượng được yêu cầu.
Ví dụ, tìm những student có giới tính là nam giới. Chỉ lấy nhiều nhất 8 records và sắp xếp thứ tự theo tên student
>>> self.env['education.student'].search([('gender', '=', 'male')], limit=8, order='name') education.student(1, 3, 5, 7, 9, 11, 13, 15) >>> self.env['education.student'].search([('gender', '=', 'male')], limit=8, order='name', count=True) 8
Model
search_count
([args]) -> intTrả về tổng số
int
records ở model hiện tại. Các records này phải thỏa mãn điều kiện Search Domain truyền vào (Tham khảo thêm Search Domain)- Tham số
args
(list(tuple)): search domain. Truyền vào một list rỗng[]
để tìm tất cả các records.
- Trả về
recordset với số lượng records thỏa mãn điều kiện tìm kiếm.
>>> self.env['education.student'].search_count([('gender', '=', 'male')]) 8
Model
name_search
([name=’’, args=None, operator=’ilike’, limit=100]) -> recordsTìm kiếm các records có
tên hiển thị
name
khớp với định dạng mẫu của tên (tham số truyền vào) khi so sánh với toán tử truyền vàođồng thời khớp với search domain (tham số truyền vào)
Example
Trên giao diện của student có trường
class_id
thuộc kiểuMany2one
. Trường này có nhiều lựa chọn với danh sách tên các lớp học. Phương thứcname_search
giúp người dùng có thể nhập tên lớp học (text) vào để tìm kiếm nhanh.Phương thức này tương đương với việc sử dụng phương thức
search()
vớisearch domain
dựa trêndisplay_name
. Sau đó sử dụng phương thứcname_get()
lên kết quả trả về từsearch()
.- Tham số
name
(str): định dạng tên để khớp tìm kiếmargs
(list(tuple)): search domain tùy chọn (không bắt buộc). (Tham khảo thêm search)operator
(str): toán tử domain dùng để khớp với định dạng tên truyền vàoname
, ví dụ'like'
,'ilike'
hoặc'='
limit
(int): số lượng giới hạn các records được trả về (không bắt buộc)
- Trả về
list của các tuple
(id, tên_hiển_thị_record)
với tất cả các records khớp với điều kiện tìm kiếm.
Ví dụ:
>>> self.env[education.student].name_search('Agatha Christie', [('gender', '=', 'female')]) [(7, 'Agatha Christie')]
Model
read
([fields])Xem nội dung các trường được yêu cầu. Lưu ý phương thức low level này được sử dụng kết hợp với phương thức RPC. Còn trong model thông thường, khuyến khích sử dụng phương thức
browse
.- Tham số
fields
: list tên các trường cần xem nội dung. (để trống nếu muốn xem tất cả)
- Trả về
list các dictionary với các key là tên trường và value là nội dung tương ứng. Mỗi mội dictionary tương ứng một record.
- Báo lỗi
AccessError: Nếu người dùng không có quyền ĐỌC với record yêu cầu.
>>> student_ha001.read() {name: 'Hank Aaron', student_code: 'HA001', gender: 'male', class_id: 5, school_id: 1, state: 'new', ...} >>> student_ha001.read(['name', 'gender']) {'name': 'Hank Aaron', 'gender': 'male'}
Model
read_group
(domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True)Lấy về danh sách các records trên giao diện list (hoặc có thể hình dung là Tree view). Các records trả về này được nhóm theo các trường
groupby
.- Tham số
domain
(list): Search Domain. Để list rỗng[]
để khớp toàn bộ records.fields
(list): list các trường sẽ có mặt trên kết quả trả về (records). Mỗi phàn tử: hoặc là tên trường, ‘field’ (hay dùng); hoặc là ‘field:agg.function()’; hoặc là ‘name:agg.function(field)’groupby
(list): list các trường mà các records sẽ được nhóm theo.offset
(int): số lượng records bị bỏ qua (không bắt buộc)limit
(int): giới hạn số lượng records được trả về (không bắt buộc)orderby
(str): sắp xếp thứ tự theo trường mong muốn.lazy
(bool): NếuTrue
, kết quả trả về sẽ chỉ được nhóm theo nhóm đầu tiên (nhóm tronggroupby
). Các nhóm còn lại được đặt trong_context
key. NếuFalse
, toàn bộ các groups đều được nhóm cùng lúc.
- Trả về
list các dictionary (mỗi dict tương ứng với một record), bao gồm:
giá trị của các trường được nhóm lại theo
groupby
_domain
: list các tuples tiêu chí của Domain Search_context
: dictionary với đối số như làgroupby
- Báo lỗi
AccessError xảy ra nếu:
người dùng không có quyền khởi tạo đối tượng được yêu cầu
người dùng bỏ qua các quy tắc truy cập để tạo trên đối tượng được yêu cầu
Fields/Views¶
Model
fields_get
([fields], [atributes])Xem nội dung thiết lập của thuộc tính trên các trường yêu cầu.
Kêt quả trả về là một dictionary (đánh indexed theo trường tên), trong đó:
Key
(str): là tên các trường trong model, ví dụ ``’name’``, ``’class_id’``, …Value
(dict): một dictionary - là giá trị của từng Key trên, trong đó:Key
(str): là tên các thuộc tính trên trường tương ứng, ví dụ'string'
,'invisible'
,'default'
, …Value
là giá trị được lưu với từng thuộc tính trên.
Lưu ý:
kết quả trả về cũng bao gồm tất cả các trường kế thừa.
Thuộc tính
string
,help
vàselection
sẽ được phiên dịch (nếu có).
- Tham số
fields
: list tên các trường mong muốn. Để trống nếu muốn lấy kết quả trên tất cả các trường.attributes
: list các thuộc tính giới hạn trả về cho từng field. Để trống nếu muốn trả về tất cả các thuộc tính.
>>> self.env['education.student'].fields_get(['name', 'class_id']) {'class_id': {'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.class', 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Class', 'type': 'many2one'}, 'name': {'change_default': False, 'company_dependent': False, 'depends': ('partner_id.name',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'name'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Name', 'translate': False, 'trim': True, 'type': 'char'}}
Model
fields_view_get
([view_id / view_type=’form’])Xem thành phần cấu tạo của View yêu cầu như là:
arch
: Kiến trúc xây dựng lên View này (trả về nội dung cả cả đoạn XML code)base_model
: tên model gốcfields
: các trường trong modelmodel
: tên model hiện tạiname
: tên của View IDtype
: kiểu View (form, tree, …)view_id
: sốid
- Tham số
view_id
(int): sốid
của View yêu cầu hoặcNone
view_type
(str): kiểu của View yêu cầu (‘form’, ‘tree’, …) nếuview_id
là Nonetoolbar
(bool): True để bao gồm cả những hàng đồng theo ngữ cảnh
- Trả về
Dictionary cấu tạo thành phần của View yêu cầu (bao gồm cả các View kế thừa và phần mở rộng)
- Báo lỗi
AttributeError
xảy ra nếu:View kế thừa có những phần không đặt ở 1 trong 4 vị trí ‘before’, ‘after’, ‘inside’, ‘replace’
Có tag khác ngoài tag ‘position’ có mặt ở Parent View.
AttributeError
xảy ra nếu có kiểu View ngoài Form, Tree, Calendar, Search, … được định nghĩa trên cấu trúc View.
>>> student_tree_view_id_integer = self.env.ref('viin_education.education_student_view_form').id >>> self.env['education.student'].fields_view_get(view_id=student_tree_view_id_integer) {'model': 'education.student', 'field_parent': False, 'arch': '<form string="Student Form">\n<header>\n<field name="state" widget="statusbar" modifiers="{}"/>\n</header>\n<sheet>\n<field name="image_1920" widget="image" class="oe_avatar" options="{"preview_image": "image_128"}" on_change="1" modifiers="{}"/>\n<div class="oe_title">\n<h1>\n<field name="name" placeholder="Full Name" required="True" on_change="1" modifiers="{"required": true}"/>\n</h1>\n</div>\n<group>\n<group>\n<span class="o_form_label o_td_label" name="address_name">\n<b>Address</b>\n</span>\n<div class="o_address_format">\n<field name="street" placeholder="Street..." class="o_address_street" on_change="1" modifiers="{}"/>\n<field name="street2" placeholder="Street 2..." class="o_address_street" on_change="1" modifiers="{}"/>\n<field name="district_id" placeholder="District" class="o_address_street" on_change="1" can_create="true" can_write="true" modifiers="{}"/>\n<field name="city" placeholder="City" class="o_address_city" on_change="1" modifiers="{}"/>\n<field name="state_id" class="o_address_state" placeholder="State" on_change="1" can_create="true" can_write="true" modifiers="{}"/>\n<field name="zip" placeholder="ZIP" class="o_address_zip" on_change="1" modifiers="{}"/>\n<field name="country_id" placeholder="Country" class="o_address_country" on_change="1" can_create="true" can_write="true" modifiers="{}"/>\n</div>\n<field name="ethnicity_id" placeholder="Ethnicity" context="{\'default_country_id\': country_id}" on_change="1" can_create="true" can_write="true" modifiers="{}"/>\n<field name="nationality_id" options="{\'no_create\': True, \'no_open\': True}" on_change="1" can_create="true" can_write="true" modifiers="{}"/>\n</group>\n<group>\n<field name="student_code" modifiers="{}"/>\n<field name="phone" widget="phone" on_change="1" modifiers="{}"/>\n<field name="mobile" widget="phone" on_change="1" modifiers="{}"/>\n<field name="email" widget="email" context="{\'gravatar_image\': True}" on_change="1" modifiers="{}"/>\n<field name="dob" on_change="1" modifiers="{}"/>\n<field name="gender" on_change="1" modifiers="{}"/>\n<field name="class_group_id" on_change="1" can_create="true" can_write="true" modifiers="{}"/>\n<field name="class_id" on_change="1" can_create="true" can_write="true" modifiers="{}"/>\n<field name="responsible_id" can_create="true" can_write="true" modifiers="{}"/>\n</group>\n</group>\n<notebook>\n<page name="general" string="General Information">\n<group>\n<group>\n<field name="student_level_id" can_create="true" can_write="true" modifiers="{}"/>\n</group>\n<group>\n<field name="school_id" can_create="true" can_write="true" modifiers="{"readonly": true}"/>\n<field name="company_id" on_change="1" can_create="true" can_write="true" invisible="1" modifiers="{"invisible": true}"/>\n</group>\n</group>\n</page>\n<page name="school_year" string="School Year">\n<field name="school_stage_ids" modifiers="{}">\n</field>\n</page>\n<page name="parents" string="Parents">\n<field name="parent_ids" modifiers="{}">\n</field>\n</page>\n<page name="documents" string="Attached Documents">\n<group>\n<field name="attachment_ids" widget="many2many_binary" can_create="true" can_write="true" modifiers="{}"/>\n</group>\n</page>\n</notebook>\n</sheet>\n<div class="oe_chatter">\n<field name="message_follower_ids" widget="mail_followers" modifiers="{}"/>\n<field name="activity_ids" widget="mail_activity" modifiers="{}"/>\n<field name="message_ids" widget="mail_thread" modifiers="{}"/>\n</div>\n</form>', 'name': 'education.student.form', 'type': 'form', 'view_id': 604, 'base_model': 'education.student', 'fields': {'state': {'id': None, 'select': None, 'views': {}, 'type': 'selection', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'selection': [('new', 'New'), ('studying', 'Studying'), ('off', 'Off')], 'sortable': True, 'store': True, 'string': 'Status'}, 'image_1920': {'id': None, 'select': None, 'views': {}, 'type': 'binary', 'attachment': False, 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.image_1920',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'image_1920'), 'required': False, 'searchable': True, 'sortable': False, 'store': False, 'string': 'Image'}, 'name': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.name',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'name'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Name', 'translate': False, 'trim': True}, 'street': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.street',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'street'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Street', 'translate': False, 'trim': True}, 'street2': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.street2',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'street2'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Street2', 'translate': False, 'trim': True}, 'district_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': ('partner_id.district_id',), 'domain': "[('state_id', '=?', state_id)]", 'manual': False, 'readonly': False, 'related': ('partner_id', 'district_id'), 'relation': 'res.country.district', 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'District'}, 'city': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.city',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'city'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'City', 'translate': False, 'trim': True}, 'state_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': ('partner_id.state_id',), 'domain': "[('country_id', '=?', country_id)]", 'manual': False, 'readonly': False, 'related': ('partner_id', 'state_id'), 'relation': 'res.country.state', 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'State'}, 'zip': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.zip',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'zip'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Zip', 'translate': False, 'trim': True}, 'country_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': ('partner_id.country_id',), 'domain': [], 'manual': False, 'readonly': False, 'related': ('partner_id', 'country_id'), 'relation': 'res.country', 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Country'}, 'ethnicity_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': ('partner_id.ethnicity_id',), 'domain': "[('country_id', '=?', country_id)]", 'manual': False, 'readonly': False, 'related': ('partner_id', 'ethnicity_id'), 'relation': 'res.partner.ethnicity', 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Ethnicity'}, 'nationality_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': ('partner_id.nationality_id',), 'domain': [], 'manual': False, 'readonly': False, 'related': ('partner_id', 'nationality_id'), 'relation': 'res.country', 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Nationality'}, 'student_code': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Student Code', 'translate': False, 'trim': True}, 'phone': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.phone',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'phone'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Phone', 'translate': False, 'trim': True}, 'mobile': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.mobile',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'mobile'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Mobile', 'translate': False, 'trim': True}, 'email': {'id': None, 'select': None, 'views': {}, 'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.email',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'email'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Email', 'translate': False, 'trim': True}, 'dob': {'id': None, 'select': None, 'views': {}, 'type': 'date', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.dob',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'dob'), 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Birth Day'}, 'gender': {'id': None, 'select': None, 'views': {}, 'type': 'selection', 'change_default': False, 'company_dependent': False, 'depends': ('partner_id.gender',), 'manual': False, 'readonly': False, 'related': ('partner_id', 'gender'), 'required': False, 'searchable': True, 'selection': [('male', 'Male'), ('female', 'Female'), ('other', 'Other')], 'sortable': True, 'store': False, 'string': 'Gender'}, 'class_group_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.class.group', 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Class Group'}, 'class_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.class', 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Class'}, 'responsible_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'help': 'Responsible for this student.', 'manual': False, 'readonly': False, 'relation': 'res.users', 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Responsible'}, 'student_level_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'student.level', 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Level'}, 'school_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': ('class_id.school_id',), 'domain': [], 'manual': False, 'readonly': True, 'related': ('class_id', 'school_id'), 'relation': 'education.school', 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'School'}, 'company_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': ('partner_id.company_id',), 'domain': [], 'manual': False, 'readonly': False, 'related': ('partner_id', 'company_id'), 'relation': 'res.company', 'required': False, 'searchable': True, 'sortable': True, 'store': False, 'string': 'Company'}, 'school_stage_ids': {'id': None, 'select': None, 'views': {'tree': {'arch': '<tree editable="bottom">\n<field name="class_id" can_create="true" can_write="true" modifiers="{"required": true}"/>\n<field name="school_year_id" on_change="1" can_create="true" can_write="true" modifiers="{"required": true}"/>\n<field name="start_date" optional="hide" modifiers="{"readonly": true}"/>\n<field name="end_date" optional="hide" modifiers="{"readonly": true}"/>\n<field name="state" modifiers="{"required": true}"/>\n</tree>\n', 'fields': {'class_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.class', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Class'}, 'school_year_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.school.year', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'School year'}, 'start_date': {'id': None, 'select': None, 'views': {}, 'type': 'date', 'change_default': False, 'company_dependent': False, 'depends': ('school_year_id.start_date',), 'manual': False, 'readonly': True, 'related': ('school_year_id', 'start_date'), 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Start Date'}, 'end_date': {'id': None, 'select': None, 'views': {}, 'type': 'date', 'change_default': False, 'company_dependent': False, 'depends': ('school_year_id.end_date',), 'manual': False, 'readonly': True, 'related': ('school_year_id', 'end_date'), 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'End Date'}, 'state': {'id': None, 'select': None, 'views': {}, 'type': 'selection', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': True, 'searchable': True, 'selection': [('new', 'New'), ('studying', 'Studying'), ('finished', 'Finished'), ('off', 'Off')], 'sortable': True, 'store': True, 'string': 'State'}}}, 'form': {'arch': '<form>\n<group>\n<field name="class_id" can_create="true" can_write="true" modifiers="{"required": true}"/>\n</group>\n<group>\n<field name="school_year_id" on_change="1" can_create="true" can_write="true" modifiers="{"required": true}"/>\n<field name="state" modifiers="{"required": true}"/>\n</group>\n</form>\n', 'fields': {'class_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.class', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Class'}, 'school_year_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.school.year', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'School year'}, 'state': {'id': None, 'select': None, 'views': {}, 'type': 'selection', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': True, 'searchable': True, 'selection': [('new', 'New'), ('studying', 'Studying'), ('finished', 'Finished'), ('off', 'Off')], 'sortable': True, 'store': True, 'string': 'State'}}}}, 'type': 'one2many', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'student.school.stage', 'relation_field': 'student_id', 'required': False, 'searchable': True, 'sortable': False, 'store': True, 'string': 'Stage'}, 'parent_ids': {'id': None, 'select': None, 'views': {'tree': {'arch': '<tree editable="bottom">\n<field name="parent_id" context="{\'default_is_parent\': True}" can_create="true" can_write="true" modifiers="{"required": true}"/>\n<field name="is_contact_person" widget="boolean_toggle" modifiers="{}"/>\n<field name="relationship_id" can_create="true" can_write="true" modifiers="{"required": true}"/>\n</tree>\n', 'fields': {'parent_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [('is_parent', '=', True)], 'manual': False, 'readonly': False, 'relation': 'res.partner', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Parent'}, 'is_contact_person': {'id': None, 'select': None, 'views': {}, 'type': 'boolean', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Is Contact Person'}, 'relationship_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.relationship', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Relationship'}}}, 'form': {'arch': '<form>\n<group>\n<field name="parent_id" can_create="true" can_write="true" modifiers="{"required": true}"/>\n</group>\n<group>\n<field name="relationship_id" can_create="true" can_write="true" modifiers="{"required": true}"/>\n</group>\n</form>\n', 'fields': {'parent_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [('is_parent', '=', True)], 'manual': False, 'readonly': False, 'relation': 'res.partner', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Parent'}, 'relationship_id': {'id': None, 'select': None, 'views': {}, 'type': 'many2one', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'education.relationship', 'required': True, 'searchable': True, 'sortable': True, 'store': True, 'string': 'Relationship'}}}}, 'type': 'one2many', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'student.relation', 'relation_field': 'student_id', 'required': False, 'searchable': True, 'sortable': False, 'store': True, 'string': 'Parents'}, 'attachment_ids': {'id': None, 'select': None, 'views': {}, 'type': 'many2many', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'manual': False, 'readonly': False, 'relation': 'ir.attachment', 'required': False, 'searchable': True, 'sortable': False, 'store': True, 'string': 'Attachments'}, 'message_follower_ids': {'id': None, 'select': None, 'views': {}, 'type': 'one2many', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'groups': 'base.group_user', 'manual': False, 'readonly': False, 'relation': 'mail.followers', 'relation_field': 'res_id', 'required': False, 'searchable': True, 'sortable': False, 'store': True, 'string': 'Followers'}, 'activity_ids': {'id': None, 'select': None, 'views': {}, 'type': 'one2many', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'groups': 'base.group_user', 'manual': False, 'readonly': False, 'relation': 'mail.activity', 'relation_field': 'res_id', 'required': False, 'searchable': True, 'sortable': False, 'store': True, 'string': 'Activities'}, 'message_ids': {'id': None, 'select': None, 'views': {}, 'type': 'one2many', 'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [('message_type', '!=', 'user_notification')], 'manual': False, 'readonly': False, 'relation': 'mail.message', 'relation_field': 'res_id', 'required': False, 'searchable': True, 'sortable': False, 'store': True, 'string': 'Messages'}}}
>>> self.env['education.student'].fields_view_get(view_type='tree') {'arch': '<tree string="Student Tree">\n' '<field name="name" on_change="1" modifiers="{}"/>\n' '<field name="phone" optional="hide" on_change="1" modifiers="{}"/>\n' '<field name="mobile" optional="show" on_change="1" modifiers="{}"/>\n' '<field name="email" optional="show" on_change="1" modifiers="{}"/>\n' '<field name="city" optional="hide" on_change="1" modifiers="{}"/>\n' '<field name="state" optional="show" modifiers="{}"/>\n' '<field name="student_level_id" optional="hide" can_create="true" ' 'can_write="true" modifiers="{}"/>\n' '<field name="company_id" on_change="1" can_create="true" ' 'can_write="true" invisible="1" modifiers="{"invisible": ' 'true, "column_invisible": true}"/>\n' '</tree>', 'base_model': 'education.student', 'field_parent': False, 'fields': {'name': {'change_default': False, 'company_dependent': False, 'depends': ('partner_id.name',), 'id': None, 'manual': False, 'readonly': False, 'related': ('partner_id', 'name'), 'required': False, 'searchable': True, 'select': None, 'sortable': True, 'store': False, 'string': 'Name', 'translate': False, 'trim': True, 'type': 'char', 'views': {}}, 'state': {'change_default': False, 'company_dependent': False, 'depends': (), 'id': None, 'manual': False, 'readonly': False, 'required': False, 'searchable': True, 'select': None, 'selection': [('new', 'New'), ('studying', 'Studying'), ('off', 'Off')], 'sortable': True, 'store': True, 'string': 'Status', 'type': 'selection', 'views': {}}, 'student_level_id': {'change_default': False, 'company_dependent': False, 'context': {}, 'depends': (), 'domain': [], 'id': None, 'manual': False, 'readonly': False, 'relation': 'student.level', 'required': False, 'searchable': True, 'select': None, 'sortable': True, 'store': True, 'string': 'Level', 'type': 'many2one', 'views': {}}, ...}, 'model': 'education.student', 'name': 'education.student.tree', 'type': 'tree', 'view_id': 603}
Search Domain¶
Search Domain là danh sách các tiêu chuẩn. Mỗi tiêu chuẩn có cấu trúc là một List hoặc Tuple có 3 phần tử, (field_name, operator, value), trong đó:
Field_name (str)¶
Là tên một trường của một model, ví dụ
'name'
Hoặc là một kiểu “chấm” trường với 1 trường quan hệ
Many2one
, ví dụ:'class_id.name'
Warning
Lưu ý: “chấm” trường Chỉ dùng được trên Python code. Không dùng được trên XML code
Operator (str)¶
Toán tử sử dụng để so sánh field_name
với giá trị value
.
- Toán tử
=
:Bằng
!=
:Không bằng
>
:Lớn hơn
>=
:Lớn hơn hoặc bằng
<
:Nhỏ hơn
<=
:Nhở hơn hoặc bằng
=?
:Không tính hoặc bằng (trả về
True
nếuvalue
làNone``hoặc ``False
, còn lại thì hoạt động như=
)
=like
:Khớp
field_name
với pattern củavalue
. Không quan tâm chữ hoa chữ thường.Dấu gạch dưới
_
trong partern đó có tác dụng khớp với bất kỳ ký tự nào.Dấu phần trăm
%
trong pattern đó có tác dụng khớp với bất kỳ chuỗi string không có hoặc có nhiều ký tự.
Ví dụ:
('name', '=like', '_ank Aaron')
trả về ‘Hank Aaron’, ‘hank aaron’, ‘hAnK aAROn’, …
like
:Khớp
field_name
với pattern của%value%
. Khá giống với=like
nhưng có dấu%
ở đầu và cuối.Ví dụ:
('name', '=like', 'nk Aar')
trả về ‘Hank Aaron’, ‘hank aaron’, ‘hAnK aAROn’, ‘TiohaNK Aaronie’, …
not like
:Ngược lại với
like
ilike
:Tương tự
like
, nhưng khớp chính xác chữ hoa chữ thường.Ví dụ:
('name', '=like', 'Hank Aaron')
trả về ‘Hank Aaron’, ‘Mc Hank Aaron’, ‘TioHank Aaronie’, …
not ilike
:Ngược lại với
ilike
=ilike
:Tương tự
=like
, nhưng yêu cầu khớp chính xác chữ hoa chữ thường.
in
:Kiểm tra giá trị của
field_name
có bằng với 1 trong các giá phần tử của listvalue
hay không. Yêu cầuvalue
phải là một list
not in
:Ngược lại của
in
child_of
:Toán tử phả hệ. Kiểm tra trường
field_name
có phải là “con” của recordvalue
(có thể là 1 phần tử hay một list các phần tử).Xem xét ngữ nghĩa của model, tức là dựa theo trường quan hệ, đặt tên bởi
_parent_name
(thuộc tính của model).
parent_of
:Toán tử phả hệ. Kiểm tra trường
field_name
có phải là “cha” của recordvalue
(có thể là 1 phần tử hay một list các phần tử).Xem xét ngữ nghĩa của model, tức là dựa theo trường quan hệ, đặt tên bởi
_parent_name
(thuộc tính của model).
Value¶
Kiểu của biến. Yêu cầu phải tương thích (qua operator
) với trường tên tương ứng.
Multiple Domain¶
Có thể áp dụng một hoặc nhiều tiêu chuẩn cho một Domain Search. Các Domain được gôp chung bằng việc sử dụng các toán tử logic dưới dạng *Đảo dấu lên đầu* (prefix form):
- Logical operator
'&'
: Toán tử logicAND
'|'
: Toán tử logicOR
'!'
: Toán tử logicNOT
Công thức đảo chiều chung khi kết hợp 2 hoặc nhiều tiêu chí (domain):
Toán tử mặc dịnh là
'&'
. Nên nếu không thực sự cần thiết thì có thể bỏ quaA & B
‣&, A, B
A & B | C
‣A &, |, B, C
‣&, A, |, B, C
Tip
Khi đổi vị trí toán tử từ trong ra ngoài, ưu tiên các toán tử nhóm 2 vế nội bộ trước, ví dụ ưu tiên thao tác với vế
B | C
trước, sau đó mới thao tác tớiA &
sau cùng.Khi đọc search domain để phân tích và hiểu nội dung, chúng ta cần đọc từ phải phải sang trái hoặc từ dưới lên trên.
Example
Tìm tất cả các học sinh:
đang học
state is ‘studying’
có lớp học
class_id is NOT False
thuộc nhóm lớp hoặc ‘Information Technology’ hoặc ‘Business Administration’ (tương ứng id là 1 hoặc 2)
class_group_id.name is ‘Information Technology’ OR ‘Business Administration’
state == 'studying' AND class_id != False AND (class_group_id.name == 'Information Technology' OR class_group_id.name == 'Business Administration')
Sử dụng Domain Search:
# on Model field - Python code
[('state', '=', 'studying'),
('class_id', '!=', False),
'|', ('class_group_id.name', '=', 'Information Technology'),
('class_group_id.name', '=', 'Business Administration')]
# on View - XML code
[('state', '=', 'studying'),
('class_id', '!=', False),
'|', ('class_group_id', '=', 1),
('class_group_id', '=', 2)]
Chú ý:
Trên code của Domain Search chỉ có duy nhất dấu
'|'
. Lý do là vì toán tử mặc dịnh là'&'
(AND
) nên không cần sử dụng ở trường hợp này và vẫn đảm bảo đúng logic. Ví dụ nếu viết đầy đủ:['&', '&', ('state', '=', 'studying'), ('class_id', '!=', False), '|', ('class_group_id', '=', 1), ('class_group_id', '=', 2)]
Unlink¶
Model
unlink
()Xóa một hoặc nhiều records.
- Báo lỗi
AccessError xảy ra nếu:
người dùng không có quyền khởi tạo đối tượng được yêu cầu
người dùng bỏ qua các quy tắc truy cập để tạo trên đối tượng được yêu cầu
UserError: Nếu record đang bị xóa là property mặc định của các record khác.
Toán tử trên Recordset¶
Recordset có tính chất bất biến. Tuy nhiên, có thể sử dụng những toán tử dưới đây để kết hợp sets của model đó để trả về một recordset mới.
record in set
:trả về
True
nếurecord
cần kiểm tra (yêu cầu phải là 1 record hoặc recordset có duy nhất 1 record) có mặt trong tập các recordsset
hay không.
record not in set
:Ngược lại của
record in set
set1 <= set2
vàset1 < set2
:Trả về
True
nếuset1
là tập con củaset2
set2 >= set2
vàset2 > set2
:Trả về
True
nếuset1
là tập cha củaset2
set1 | set2
:trả về 1 recordset mới chứa toàn các records có mặt trong cả
set1
vàset2
. Recordset mới không chứa record trùng lặp.
set1 & set2
:trả về 1 recordset mới chỉ chứa các records có mặt ở cả 2 set,
set1
vàset2
.
set1 - set2
:trả về 1 recordset mới chỉ chứa các records có mặt ở
set1
nhưng không có mặt ởset2
Filter¶
Model
filtered
(func)Lọc những records trong 1 recordset. Những records này phải thỏa mãn điều kiện đưa ra trong
func
.- Tham số
func
(callable hoặc str): một hàm hoặc một chuỗi các “chấm” tên trường.
- Trả về
recordset của các records thỏa mãn
func
, hoặc có thể trả về recordset rỗng.
students = self.env['education.student'].search([]) # Only keep new students students.filtered(lambda student: student.state == 'new') # Only keep students that have teacher students.filtered(partner_id.is_teacher)
Note
Không thể dùng
filtered
để lọc trên Database. Sử dụng phương thứcsearch
để làm điều đó.Model
filtered_domain
(domain)Tương tự
filtered
:students = self.env['education.student'].search([]) # Only keep new students students.filtered_domain([('state', '=', 'new')])
Map¶
Model
mapped
(func)Áp dụng hàm
func
lên từng record trong 1 recordset.Kết quả trả về
là 1 list. List trả về có thứ tự các phần tử giống với thứ tự của recordset gốc.
là 1 recordset (nếu
func
trả về recordset). Thứ tự các phần tử trả về là không đồng nhất với thứ tự của recordset gốc.
- Tham số
func
(callable hoặc str): một hàm hoặc một chuỗi các “chấm” tên trường.
- Trả về
list hoặc recordset.
# return a list of combi: student name + class name students.mapped(lambda student: '%s, %s' % (student.name, student.class_id.name)) # return a list of student names students.mapped('name') # return a recordset of partner students.mapped('partner_id') # return a recordset of all students that have relationship with a partner, with duplicated removed students.mapped('partner_id.student_ids')
Tip
Khi chấm đến trường quan hệ thì không cần dùng
mapped
>>> students.partner_id # == students.mapped('partner_id') >>> students.partner_id.student_ids # == students.mapped('partner_id.student_ids') >>> students.partner_id.mapped('name') # == student.mapped('partner_id.name')
Sort¶
Model
sorted
(key=None, reverse=False)Sắp xếp recordset theo thứ tự của
key
.- Tham số
key
(callable hoặc str hoặc None): * hoặc là 1 hàm của 1 đối số trả về key so sánh cho mỗi bản ghi * hoặc là tên một trường cơ bản trên model (không hỗ trợ chấm trường quan hệ) * hoặc là giá trị None. Trường hợp này sẽ sắp xếp theo thứ tự mặc địnhreverse
(bool): nếuTrue
, kết quả trả về được sắp xếp theo thứ tự đảo ngược.
# sort students by name students.sorted(key=lambda r: r.name) # also ok students.sorted(key='name') # sort students by code_name or name students.sorted(key=lambda r: r.student_code or r.name) # just sort students by default order students.sorted()
Quản lý Error¶
Module Exceptions của Odoo cung cấp sẵn cho chúng ta một số các kiểu lỗi trường hợp ngoại lệ.
RPC layer chấp nhận những kiểu ngoại lệ này. Tất cả những kiểu ngoại lệ khác (có thể từ nguồn ngoài Odoo) sẽ được coi là ‘Server Error’
Note
Nếu bạn muốn định nghĩa 1 kiểu lỗi ngoại lệ riêng, tham khảo thêm module odoo.addons.test_exceptions
.
Các kiểu exceptions:
odoo.exceptions.AccessDenied (message=’Access Denied’)
Trường hợp đăng nhập Login/password error.
Note
Không bao gồm traceback (không print dấu vết sau khi exception xảy ra - không bao gồm thông báo lỗi, số dòng gây ra lỗi và call stack của function gây lỗi).
Example
Khi người dùng đăng nhập với mật khẩu không đúng.
odoo.exceptions.AccessError (message)
Trường hợp lỗi với quyền truy cập
Example
Khi người dùng truy cập đến record nhưng không được cấp quyền truy cập.
odoo.exceptions.CacheMiss (record, field)
Không tìm thấy giá trị trong cache
Example
Khi đọc một giá trị đã được đẩy từ cache lên database.
odoo.exceptions.MissingError (message)
Một hoặc nhiều records không tồn tại
Example
Khi cập nhật giá trị trường của một record đã bị xóa (không còn tồn tại trong database)
odoo.exceptions.RedirectWarning (message, action, button_text, additional_context=None)
Cảnh báo với tính năng có thể redirect người dùng thay vì chỉ đơn giản là hiển thị một thông báo cảnh báo.
- tham số
message
(str): nội dung cảnh báoaction_id
(int): id của hành động khi thực hiện việc dịch chuyểnbutton_text
(str): Tên của button sẽ kích hoạt việc dịch chuyểnadditional_context
(dict): tham số truyền tới action_id. Ví dụ, có thể được dùng để giới hạn một giao diện đối với active_ids
odoo.exceptions.UserError (message)
Lỗi nói chung do client tạo ra, phát sinh trong lúc phát triển tính năng module mới, và quản lý.
Điển hình là khi người dùng cố gắng làm điều gì đó:
Vô nghĩa với trạng thái hiện tại của bản ghi.
Sai về mặt logic / luồng nghiệp vụ.
So sánh về mặt ngữ nghĩa với 400 mã trạng thái HTTP chung.
odoo.exceptions.ValidationError (message)
Vi phạm các ràng buộc của Python
Example
Khi tạo một record mới, người dùng nhập giá trị không hợp lệ cho một trường không được chọn