ToB企服应用市场:ToB评测及商务社交产业平台

标题: odoo ORM API学习总结兼orm学习教程 [打印本页]

作者: 写过一篇    时间: 2023-3-11 11:14
标题: odoo ORM API学习总结兼orm学习教程
环境

odoo-14.0.post20221212.tar
ORM API学习总结/学习教程

模型(Model)

Model字段被定义为model自身的属性
  1. from odoo import models, fields
  2. class AModel(models.Model):
  3.     _name = 'a.model.name'
  4.     field1 = fields.Char()
复制代码
警告
字段的名称和方法的名称不能相同,最后定义的方法、函数名称会覆盖前面定义的相同名称。
默认的,字段的标签(Lable,即用户可见字段名称)为对应字段名称开头字母改成大写后的值,可通过 string 字段属性改成修改字段Label
  1. field2 = fields.Integer(string="Field Label")
复制代码
可通过default,定义默认值:
  1. name = fields.Char(default="a value")
复制代码
默认值也可以通过函数获取:
  1.     def _default_name(self):
  2.         return 'Title'
  3. name = fields.Char(default=lambda self: self._default_name())
复制代码
API

BaseModel

class odoo.models.BaseModel[源代码]
Odoo模型的基类。Odoo mode可通过继承一下类来创建Model:
系统为每个数据库自动实例化每个模型一次。这些实例表示每个数据库上的可用模型,取决于该数据库上安装的模块。每个实例的实际类都是从创建和继承相应模型的Python类构建的。
每个模型实例都是一个“记录集(recordset)”,即模型记录的有序集合。记录集由 browse(), search()或字段访问等方法返回。记录没有显式的表示:单条记录表示为一条记录的记录集。
要创建不需要实例化的类,可以将 _register 属性设置为False
AbstractModel

odoo.models.AbstractModel[源代码]
odoo.models.BaseModel的别名
Model

class odoo.models.Model[源代码]
常规数据库持久化Odoo模型的主要父类。
通过继承此类来创建Odoo模型的:
  1. class user(Model):
  2.     ...
复制代码
系统将为安装了该类模块的每个数据库实例化一次类
TransientModel

class odoo.models.TransientModel[源代码]
用于临时记录的父类模型,旨在暂时保持,并定期进行清理
TransientModel具有简化的访问权限管理,所有用户都可以创建新记录,并且只能访问他们创建的记录。超级用户可以无限制地访问所有TransientModel记录。
字段(Fields)

class odoo.fields.Field[源代码]
字段拥有以下属性
基础字段

class odoo.fields.Boolean[源代码]

bool的封装
class odoo.fields.Char[源代码]

基本字符串字段,长度有限,通常在客户端显示为单行字符串
参数:
class odoo.fields.Float[源代码]

