odoo ORM API學習總結兼orm學習教程

授客發表於2023-03-11

環境

odoo-14.0.post20221212.tar

ORM API學習總結/學習教程

模型(Model)

Model欄位被定義為model自身的屬性

from odoo import models, fields
class AModel(models.Model):
    _name = 'a.model.name'

    field1 = fields.Char()

警告

欄位的名稱和方法的名稱不能相同,最後定義的方法、函式名稱會覆蓋前面定義的相同名稱。

預設的,欄位的標籤(Lable,即使用者可見欄位名稱)為對應欄位名稱開頭字母改成大寫後的值,可透過 string 欄位屬性改成修改欄位Label

field2 = fields.Integer(string="Field Label")

可透過default,定義預設值:

name = fields.Char(default="a value")

預設值也可以透過函式獲取:

    def _default_name(self):
        return 'Title'

name = fields.Char(default=lambda self: self._default_name())

API

BaseModel

class odoo.models.BaseModel[原始碼]

Odoo模型的基類。Odoo mode可透過繼承一下類來建立Model:

  • Model 用於常規資料庫持久化模型
  • TransientModel 用於臨時資料,儲存在資料庫中,但每隔一段時間就會自動清空
  • AbstractModel 用於多繼承模組共享的抽象父類,不會在資料庫中建立模型表

系統為每個資料庫自動例項化每個模型一次。這些例項表示每個資料庫上的可用模型,取決於該資料庫上安裝的模組。每個例項的實際類都是從建立和繼承相應模型的Python類構建的。

每個模型例項都是一個“記錄集(recordset)”,即模型記錄的有序集合。記錄集由 browse(), search()或欄位訪問等方法返回。記錄沒有顯式的表示:單條記錄表示為一條記錄的記錄集。

