• odoo ORM API学习总结兼orm学习教程


    环境

    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-具有此选项的所有记录都将设置为字段定义的默认值
      • -一个可调用对象,其第一个也是唯一的参数将是包含指定的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
    object ...>
    >>> records.env.user
    res.user(3)
    >>> records.env.cr
    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
    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#

  • 相关阅读:
    深度学习实战51-基于Stable Diffusion模型的图像生成原理详解与项目实战
    核酸检测小程序实战教程
    开源联合、聚力共赢丨2023 CCF中国开源大会10月开幕
    什么是MISRA C++
    如何在CentOS7搭建DashDot服务器仪表盘并实现远程监控
    Redis的三种启动方式与检测运行情况
    XCZU19EG板卡设计资料:610-基于6U VPX 的FPGA XCZU19EG存储阵列
    ceph 线程池分析
    ResNet 训练CIFAR10数据集,并做图片分类
    JavaScript 保留关键字
  • 原文地址:https://www.cnblogs.com/shouke/p/17135896.html