float的封装
精度数字由可选的digitals属性给出。
参数
Float类为此提供了一些静态方法:
round()以给定精度对浮点值进行舍入。is_zero()检查浮点值在给定精度下是否等于零。compare()按给定精度比较两个浮点值。
例子:
  1. fields.Float.round(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
复制代码
  1. fields.Float.is_zero(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
复制代码
  1. 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[源代码]

封装二进制内容(比如一个文件)。
参数:
class odoo.fields.Html[源代码]

html代码内容的封装
参数:略
class odoo.fields.Image[源代码]

图片的封装,扩展Binary
如果图像大小大于像素的max_width/max_height限制,则通过保持纵横比将图像大小调整到该限制。
参数:
如果没有指定 max_width/max_height 或者设置为0,且verify_resolution为False,则不会验证字段内容,此时应该使用Binary字段。
class odoo.fields.Monetary[源代码]

封装以给定res_currency表示的浮点值。
小数精度和货币符号取自currency_field属性。
参数:
class odoo.fields.Selection[源代码]

封装不同值之间的互斥选择。
说明:Selection字段的可选值,存储在public.ir_model_fields_selection表中,通过field_id字段通过public.ir_model_fields表进行
  1. -- 查询Selection字段ID
  2. SELECT id FROM public.ir_model_fields
  3. where model = 'stock.quality' and name='state'
  4. -- 查询Selection字段可选值
  5. select * from public.ir_model_fields_selection where field_id = 13028; -- 13028为Selection字段ID
复制代码
参数:
selection属性选择是强制性的,除非是related或扩展的字段
class odoo.fields.Text[源代码]

类似Char,用于更长的内容,没有大小,通常展示为多行文本框。
参数:
translate (bool 或者可调用对象) – 同 Char
Date(time) 字段

当将一个值赋值给 Date/Datetime 字段时,以下选择是合法的:
Date 和Datetime 字段类拥有以下辅助函数,用于尝试转换为兼容类型:
示例
解析来自外部的日期/日期时间:
  1. fields.Date.to_date(self._context.get('date_from'))
复制代码
Date/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对象的封装
class odoo.fields.Datetime[源代码]

Python datetime对象的封装
关系字段(Relational Fields)

class odoo.fields.Many2one[源代码]

Many2one字段的值是大小为0(无记录)或1(单个记录)的记录集。
参数:
class odoo.fields.One2many[源代码]

One2many字段的值为 comodel_name中所有满足条件的记录的结果集,而目标模型中的 inverse_name 则等价于当前记录。
参数:
comodel_name 和inverse_name 参数是必选参数,除非是相关或者扩展字段
class odoo.fields.Many2many[源代码]

Many2many字段的值为一个结果集。
参数:
relation, column1 和column2 参数可选。 如果未给定,自动根据模型名称生成,提供的不同的model_name 和comodel_name 。
注意,ORM不支持在给定模型,使用同样的comodel,创建多个省略了relation参数的字段,因为这些字段将使用相同的表。ORM阻止两个Many2many字段使用相同的relation参数,除非:
参数:
注意:odoo不会在当前模型对应表中为One2many,Many2many类型的属性建立对应的表字段,但会为Many2one类型的属性建立对应表字段,针对Many2many类型的属性,odoo会建立一张辅助表,表名默认格式为model1_table_name_model2_table_name_rel,该表拥有两列,一列为当前模型表主键ID(model1_table_name_id),一列为关系字段关联模型表的主键ID(model2_table_name_id),这样通过两表记录ID就可以查询所需记录了
伪关系字段

计算字段

可以使用 compute 参数计算字段(而不是直接从数据库中读取)它必须将计算值分配给字段。如果它使用其他字段的值,则应使用depends()指定这些字段
  1. from odoo import api
  2. total = fields.Float(compute='_compute_total')
  3. @api.depends('value', 'tax')
  4. def _compute_total(self):
  5.     for record in self:
  6.         record.total = record.value + record.value * record.tax
复制代码
警告
虽然可以对多个字段使用相同的计算方法,但不建议对reverse方法使用相同的方法。
在reverse的计算过程中,所有使用所述inverse的字段都受到保护,这意味着即使它们的值不在缓存中,也无法计算它们。
如果访问了这些字段中的任何一个字段,且并且其值不在缓存中,ORM将简单的为这些字段返回默认值False。这意味着这些inverse字段的值(触发inverse方法的值除外)可能不会给出正确的值,这可能会破坏inverse方法的预期行为
相关字段(Related fields)

计算字段的一种特殊情况是相关(代理)字段,它提供当前记录上子字段的值。它们是通过设置related参数来定义的,与常规计算字段一样,它们可以存储:
  1. nickname = fields.Char(related='user_id.partner_id.name', store=True)
复制代码
related字段的值是通过遍历一系列关系字段并读取所访问模型上的字段来给出的。要遍历的字段的完整序列由related属性指定
如果未重新定义某些字段属性,则会自动从源字段中复制这些属性:string、help、required(仅当序列中的所有字段都是必需的时)、groups、digits、size、translate、cleaning”、“selection、comodel_name、domain和context。所有无语义属性都从源字段复制。
默认的, related字段:
像计算字段那样,添加 store=True 以存储related字段。当其依赖被修改时,会自动重新计算related字段。
小技巧
如果不希望在任何依赖项更改时重新计算related字段,则可以指定精确的字段依赖项:
  1. nickname = fields.Char(
  2.     related='partner_id.name', store=True,
  3.     depends=['partner_id'])
  4. # nickname仅在partner_id被修改时才会被重新计算,而不会在partner名称被修改时重新计算
复制代码
警告
不可以在related字段依赖项中包含 Many2many 或者 One2many 字段
related 可以用于引用另一个模型中的 One2manyMany2many 字段,前提是通过当前模型的一个Many2one关系来实现的。 One2many 和Many2many 不被支持,无法正确的汇总结果:
  1. m2o_id = fields.Many2one()
  2. m2m_ids = fields.Many2many()
  3. o2m_ids = fields.One2many()
  4. # Supported
  5. d_ids = fields.Many2many(related="m2o_id.m2m_ids")
  6. e_ids = fields.One2many(related="m2o_id.o2m_ids")
  7. # Won't work: use a custom Many2many computed field instead
  8. f_ids = fields.Many2many(related="m2m_ids.m2m_ids")
  9. g_ids = fields.One2many(related="o2m_ids.o2m_ids")
复制代码
自动生成的字段

访问日志字段

如果启用_log_access,自动设置并更新这些字段。当未用到这些字段时,以禁用它以阻止创建或更新表中这些字段。
默认的 _log_access被设置为 _auto的值。
警告
必须对odoo.models.TransientModel模型开启_log_access
保留字段名称

除了自动字段之外,还有一些字段名是为预定义行为保留的。当需要相关行为时,应在模型上定义它们:
记录集(Recordset)

与模型和记录的交互是通过记录集执行的,记录集是同一模型的记录的有序集合。
警告
与名称所暗示的相反,记录集当前可能包含重复项。这在未来可能会改变。
在模型上定义的方法是在记录集上执行的,方法的self是一个记录集:
  1. class AModel(models.Model):
  2.     _name = 'a.model'
  3.     def a_method(self):
  4.         # self can be anything between 0 records and all records in the
  5.         # database
  6.         self.do_operation()
复制代码
对记录集进行迭代将产生新的单条记录的记录集,这与对Python字符串进行迭代产生单个字符的字符串非常相似:
  1. def do_operation(self):
  2.     print(self) # => a.model(1, 2, 3, 4, 5)
  3.     for record in self:
  4.         print(record) # => a.model(1), then a.model(2), then a.model(3), ...
复制代码
字段访问

记录集提供了一个“Active Record” 接口:模型字段可直接作为记录的属性直接读取和写入。
注解
当访问潜在多条记录的记录集上的非关系字段时,使用mapped(),该函数返回一个列表:
  1. total_qty = sum(self.mapped('qty')) # mapped返回一个列表,形如[2,4,5]
复制代码
字段值也可以像字典项一样访问。设置字段的值会触发对数据库的更新:
  1. >>> record.name
  2. Example Name
  3. >>> record.company_id.name
  4. Company Name
  5. >>> record.name = "Bob"
  6. >>> field = "name"
  7. >>> record[field]
  8. Bob
复制代码
警告
记录缓存和预取

Odoo为记录的字段维护一个缓存,这样,不是每个字段的访问都会发出数据库请求。
以下示例仅为第一条语句查询数据库:
  1. record.name             # 第一次访问从数据库获取值
  2. record.name             # 第二次访问从缓存获取值
复制代码
为了避免一次读取一条记录上的一个字段,Odoo会按照一些启发式方法预取个记录和字段,以获得良好的性能。一旦必须在给定记录上读取字段,ORM实际上会在更大的记录集上读取该字段,并将返回的值存储在缓存中以供后续使用。预取的记录集通常是通过迭代获得记录的记录集。此外,所有简单的存储字段(布尔值、整数、浮点值、字符、文本、日期、日期时间、选择、many2one)都会被提取;它们对应于模型表的列,并在同一查询中高效地获取。
考虑以下示例,其中partners为包含1000条记录的记录集。如果不进行预取,循环将对数据库进行2000次查询。使用预取,只进行一次查询
  1. for partner in partners:
  2.     print partner.name          # first pass prefetches 'name' and 'lang'
  3.                                 # (and other fields) on all 'partners'
  4.     print partner.lang
复制代码
预取也适用于辅助记录:当读取关系字段时,它们的值(即记录)将被订阅以供将来预取。访问这些辅助记录之一将预取同一模型中的所有辅助记录。这使得以下示例仅生成两个查询,一个用于合作伙伴,另一个用于国家/地区:
  1. countries = set()
  2. for partner in partners:
  3.     country = partner.country_id        # first pass prefetches all partners
  4.     countries.add(country.name)         # first pass prefetches all countries
复制代码
方法修饰器

Odoo API模块定义了Odoo环境和方法修饰符
环境(Environment)

Environment 存储ORM使用的各种上下文数据:数据库游标(用于数据库查询)、当前用户(用于访问权限检查)和当前上下文(存储任意元数据)。环境还存储缓存。
所有记录集都有一个环境,它是不可变的,可以使用env访问,并提供对以下的访问:
  1. >>> records.env
  2. <Environment object ...>
  3. >>> records.env.user
  4. res.user(3)
  5. >>> records.env.cr
  6. <Cursor object ...)
  7. >>> self.env.context # 返回字典数据,等价于 self._context
  8. {'lang': 'en_US', 'tz': 'Europe/Brussels'}
  9. >>> self._context
  10. {'lang': 'en_US', 'tz': 'Europe/Brussels'}
复制代码
从其他记录集创建记录集时,将继承环境。环境可用于获取其他模型中的空记录集,并查询该模型:
  1. >>> self.env['res.partner']
  2. res.partner()
  3. >>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]])
  4. 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)