要建立不需要例項化的類,可以將 _register 屬性設定為False

  • _auto= False

    是否應該建立資料庫表。如果設定為 False, 應該重寫 init()來建立資料庫表。預設設。針對ModelTransientModel自動設定為False,針對AbstractModel自動設定為False。可透過繼承AbstractModel來建立不需要任何資料表的模型

  • _log_access

    ORM是否自動生成和更新 Access Log fields。預設_auto的值。

  • _table= None

    模型對應的資料庫表的名稱。如果_auto設定為True的話。

  • _sequence= None

    用於ID欄位的SQL序列

  • _sql_constraints= []

    sql約束,格式:[(name, sql_def, message)]

  • _register= True

    registry visibility

  • _abstract= True

    是否為抽象模型

  • _transient= False

    是否為transient模型

  • _name= None

    模型名稱(以 點分式命名的模組名稱,比如estate.users

  • _description= None

    模組描述,非整數名稱

  • _inherit= None

    繼承的Python模型:需要繼承模型的名稱(_name屬性值)或者名稱列表(list型別)

  • _inherits= {}(不太理解)

    dictionary {‘parent_model’: ‘m2o_field’} mapping the _name of the parent business objects to the names of the corresponding foreign key fields to use:

    _inherits = {
        'a.model': 'a_field_id',
        'b.model': 'b_field_id'
    }
    

    implements composition-based inheritance: the new model exposes all the fields of the inherited models but stores none of them: the values themselves remain stored on the linked record.

    警告

    if multiple fields with the same name are defined in the _inherits-ed models, the inherited field will correspond to the last one (in the inherits list order).

  • _rec_name= None

    用於標記記錄的欄位,預設值:name

  • _order= 'id'

    用於搜尋結果的預設排序欄位

  • _check_company_auto= False

    執行writecreate, 對擁有 check_company=True屬性的關聯欄位呼叫_check_company 以確保公司一致性

  • _parent_name= 'parent_id'

    用作父欄位的many2one欄位

  • _parent_store= False

    設定為True以計算parent_path欄位。與parent_path 欄位一起,設定記錄樹結構的索引儲存,以便使用child_ofparent_of域運算子對當前模型的記錄進行更快的分層查詢

  • _date_name= 'date'

    用於預設日曆檢視的欄位

  • _fold_name= 'fold'

    用於確定看板檢視中摺疊組的欄位

AbstractModel

odoo.models.AbstractModel[原始碼]

odoo.models.BaseModel的別名

Model

class odoo.models.Model[原始碼]

常規資料庫持久化Odoo模型的主要父類。

透過繼承此類來建立Odoo模型的:

class user(Model):
    ...

系統將為安裝了該類模組的每個資料庫例項化一次類

  • _auto= True

    是否應該建立資料庫表。如果設定為 False, 應該重寫 init()來建立資料庫表。預設設。針對ModelTransientModel自動設定為False,針對AbstractModel自動設定為False。可透過繼承AbstractModel來建立不需要任何資料表的模型

  • _abstract= False

    是否為抽象模型

  • _transient= False

    是否為transient模型

TransientModel

class odoo.models.TransientModel[原始碼]

用於臨時記錄的父類模型,旨在暫時保持,並定期進行清理

TransientModel具有簡化的訪問許可權管理,所有使用者都可以建立新記錄,並且只能訪問他們建立的記錄。超級使用者可以無限制地訪問所有TransientModel記錄。

  • _auto= True

    是否應該建立資料庫表。如果設定為 False, 應該重寫 init()來建立資料庫表。預設設。針對ModelTransientModel自動設定為False,針對AbstractModel自動設定為False。可透過繼承AbstractModel來建立不需要任何資料表的模型

  • _abstract= False

    是否為抽象模型

  • _transient= False

    是否為transient模型

欄位(Fields)

class odoo.fields.Field[原始碼]

欄位擁有以下屬性

  • string (str) – 使用者看到的欄位的標籤;如果未設定,ORM將採用類中的欄位名開頭字母改成大寫後的

  • help (str) – 使用者看到的欄位的提示條(設定該屬性後,當滑鼠懸停在欄位標籤上方時,會自動浮現提示條,顯示該屬性的文字內容)。

  • invisible – 欄位是否可見。預設為False,即可見

  • readonly (bool) – 欄位在使用者介面是否只讀,預設值 False,僅對UI起作用

  • required (bool) – 欄位在使用者介面是否必填,預設 False。這透過在資料庫層面為列新增NOT NULL 約束來實現

  • index (bool) – 是否為欄位新增索引。注意:對不儲存、虛擬欄位不起作用。預設值: False

  • default (值或者可呼叫物件) – 設定欄位的預設值。可以是靜態值,或者以結果集為入參,返回某個值的函式。使用 default=None捨棄該欄位的預設值。

  • states (dict) –將state值對映到UI屬性-值對列表的字典對映,簡單說就是允許使用者介面依據state欄位的值來動態設定對應欄位的UI屬性,因此,它要求存在一個state欄位並在檢視中使用(即使是隱藏的),state屬性的名稱是在odoo硬編碼且不允許修改的,可用屬性有: readonly, required, invisible。例如states={'done':[('readonly',True)]},表示當state值為done時,將使用者介面states所在欄位在設定為只讀(僅針對UI層面)

    用法舉例:

    state = fields.Selection([
            ('draft', 'To Submit'),
            ('cancel', 'Cancelled'),
            ('confirm', 'To Approve'),
            ('refuse', 'Refused'),
            ('validate1', 'Second Approval'),
            ('validate', 'Approved')
            ], string='Status', readonly=True, copy=False, default='confirm')
    date_from = fields.Datetime(
            'Start Date', readonly=True, index=True, copy=False,
            states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    
  • groups (str) – 值為逗號分隔的組XML ID列表,如groups='base.group_user,base.group_system',可限制欄位只能被給定組使用者訪問。

  • company_dependent (bool) –

    欄位值是否依賴於當前公司,如果設定為True,則表示依賴當前公司,即欄位值和公司繫結。這個屬性的作用就是讓同一欄位,可以根據不同公司,儲存不同的值,假設一個使用者屬於多個公司,他在不同公司的職務也不一樣,此時就可以設定該屬性為True

    該值未儲存在當前模型表中。它註冊為ir.property,也就是說它的值儲存在ir_property表中,透過查詢該表來獲取該欄位的值。

  • copy (bool) – 當記錄重複時,該欄位值是否被複製(在使用 ORM copy()方法複製並生成新記錄時,不復制該欄位的值)。 (針對普通欄位,預設值為: True ,針對one2many和計算欄位,包括屬性欄位(property fields,個人理解註冊ir.property的欄位)和關係欄位,預設值為False

  • store (bool) – 該欄位是否儲存到資料庫,針對計算欄位,預設值為False,其它欄位預設為True

  • group_operator (str) –

    在當前欄位上分組時,供 read_group() 使用的聚合函式

    支援的聚合函式:

    • array_agg : 值,包括空值,連線成一個陣列
    • count : 記錄數
    • count_distinct : 不重複記錄數
    • bool_and : 如果所有值都為真,則為真,否則為假
    • bool_or : 如果至少有一個值為真,則為真,否則為假
    • max : 所有值的最大值
    • min : 所有值的最小值
    • avg :所有值的平均值(算術平均值)
    • sum : 所有值的總和
  • group_expand (str) –

    用於在當前欄位上分組時用於擴充套件 read_group 結果的函式

    @api.model
    def _read_group_selection_field(self, values, domain, order):
        return ['choice1', 'choice2', ...] # available selection choices.
    
    @api.model
    def _read_group_many2one_field(self, records, domain, order):
        return records + self.search([custom_domain])
    

基礎欄位

class odoo.fields.Boolean[原始碼]

bool的封裝

class odoo.fields.Char[原始碼]

基本字串欄位,長度有限,通常在客戶端顯示為單行字串

引數:

  • size(int) – 為該欄位可儲存最大值

  • trim(bool) – 說明該值是否被修剪(預設情況下, True)。請注意,修剪操作僅由 Web 客戶端應用。

  • translate(bool 或者可呼叫物件)– 啟用欄位值的翻譯;用於translate=True整體翻譯欄位值;translate 也可以是可呼叫的,從而使得translate(callback,value)透過使用callback(term)來檢索術語的翻譯來翻譯value`

class odoo.fields.Float[原始碼]

float的封裝

精度數字由可選的digitals屬性給出。

引數

  • digits (tuple(int, int), 或者str ) – 一個元組(total, decimal) 或者引用DecimalPrecision 記錄的字串

    digits=(8,2) 表示總的8位,小數點佔2位
    

Float類為此提供了一些靜態方法:

round()以給定精度對浮點值進行舍入。is_zero()檢查浮點值在給定精度下是否等於零。compare()按給定精度比較兩個浮點值。

例子:

fields.Float.round(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
fields.Float.is_zero(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
field.Float.compare(self.product_uom_qty, self.qty_done, precision_rounding=self.product_uom_id.rounding)

比較助手出於歷史目的使用__cmp_語義,因此使用此助手的正確慣用方式如下:

如果result==0,則第一個和第二個浮點數相等,如果result<0,第一個浮點數小於第二個,如果result>0,第一個浮動點數大於第二個浮動點數

class odoo.fields.Integer[原始碼]

int的封裝

高階欄位

class odoo.fields.Binary[原始碼]

封裝二進位制內容(比如一個檔案)。

引數:

  • attachment(bool) – 欄位是否儲存為ir_attachment還是該model表的一列(預設為: True`,即儲存為前者。
class odoo.fields.Html[原始碼]

html程式碼內容的封裝

引數:略

class odoo.fields.Image[原始碼]

圖片的封裝,擴充套件Binary

如果影像大小大於畫素的max_width/max_height限制,則透過保持縱橫比將影像大小調整到該限制。

引數:

  • max_width(int ) – 影像的最大寬度(預設值:0,無限制)

  • max_height ( int) – 影像的最大高度(預設值:0,無限制)

  • verify_resolution ( bool) – 是否應驗證影像解析度以確保它不會超過最大影像解析度(預設值:True。最大影像解析度請參閱odoo.tools.image.ImageProcess(預設值:50e6)。

    引數

如果沒有指定 max_width/max_height 或者設定為0,且verify_resolutionFalse,則不會驗證欄位內容,此時應該使用Binary欄位。

class odoo.fields.Monetary[原始碼]

封裝以給定res_currency表示的浮點值。

小數精度和貨幣符號取自currency_field屬性。

引數:

  • currency_field (str) –擁有表示該貨幣欄位的res_currencyMany2one欄位名稱(預設: 'currency_id')
class odoo.fields.Selection[原始碼]

封裝不同值之間的互斥選擇。

說明:Selection欄位的可選值,儲存在public.ir_model_fields_selection表中,透過field_id欄位透過public.ir_model_fields表進行

-- 查詢Selection欄位ID
SELECT id FROM public.ir_model_fields
where model = 'stock.quality' and name='state' 

-- 查詢Selection欄位可選值
select * from public.ir_model_fields_selection where field_id = 13028; -- 13028為Selection欄位ID

引數:

  • selection (list(tuple(str, str)) 或者可呼叫物件 或者 str)) – 指定欄位的可選值。其值為包含2元組的列表,或者返回前者模型方法,或者方法名稱

  • selection_add (list(tuple(str, str)) –

    在重寫欄位的情況下,提供selection的擴充套件。它是一個包含二元組(value, label)或者單元組(value,)的列表,其中,單元組中的value必須作為value出現在selection列表中的元組中。新值插入順序和原有selection中元組順序保持一致:

    selection = [('a', 'A'), ('b', 'B')]
    selection_add = [('c', 'C'), ('b',)]
    > result = [('a', 'A'), ('c', 'C'), ('b', 'B')]
    
  • ondelete

    為帶有selection_add的任何重寫欄位提供回退機制。這是一個將selection_add中的每個選項對映到回退操作的dict。

    此回退操作將應用於其selection_add選項對映到該操作的所有記錄。

    這些操作可以是以下任一操作:

    • set null預設情況下,具有此選項的所有記錄的選擇值都將設定為False。
    • cascade–具有此選項的所有記錄將與選項本身一起刪除。
    • set default-具有此選項的所有記錄都將設定為欄位定義的預設值
    • <callable> -一個可呼叫物件,其第一個也是唯一的引數將是包含指定的Selection選項的記錄集,用於自定義處理

selection屬性選擇是強制性的,除非是related或擴充套件的欄位

class odoo.fields.Text[原始碼]

類似Char,用於更長的內容,沒有大小,通常展示為多行文字框。

引數:

translate (bool 或者可呼叫物件) – 同 Char

Date(time) 欄位

當將一個值賦值給 Date/Datetime 欄位時,以下選擇是合法的:

  • datedatetime 物件.
  • 正確格式的字元:
    • Date欄位採用YYYY-MM-DD
    • Datetime欄位採用 YYYY-MM-DD HH:MM:SS
  • False 或者 None.

DateDatetime 欄位類擁有以下輔助函式,用於嘗試轉換為相容型別:

示例

解析來自外部的日期/日期時間:

fields.Date.to_date(self._context.get('date_from'))

Date/Datetime 比較最佳實踐:

  • Date欄位只能和date物件比較
  • Datetime欄位只能和datetime物件比較

Datetime 欄位在資料庫中儲存為不帶時區的時間戳,並以UTC時區儲存。因為這樣可使Odoo資料庫獨立於託管伺服器系統的時區。時區轉換完全由客戶端管理。

Common operations with dates and datetimes such as addition, subtraction or fetching the start/end of a period are exposed through both Date and Datetime. These helpers are also available by importing odoo.tools.date_utils.

class odoo.fields.Date原始碼

Python date物件的封裝

  • static add(value, *args, **kwargs)

    返回 valuerelativedelta之和

    • 引數

      value – 初始datedatetime

      args – 傳遞給relativedelta的位置引數

      kwargs – 傳遞給relativedelta的關鍵詞引數

    • 返回

      date/datetime結果物件

    示例:

    from odoo.fields import Date
    
    print(Date.add(datetime.now(), years=1)) # 輸出形如:2024-01-03
    # 常見引數:
    # years, months, days, leapdays, weeks, hours, minutes, seconds, microseconds
    
  • static subtract(value, *args, **kwargs)[原始碼]

    返回 valuerelativedelta之差

    • 引數

      value – 初始date 或者datetime

      args – 傳遞給 relativedelta位置引數

      kwargs – 傳遞給 relativedelta的關鍵詞引數

    • 返回

      date/datetime結果物件

  • static context_today(record, timestamp=None)[原始碼]

    按客戶端時區以適合date欄位的格式返回當前日期

    註解

    該方法可能用於計算預設值

    • 引數

      record – 從中獲取時區的記錄集

      timestamp (datetime) – 替代當前日期時間(datetime)的可選的datetime物件

    • 返回型別

      date

  • static end_of(value, granularity)[原始碼]

    從日期或日期時間獲取時間段的結束

    • 引數

      value – 初始datedatetime

      granularity – 字串表示的時間段型別, 可以是year, quarter, month, week, day 或者hour

    • 返回

      與指定時段的起始對應的date/datetime物件

    示例:

    print(datetime.now()) # 2023-01-03 10:12:32.332208
    print(Date.end_of(datetime.now(), 'year')) # 輸出形如:2023-12-31 23:59:59.999999
    print(Date.end_of(datetime.now(), 'month')) # 輸出形如:2023-01-31 23:59:59.999999
    
  • static start_of(value, granularity)[原始碼]

    從日期或日期時間獲取時間段的開始

    • 引數

      value – 初始datedatetime

      granularity – 字串表示的時間段型別, 可以是year, quarter, month, week, day 或者hour

    • 返回

      與指定時段的起始對應的date/datetime物件

    示例:

    print(datetime.now()) # 2023-01-03 10:18:57.071276
    print(Date.start_of(datetime.now(), 'year')) # 輸出形如:2023-01-01 00:00:00
    print(Date.start_of(datetime.now(), 'month')) # 輸出形如:2023-01-01 00:00:00
    print(Date.start_of(datetime.now(), 'hour')) # 輸出形如:2023-01-03 10:00:00
    
  • static to_date(value)[原始碼]

    嘗試轉換 valuedate 物件

    警告

    如果value為datetime物件,它將被轉換為date物件,且所有日期時間特定資訊(HMS, TZ, …)都會丟失。

    • 引數

      value (str 或 date 或 datetime) –需要轉換的值

    • 返回

      代表 value的物件

    • 返回型別

      date型別或者None

  • static to_string(value)[原始碼]

    date 或者datetime 物件轉為字串

    • 引數

      value – 需要轉換的日期或者日期時間物件

    • 返回

      以伺服器日期格式返回代表 value 的字串。如果 valuedatetime型別,自動捨棄小時,分,秒,時區資訊。

    • 返回型別:str

    示例:

    print(Date.to_string(datetime.now())) # 輸出形如:2023-01-03
    
  • static today(*args)[原始碼]

    返回當前日期

    示例:

    print(Date.today()) # 格式形如:2023-01-03
    
class odoo.fields.Datetime[原始碼]

Python datetime物件的封裝

  • static context_timestamp(record, timestamp)[原始碼]

    返回轉換為客戶端時區的給定時間戳。

    註解

    此方法不是用作預設初始值設定項,因為datetime欄位在客戶端顯示時會自動轉換。對於預設值,應使用now()

    • 引數

      record – 從中獲取時區的記錄集。

      timestamp (datetime) – 待轉換為客戶端時區的naive datetime值 (UTC表示的)

    • 返回

      按上下文時區轉換為時區敏感的datetime

    • 返回型別

      datetime

  • static add(value, *args, **kwargs)[原始碼]

    參考Date.add

  • static subtract(value, *args, **kwargs)[原始碼]

    參考Date.subtract

  • static end_of(value, granularity)[原始碼]

    參考Date.end_of

  • static start_of(value, granularity)[原始碼]

    參考Date.start_of

  • static to_string(value)[原始碼]

    參考Date.to_string

  • static today(args)[原始碼]

    返回當天,午夜 (00:00:00)

    示例:

    from odoo.fields import Datetime
    
    print(Datetime.today()) # 輸出形如:2023-01-03 00:00:00
    print(Datetime.now()) # 輸出當前時間 2023-01-03 12:33:00
    
  • static to_datetime(value)[原始碼]

    將ORM value 轉為 datetime

    • 引數

      value (str 或者 date 或者 datetime) – 需要轉換的值

    • 返回

      代表 value的物件

    • 返回型別

      datetime 或者None

關係欄位(Relational Fields)

class odoo.fields.Many2one[原始碼]

Many2one欄位的值是大小為0(無記錄)或1(單個記錄)的記錄集。

引數:

  • comodel_name (str) – 目標模型的名稱,comodel_name是必選引數,除非是相關或擴充套件欄位(不太理解,原文:name of the target model Mandatory except for related or extended fields)
  • domain – 用於設定客戶端側候選值的可選 domain (domain 或者字串)
  • context (dict) – 處理該欄位時供客戶端使用的上下文
  • ondelete (str) – 當引用的記錄被刪除時,怎麼處理:可選值有:'set null', 'restrict', 'cascade'
  • auto_join (bool) – 是否在搜尋該欄位時生成JOIN (預設: False)
  • delegate (bool) – 將其設定為True以標記可透過當前模型訪問目標模型的欄位(對應_inherits)
  • check_company (bool) – 標記需要在 _check_company()中校驗的欄位。取決於欄位屬性,新增一個預設的公司domain
class odoo.fields.One2many[原始碼]

One2many欄位的值為 comodel_name中所有滿足條件的記錄的結果集,而目標模型中的 inverse_name 則等價於當前記錄。

引數:

  • comodel_name (str) – 目標模型的名稱
  • inverse_name (str) – 目標模型中反向Many2one欄位名稱,根據該欄位反向查詢記錄
  • domain – 用於設定客戶端候選值的條件 (domain 或者字串),可選
  • context (dict) – 處理該欄位時供客戶端使用的上下文
  • auto_join (bool) – 是否在搜尋該欄位時生成JOIN (預設: False)
  • limit (int) – 讀取時用的可選限制

comodel_nameinverse_name 引數是必選引數,除非是相關或者擴充套件欄位

class odoo.fields.Many2many[原始碼]

Many2many欄位的值為一個結果集。

引數:

  • comodel_name – 目標模型的名稱,必選引數,除非是關聯或者擴充套件欄位
  • relation (str) – 資料庫中儲存關係的表名,可選引數。
  • column1 (str) – relation表中引用"這些"記錄的列名,可選引數
  • column2 (str) – relation表中引用"那些"記錄的列名,可選引數

relation, column1column2 引數可選。 如果未給定,自動根據模型名稱生成,提供的不同的model_namecomodel_name

注意,ORM不支援在給定模型,使用同樣的comodel,建立多個省略了relation引數的欄位,因為這些欄位將使用相同的表。ORM阻止兩個Many2many欄位使用相同的relation引數,除非:

  • 兩個欄位都使用相同的模型, comodel並顯示指定relation引數,否則
  • 至少有一個欄位屬於攜帶_auto = False的模型

引數:

  • domain – 用於設定客戶端候選值的條件 (domain 或者字串),可選
  • context (dict) – 處理該欄位時供客戶端使用的上下文
  • check_company (bool) – 標記需要在 _check_company()中校驗的欄位。取決於欄位屬性,新增一個預設的公司條件
  • limit (int) – 讀取時用的可選限制

注意:odoo不會在當前模型對應表中為One2manyMany2many型別的屬性建立對應的表欄位,但會為Many2one型別的屬性建立對應表欄位,針對Many2many型別的屬性,odoo會建立一張輔助表,表名預設格式為model1_table_name_model2_table_name_rel,該表擁有兩列,一列為當前模型表主鍵ID(model1_table_name_id),一列為關係欄位關聯模型表的主鍵ID(model2_table_name_id),這樣透過兩表記錄ID就可以查詢所需記錄了

偽關係欄位

  • class odoo.fields.Reference[原始碼]

    偽關係欄位(資料庫中沒有FK)。該欄位值儲存為資料庫中遵循模式"res_model,res_id"的字串。

  • class odoo.fields.Many2oneReference[原始碼]

    該欄位的值儲存為資料庫中的一個整數。與odoo.fields.Reference欄位相反,必須在Char型別欄位中指定模型,其中,該欄位的名稱必須在當前Many2oneReference欄位中的model_field屬性中指定

    引數:model_field (str) – 儲存模型的欄位名稱。

計算欄位

可以使用 compute 引數計算欄位(而不是直接從資料庫中讀取)它必須將計算值分配給欄位。如果它使用其他欄位的值,則應使用depends()指定這些欄位

from odoo import api
total = fields.Float(compute='_compute_total')

@api.depends('value', 'tax')
def _compute_total(self):
    for record in self:
        record.total = record.value + record.value * record.tax
  • 當使用子欄位時,依賴可使用分點路徑:

    @api.depends('line_ids.value')
    def _compute_total(self):
        for record in self:
            record.total = sum(line.value for line in record.line_ids)
    
  • 預設情況下,不存才計算欄位。他們在請求時被計算並返回。 設定store=True 將在資料庫中儲存計算及欄位並啟動開啟欄位搜尋。

  • 也可以透過設定search引數開啟在計算欄位上的搜尋。該引數值為一個返回搜尋條件的方法名稱 。

    upper_name = field.Char(compute='_compute_upper', search='_search_upper')
    
    def _search_upper(self, operator, value):
        if operator == 'like':
            operator = 'ilike'
        return [('name', operator, value)]
    

    在對模型進行實際搜尋之前處理domain時呼叫該搜尋方法。它必須返回與條件field operator value等效的domain

  • 計算欄位預設值。為了允許對計算欄位進行設定,使用inverse引數。該引數值為反向計算並設定相關欄位的函式的名稱:

    document = fields.Char(compute='_get_document', inverse='_set_document')
    
    def _get_document(self):
        for record in self:
            with open(record.get_document_path) as f:
                record.document = f.read()
    def _set_document(self):
        for record in self:
            if not record.document: continue
            with open(record.get_document_path()) as f:
                f.write(record.document)
    
  • 可以用同一方法同時計算多個欄位,只需對所有欄位使用同一方法並設定所有欄位

    discount_value = fields.Float(compute='_apply_discount')
    total = fields.Float(compute='_apply_discount')
    
    @api.depends('value', 'discount')
    def _apply_discount(self):
        for record in self:
            # compute actual discount from discount percentage
            discount = record.value * record.discount
            record.discount_value = discount
            record.total = record.value - discount
    

警告

雖然可以對多個欄位使用相同的計算方法,但不建議對reverse方法使用相同的方法。

reverse的計算過程中,所有使用所述inverse的欄位都受到保護,這意味著即使它們的值不在快取中,也無法計算它們。

如果訪問了這些欄位中的任何一個欄位,且並且其值不在快取中,ORM將簡單的為這些欄位返回預設值False。這意味著這些inverse欄位的值(觸發inverse方法的值除外)可能不會給出正確的值,這可能會破壞inverse方法的預期行為

相關欄位(Related fields)

計算欄位的一種特殊情況是相關(代理)欄位,它提供當前記錄上子欄位的值。它們是透過設定related引數來定義的,與常規計算欄位一樣,它們可以儲存:

nickname = fields.Char(related='user_id.partner_id.name', store=True)

related欄位的值是透過遍歷一系列關係欄位並讀取所訪問模型上的欄位來給出的。要遍歷的欄位的完整序列由related屬性指定

如果未重新定義某些欄位屬性,則會自動從源欄位中複製這些屬性:stringhelprequired(僅當序列中的所有欄位都是必需的時)、groupsdigitssizetranslatecleaning”、“selectioncomodel_namedomaincontext。所有無語義屬性都從源欄位複製。

預設的, related欄位:

  • 不被儲存
  • 不被複制
  • 只讀
  • 超級使用者模式下被計算

像計算欄位那樣,新增 store=True 以儲存related欄位。當其依賴被修改時,會自動重新計算related欄位。

小技巧

如果不希望在任何依賴項更改時重新計算related欄位,則可以指定精確的欄位依賴項:

nickname = fields.Char(
    related='partner_id.name', store=True,
    depends=['partner_id'])
# nickname僅在partner_id被修改時才會被重新計算,而不會在partner名稱被修改時重新計算

警告

不可以在related欄位依賴項中包含 Many2many 或者 One2many 欄位

related 可以用於引用另一個模型中的 One2manyMany2many 欄位,前提是透過當前模型的一個Many2one關係來實現的。 One2manyMany2many 不被支援,無法正確的彙總結果:

m2o_id = fields.Many2one()
m2m_ids = fields.Many2many()
o2m_ids = fields.One2many()

# Supported
d_ids = fields.Many2many(related="m2o_id.m2m_ids")
e_ids = fields.One2many(related="m2o_id.o2m_ids")

# Won't work: use a custom Many2many computed field instead
f_ids = fields.Many2many(related="m2m_ids.m2m_ids")
g_ids = fields.One2many(related="o2m_ids.o2m_ids")

自動生成的欄位

  • odoo.fields.id

    ID欄位

    如果當前記錄集長度為1,返回記錄集中唯一記錄的ID。否則丟擲一個錯誤

訪問日誌欄位

如果啟用_log_access,自動設定並更新這些欄位。當未用到這些欄位時,以禁用它以阻止建立或更新表中這些欄位。

預設的 _log_access被設定為 _auto的值。

  • odoo.fields.create_date

    建立記錄時儲存建立時間,Datetime型別

  • odoo.fields.create_uid

    儲存記錄建立人, Many2one to a res.users

  • odoo.fields.write_date

    儲存記錄最後更新時間,Datetime型別

  • odoo.fields.write_uid

    儲存記錄最後更新人, Many2one to a res.users.

警告

必須對odoo.models.TransientModel模型開啟_log_access

保留欄位名稱

除了自動欄位之外,還有一些欄位名是為預定義行為保留的。當需要相關行為時,應在模型上定義它們:

  • odoo.fields.name

    _rec_name的預設值,用於在需要代表性“命名”的上下文中顯示記錄。odoo.fields.Char型別

  • odoo.fields.active

    切換記錄的全域性可見性,如果active設定為False,則記錄在大多數搜尋和列表中不可見。odoo.fields.Boolean型別

  • odoo.fields.state

    物件的宣告週期階段,供fields.[Selectionstates 屬性使用

  • odoo.fields.parent_id

    _parent_name的預設值,用於以樹結構組織記錄,並在domain中啟用child_ofparent_of運算子。Many2one欄位。

  • odoo.fields.parent_path

    _parent_store設定為True時,用於儲存反映[_parent_name]樹結構的值,並最佳化搜尋domain中的child_ofparent_of運算子。必須使用index=True宣告才能正確操作。odoo.fields.Char型別

  • odoo.fields.company_id

    用於Odoo多公司行為的主欄位名。供:meth:~Odoo.models._check_company用於檢查多公司一致性。定義記錄是否在公司之間共享(沒有值)還是僅由給定公司的使用者訪問。Many2one:型別:res_company

記錄集(Recordset)

與模型和記錄的互動是透過記錄集執行的,記錄集是同一模型的記錄的有序集合。

警告

與名稱所暗示的相反,記錄集當前可能包含重複項。這在未來可能會改變。

在模型上定義的方法是在記錄集上執行的,方法的self是一個記錄集:

class AModel(models.Model):
    _name = 'a.model'
    def a_method(self):
        # self can be anything between 0 records and all records in the
        # database
        self.do_operation()

對記錄集進行迭代將產生新的單條記錄的記錄集,這與對Python字串進行迭代產生單個字元的字串非常相似:

def do_operation(self):
    print(self) # => a.model(1, 2, 3, 4, 5)
    for record in self:
        print(record) # => a.model(1), then a.model(2), then a.model(3), ...

欄位訪問

記錄集提供了一個“Active Record” 介面:模型欄位可直接作為記錄的屬性直接讀取和寫入。

註解

當訪問潛在多條記錄的記錄集上的非關係欄位時,使用mapped(),該函式返回一個列表:

total_qty = sum(self.mapped('qty')) # mapped返回一個列表,形如[2,4,5]

欄位值也可以像字典項一樣訪問。設定欄位的值會觸發對資料庫的更新:

>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> field = "name"
>>> record[field]
Bob

警告

  • 嘗試讀取多條記錄上的欄位將引發非關係欄位的錯誤。
  • 訪問一個關係欄位(Many2oneOne2manyMany2many),總是返回記錄集,如果未設定欄位的話,則返回空記錄集。

記錄快取和預取

Odoo為記錄的欄位維護一個快取,這樣,不是每個欄位的訪問都會發出資料庫請求。

以下示例僅為第一條語句查詢資料庫:

record.name             # 第一次訪問從資料庫獲取值
record.name             # 第二次訪問從快取獲取值

為了避免一次讀取一條記錄上的一個欄位,Odoo會按照一些啟發式方法預取個記錄和欄位,以獲得良好的效能。一旦必須在給定記錄上讀取欄位,ORM實際上會在更大的記錄集上讀取該欄位,並將返回的值儲存在快取中以供後續使用。預取的記錄集通常是透過迭代獲得記錄的記錄集。此外,所有簡單的儲存欄位(布林值、整數、浮點值、字元、文字、日期、日期時間、選擇、many2one)都會被提取;它們對應於模型表的列,並在同一查詢中高效地獲取。

考慮以下示例,其中partners為包含1000條記錄的記錄集。如果不進行預取,迴圈將對資料庫進行2000次查詢。使用預取,只進行一次查詢

for partner in partners:
    print partner.name          # first pass prefetches 'name' and 'lang'
                                # (and other fields) on all 'partners'
    print partner.lang

預取也適用於輔助記錄:當讀取關係欄位時,它們的值(即記錄)將被訂閱以供將來預取。訪問這些輔助記錄之一將預取同一模型中的所有輔助記錄。這使得以下示例僅生成兩個查詢,一個用於合作伙伴,另一個用於國家/地區:

countries = set()
for partner in partners:
    country = partner.country_id        # first pass prefetches all partners
    countries.add(country.name)         # first pass prefetches all countries

方法修飾器

Odoo API模組定義了Odoo環境和方法修飾符

  • odoo.api.autovacuum(method)[原始碼]

    修飾一個方法,使其由日常vacuum cron作業(模型ir.autovacuum)呼叫。這通常用於垃圾收集之類的不需要特定cron作業的任務

  • odoo.api.constrains(*args)[原始碼]

    裝飾一個約束檢查器

    每個引數必須是校驗使用的欄位名稱:

    @api.constrains('name', 'description')
    def _check_description(self):
        for record in self:
            if record.name == record.description:
                raise ValidationError("Fields name and description must be different")
    

    當記錄的某個命名欄位被修改時呼叫裝飾器函式。

    如果校驗失敗,應該丟擲 ValidationError

    警告

    @constrains 僅支援簡單的欄位名稱,不支援並忽略點分名稱(關係欄位的欄位,比如 partner_id.customer)

    @constrains 僅當修飾方法中宣告的欄位包含在createwrite呼叫中時才會觸發。這意味著檢視中不存在的欄位在建立記錄期間不會觸發呼叫。必須重寫create,以確保始終觸發約束(例如,測試是否缺少值)

  • odoo.api.depends(*args)[原始碼]

    返回一個裝飾器,該裝飾器指定compute方法的欄位依賴關係(對於新型函式欄位)。引數支援是由點分隔的欄位名序列組成的字串:

    pname = fields.Char(compute='_compute_pname')
    
    @api.depends('partner_id.name', 'partner_id.is_company')
    def _compute_pname(self):
        for record in self:
            if record.partner_id.is_company:
                record.pname = (record.partner_id.name or "").upper()
            else:
                record.pname = record.partner_id.name
    

    有的也可能傳遞一個函式作為引數,這種情況下,依賴透過呼叫 在這種情況下,透過使用欄位的模型呼叫函式來提供依賴項

  • odoo.api.depends_context(*args)[原始碼]

    返回一個修飾符,該修飾符指定非儲存的“compute”方法的上下文依賴項。每個引數都是上下文字典中的鍵:

    price = fields.Float(compute='_compute_product_price')
    
    @api.depends_context('pricelist')
    def _compute_product_price(self):
        for product in self:
            if product.env.context.get('pricelist'):
                pricelist = self.env['product.pricelist'].browse(product.env.context['pricelist'])
            else:
                pricelist = self.env['product.pricelist'].get_default_pricelist()
            product.price = pricelist.get_products_price(product).get(product.id, 0.0)
    

    所有依賴項都必須是可雜湊的。以下鍵具有特殊支援:

    • company (上下文中的值或當前公司id),
    • uid (當前使用者ID和超級使用者標記),
    • active_test (env.context或者field.context中的值).
  • odoo.api.model(method)[原始碼]

    修飾一個record-style的方法,其中self是一個空記錄集,但其內容不相關,只有模型相關,可以理解為不會建立對應資料庫記錄的模型物件。模型層面的操作需要新增此修飾器,相當於類靜態函式

    @api.model
    def method(self, args):
        ...
    
  • odoo.api.model_create_multi(method)[原始碼]

    修飾一個以字典列表為引數,並建立多條記錄的方法。可能僅透過一個字典或者字典列表呼叫該方法:

    record = model.create(vals)
    records = model.create([vals, ...])
    
  • odoo.api.onchange(*args)[原始碼]

    返回一個修飾器來修飾給定欄位的onchange方法。

    在出現欄位的表單檢視中,當修改某個給定欄位時,將呼叫該方法。在包含表單中存在的值的偽記錄上呼叫該方法。該記錄上的欄位賦值將自動返回客戶端。

    每個引數必須是欄位名:

    @api.onchange('partner_id')
    def _onchange_partner(self):
        self.message = "Dear %s" % (self.partner_id.name or "")
        return {
            'warning': {'title': "Warning", 'message': "What is this?", 'type': 'notification'},
        }
    

    如果型別設定為通知(notification),則警告將顯示在通知中。否則,它將作為預設值顯示在對話方塊中

    警告

    @onchange 僅支援簡單的欄位名稱,不支援並自動忽略點分名稱(關係欄位的欄位,比如partner_id.tz)

    危險

    由於 @onchange 返回偽記錄的記錄集,對上述記錄集呼叫任何一個CRUD方法(create(), read(), write(), unlink())都是未定義的行為,因為它們可能還不存在於資料庫中。相反,只需像上面的示例中所示那樣設定記錄的欄位或呼叫update()方法

    警告

    one2many 或者many2many欄位不可能透過onchange修改其自身。這是客戶端限制 - 檢視 #2693

  • odoo.api.returns(model, downgrade=None, upgrade=None)[原始碼]

    為返回model例項的方法返回一個修飾器

    • 引數

      model – 模型名稱,或者表示當前模型的'self'

      downgrade – 一個用於轉換record-style的value為傳統風格輸出的函式downgrade(self, value, *args, **kwargs)

      upgrade – 一個用於轉換傳統風格(traditional-style)的value為record-style的輸出的函式upgrade(self, value, *args, **kwargs)

    引數 self, *args**kwargs以record-style方式傳遞給方法

    修飾器將方法輸出適配api風格: id, ids 或者False 對應傳統風格,而記錄集對應記錄風格:

    @model
    @returns('res.partner')
    def find_partner(self, arg):
        ...     # return some record
    
    # output depends on call style: traditional vs record style
    partner_id = model.find_partner(cr, uid, arg, context=context)
    
    # recs = model.browse(cr, uid, ids, context)
    partner_record = recs.find_partner(arg)
    

    注意,被修飾的方法必須滿足那約定。

    這些修飾器是自動繼承的:重寫被修飾的現有方法的方法將被相同的@return(model)修飾

環境(Environment)

Environment 儲存ORM使用的各種上下文資料:資料庫遊標(用於資料庫查詢)、當前使用者(用於訪問許可權檢查)和當前上下文(儲存任意後設資料)。環境還儲存快取。

所有記錄集都有一個環境,它是不可變的,可以使用env訪問,並提供對以下的訪問:

  • 當前使用者 (user)
  • 遊標 (cr)
  • 超級使用者標識(su)
  • 或者上下文 (context)
>>> records.env
<Environment object ...>
>>> records.env.user
res.user(3)
>>> records.env.cr
<Cursor object ...)
>>> self.env.context # 返回字典資料,等價於 self._context
{'lang': 'en_US', 'tz': 'Europe/Brussels'}
>>> self._context
{'lang': 'en_US', 'tz': 'Europe/Brussels'}

從其他記錄集建立記錄集時,將繼承環境。環境可用於獲取其他模型中的空記錄集,並查詢該模型:

>>> self.env['res.partner']
res.partner()
>>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]])
res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)

