Basic Server-Side Development¶
Ở phần Application Models chúng ta đã được học cách khai báo và mở rộng các model, sử dụng các trường dữ liệu (fields), trường tính toán (compute fields) cũng như các cách để ràng buộc trường giá trị trong một ứng dụng cụ thể. Phần này tập trung vào việc định nghĩa ra các phương thức (methods), thao tác với tập dữ liệu (recordset) và mở rộng các phương thức thừa kế.
Định nghĩa phương thức và sử dụng API decorators¶
Phần này chúng ta sẽ học cách định nghĩa ra một phương thức và gọi nó bằng một button trên giao diện người dùng hoặc gọi nó từ một hàm khác.
Chuẩn bị¶
Chúng ta tiếp tục sử dụng add-on viin_education đã tạo ở Creating Odoo Add-On Modules. Bạn cần thêm
trường state vào model education.student
, đoạn code sẽ như sau:
class EducationStudent(models.Model):
_name = 'education.student'
# ...
state = fields.Selection(string='Status', selection=[('new', 'New'),
('studying', 'Studying'),
('off', 'Off')], default='new')
Tham khảo phần Thêm models trong Creating Odoo Add-On Modules để biết thêm thông tin.
Các bước thực hiện¶
Giả sử chúng ta muốn định nghĩa ra một phương thức có thể thay đổi trạng thái của các học sinh được chọn, hãy thêm đoạn code
sau vào education.student
.
Đầu tiên tạo một helper method để kiểm tra xem đầu vào của trạng thái có hợp lệ hay không.
@api.model def is_allowed_state(self, current_state, new_state): allowed_states = [('new', 'studying'), ('studying', 'off'), ('off', 'studying'), ('new', 'off')] return (current_state, new_state) in allowed_states
Bước 2 tạo một phương thức cho phép thay đổi state mới.
def change_student_state(self, state): for student in self: if student.is_allowed_state(student.state, state): student.state = state else: continue
Bước 3 tạo các phương thức để thay đổi sang một trạng thái mới tương ứng.
def change_to_new(self): self.change_student_state('new') def change_to_studying(self): self.change_student_state('studying') def change_to_off(self): self.change_student_state('off')
Thêm các button và field status như đoạn code dưới đây vào header của form view. Nó sẽ gọi các method ở phía trên từ giao diện người dùng.
<form> ... <header> ... <button name="change_to_new" type="object" string="New"/> <button name="change_to_studying" type="object" string="Studying"/> <button name="change_to_off" type="object" string="Off"/> <field name="state" widget="statusbar"/> ... </header> ... </form>
Nâng cấp module để xem kết quả…
Cơ chế hoạt động¶
Các phương thức trên đều là các phương thức cơ bản của Python, điều đáng chú ý ở đây là một số phương thức sử dụng decorator
đến từ module odoo.api
.
Tip
API decorator ban đầu được Odoo giới thiệu bản 9.0 để hỗ trợ cả framework cũ và mới. Kể từ Odoo 10.0 API cũ không còn được hỗ trợ nữa, tuy nhiên một số decorators như @api.model vẫn còn được sử dụng.
Khi viết một phương thức mới, nếu không sử dụng bất kỳ một decorator nào có nghĩa là phương thức đó đang được thực
thi trên một tập bản ghi (recordset). Điều này có nghĩa là self
ở phương thức này là recordset tham chiếu đến số lượng
bản ghi tương ứng trên cơ sở dữ liệu (có thể bao gồm recordset rỗng). Do đó phải dùng vòng lặp để làm việc với từng bản ghi
cụ thể.
Ngược lại khi dùng @api.model
trên một phương thức, thì self
ở đây chỉ liên quan đến model và không còn liên quan
đến tập bản ghi tương ứng model đó nữa. Nó có khái niệm tương tự với việc sử dụng @classmethod
decorator của Python.
Bước 1 chúng ta đã tạo phương thức is_allowed_state()
. Mục đích của phương thức này để kiểm tra xem trạng
thái muốn thay đổi có cho phép hay không, như ở ví dụ này chúng ta không muốn thay đổi trạng thái của student từ off sang new.
Đó là lý do mà chúng ta không thêm điều kiện (‘off’, ‘new’) vào hàm. Như bạn thấy hàm này không quan tâm đến recordset liên quan
đến model vậy có thể sử dụng @api.model
trong trường hợp này. Lúc này cho dù bạn có 10 bản ghi student, lúc thực thi self
trong hàm cũng chỉ là recordset student rỗng.
Ở bước 2, phương thức change_student_state()
có nhiệm vụ thay đổi trạng thái của student. Khi hàm này được gọi,
nó sẽ thay đổi trạng thái của student với tham số state
đã cho nếu trạng thái hợp lệ. Để ý xem, chúng ta đang sử dụng vòng lặp
vì self
ở đây có thể là nhiều bản ghi.
Bước tiếp theo chúng ta tạo các hàm để chuyển sang các trạng thái tương ứng.
Bước cuối cùng thêm các button vào form view. Khi click vào button này, Odoo sẽ gọi các phương thức trong Python
ứng với giá trị của thuộc tính name
, xem Backend Views để biết thêm chi tiết. Chúng ta cũng thêm trường
state
với widget statusbar
để hiển thị trạng thái của student trên form view. Ví dụ khi người dùng click vào button có name="change_to_studying"
từ giao diện,
phương thức change_to_studying()
ở bước 3 sẽ được gọi.
Thông báo lỗi tới người dùng¶
Trong quá trình thực thi phương thức, đôi khi cần phải hủy bỏ quá trình xử lý vì hành động do người dùng thực hiện không hợp lệ hoặc thỏa mãn điều kiện để xảy ra lỗi. Phần này sẽ hướng dẫn cho bạn cách quản lý những trường hợp đó bằng cách hiển thị ra những thông báo lỗi hữu ích.
Chuẩn bị¶
Tiếp tục phần trước, giả sử instance của bạn đã sẵn sàng và cài sẵn module viin_education
.
Thực hiện thế nào¶
Chúng ta sẽ thay đổi phương thức change_student_state
để hiển thị lỗi cho người dùng khi họ thay đổi trạng thái student
sang một trạng thái không cho phép. Thực hiện như các bước sau đây:
Thêm các import sau đây vào đầu nội dung
education_student.py
from odoo import _ from odoo.exceptions import UserError
Thay đổi phương thức
change_student_state
như sau:def change_student_state(self, state): for student in self: if student.is_valid_state(student.state, state): student.state = state else: raise UserError(_("Changing student status from %s to %s is not allowed.") % (student.state, state))
Cơ chế hoạt động¶
Khi người dùng thay đổi trạng thái của bản ghi student có state không được cho phép, cụ thể là student có trạng thái off
sang new
, trên giao diện phía client sẽ hiển thị một popup như sau:
Mọi ngoại lệ (exception) không được định nghĩa trong odoo.exceptions
sẽ được xử lý như là internal server error (HTTP Status 500)
với stack strace. UserError
sẽ hiển thị thông báo lỗi trên giao diện người dùng. Việc sử dụng raise UserError để
đảm bảo rằng thông điệp cảnh báo hiển thị tới người dùng một cách thân thiện nhất.
Như ví dụ trên nội dung của của UserError đang sử dụng function _()
được định nghĩa trong odoo
. Function này dùng
để đánh dấu rằng đoạn văn bản này có thể dịch. Vui lòng đọc Internationalization để biết thêm chi tiết.
Important
_()
:Ngoài ra còn một số lớp ngoại lệ được định nghĩa trong odoo.exceptions
ValidationError
: Ngoại lệ thường được dùng khi xử lý Python constraint. Vui lòng xem Application Models phần Thêm ràng buộc trong model để biết thêm chi tiết.AccessError
: Lỗi này thường được raise tự động khi người dùng cố gắng truy cập thứ gì đó không được phép. Bạn cũng có thể sử dụng nó một cách thủ công trong đoạn code của mình.RedirectWarning
: Hiển thị popup gồm thông báo lỗi và nút để chuyển đến một action tùy ý.Warning
: Loại bỏ từ phiên bản 9.0 và thay thế bằngUserError
.
Tạo instance của một model bất kỳ¶
Khi sử dụng Odoo, nếu bạn muốn làm việc trên các model khác nhau, không chỉ đơn giản là tạo instance trực tiếp từ class của model đó mà bạn cần lấy một recordset cho model đó mới có thể sử dụng được. Phần này chúng ta sẽ tìm hiểu cách để lấy một recordset rỗng của bất kỳ một model nào trong phương thức của một model.
Chuẩn bị¶
Tiếp tục phần trước, giả sử instance của bạn đã sẵn sàng và cài sẵn module viin_education
.
Chúng ta sẽ viết một phương thức đơn giản trong education.class
để lấy ra tất cả học sinh. Để làm điều này, chúng ta
cần lấy một recordset rỗng của education.student
. Chắc chắn rằng bạn đã thêm model education.class
và access rights
cho model đó. Thêm đoạn code dưới đây, nếu đã có bạn có thể bỏ qua…
Thêm trường class_id
vào model education.student
class EducationStudent(models.Model):
_name = 'education.student'
# ...
class_id = fields.Many2one('education.class', string='Class', ondelete="restrict")
Thêm trường student_ids
vào model education.class
class EducationClass(models.Model):
_name = 'education.class'
# ...
student_ids = fields.One2many('education.student', 'class_id', string='Students')
Các bước thực hiện¶
Trong class
EducationClass
thêm phương thức sau:class EducationClass(models.Model): # ... def get_all_students(self): # Khởi tạo đối tượng education.student (đây là một recordset rỗng của model education.student) student = self.env['education.student'] all_students = student.search([]) print("All Students: ", all_students)
Thêm button sau trong form view lớp học
... <button name="get_all_students" type="object" string="Log All Students" /> ...
Sau khi nâng cấp module, bạn sẽ thấy nút Log All Students trên form view lớp học. Click vào nút này để xem dữ liệu recordset của student trong server log. Kết quả ở ví dụ và của bạn có thể khác nhau tùy vào số lượng bản ghi student mà bạn tạo…
... All Students: education.student(1, 2, 3, 4) ...
Cơ chế hoạt động¶
Khi khởi động, Odoo sẽ load tất cả các module và kết hợp các lớp khác nhau từ Model. Các lớp này được lưu trữ trong Odoo registry,
được lập chỉ mục theo name. Thuộc tính env
của bất kỳ recordset nào được truy cập dưới dạng self.env
, là một instance của
lớp Environment
được định nghĩa trong module odoo.api
Lớp Environment
đóng vai trò cốt lõi trong Odoo, nó cung cấp:
self.env['model_name']
để lấy recordset rỗng của model model_name
(hay còn gọi là tạo đối tượng).self.env.cr
là con trỏ (cursor) của cơ sở dữ liệu để truy vấn Raw SQL.self.env.user
tham chiếu tới user hiện tại đang thực hiện.self.env.context
hoặc self._context
là một dictionary chứa các dữ liệu môi trường.Tạo mới các bản ghi¶
Phần này chúng ta sẽ tìm hiểu cách để tạo mới các bản ghi, đây là điều quan trọng khi viết các phương thức logic nghiệp vụ.
Chuẩn bị¶
Chúng ta sẽ tiếp tục sử dụng module viin_education
, thêm trường student_ids
vào model education.class
, class
EducationClass
sẽ trở thành như sau:
class EducationClass(models.Model):
_name = 'education.class'
_description = 'Education Class'
# ...
student_ids = fields.One2many('education.student', 'class_id', string='Students')
Các bước thực hiện¶
Thêm phương thức
create_classes
vào trongeducation.class
def create_classes(self): # Giá trị để tạo bản ghi student 01 student_01 = { 'name': 'Student 01', } # Giá trị để tạo bản ghi student 02 student_02 = { 'name': 'Student 02' } # Giá trị để tạo bản ghi lớp học class_value = { 'name': 'Class 01', # Đồng thời tạo mới 2 học sinh 'student_ids': [ (0, 0, student_01), (0, 0, student_02) ] } record = self.env['education.class'].create(class_value)
Thêm button Create Classes vào form lớp học:
... <button name="create_classes" type="object" string="Create Classes" /> ...
Cơ chế hoạt động¶
Để tạo một bản ghi mới cho một model, chúng ta có thể gọi phương thức create(values)
ở bất kỳ recorset nào. Phương thức
này trả về 1 recordset mới, với các trường giá trị cụ thể trong values dictionary. Trong dictionary này, key là tên
trường, value là giá trị tương ứng trường đó. Tùy thuộc vào từng loại trường bạn cần phải chuyển đổi type tương ứng cho các
giá trị đó.
Text
: giá trị là chuỗi Python.Float
và Integer
: giá trị là Python float hoặc integer.Boolean
: giá trị là Python bool hoặc integer.Date
: giá trị là Python datetime.date
object.Datetime
: giá trị là Python datetime.datetime
object.Binary
: giá trị là chuỗi mã hóa Base64. Module base64
trong thư viện tiêu chuẩn của Python cung cấp phương thức
encodebytes(bytestring)
cho phép mã hóa một chuỗi trong Base64.Many2one
: là một số kiểu integer - ID của bản ghi quan hệ lưu trong cơ sở dữ liệu.One2Many
và Many2many
: giá trị là một cú pháp đặc biệt, sử dụng các command sau:Lệnh | Chức năng |
|
|
Tạo mới bản ghi có quan hệ với bản ghi chính với |
|
Thay thế toàn bộ danh sách các bản ghi quan hệ hiện tại bằng danh sách mới đã cho |
Khi click vào button Create Classes
ở phía trên, 3 bản ghi sẽ được tạo. Lúc này vào giao diện list view lớp học sẽ xuất
hiện 1 bản ghi có tên Class 01, và 2 students lần lượt là Student 01, Student 02 trong list view Student.
Mở rộng¶
Hàm create
còn hỗ trợ bạn tạo cùng lúc nhiều bản ghi. Để tạo bạn cần truyền list dictionary vào phương thức create
.
Cùng xem đoạn code sau:
...
class_01 = {
'name': 'Class 01'
}
class_02 = {
'name': 'Class 02'
}
record = self.env['education.class'].create([class_01, class_02])
Cập nhật giá trị của bản ghi¶
Logic nghiệp vụ thường yêu cầu chúng ta cập nhật các bản ghi bằng cách thay đổi giá trị của một số lĩnh vực. Phần này chỉ cho bạn cách thay đổi các trường trên bản ghi cụ thể.
Chuẩn bị¶
Chúng ta sẽ tiếp tục sử dụng module viin_education
, sử dụng model education.class
để làm ví dụ…
Các bước thực hiện¶
Thêm phương thức
change_class_name
vào trong modeleducation.class
def change_class_name(self): self.ensure_one() self.name = 'Class 12A1'
Thêm button Create Classes vào form lớp học để tạo bản ghi:
... <button name="change_class_name" type="object" string="Change Name" /> ...
Khởi động lại instance và upgrade module
viin_education
. Click vào nút Change Name, dữ liệu lớp học sẽ thay đổi.
Cơ chế hoạt động¶
Phương thức này bắt đầu bằng việc kiểm tra xem self
có đúng một bản ghi hay không thông qua phương thức ensure_one
.
Nó sẽ raise exception nếu không thỏa mãn điều kiện và quá trình xử lý bị hủy bỏ. Điều này là cần thiết vì chúng tôi không
muốn thay đổi tên của nhiều bản ghi. Nếu bạn muốn update nhiều bản ghi hãy xóa dòng self.ensure_one()
. Cuối cùng là
việc cập nhật tên của lớp học bằng cách thay đổi giá trị của thuộc tính đại diện cho trường name của bản ghi lớp học.
Có 3 cách để cập nhật giá trị của bản ghi:
Gán trực tiếp giá trị cho thuộc tính đại diện trường của bản ghi.
Sử dụng hàm
write
với tham số là một dictionary, đoạn code trên sẽ như sau:def change_class_name(self): self.ensure_one() self.write({ 'name': 'Class 12A1' })
Warning
Hàm này có thể việc với nhiều records, chỉ hoạt động với các bản ghi thật sự tồn tại trong cơ sở dữ liệu. Vui lòng đọc Advanced Server-Side Development Techniques để biết thêm chi tiết.
Sử dụng hàm
update
với tham số là một dictionaryHàm này có thể việc với nhiều records dùng trong các trường hợp đặc biệt, thường được sử dụng với các bản ghi ảo (pseudo-records). Vui lòng đọc Advanced Server-Side Development Techniques để biết thêm chi tiết.
Tương tự như create
, khi cập nhật các trường quan hệ One2many và Many2many cũng có các command tương ứng:
Lệnh |
Chức năng |
|
Tạo mới bản ghi có quan hệ với bản ghi chính với |
|
Cập nhật bản ghi có id = |
|
Loại bỏ bản ghi có id = |
|
Loại bỏ bản ghi có id = |
|
Thêm bản ghi có id = |
|
Loại bỏ toàn bộ bản ghi trong danh sách bản ghi quan hệ. Tương ứng với (3, id, 0) cho mỗi bản ghi cụ thể trong danh sách đó. |
|
Thay thế toàn bộ danh sách bản ghi quan hệ hiện tại bằng danh sách mới đã cho. |
Tìm kiếm bản ghi¶
Tìm kiếm bản ghi cũng là một hoạt động phổ biến và vô cùng quan trọng trong các phương thức logic nghiệp vụ.
Chuẩn bị¶
Tiếp tục sử dụng module viin_education
, chúng tôi sẽ viết phương thức mới có tên find_student
trong model education.student
cho phép tìm kiếm các học sinh có tên chứa “John” hoặc thuộc lớp có tên là “12A1”
Các bước thực hiện¶
Thêm phương thức
find_student
:def find_student(self): ...
Thêm search domain vào phương thức
find_student
:def find_student(self): # miền lọc theo điều kiện: tên có chứa từ John hoặc lớp tên là 12A1 domain = ['|', ('name', 'ilike', 'John'), ('class_id.name', '=', '12A1')]
Gọi phương thức
search
vớidomain
phía trên, nó sẽ trả về recordsetdef find_student(self): domain = ['|', ('name', 'ilike', 'John'), ('class_id.name', '=', '12A1')] students = self.search(domain)
Cơ chế hoạt động¶
Bước 1 định nghĩa method.
Bước 2 tạo biến domain
chứa miền tìm kiếm. Để có giải thích đầy đủ về cú pháp miền tìm kiếm
vui lòng xem Backend Views.
Bước 3 gọi phương thức search()
với domain, do cùng model nên chỉ cần self.search()
là đủ. Nó trả về một tập bản ghi chứa tất cả các bản
ghi khớp với miền điều kiện. Ở ví dụ này chúng tôi chỉ sử dụng search
với domain, tuy nhiên không chỉ có vậy nó còn hỗ trợ
khá nhiều tham số khác nữa:
offset
: Được sử dụng để bỏ qua N bản ghi đầu tiên phù hợp với truy vấn, thường được sử dụng cùng với limit
để
chia nhỏ các bản ghi thành từng phần (mặc định: 0). Ví dụ: phân trang..limit
: Số lượng bản ghi tối đa để trả về (mặc định: None)order
: Sắp xếp các bản ghi được trả về. Mặc định sử dụng thuộc tính _order
của model.count
: True nếu bạn muốn kết quả trả về là số lượng bản ghi (mặc định: False)Note
Sử dụng search_count(domain)
thay vì search(domain, count=True)
để thể hiện nội dung một cách rõ ràng hơn do cả 2
đều cho cùng một kết quả.
Đôi khi bạn muốn tìm kiếm từ 1 model khác, để làm điều này bạn cần lấy recordset model đó (vui lòng đọc Tạo instance của một model bất kỳ).
Ví dụ chúng tôi muốn tìm kiếm các bản ghi trên model hr.employee
def find_employees(self): # tạo đối tượng sale order Employee = self.env['hr.employee'] # tìm kiếm nhân viên là nữ và có họ Nguyễn domain = ['&', ('gender', '=', 'female'), ('name', 'ilike' 'Nguyễn%')] # gọi phương thức search employees = Employee.search(domain)Note
domain
['&', ('gender', '=', 'female'), ('name', 'ilike' 'Nguyễn%')]
và[('gender', '=', 'female'), ('name', 'ilike' 'Nguyễn%')]
đều cho kết quả giống nhau vì mặc định'&'
được tự động thêm nếu không chỉ định.
Trước đó chúng tôi có nói rằng search()
trả về tất cả các bản ghi khớp với điều kiện. Trong một số trường hợp điều này
không thực sự hoàn toàn đúng, ví dụ:
active
kiểu Boolean
nếu muốn lấy tất cả bản ghi thì phải dùng context active_test=False
vì mặc định search chỉ trả về các bản ghi có active=True
self.env['model_name'].with_context(active_test=False).search(domain)
Kết hợp các recordset¶
Chuẩn bị¶
Phần này bạn cần có hai hoặc nhiều bản ghi cùng một model.
Các bước thực hiện¶
Ví dụ với các recordset trên model education.student
education.student(1,) + education.student(2,)
=education.student(1, 2)
education.student(1, 2) | education.student(2, 3)
=education.student(1, 2, 3)
education.student(1, 2) & education.student(2, 3)
=education.student(2, )
education.student(1, 2) - education.student(2, )
=education.student(1, )
education.student(1, 2) >= education.student(1,)
=> True (Tương tự với>
và ngược lại với<=
và<
)education.student(1, 2) <= education.student(4,)
=> False (Tương tự với<
và ngược lại với>=
và>
)
Cơ chế hoạt động¶
education.student(2, )
.education.student(2, )
.See also
Odoo ORM Basic phần Toán tử trên Recordset
Lọc recordset¶
Trong một số trường hợp bạn muốn lọc ra các recordset đã có sẵn với một số tiêu chí mà mình mong muốn. Bạn có thể dùng vòng
lặp để duyệt thủ công từng recordset, tuy nhiên Odoo đã cung cấp phương thức filtered()
để thực hiện việc này một cách
nhanh chóng hơn.
Chuẩn bị¶
Hãy chắc chắn rằng instance của bạn đang chạy và có model education.class
như đã định nghĩa trong Tạo instance của một model bất kỳ.
Các bước thực hiện¶
Ví dụ trong phần này là lọc lớp học có 5 học sinh trở lên. Bạn cần thực hiện các bước sau đây:
Định nghĩa phương thức
classes_has_student
với đầu vào là tập bản ghi lớp học@api.model def classes_has_student(self, all_classes): ...
Gọi phương thức
filtered
với tham số là 1 callback@api.model def classes_has_student(self, all_classes): return all_classes.filtered(lambda c: len(c.student) >= 5)
Bạn có thể kiểm tra kết quả bằng cách print ra server log …
Cơ chế hoạt động¶
filtered(callback)
trả về recordset (có thể rỗng). Nó duyệt tất cả các bản ghi qua vòng lặp để kiểm tra callback
đã
cho return True hay False, nếu True sẽ được thêm vào tập recordset trả về.
Ví dụ trên chúng tôi sử dụng hàm lambda, tuy nhiên bạn cũng có thể sử dụng một hàm riêng biệt như sau:
@api.model
def classes_has_student(self, all_classes):
def has_student(_class):
return len(_class.student) > 5
return all_classes.filtered(has_student)
Hơn nữa bạn cũng có thể truyền tham số cho filtered
với một field. Ví dụ bạn chỉ cần lọc ra các lớp có học sinh:
@api.model
def classes_has_student(self, all_classes):
return all_classes.filtered('student_ids')
Odoo còn cung cấp phương thức filtered_domain(domain)
để lọc recordset qua domain.
Note
filtered
chỉ làm việc với dữ liệu có sẳn của recordset trên bộ nhớ, không truy cập cơ sở dữ liệu để lấy dữ liệu.
Do đó bạn có thể lọc bản ghi thông qua các trường không tồn tại trong cơ sở dữ liệu…
See also
Odoo ORM Basic phần Filter
Sử dụng mapped¶
Các bước thực hiện¶
Để lấy tên của học sinh từ một tập bản ghi lớp học bạn cần thực hiện các bước sau:
Định nghĩa phương thức có tên là
get_student_names()
:@api.model def get_student_names(self, classes): ...
Gọi phương thức
mapped()
để lấy danh sách tên của học sinh.@api.model def get_student_names(self, classes): return classes.mapped('student_ids.name')
Cơ chế hoạt động¶
Chúng tôi gọi tới hàm mapped(path)
để duyệt qua các giá trị name
của từng recordset, path
là một chuỗi bao gồm tên
của trường phân cách bởi dấu chấm. mapped()
có 3 tùy chọn:
# return list student's name ['Student 01', 'Student 02', ...] class_records.student_ids.mapped('name') # hoặc như này đều được class_records.mapped('student_ids.name') # return list class name. Ví dụ: ['Class 01', Class 02'] class_records.mapped('name')
# return recordset of student. ví dụ: education.student(1, 2) ... class_records.mapped('student_ids') # return recordset of school. ví dụ: education.school(1, ) class_records.mapped('student_ids.school_id')
callback
. ví dụ:# return list class name. Ví dụ: ['Class 01', Class 02'] class_record.mapped(lambda r: r.name)
Tip
Trong trường hợp chỉ 1 bản ghi thì không nhất thiết phải dùng mapped
. Ví dụ: class_record.name
.
Tuy nhiên nếu có 2 bản ghi trở lên mà không sử dụng mapped sẽ gây lỗi expected singleton
. Ví dụ class_records.name
.
Note
Tương tự như filtered
, mapped
cũng chỉ hoạt động trên dữ liệu có sẵn của recordset trên bộ nhớ, không truy
cập cơ sở dữ liệu.
See also
Odoo ORM Basic phần Mapped
Sắp xếp các recordset¶
Sắp xếp các recordset là điều cần thiết, đôi khi bạn cần sắp xếp lại các recordset đã có theo một thứ tự cụ thể. Nó cũng hữu ích trong việc sắp xếp lại recordset nếu bạn sử dụng các toán tử để kết hợp các recordset với nhau, điều này có thể làm mất thứ tự của chúng.
Phần này sẽ hướng dẫn cách sử dụng phương thức sorted
để sắp xếp lại các recordset đang tồn tại. Chúng tôi sẽ sắp xếp
các học sinh theo ngày sinh.
Chuẩn bị¶
Thêm field dob
vào model education.student
, nếu đã có bạn có thể bỏ qua bước này.
dob = fields.Date(string='Date of birth')
Các bước thực hiện¶
Bạn cần thêm đoạn code sau để sắp xếp học sinh theo ngày sinh
@api.model
def sort_students_by_dob(self, students):
# sắp xếp tăng dần theo dob
return students.sorted(key='dob')
Cơ chế hoạt động¶
Bên trong sorted
tìm nạp đến dữ liêu các trường đã truyền qua đối số key
. Sau đó sắp xếp lại bằng việc sử dụng Python
built-in method sorted
rồi trả về tập recordset mới đã được sắp xếp.
sorted
còn hỗ trợ 1 tham số tùy chọn reverse
(mặc định = False) giúp bạn có muốn đảo ngược thứ tự bản ghi hay không.
Trường hợp muốn đảo ngược chỉ cần truyền reverse=True như sau:
@api.model
def sort_students_by_dob(self, students):
# sắp xếp tăng dần theo dob
return students.sorted(key='dob', reverse=True)
Khi gọi sorted` mà không sử dụng đối số nào, thuộc tính ``_order
trong model sẽ được sủ dụng.
Mở rộng logic nghiệp vụ trong model¶
Một điều rất hay trong Odoo là chia các tính năng của ứng dụng thành các module khác nhau. Bằng cách đó, bạn có thể chỉ cần bật / tắt các tính năng trong cài đặt hoặc gỡ cài đặt ứng dụng. Khi bạn thêm các tính năng mới vào ứng dụng bất kỳ, việc tùy chỉnh hành vi của các phương thức hiện có của model là rất cần thiết. Đôi khi, bạn cũng muốn thêm các trường mới vào model hiện có. Đây là một rất dễ dàng trong Odoo và một trong những tính năng mạnh mẽ nhất của framework này.
Trong phần này, chúng tôi sẽ hướng dẫn cách mở rộng model, chỉnh sửa phương thức và thêm trường mới.
Chuẩn bị¶
Giả sử trong model education.class
đã tồn tại đoạn code sau:
def add_student(self): self.ensure_one() self.write({ 'student_ids': [(0, 0, { 'name': 'Student' })] })
Và button trong views
... <button name="add_student" type="object" string="Add Student" /> ...
Các bước thực hiện¶
Tạo file education_class_size.py
trong viin_education/models
với đoạn code sau:
from odoo import fields, models, from odoo.exceptions import UserError class EducationClass(models.Model): _inherit = 'education.class' max_student = fields.Integer(string='Max Student', default=20) def add_student(self): # Kiểm tra max student trước khi thực hiện if len(self.student_ids) > self.max_student: raise UserError('The number of students has exceeded %s' % self.max_student) # Gọi lại nội dung phương thức cha super(EducationClass, self).add_student()
Cơ chế hoạt động¶
Đầu tiên chúng tôi định nghĩa 1 model mở rộng từ education.class
, thêm trường max_student
và chỉnh sửa phương thức add_student
.
Ở cuối phương thức có sử dụng từ khóa super
, nó giúp thực thi nội dung phương thức của lớp cha mà không cần phải định nghĩa lại.
Tùy theo yêu cầu của bài toán mà bạn có thể linh động chỉnh sửa trước hoặc sau khi gọi super
. Quá trình thực hiện như sau:
add_student()
của lớp cha education.class
.add_student()
qua super()
.Mở rộng¶
Bất kỳ model nào được định nghĩa và kế thừa models.Model
đều có phương thức create()
, write()
và unlink()
.
Tùy theo nhu cầu mà bạn cần mở rộng các phương thức này để thực hiện các logic nghiệp vụ khác nhau trong khi thêm, sửa hoặc
xóa bản ghi. Tương tự cách mở rộng phương thức ở ví dụ phía trên, bạn có thể chỉnh sửa lại các phương thức này theo ý muốn
của mình. Ví dụ:
@api.model
def create(self, values):
# thực hiện logic của bạn
return super(EducationClass, self).create(values)
def write(self, values):
# thực hiện logic của bạn
return super(EducationClass, self).write(values)
def unlink(self):
# thực hiện logic của bạn
return super(EducationClass, self).unlink()
Nhóm dữ liệu bằng read_group()¶
Thông thường để tổng hợp dữ liệu theo tiêu chí, chúng ta thường sử dụng group by
và aggregate
function trong SQL.
Để nhanh chóng hơn Odoo đã hỗ trợ chúng ta phương thức read_group()
giúp thực hiện điều tương tự. Phần này chúng tôi sẽ
hướng dẫn cách sử dụng read_group
để tổng hợp dữ liệu.
Ví dụ muốn nhóm số lượng học sinh theo từng lớp học, bạn cần thêm đoạn code sau vào model education.student
:
@api.model
def group_by_class(self):
group_result = self.read_group(
[('state', '=', 'studying')], # domain
['class_id'], # field
['class_id'] # group by
)
return group_result
Để kiểm tra kết quả thực tế, bạn cần thêm một nút vào giao diện người dùng để trigger method này. Sau đó, bạn có thể in kết quả trong server log.
Kêt quả trả về dưới dạng list các dictionary dạng như sau:
[
# lớp học với class_id = 1 có 6 học sinh
{'class_id_count': 6, 'class_id': (1, <odoo.tools.func.lazy object at 0x7f92ca08e460>), '__domain': [('class_id', '=', 1)]},
# lớp học với class_id = 2 có 3 học sinh
{'class_id_count': 3, 'class_id': (2, <odoo.tools.func.lazy object at 0x7f92ca224dc0>), '__domain': [('class_id', '=', 2)]}
...
]
Vui lòng đọc thêm trong Odoo ORM Basic - Các hàm ORM cơ bản.
Tip
read_group()
nhanh hơn rất nhiều so với việc đọc và xử lý các giá trị từ recordset. Vì vậy, đối với báo cáo hoặc đồ thị,
bạn nên sử dụng nó.