警告
在sudo模式下没有应用健康检查!在sudo模式下,用户可以访问任何公司,即使不是在他允许的公司。
这允许触发公司间修改,即使当前用户无权访问目标公司
Environment.companies
返回用户启用的公司的记录集。
如果未在上下文 (allowed_company_ids)中指定, 返回当前用户的主公司(If not specified in the context(allowed_company_ids), fallback on current user companies)
警告
在sudo模式下没有应用健康检查!在sudo模式下,用户可以访问任何公司,即使不是在他允许的公司。
这允许触发公司间修改,即使当前用户无权访问目标公司
修改环境

需要注意的是,上下文是和记录集绑定的,修改后的上下文并不会在其它记录集中共享。
SQL执行

环境上的cr属性是当前数据库事务的游标,允许直接执行SQL,无论是对于难以使用ORM表达的查询(例如复杂join),还是出于性能原因
  1. self.env.cr.execute("some_sql", params)
复制代码
由于模型使用相同的游标,并且Environment保存各种缓存,因此当在原始SQL中更改数据库时,这些缓存必须失效,否则模型的进一步使用可能会变得不连贯。在SQL中使用CREATE、UPDATE或DELETE,但不使用SELECT(只读取数据库)时,必须清除缓存。
注解
可以使用 invalidate_cache()执行缓存的清理
警告
执行原始SQL绕过ORM,从而绕过Odoo安全规则。请确保在使用用户输入时对查询进行了清洗,如果确实不需要使用SQL查询,请使用ORM实用程序。
常用ORM方法Common ORM methods

创建/更新(Create/update)

搜索/读取(Search/Read)

字段/视图(Fields/Views)s

搜索域(Search domains)

域是一个标准列表,每个标准都是(field_name,operator,value)的三元组(一个“列表”或“元组”),其中:
<ul>field_name (str)
当前模块的字段名称 或通过Many2one,使用点符号的关系遍历,例如 'street' 或者'partner_id.country'
operator (str)
用于比较field_name与value的运算符。有效运算符为:
<ul>=
等于
!=
不等于
>
大于
>=
大于等于

', 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', '', 5)]).ids[6, 7, 8, 9]# env>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).env# 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()# 执行 sqlself.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,)[/code]获取context上下文目标key值示例
  1. [('name','=','ABC'),
  2. ('language.code','!=','en_US'),
  3. '|',('country_id.code','=','be'),
  4.      ('country_id.code','=','de')]
复制代码
参考连接

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4