Environment.ref(xml_id, raise_if_not_found=True)[原始碼]

返回與給定xml_id對應的記錄。

Environment.lang

返回當前語言程式碼。返回型別str

Environment.user

返回當前使用者(作為一個例項)。返回型別res_users

Environment.company

返回當前公司(作為一個例項)

如果未在上下文 (allowed_company_ids)中指定, 返回當前使用者的主公司(If not specified in the context(allowed_company_ids), fallback on current user companies)

  • 引發

    AccessError – 非法或者為授權 allowed_company_ids 上下文key內容

  • 返回

    當前公司(預設值=self.user.company_id)

  • 返回型別

    res.company

警告

在sudo模式下沒有應用健康檢查!在sudo模式下,使用者可以訪問任何公司,即使不是在他允許的公司。

這允許觸發公司間修改,即使當前使用者無權訪問目標公司

Environment.companies

返回使用者啟用的公司的記錄集。

如果未在上下文 (allowed_company_ids)中指定, 返回當前使用者的主公司(If not specified in the context(allowed_company_ids), fallback on current user companies)

  • 引發

    AccessError – 非法或者為授權 allowed_company_ids 上下文key內容

  • 返回

    當前公司(預設值=self.user.company_id)

  • 返回型別

    res.company

警告

在sudo模式下沒有應用健康檢查!在sudo模式下,使用者可以訪問任何公司,即使不是在他允許的公司。

