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.

Thuộc tính chung của Model:

_name = None

Tê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 = None

Tên không theo form quy chuẩn của model. Ví dụ: _description = 'student'

_inherit = None

Tham 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 = None

Trườ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ới ModelTransientModelFalse với AbstractModel.

_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 = None

Tên bảng dữ liệu SQL được sử dụng bởi model nếu _auto = True.

_sequence = None

Trì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 = False

Trong 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ính check_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 = False

Gán giá trị bằng True để tính toán và gán lại giá trị cho parent_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_ofparent_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ường student_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ảng ir.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ặp

  • bool_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, One2manyMany2many:

  • Đọ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)

    • One2manyMany2many : 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ức create hoặc write 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_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ặc school_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ải school_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ệ one2manymany2many để 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_computeage_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

    1. @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.

    2. 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.

    3. Trường onchange có mặc định store = True.

    4. Không cần khai báo trên Field.

  • compute

    1. @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.

    2. 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àm compute. Và sau đó cứ mỗi lần thay đổi năm sinh sẽ tính toán lại giá trị age_compute

    3. Trường compute có mặc định store = False.

    4. 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ức compute (store=``False``) sẽ phụ thuộc vào giá trị trong object context (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ại

  • uid : 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à recordset

  • Nộ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ức create 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 trong self 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ức create 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) -> records

Tạ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
  • 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àm create. 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ủa self

Model .copy (default=None) -> new record

Tạ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_values

Phươ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) -> record

Phươ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ường name.

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ức name_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) -> Bool

Cậ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ường class_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
  • 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ặc Float)

  • 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à type str, 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ới create().

    • (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ới create().

    • (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ới create().

    • (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ách ids.

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ới fnames), thì chỉ giới hạn flush với những records được truyền vào

Search / Read

Model browse ([ids]) -> records

Trả 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]) -> records

Tì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ự theo

  • count (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]) -> int

Trả 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]) -> records

Tì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ểu Many2one. 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ức name_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ới search domain dựa trên display_name. Sau đó sử dụng phương thức name_get() lên kết quả trả về từ search().

Tham số
  • name (str): định dạng tên để khớp tìm kiếm

  • args (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ào name, 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ếu True, kết quả trả về sẽ chỉ được nhóm theo nhóm đầu tiên (nhóm trong groupby). Các nhóm còn lại được đặt trong _context key. Nếu False, 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
  • 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, helpselection 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ốc

  • fields: các trường trong model

  • model: tên model hiện tại

  • name: tên của View ID

  • type: kiểu View (form, tree, …)

  • view_id: số id

Tham số
  • view_id (int): số id của View yêu cầu hoặc None

  • view_type (str): kiểu của View yêu cầu (‘form’, ‘tree’, …) nếu view_id là None

  • toolbar (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="{&quot;preview_image&quot;: &quot;image_128&quot;}" on_change="1" modifiers="{}"/>\n<div class="oe_title">\n<h1>\n<field name="name" placeholder="Full Name" required="True" on_change="1" modifiers="{&quot;required&quot;: 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="{&quot;readonly&quot;: true}"/>\n<field name="company_id" on_change="1" can_create="true" can_write="true" invisible="1" modifiers="{&quot;invisible&quot;: 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="{&quot;required&quot;: true}"/>\n<field name="school_year_id" on_change="1" can_create="true" can_write="true" modifiers="{&quot;required&quot;: true}"/>\n<field name="start_date" optional="hide" modifiers="{&quot;readonly&quot;: true}"/>\n<field name="end_date" optional="hide" modifiers="{&quot;readonly&quot;: true}"/>\n<field name="state" modifiers="{&quot;required&quot;: 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="{&quot;required&quot;: true}"/>\n</group>\n<group>\n<field name="school_year_id" on_change="1" can_create="true" can_write="true" modifiers="{&quot;required&quot;: true}"/>\n<field name="state" modifiers="{&quot;required&quot;: 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="{&quot;required&quot;: true}"/>\n<field name="is_contact_person" widget="boolean_toggle" modifiers="{}"/>\n<field name="relationship_id" can_create="true" can_write="true" modifiers="{&quot;required&quot;: 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="{&quot;required&quot;: true}"/>\n</group>\n<group>\n<field name="relationship_id" can_create="true" can_write="true" modifiers="{&quot;required&quot;: 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="{&quot;invisible&quot;: '
         'true, &quot;column_invisible&quot;: 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ếu valueNone``hoặc ``False, còn lại thì hoạt động như =)

=like:
  • Khớp field_name với pattern của value. 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 list value hay không. Yêu cầu value 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 record value (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 record value (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ử logic AND

  • '|': Toán tử logic OR

  • '!': Toán tử logic NOT

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ỏ qua

  • A & B&, A, B

  • A & B | CA &, |, 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ới A & 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)]
    

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ếu record 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 records set hay không.

  • record not in set :

    Ngược lại của record in set

  • set1 <= set2set1 < set2 :

    Trả về True nếu set1 là tập con của set2

  • set2 >= set2set2 > set2 :

    Trả về True nếu set1 là tập cha của set2

  • set1 | set2 :

    trả về 1 recordset mới chứa toàn các records có mặt trong cả set1set2. 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, set1set2.

  • 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ức search để 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 định

  • reverse (bool): nếu True, 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áo

    • action_id (int): id của hành động khi thực hiện việc dịch chuyển

    • button_text (str): Tên của button sẽ kích hoạt việc dịch chuyển

    • additional_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