這允許觸發公司間修改,即使當前使用者無權訪問目標公司

修改環境

  • Model.with_context([context][, **overrides]) -> records[原始碼]

    返回附加到擴充套件上下文的此記錄集的新版本。

    擴充套件上下文是提供的合併了overridescontext,或者是合併了overrides當前context

    # current context is {'key1': True}
    r2 = records.with_context({}, key2=True)
    # -> r2._context is {'key2': True}
    r2 = records.with_context(key2=True)
    # -> r2._context is {'key1': True, 'key2': True}
    

需要注意的是,上下文是和記錄集繫結的,修改後的上下文並不會在其它記錄集中共享。

  • Model.with_user(user)[原始碼]

    以非超級使用者模式返回附加到給定使用者的此記錄集的新版本,即傳入一條使用者記錄並返回該使用者的環境,除非user是超級使用者(按照約定,超級使用者始終處於超級使用者模式)

  • Model.with_company(company)[原始碼]

    返回具有已修改上下文的此記錄集的新版本,這樣:

    result.env.company = company
    result.env.companies = self.env.companies | company
    
    • 引數

      company (res_company 或者 int) – 新環境的主公司

    警告

    噹噹前使用者使用未經授權的公司時,如果不是在sudoed環境中訪問該公司,則可能會觸發AccessError

  • Model.with_env(env)[原始碼]

    返回附加到所提供環境的此記錄集的新版本。

    • 引數

      env (Environment) –

    警告

    新環境將不會從當前環境的資料快取中受益,因此稍後的資料訪問可能會在從資料庫重新獲取資料時產生額外的延遲。返回的記錄集具有與self相同的預取物件。

  • Model.sudo([flag=True])[原始碼]

    根據flag,返回啟用或禁用超級使用者模式的此記錄集的新版本。超級使用者模式不會更改當前使用者,只是繞過訪問許可權檢查。

    警告

    使用sudo可能會導致資料訪問跨越記錄規則的邊界,可能會混淆要隔離的記錄(例如,多公司環境中來自不同公司的記錄)。

    這可能會導致在多條記錄中選擇一條記錄的方法產生不直觀的結果,例如獲取預設公司或選擇物料清單。

    註解

    因為必須重新評估記錄規則和訪問控制,所以新的記錄集將不會從當前環境的資料快取中受益,因此以後的資料訪問可能會在從資料庫重新獲取時產生額外的延遲。返回的記錄集具有與self相同的預取物件。

SQL執行

環境上的cr屬性是當前資料庫事務的遊標,允許直接執行SQL,無論是對於難以使用ORM表達的查詢(例如複雜join),還是出於效能原因

self.env.cr.execute("some_sql", params)

由於模型使用相同的遊標,並且Environment儲存各種快取,因此當在原始SQL中更改資料庫時,這些快取必須失效,否則模型的進一步使用可能會變得不連貫。在SQL中使用CREATEUPDATEDELETE,但不使用SELECT(只讀取資料庫)時,必須清除快取。

註解

可以使用 invalidate_cache()執行快取的清理

  • Model.invalidate_cache(fnames=None, ids=None)[原始碼]

    修改某些記錄後,使記錄快取無效。如果fnamesids都為None,則清除整個快取。

    引數:

    fnames–已修改欄位的列表,None表示所有欄位

    ids–修改的記錄ID的列表,None表示所有記錄

警告

執行原始SQL繞過ORM,從而繞過Odoo安全規則。請確保在使用使用者輸入時對查詢進行了清洗,如果確實不需要使用SQL查詢,請使用ORM實用程式。

常用ORM方法Common ORM methods

建立/更新(Create/update)

  • Model.create(vals_list) → records[原始碼]

    為模型建立新記錄

    使用字典列表vals_list中的值初始化新記錄,如果需要,使用default_get()中的值

    • 引數

      vals_list (list) --模型欄位的值,作為字典列表:[{'field_name':field_value,…},…]為了向後相容,vals_list可以是一個字典。它被視為單個列表[vals],並返回一條記錄。有關詳細資訊請參見write()

    • 返回

      建立的記錄

    • 引發

      AccessError

      • 如果使用者對請求的物件沒有建立許可權

      • 如果使用者嘗試繞過訪問規則在請求的物件上建立

      ValidationError – 如果使用者嘗試為欄位輸入不在選擇範圍內的無效值

      UserError–如果將在物件層次結構中建立迴圈,操作的一個結果(例如將物件設定為其自己的父物件)

  • Model.copy(default=None)[原始碼]

    使用預設值更新複製的記錄self

    • 引數

      default (dict) – 用於覆蓋複製記錄的原始值的欄位值的字典,形如: {'field_name': overridden_value, ...}

    • 返回

      新記錄

  • Model.default_get(fields_list) → default_values[原始碼]

    返回fields_list中欄位的預設值。預設值由上下文、使用者預設值和模型本身決定

    • 引數

      fields_list (list) – 需要獲取其預設值的欄位名稱

    • 返回

      將欄位名對映到相應的預設值(如果它們具有的話)的字典。

    • 返回型別

      dict

    註解

    不考慮未請求的預設值,不需要為名稱不在fields_list中的欄位返回值。

  • Model.name_create(name) → record[原始碼]

    透過呼叫create()建立新記錄,呼叫時create()時只提供一個引數值:新記錄的顯示名稱。

    新記錄將使用適用於此模型的任何預設值初始化,或透過上下文提供。create()的通常行為適用

    • 引數

      name – 要建立記錄的顯示名稱

    • 返回型別

      元組

    • 返回

      建立的記錄的name_get() 成對值

  • Model.write(vals)[原始碼]

    使用提供的值更新當前記錄集中的所有記錄

    引數:

    vals (dict) –需要更新的欄位及對應的值,比如:{'foo': 1, 'bar': "Qux"} ,將設定 foo 值為 1bar"Qux",如果那些為合法的話,否則將觸發錯誤。需要特別注意的是,需要更新的欄位越多,更新速度越慢(筆者實踐時發現的,但是沒驗證是否和欄位型別有關,特別是關係欄位,關係欄位的更新可能會呼叫對應模型的write方法,該方法如果被重寫了,也可能會導致耗時的增加,總的來說,遵守一個原則,僅更新需要更新的欄位)

    • 引發

      AccessError

      • 如果使用者對請求的物件沒有建立許可權

      • 如果使用者嘗試繞過訪問規則在請求的物件上建立

      ValidationError – 如果使用者嘗試為欄位輸入不在選擇範圍內的無效值

      UserError–如果將在物件層次結構中建立迴圈,操作的一個結果(例如將物件設定為其自己的父物件)(官方原文:if a loop would be created in a hierarchy of objects a result of the operation (such as setting an object as its own parent)

  • 對於數字型欄位(odoo.fields.Integer,odoo.fields.Float) ,值必須為對應型別

  • 對於 odoo.fields.Boolean, 值必須為bool型別

  • 對於odoo.fields.Selection, 值必須匹配選擇值(通常為str,有時為int)

  • 對於odoo.fields.Many2one,值必須為記錄的資料庫標識

  • 其它非關係欄位,使用字串值

    危險

    出於歷史和相容性原因,odoo.fields.Dateodoo.fields.Datetime欄位使用字串作為值(寫入和讀取),而不是datedatetime。這些日期字串僅為UTC格式,並根據odoo.tools.misc.DEFAULT_SERVER_DATE_FORMATodoo.tools.miisc.DEFAULT_SERVER _DATETIME_FORMAT進行格式化

  • odoo.fields.One2manyodoo.fields.Many2many使用特殊的“命令”格式來操作儲存在欄位中/與欄位關聯的記錄集。

    這種格式是一個按順序執行的三元組列表,其中每個三元組都是要對記錄集執行的命令。並非所有命令都適用於所有情況。可能的命令有:

    • (0, 0, values)

      從提供的values字典建立新記錄,形如 (0, 0, {'author': user_root.id, 'body': 'one'})

    • (1, id, values)

      使用values字典中的值更新id值為給定id值的現有記錄。不能在 create()中使用。

    • (2, id, 0)

      從記錄集中刪除id為指定id的記錄,然後(從資料庫中)刪除它

      不能在 create()中使用。

    • (3, id, 0)

      從記錄集中刪除id為指定id的記錄,但不刪除它。不能在 create()中使用。

    • (4, id, 0)

      新增一條id為指定id的已存在記錄到記錄集

    • (5, 0, 0)

      從結果集移除所有記錄, 等價於顯示的對每條記錄使用命令3。 不能在 create()中使用。

    • (6, 0, ids)

      根據ids列表,替換所有已存在記錄, 等價於使用命令(5, 0, 0),隨後對ids中的每個id使用命令(4, id, 0)。實踐發現,針對One2many欄位,如果ids對應記錄的Many2one欄位沒儲存當前模型主鍵ID值時,無法使用該命令。

    實際使用時,這些命令可以組合使用,如下,給fieldName設定值時,會先指定命令5,在執行命令 0

    Model.write({'fieldName': [(5, 0, 0), (0, 0, dict_value)]})
    
  • Model.flush(fnames=None, records=None)[原始碼]

    處理所有待定的計算(在所有模型上),並將所有待定的更新重新整理到資料庫中(Process all the pending computations (on all models), and flush all the pending updates to the database)。

    • 引數

      fnames – 需要重新整理的欄位名稱列表。如果給定,則將處理範圍限制為當前模型的給定欄位。

      records – 如果給定 (協同 fnames), 限制處理範圍為給定的記錄

搜尋/讀取(Search/Read)

  • Model.browse([ids]) → records[原始碼]

    在當前環境中查詢ids引數指定的記錄並返回記錄結果集,如果為提供引數,或者引數為[],則返回空結果集

    self.browse([7, 18, 12])
    res.partner(7, 18, 12)
    
    • 引數

      ids (int 或者 list(int) 或 None) – id(s)

    • 返回

      recordset

  • Model.search(args[, offset=0][, limit=None][, order=None][, count=False])[原始碼]

    基於args 搜尋域搜尋記錄

    • 引數

      args搜尋域。使用[]代表匹配所有記錄。

      offset (int) – 需要忽略的結果記錄數 (預設: 0)

      limit (int) – 最大返回記錄數 (預設返回所有)

      order (str) – 排序字串

      count (bool) – 如果為True,僅計算並返回匹配的記錄數 (預設: False)

    • 返回

      最多limit條符合搜尋條件的記錄

    • 引發

      AccessError –如果使用者嘗試繞過訪問規則讀取請求的物件

  • Model.search_count(args)int[原始碼]

    返回當前模型中匹配提供的搜尋域args的記錄數.

  • Model.name_search(name='', args=None, operator='ilike', limit=100) → records[原始碼]

    搜尋比較顯示名稱與給定name匹配(匹配方式為給定operator),且匹配搜尋域args的記錄

    例如,這用於基於關係欄位的部分值提供建議。有時被視為name_get()的反函式,但不能保證是。

    此方法等效於使用基於display_name的搜尋域呼叫search(),然後對搜尋結果執行“name_get()”關於搜尋結果

    • 引數

      name (str) – 需要匹配的名稱

      args (list) – 可選的搜尋域, 進一步指定限制

      operator (str) – 用於匹配name的域操作,比如 'like' 或者 '='

      limit (int) – 可選引數,返回最大記錄數

    • 返回型別

      list

    • 返回

      所有匹配記錄的對值(id, text_repr)列表

  • Model.read([fields])[原始碼]

    讀取self中記錄的指定欄位, 低階/RPC方法。Python程式碼中,優選browse().

    • 引數

      fields – 需要返回的欄位名稱(預設返回所有欄位)

    • 返回

      字典的列表,該字典為欄位名稱同其值對映,每條記錄一個字典

    • 引發

      AccessError – 如果使用者沒有給定記錄的讀取許可權

  • Model.read_group(domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True)[原始碼]

    獲取列表檢視中按給定groupby欄位分組的記錄列表。

    • 引數

      domain (list) – 搜尋域。使用[]表示匹配所有

      fields (list) – 物件上指定的列表檢視中存在的欄位列表。每個元素要麼是“field”(欄位名,使用預設聚合),要麼是“field:agg”(使用聚合函式“agg”聚合欄位),要麼就是“name:agg(field)”(使用“agg'聚合欄位並將其當做“name”返回)。可能的聚合函式為PostgreSQL提供的函式(https://www.postgresql.org/docs/current/static/functions-aggregate.html),且“count_distict”,具有預期含義。

      groupby (list) – 記錄分組依據的分組依據描述列表。groupby描述要麼是欄位(然後將按該欄位分組),要麼是字串“field:groupby_function”。目前,唯一支援的函式是dayweekmonthquarteryear,它們只適用於date/datetime欄位

      offset (int) – 需要跳過的記錄數,可選引數。

      limit (int) – 需要返回的最大記錄數,可選引數

      orderby (str) – 排序字串(當前僅支援Many2one欄位)。可選引數。

      lazy (bool) – 如果為True,則結果只按第一個groupby分組,其餘groupby放入__context鍵中。如果為False,則在一個呼叫中完成所有groupby。

    • 返回

      字典列表(每條記錄一個字典)。包含:按groupby引數中指定欄位分組後的欄位的值

      __domain: 指定搜尋條件的元組的列表

      __context: 擁有類似groupby引數的字典

    • 返回型別

      [{‘field_name_1’: value, …]

    • 引發

      AccessError

      如果使用者對所請求的物件沒有讀取許可權,

      如果使用者嘗試繞過對訪問規則讀取所請求物件

  • Model.copy_data()

    複製當前模型記錄的資料,返回一個字典,字典key為模型欄位名稱,key值為對應的欄位值。注意:返回字典key不包含Odoo系統自動生成的模型表欄位:create_uidcreate_datewrite_datewrite_uidid

欄位/檢視(Fields/Views)s
  • Model.fields_get([fields][, attributes])[原始碼]

    返回每個欄位的定義

    返回的值是包含字典的字典(按欄位名索引)。包括繼承欄位。將轉換string、help和selection(如果存在)屬性

    • 引數

      fields – 欄位列表, 如果未提供或者為[]則表示所有

      attributes – 每個欄位需要返回的屬性描述列表。 如果未提供或者為[]則表示所有

  • Model.fields_view_get([view_id | view_type='form'])[原始碼]

    獲取所請求檢視的詳細組成,如欄位、模型、檢視架構

    • 引數

      view_id (int) – 檢視的ID或者None

      view_type (str) – 返回檢視的型別,如果view_idNone的話(‘form’, ‘tree’, …)

      toolbar (bool) – 設定為True以包含上下文操作

      submenu – 已棄用

    • 返回

      請求檢視的組成(包括繼承的檢視和擴充套件)

    • 返回型別

      dict

    • 引發

      AttributeError

      如果繼承的檢視具有除“before”、“after”、“inside”、“replace”以外的未知位置

      則如果在父檢視中找到除“position”以外的標記

      Invalid ArchitectureError – 如果框架中有定義form, tree, calendar, search 等以外的檢視

搜尋域(Search domains)

域是一個標準列表,每個標準都是(field_name,operator,value)的三元組(一個“列表”或“元組”),其中:

  • field_name (str)

    當前模組的欄位名稱 或透過Many2one,使用點符號的關係遍歷,例如 'street' 或者'partner_id.country'

  • operator (str)

    用於比較field_namevalue的運算子。有效運算子為:

    • =

      等於

    • !=

      不等於

    • >

      大於

    • >=

      大於等於

    • <

      小於

    • <=

      小於等於

    • =?

      未設定或者等於(如果valueNone或者False則返回True,否則與=一樣)

    • =like

      field_namevalue模式匹配。模式中的下劃線_匹配任何單個字元;百分號%匹配任何零個或多個字元的字串

    • like

      field_name%value%模式匹配。類似=like,但是匹配前使用%包裝value

    • not like

      不匹配 %value% 模式

    • ilike

      大小寫敏感的like

    • not ilike

      大小寫敏感的 not like

    • =ilike

      大小寫敏感的 =like

    • in

      等於value中的任意項,value應該為項列表

    • not in

      不等於value中的任意項

    • child_of

      value記錄的child(後代)(value可以是一個項或一個項列表)。考慮模型的語義(即遵循由_parent_name命名的關係欄位)。

    • parent_of

      value記錄的parent(祖先)(value可以是一個項或一個項列表)。考慮模型的語義(即遵循由_parent_name命名的關係欄位)

  • value

    變數型別,必須可同命名欄位比較(透過 operator)

可以使用字首形式的邏輯運算子組合域條件:

  • '&'

    邏輯 AND, 預設操作,以將條件相互結合。Arity 2 (使用下2個標準或組合)

  • '|'

    邏輯 OR arity 2

  • '!'

    邏輯 *NOT * arity 1

例子:

搜尋來自比利時或德國名為ABC,且語言不為英語的合作伙伴:

[('name','=','ABC'),
 ('language.code','!=','en_US'),
 '|',('country_id.code','=','be'),
     ('country_id.code','=','de')]

該域被解釋為:

    (name is 'ABC')
AND (language is NOT english)
AND (country is Belgium OR Germany)
  • Model.unlink()[原始碼]

    刪除當前記錄集中的記錄

    引發

    AccessError

    如果使用者沒有所請求物件的unlink許可權

    如果使用者嘗試繞過訪問規則對請求物件執行unlink

    UserError –如果記錄為其它記錄的預設屬性

記錄(集)資訊

  • Model.ids

    返回與self對應的真實記錄ID

  • odoo.models.env

    返回給定記錄集的環境。型別Environment

  • Model.exists() → records[原始碼]

    返回self中存在的記錄子集並將刪除的記錄標記為快取中的記錄. 可用作對記錄的測試:

    if record.exists():
        ...
    

    按約定,將新記錄作為現有記錄返回

  • Model.ensure_one()[原始碼]

    驗證當前記錄集只擁有一條記錄

    引發odoo.exceptions.ValueErrorlen(self) != 1

  • Model.name_get() → [id, name, ...][原始碼]

    返回self中記錄的文字表示形式。預設情況下,為display_name欄位的值。

    • 返回

      每個記錄的 (id, text_repr) 對值列表

    • 返回型別

      list(tuple)

  • Model.get_metadata()[原始碼]

    返回關於給定記錄的後設資料

    • 返回

      每個請求記錄的所有權字典列表 list of ownership dictionaries for each requested record

    • 返回型別

      具有以下關鍵字的字典列表:

      • id: 物件ID
      • create_uid: 建立記錄的使用者
      • create_date: 建立記錄的日期
      • write_uid: 上次更改記錄的使用者
      • write_date: 上次更改記錄的日期
      • xmlid: 用於引用此記錄的XML ID(如果有),格式為module.name
      • noupdate: 一個布林值,指示記錄是否將被更新

操作

記錄集是不可變的,但可以使用各種集合操作組合同一模型的集合,從而返回新的記錄集

  • record in set 返回 record (必須為只包含一個元素的記錄集) 是否在 set中。 record not in set 則剛好相反
  • set1 <= set2 andset1 < set2 返回set1是否是set2的子集
  • set1 >= set2 and set1 > set2 返回set1是否是set2的超集
  • set1 | set2 返回兩個記錄集的並集。一個包含出現在兩個源記錄集中的所有記錄的記錄集
  • set1 & set2 返回兩個記錄集的交集。一個只包含同時存在兩個源記錄集中的記錄的記錄集。
  • set1 - set2 返回一個包含僅出現在set1中的記錄的記錄集

記錄集是可迭代的,因此通常的Python工具可用於轉換(map()sorted()ifilter(),…),然後這些函式返回listiterator,刪除對結果呼叫方法或使用集合操作的能力。

因此,記錄集提供以下返回記錄集本身的操作(如果可能):

Filter
  • Model.filtered(func)[原始碼]

    • 引數

      func (可呼叫物件 或者 str) – 一個函式或者點分欄位名稱序列

    • 返回

      滿足func的記錄集,可能為空。

    # only keep records whose company is the current user's
    records.filtered(lambda r: r.company_id == user.company_id)
    
    # only keep records whose partner is a company
    records.filtered("partner_id.is_company")
    
  • Model.filtered_domain(domain)[原始碼]

Map
  • Model.mapped(func)[原始碼]

    self中的所有記錄應用func,並將結果作為列表或記錄集返回(如果func返回記錄集)。後者返回的記錄集的順序是任意的。

    • 引數

      func (可呼叫物件 或 str) – 一個函式或者點分欄位名稱序列

    • 返回

      如果funcFalse則返回self 作用於所有self中記錄的func的返回結果

    • 返回型別

      list 或 recordset

    # returns a list of summing two fields for each record in the set
    records.mapped(lambda r: r.field1 + r.field2)
    

    提供的函式可以是獲取欄位值的字串:

    # returns a list of names
    records.mapped('name')
    
    # returns a recordset of partners
    records.mapped('partner_id')
    
    # returns the union of all partner banks, with duplicates removed
    records.mapped('partner_id.bank_ids')
    

註解

V13開始, 支援多多關係欄位訪問,像mapped呼叫那樣工作:

records.partner_id  # == records.mapped('partner_id')
records.partner_id.bank_ids  # == records.mapped('partner_id.bank_ids')
records.partner_id.mapped('name')  # == records.mapped('partner_id.name')
Sort
  • Model.sorted(key=None, reverse=False)[原始碼]

    返回按key排序的記錄集 self

    • 引數

      key (可呼叫物件或者str 或者 None) – 一個引數的函式,為每個記錄返回一個比較鍵,或欄位名,或None,如果為None,記錄按照預設模型的順序排序

      reverse (bool) – 如果為True, 返回逆序排序的結果

    # sort records by name
    records.sorted(key=lambda r: r.name)
    

繼承與擴充套件(Inheritance and extension)

Odoo提供三種不同的機制,以模組化方式擴充套件模型:

  • 從現有模型建立新模型,向副本中新增新資訊,但保留原始模組
  • 擴充套件其他模組中定義的模型,替換以前的版本
  • 將模型的一些欄位委派給它包含的記錄

經典繼承

當同時使用_inherit_name 屬性時,Odoo使用現有模型(透過_inherit提供)作為base建立新模型。新模型從其base中獲取所有欄位、方法和元資訊(預設值等)。

class Inheritance0(models.Model):
    _name = 'inheritance.0'
    _description = 'Inheritance Zero'

    name = fields.Char()

    def call(self):
        return self.check("model 0")

    def check(self, s):
        return "This is {} record {}".format(s, self.name)

class Inheritance1(models.Model):
    _name = 'inheritance.1'
    _inherit = 'inheritance.0'
    _description = 'Inheritance One'

    def call(self):
        return self.check("model 1")

使用它們:

a = env['inheritance.0'].create({'name': 'A'})
b = env['inheritance.1'].create({'name': 'B'})

a.call()
b.call()

輸出:

“This is model 0 record A” “This is model 1 record B”

第二個模型繼承了第一個模型的check方法及其name欄位,但重寫了call方法,就像使用標準Python繼承一樣。

說明:

  • 以上為官方文件給出的案例,筆者實踐發現是無法直接執行的。

  • 模型繼承會繼承父類中的所有屬性,會複製欄位、屬性和方法。

  • 可以同時繼承多個模型,比如:

    _inherit = ['res.partner', 'md.status.mixin']
    

擴充套件

當使用_inherit但省略_name時,新模型將替換現有模型,實質上就是在原有模型上擴充套件。這對於將新欄位或方法新增到現有模型(在其他模組中建立)或自定義或重新配置它們(例如更改其預設排序順序)非常有用:

class Extension0(models.Model):
    _name = 'extension.0'
    _description = 'Extension zero'

    name = fields.Char(default="A")
    
    def func():
        print('test a')

class Extension1(models.Model):
    _inherit = 'extension.0'

    description = fields.Char(default="Extended")
    
    def func(): # 重寫函式
        print('test b')
record = env['extension.0'].create({})
record.read()[0]

返回:

{'name': "A", 'description': "Extended"}

註解

它還會返回各種自動生成的欄位,除非它們被禁用了。

env['extension.0'].func({})

返回:

test b

注意:

如果同時繼承抽象模組和非抽象模組,並把_name配置為非抽象模組,抽象模組的欄位也會新增到非抽象模組對應的表

委託(Delegation)

第三種繼承機制提供了更大的靈活性(可以在執行時更改),但威力更小:使用_inherits模型,將當前模型中未找到的任何欄位的查詢委託給“children”模型。委託透過Reference執行在父模型上自動設定的欄位。

主要區別在於意義。使用委託時,模型has one而不是is one,從而將關係轉換為組合而不是繼承:

class Screen(models.Model):
    _name = 'delegation.screen'
    _description = 'Screen'

    size = fields.Float(string='Screen Size in inches')

class Keyboard(models.Model):
    _name = 'delegation.keyboard'
    _description = 'Keyboard'

    layout = fields.Char(string='Layout')

class Laptop(models.Model):
    _name = 'delegation.laptop'
    _description = 'Laptop'

    _inherits = {
        'delegation.screen': 'screen_id',
        'delegation.keyboard': 'keyboard_id',
    }

    name = fields.Char(string='Name')
    maker = fields.Char(string='Maker')

    # a Laptop has a screen
    screen_id = fields.Many2one('delegation.screen', required=True, ondelete="cascade")
    # a Laptop has a keyboard
    keyboard_id = fields.Many2one('delegation.keyboard', required=True, ondelete="cascade")

record = env['delegation.laptop'].create({
    'screen_id': env['delegation.screen'].create({'size': 13.0}).id,
    'keyboard_id': env['delegation.keyboard'].create({'layout': 'QWERTY'}).id,
})
record.size
record.layout

將產生結果:

13.0
'QWERTY'

可以直接修改委託欄位:

record.write({'size': 14.0})

警告

使用委託繼承時,方法不是被繼承的,只有欄位

警告

  • _inherits 或多或少已實現,如果可以的話避免用它(_inherits is more or less implemented, avoid it if you can)
  • 鏈式的_inherits基本上沒有實現,我們不對最終行為做任何保證。(chained _inherits is essentially not implemented, we cannot guarantee anything on the final behavior)

欄位增量定義

欄位定義為模型類的類屬性。如果擴充套件了模型,還可以透過在子類上重新定義具有相同名稱和型別的欄位來擴充套件欄位定義。在這種情況下,欄位的屬性取自父類,並由子類中給定的屬性覆蓋。

例如,下面的第二個類僅在state欄位上新增工具提示:

class First(models.Model):
    _name = 'foo'
    state = fields.Selection([...], required=True)

class Second(models.Model):
    _inherit = 'foo'
    state = fields.Selection(help="Blah blah blah")

入門實踐

模型定義

odoo14\custom\estate\models\estate_property_tag.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from odoo import models, fields

class EstatePropertyTag(models.Model):
    _name = 'estate.property.tag'
    _description = 'estate property tag'
    _order = 'name'

    name = fields.Char(string='tag', required=True)
    color = fields.Integer(string='Color')

odoo14\custom\estate\models\estate_property_offer.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from odoo import models, fields

class EstatePropertyOffer(models.Model):
    _name = 'estate.property.offer'
    _description = 'estate property offer'

    property_id = fields.Many2one('estate.property', required=True)
    price = fields.Integer()

odoo14\custom\estate\models\estate_property_type.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from odoo import models, fields

class EstatePropertyType(models.Model):
    _name = 'estate.property.type'
    _description = 'estate property type'

    name = fields.Char(string='name', required=True)

odoo14\custom\estate\models\estate_property.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from odoo import models, fields

class EstateProperty(models.Model):
    _name = 'estate.property'
    _description = 'estate property table'
    _order = 'id desc'

    name = fields.Char(required=True)
    property_type_id = fields.Many2one("estate.property.type", string="PropertyType")
    tag_ids = fields.Many2many("estate.property.tag")
    offer_ids = fields.One2many("estate.property.offer", "property_id", string="PropertyOffer")

ORM操作實踐

>>> self.env['estate.property.type']
estate.property.type()

# 建立單條記錄
>>> self.env['estate.property.type'].create({'name':'house'})
estate.property.type(1,)

# 按id查詢記錄
>>> self.env['estate.property.type'].browse([1])
estate.property.type(1,)
# 未給定id列表,或者未提供引數的情況下,返回空記錄集
>>> self.env['estate.property.type'].browse()
estate.property.type()
>>> self.env['estate.property.type'].browse([])
estate.property.type()

# 複製記錄
>>> self.env['estate.property.type'].browse([1]).copy({'name':'garden'})
estate.property.type(2,)

# 針對僅獲取單條記錄的記錄集,可透過 records.fieldName 的方式引用對應欄位(讀取欄位值,或者給欄位賦值)
>>> self.env['estate.property.type'].browse([2]).name 
'garden'

# 更新記錄
>>> self.env['estate.property.type'].browse([1]).name
'house'
>>> self.env['estate.property.type'].browse([1]).write({'name':'garden'})
True
>>> self.env['estate.property.type'].browse([1]).name
'garden'
# 針對僅獲取單條記錄的記錄集,可透過 records.fieldName 的方式引用對應欄位(讀取欄位值,或者給欄位賦值)
>>> self.env['estate.property.type'].browse([1]).name = 'house'
>>> self.env['estate.property.type'].browse([1]).name
'house'
# 不能直接透過以下方式,試圖在write函式指定id的方式來更新記錄 # 不會修改任何記錄,也未新增任何記錄
>>> self.env['estate.property.type'].write({'id':1, 'name':'apartment'}) 
True
>>> self.env['estate.property.type'].browse([1]).name
'house'


# 透過search api查詢記錄集
>>> self.env['estate.property.type'].search([])
estate.property.type(1, 2)

# 批次建立記錄
# 建立測試用資料
>>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag1', 'color': 2}, {'name': 'tag1', 'color': 3}])
estate.property.tag(1, 2, 3)

# 注意:Many2one型別欄位的值,必須設定為對應記錄的主鍵id
>>> self.env['estate.property'].create({'name': 'house in beijing', 'property_type_id': 1, 'tag_ids':[(0,0, {'name': 'tag1', 'color': 3})]})
estate.property(1,)
>>> self.env['estate.property'].search([]) 
estate.property(1,)

# 查詢關係欄位值
>>> self.env['estate.property'].browse([1]).property_type_id # Many2one
estate.property.type(1,)
>>> self.env['estate.property'].browse([1]).tag_ids  # Many2many
estate.property.tag(4,)

# 更新Many2many關係欄位值
>>> self.env['estate.property'].browse([1]).tag_ids.write({'name': 'tag4', 'color': 4})
True
>>> self.env['estate.property'].browse([1]).tag_ids.color
4

>>> self.env['estate.property.tag'].search([])
estate.property.tag(1, 2, 3, 4)

# 查詢關係欄位值
>>> self.env['estate.property'].browse([1]).offer_ids # One2many
estate.property.offer()

## 更新One2many關係欄位值
# 為關係欄位建立關聯記錄
# (0, 0, values)
# 從提供的`values`字典建立新記錄。
>>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1})]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1,)
>>> self.env['estate.property'].browse([1]).offer_ids.property_id
estate.property(1,)

# 更新關係欄位所代表記錄物件的屬性值
# (1, id, values)
# 使用 values 字典中的值更新id值為給定 id 值的現有記錄。不能在create()中使用。
>>> self.env['estate.property'].browse([1]).offer_ids = [(1, 1, {'price': 30000})]
>>> self.env['estate.property'].browse([1]).offer_ids.price
30000

# 刪除關係欄位關聯記錄
# (3, id, 0)
# 從記錄集中刪除id為id的記錄,但不從資料庫中刪除它,可以理解為僅解除關聯。不能在create()中使用。
>>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer()

# 將已存在記錄同關係欄位關聯
# (4, id, 0)
# 新增一條id為id已存在記錄到記錄集
>>> self.env['estate.property.offer'].browse([1])
estate.property.offer(1,)
>>> self.env['estate.property'].browse([1]).offer_ids = [(4,1,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1,)

# 為關係欄位一次建立多條關聯記錄
>>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1, 'price': 100000}),(0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 300000})]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1, 2, 3, 4, 5)

# 替換關係欄位關聯的記錄
# (6, 0, ids) 
# 根據ids列表,替換所有已存在記錄, 等價於使用命令(5, 0, 0),隨後對ids中的每個id使用命令(4, id, 0)。
>>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0),(3,2,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(3, 4, 5)
>>> self.env['estate.property'].browse([1]).offer_ids = [(6, 0, [1,2])] # 報錯, 因為ID 1,2 對應的記錄,其Many2one欄位值為null

# 為Many2many關係欄位建立多條關聯記錄
>>> self.env['estate.property'].create({'name': 'house in shanghai'})
estate.property(2,)
>>> self.env['estate.property'].browse([2])
estate.property(2,)
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag()
>>> self.env['estate.property'].browse([2]).tag_ids = [(0, 0, {'name': 'tag5', 'color': 5}), (0, 0, {'name': 'tag6', 'color': 6}), (0, 0, {'name': 'tag7', 'color': 7})]
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(5, 6, 7)

# 刪除關係欄位關聯的記錄
# (2, id, 0)
# 從記錄集中刪除id為id的記錄,然後(從資料庫中)刪除它,不能在create()中使用
>>> self.env['estate.property'].browse([2]).tag_ids = [(2, 5, 0)]
2023-01-29 08:48:25,491 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [5]
>>> print( self.env['estate.property.tag'].browse([5]).exists())
estate.property.tag()
>>> if self.env['estate.property.tag'].browse([5]).exists():
...     print('exists record with id equal 5')
...
>>>

# 建立測試用資料
>>> self.env['estate.property.tag'].create({'name': 'tag8', 'color': 8})
estate.property.tag(8,)
>>> self.env['estate.property.tag'].create({'name': 'tag9', 'color': 9})
estate.property.tag(9,)
>>> self.env['estate.property'].browse([2])
estate.property(2,)

# 替換關係欄位關聯的記錄
# (6, 0, ids) 
# 根據ids列表,替換所有已存在記錄, 等價於使用命令(5, 0, 0),隨後對ids中的每個id使用命令(4, id, 0)。
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(6, 7)
>>> self.env['estate.property'].browse([2]).tag_ids = [(6, 0 , [8, 9])]
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(8, 9)
>>>

# 透過mapped獲取記錄欄位值(關聯記錄的屬性值)列表
>>> self.env['estate.property'].browse([2]).tag_ids.mapped('name')
['tag8', 'tag9']
>>> self.env['estate.property'].browse([2]).mapped('tag_ids')
estate.property.tag(8, 9)
>>> self.env['estate.property'].browse([2]).mapped('tag_ids').mapped('id')) 
[8, 9]
              

# search api 應用
# 搜尋域
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)])
estate.property.tag(6, 7, 8, 9)

# 偏移
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1)
estate.property.tag(7, 8, 9)

# 限制返回記錄集中的最大記錄數
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2)
estate.property.tag(7, 8)

# 返回記錄集中的記錄排序
# 降序
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id desc')
estate.property.tag(8, 7)
# 升序
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id')
estate.property.tag(7, 8)
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id asc')
estate.property.tag(7, 8)

# 僅返回記錄數
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], count=True)
4
# 利用search_count api實現等價效果
>>> self.env['estate.property.tag'].search_count(args=[('id', '>', 5)])
4

# 搜尋域條件組合
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5),('color', '<', 8)])
estate.property.tag(6, 7)


# 獲取記錄(集)資訊
# ids
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).ids
[6, 7, 8, 9]
# env
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).env
<odoo.api.Environment object at 0x0000020E31C80080>
# name_get api 使用
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).name_get()
[(6, 'tag6'), (7, 'tag7'), (8, 'tag8'), (9, 'tag9')]
# get_metadata api 使用
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).get_metadata()
[{'id': 6, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8,41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 7, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 8, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023,1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 9, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}]


# 利用 read_group 實現按組讀取
>>> self.env['estate.property.tag'].create({'name': 'tag10', 'color': 9})
estate.property.tag(10,)
>>> self.env['estate.property.tag'].read_group([], fields=['color'], groupby=['color'])
[{'color_count': 1, 'color': 6, '__domain': [('color', '=', 6)]}, {'color_count': 1, 'color': 7, '__domain': [('color', '=', 7)]}, {'color_count': 1, 'color': 8, '__domain': [('color', '=', 8)]}, {'color_count': 2, 'color': 9, '__domain': [('color', '=', 9)]}]


# 獲取欄位定義
>>> self.env['estate.property.tag'].fields_get(['name'])
{'name': {'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': True, 'searchable': True, 'sortable': True
, 'store': True, 'string': 'tag', 'translate': False, 'trim': True}}

# 回滾
>>> self.env.cr.rollback()
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id')
estate.property.tag()

# 執行 sql
self.env.cr.execute('TRUNCATE TABLE estate_property_tag_test CASCADE;')
self.env.cr.commit()
# 重置自增主鍵ID 為1(每個表的主鍵ID存在名為 tableName_id_seq 的序列中)
self.env.cr.execute('ALTER SEQUENCE estate_property_tag_test_id_seq RESTART WITH 1;')
self.env.cr.commit()

>>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag2', 'color': 2}, {'name': 'tag3', 'color': 3}])
estate.property.tag(1, 2, 3)

# 批次更新記錄欄位值 #記錄集存在多條記錄的情況下,不能透過 records.fieldName = 目標值 實現批次更新
>>> self.env['estate.property.tag'].browse([1,3]).write({'color':1})  
True
>>> self.env['estate.property.tag'].browse([1,3]).mapped('color')
[1, 1]

# 修改查詢記錄集context
>>> self.env['estate.property.tag'].browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels'}
>>> self.env['estate.property.tag'].with_context(is_sync=False).browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}

# with_context和sudo共存時的使用方式
>>> self.env['estate.property.tag'].with_context(is_sync=False).sudo().browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
>>> self.env['estate.property.tag'].sudo().with_context(is_sync=False).browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}

# 修改建立記錄時返回記錄的context(更新記錄(write)也是一樣的用法)
# 如此,可以透過重寫對應模型的create或者write方法,並在方法中透過self.env.context獲取目標key值,進而執行需求實現需要採取的動作,參見下文
>>> self.env['estate.property.tag'].with_context(is_sync=False).create({'name': 'tag4', 'color': 4}).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}


# 刪除記錄
>>> self.env['estate.property.tag'].search([])
estate.property.tag(1, 2, 3, 4)
>>> self.env['estate.property.tag'].search([('id', '>', 2)]).unlink()
2023-01-29 09:55:47,796 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [3, 4]
True

# 遍歷記錄集
>>> for record_set in self. self.env['estate.property.tag.test'].search([]):
...     print(record_set)
...
estate.property.tag.test(1,)
estate.property.tag.test(2,)

獲取context上下文目標key值示例

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from odoo import models, fields,api

class EstatePropertyTag(models.Model):
    _name = 'estate.property.tag'
    _description = 'estate property tag'
    _order = 'name'

    name = fields.Char(string='tag', required=True)
    color = fields.Integer(string='Color')


    @api.model
    def create(self, vals_list):
        res = super(EstatePropertyTag, self).create(vals_list)
        # 獲取上下文目標key值
        if not self.env.context.get('is_sync', True):
            # do something you need
        return res

參考連線

https://www.odoo.com/documentation/14.0/zh_CN/developer/reference/addons/orm.html#