本文参考:ORM API — Odoo 18.0 documentation
Odoo 环境(Environment)
odoo.api.Environment 类在 Odoo 中用于存储和管理与 ORM 相关的各种上下文数据。环境类在 Odoo 中充当了一个数据容器,提供了对数据库、用户、上下文等信息的访问。
构造函数
class odoo.api.Environment(cr, uid, context, su=False, uid_origin=None)
- cr:当前数据库游标(用于数据库查询)。
- uid:当前用户的 ID(用于访问权限检查)。
- context:当前上下文字典(可以存储任意元数据)。
- su:是否以超级用户模式运行。如果为 True,表示用户具有超级权限。
- uid_origin:原始用户 ID,通常用于跟踪多层次的权限变更。
主要功能
Environment 类提供了以下功能:
- 获取模型数据
通过环境对象,你可以访问模型的数据。例如,通过 self.env['res.partner'] 你可以访问到 res.partner 模型。
示例:>>> 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)
- 这段代码演示了如何通过环境对象查询 res.partner 模型中符合某些条件的记录。
- 懒加载属性
环境对象提供了一些懒加载属性来访问当前上下文数据:
- Environment.lang:返回当前语言的语言代码(如 en_US)。
- Environment.user:返回当前用户实例。
- Environment.company:返回当前公司的实例,如果上下文中没有指定,则默认为当前用户的主要公司。
- Environment.companies:返回当前用户有权限访问的公司记录集。
>>> records.env.lang 'en_US' >>> records.env.user res.user(3) >>> records.env.company res.company(1)
- 这些属性使你可以方便地访问当前环境中的语言、用户和公司信息。
注意事项
- Environment.company 和 Environment.companies:
- 如果上下文中没有指定公司,则会回退到当前用户的主要公司或允许的公司列表。
- 警告:在超级用户模式下,Environment.company 和 Environment.companies 不会进行任何检查,这意味着用户可以访问任何公司,即使当前公司不在用户的允许公司列表中。
- 这允许用户在跨公司操作时执行修改,尽管他们没有访问其他公司的权限。
- superuser mode(超级用户模式):
- 如果 su=True,则表示用户具有超级用户权限,可以绕过正常的权限检查。超级用户模式下,用户可以访问和修改任何公司、模型或记录,不受通常的访问控制限制。
总结
- 环境对象 (Environment) 是 Odoo ORM 的核心之一,它提供对数据库、用户、上下文和公司等重要数据的访问。
- 懒加载属性 和 模型数据查询 功能,使得通过 Environment 对象能够高效地管理和访问 Odoo 系统中的数据。
- 在 超级用户模式 下,权限控制会被绕过,允许用户访问和修改任何记录或模型,这在某些特定场景下非常有用,但也需要谨慎使用。
Odoo 环境方法(Useful Environment Methods)
Odoo 中的 Environment 类提供了多种方法,用于操作和扩展上下文、用户和模型等。以下是一些有用的环境方法及其解释。
1. Environment.ref(xml_id, raise_if_not_found=True)
- 功能:返回与给定 xml_id 对应的记录。
- 参数:
- xml_id(str):记录的 xml_id,格式为 <module.id>。
- raise_if_not_found(bool):如果记录未找到,是否抛出异常。默认 True,表示找不到记录时会抛出 ValueError 异常。
- 返回:返回找到的记录,若未找到且 raise_if_not_found 为 True,则抛出异常;如果为 False,则返回 None。
- 示例:
record = env.ref('module_name.record_id')
2. Environment.is_superuser()
- 功能:返回环境是否处于超级用户模式(superuser mode)。
- 返回:布尔值,若是超级用户模式返回 True,否则返回 False。
3. Environment.is_admin()
- 功能:返回当前用户是否属于 “Access Rights” 组,或者是否在超级用户模式下。
- 返回:布尔值,若当前用户是管理员或在超级用户模式下,返回 True,否则返回 False。
4. Environment.is_system()
- 功能:返回当前用户是否属于 “Settings” 组,或者是否在超级用户模式下。
- 返回:布尔值,若当前用户是系统管理员或在超级用户模式下,返回 True,否则返回 False。
5. Environment.execute_query(query)
- 功能:执行给定的 SQL 查询,返回查询结果作为元组列表。
- 参数:query 是一个 SQL 查询对象(odoo.tools.sql.SQL)。
- 返回:查询结果作为元组列表,若没有结果则返回空列表。
- 示例:
result = env.execute_query('SELECT * FROM res_partner WHERE active = TRUE')
更改环境(Altering the Environment)
这些方法用于创建新的记录集,附加扩展上下文或其他环境数据。
1. Model.with_context([context][, **overrides])
- 功能:返回附加了扩展上下文的新版本记录集。新的上下文是将提供的上下文与当前上下文合并后的结果。
- 示例:
r2 = records.with_context({}, key2=True) # -> r2._context is {'key2': True} r2 = records.with_context(key2=True) # -> r2._context is {'key1': True, 'key2': True}
2. Model.with_user(user)
- 功能:返回一个新的记录集,附加到指定的用户,并且以非超级用户模式运行,除非指定的用户本身是超级用户。
- 参数:user 是用户的 ID 或记录。
- 示例:
r2 = records.with_user(user)
3. Model.with_company(company)
- 功能:返回一个新的记录集,修改环境上下文,使得:
- result.env.company 为新的公司。
- result.env.companies 包含当前环境中的所有公司以及指定的公司。
- 参数:company 是公司 ID 或公司记录。
- 警告:如果当前用户没有访问权限访问该公司,访问 company 或 companies 时可能会触发 AccessError,除非使用 sudo。
4. Model.with_env(env: api.Environment)
- 功能:返回一个新的记录集,附加到提供的环境对象。
- 参数:env 是一个 Environment 对象。
- 示例:
r2 = records.with_env(new_env)
5. Model.sudo([flag=True])
- 功能:返回一个新的记录集,启用或禁用超级用户模式(取决于 flag 参数)。超级用户模式绕过访问权限检查。
- 参数:flag(bool)表示是否启用超级用户模式,默认为 True。
- 警告:使用超级用户模式可能会导致数据跨越记录规则的边界,特别是在多公司环境中,可能会导致不应访问的记录混合在一起。
总结
- 环境方法 提供了对 Odoo 环境数据的强大控制能力,可以用来查询记录、判断用户权限、执行 SQL 查询、修改上下文、切换用户或公司等。
- 使用 sudo 方法可以绕过访问权限,执行特权操作,但需要小心,因为它可能会导致数据隔离问题。
- with_context 和 with_user 等方法允许用户动态修改记录集的上下文或操作用户,便于灵活处理不同的数据和操作环境。
SQL 执行(SQL Execution)
在 Odoo 中,cr 是环境的一个属性,表示当前数据库事务的游标。可以通过 cr 执行原生 SQL 查询,通常用于执行那些无法通过 ORM 表达的查询(如复杂的连接查询),或者是为了提高性能而直接执行 SQL 查询。
基本用法
通过 self.env.cr.execute() 方法可以执行 SQL 查询。例子如下:
self.env.cr.execute("some_sql", params)
警告
直接执行原生 SQL 查询会绕过 ORM 机制,因此也绕过了 Odoo 的安全规则。使用用户输入时需要特别小心,确保 SQL 查询经过充分清理,防止 SQL 注入攻击。如果不确实需要使用原生 SQL,建议使用 ORM 提供的查询方法。
SQL 包装器
Odoo 提供了 odoo.tools.SQL 类来包装 SQL 查询及其参数。这个包装器能帮助避免 SQL 注入风险,同时提高代码的可读性和安全性。
例如,使用 SQL 包装器可以这样构造 SQL 查询:
from odoo.tools import SQL # 用 SQL 包装器构造 SQL 查询 sql = SQL("UPDATE TABLE foo SET a = %s, b = %s", 'hello', 42) self.env.cr.execute(sql)
这使得参数 hello 和 42 被安全地嵌入到 SQL 查询中,而不需要手动拼接 SQL 字符串,从而避免了 SQL 注入的风险。
SQL 类还支持组合 SQL 语句和参数,避免手动组合可能引起的错误。例子如下:
sql = SQL( "UPDATE TABLE %s SET %s", SQL.identifier('tablename'), SQL("%s = %s", SQL.identifier('columnname'), value), )
这里,SQL.identifier 用于安全地引用 SQL 标识符(如表名、列名等),确保这些标识符不会被错误地处理。
数据刷新(Flushing)
Odoo 模型中的数据库更新通常是延迟的(为了提高性能),这意味着在执行查询前,可能需要确保相关数据已更新到数据库中。这一操作称为 "flush"。调用 flush() 可以确保数据在查询前被刷新。
例如,确保某个字段的数据库值是最新的,可以使用:
self.env['model'].flush_model(['partner_id'])
之后可以执行 SQL 查询,确保查询操作反映了最新的数据:
self.env.cr.execute("SELECT id FROM model WHERE partner_id IN %s", ids) ids = [row[0] for row in self.env.cr.fetchall()]
刷新和失效缓存
在执行 SQL 查询时,Odoo 环境中使用了缓存来提高性能。当通过 SQL 修改数据库内容时,缓存中的数据可能与数据库中的数据不一致,因此需要显式地失效缓存。
可以通过以下方法来刷新缓存:
- flush_all():刷新所有待处理的计算和数据库更新。
- flush_model(fnames=None):刷新模型中指定字段的计算和更新。
- flush_recordset(fnames=None):刷新记录集中特定字段的计算和更新。
例如:
self.env['model'].flush_model(['state'])
在 SQL 查询后,可以使用 invalidate_model() 来失效模型的缓存,以确保缓存与数据库内容一致。
self.env['model'].invalidate_model(['state'])
如果执行 SQL 查询后,某些字段被修改了,那么你需要通过 modified() 方法通知 Odoo 这些字段已更新,并需要重新计算依赖字段。
records = self.env['model'].browse(ids) records.invalidate_recordset(['state']) records.modified(['state'])
modified() 方法
在执行 SQL 查询修改记录时,可以使用 modified() 方法来标记已修改的字段。这将使 Odoo 知道这些字段需要重新计算相关的计算字段。例子:
records.modified(['state'])
总结
- SQL 执行:通过 cr.execute() 可以直接执行 SQL 查询,但要小心绕过 Odoo 的安全规则。
- SQL 包装器:使用 odoo.tools.SQL 类可以安全地处理 SQL 查询及其参数,避免 SQL 注入攻击。
- 刷新与失效缓存:执行 SQL 查询时,确保相关数据已被刷新,并在修改数据后及时失效缓存,保证数据一致性。
- modified():标记字段为已修改,确保依赖字段的重新计算。
这些操作对于确保数据库和缓存的一致性以及避免安全问题至关重要。
常见 ORM 方法(Common ORM Methods)
1. 创建/更新记录(Create/Update)
- Model.create(vals_list)
用于创建新记录。新记录会根据传入的值列表初始化,必要时还会使用模型的默认值(通过 default_get() 获取)。
参数- vals_list(list[dict] 或 dict): 用于初始化模型字段的值,可以是字典的列表。每个字典包含字段名和对应的值。
- 返回创建的记录集。
- AccessError: 当前用户没有权限创建指定模型的记录。
- ValidationError: 用户尝试为选择字段输入无效的值。
- ValueError: 提供的创建值中的字段名称不存在。
- UserError: 如果操作会导致在对象层次中创建循环(例如将对象设置为其自己的父对象)时引发错误。
self.env['model'].create([{'field_name': 'value1'}, {'field_name': 'value2'}])
2. 复制记录(Model.copy(default=None))
- Model.copy(default=None)
用于复制记录并更新其默认值。
参数- default(dict): 用于覆盖复制记录字段的值。例如:{'field_name': 'new_value'}。
- 返回复制的记录。
record = self.env['model'].browse(1) new_record = record.copy({'field_name': 'new_value'})
3. 获取默认值(Model.default_get(fields_list))
- Model.default_get(fields_list)
返回指定字段的默认值,默认值的生成依赖于上下文、用户默认值、用户回退值以及模型本身的设置。
参数- fields_list(list): 要获取默认值的字段名称列表。
- 返回一个字典,键为字段名,值为字段的默认值。
defaults = self.env['model'].default_get(['field1', 'field2'])
4. 根据名称创建记录(Model.name_create(name))
- Model.name_create(name)
通过仅提供名称来创建一个新记录。此方法等同于调用 create(),但只提供一个字段值——记录的显示名称。
参数- name(str): 新记录的显示名称。
- 返回一个元组 (id, display_name),表示新创建记录的 ID 和显示名称。
record_id, display_name = self.env['model'].name_create('New Record')
5. 更新记录(Model.write(vals))
- Model.write(vals)
用于更新记录集中的所有记录,使用提供的字段值更新。
参数- vals(dict): 字段和值的字典,指定需要更新的字段及其新的值。
- 返回 True,表示更新成功。
- AccessError: 用户没有权限修改指定记录或字段。
- ValidationError: 如果指定的值对于选择字段无效。
- UserError: 如果操作会导致在对象层次中创建循环(例如设置对象为其自己的父对象)。
- 对于数字字段(Integer, Float),值应为相应的数据类型。
- 对于布尔字段,值应为布尔类型。
- 对于选择字段,值应匹配选择值(通常是字符串,有时是整数)。
- 对于 Many2one 字段,值应为关联记录的数据库 ID。
- 对于 One2many 或 Many2many 字段,值应为包含相应操作的命令列表,如 create(), update(), delete() 等。
record.write({'field_name': 'new_value'})
总结
- 创建记录:create() 方法用于创建新记录,接受字段值的字典列表。
- 复制记录:copy() 用于复制现有记录,并可通过 default 参数修改某些字段的默认值。
- 获取默认值:default_get() 方法返回字段的默认值,帮助初始化新记录。
- 根据名称创建记录:name_create() 仅使用名称来创建记录,并返回记录的 ID 和名称。
- 更新记录:write() 方法用于批量更新记录集,更新字段的值。
这些方法是 Odoo ORM 中的核心工具,帮助开发者轻松地管理数据库记录的创建、更新和复制等操作。
搜索/读取 (Search/Read)
1. Model.browse([ids])
- 功能
根据提供的记录 ID 列表返回记录集。 - 参数
- ids(int 或 可迭代对象): 一个或多个记录的 ID。如果为 None,返回空记录集。
- 返回值
- 返回与提供的 ID 对应的记录集。
- 示例
records = self.env['model'].browse([1, 2, 3])
2. Model.search(domain, offset=0, limit=None, order=None)
- 功能
根据搜索条件 domain 查找符合条件的记录。 - 参数
- domain(list): 搜索条件,使用空列表可匹配所有记录。
- offset(int): 要跳过的结果数量(默认为 0)。
- limit(int): 返回的最大记录数(默认返回全部)。
- order(str): 排序规则,例如 'field_name asc'。
- 返回值
- 符合条件的记录集。
- 示例
records = self.env['model'].search([('field_name', '=', 'value')], limit=10, order='field_name asc')
3. Model.search_count(domain, limit=None)
- 功能
返回满足条件的记录数量。 - 参数
- domain(list): 搜索条件。
- limit(int): 可选,限制计数的最大值。
- 返回值
- 匹配条件的记录数量(int)。
- 示例
count = self.env['model'].search_count([('field_name', '=', 'value')])
4. Model.search_fetch(domain, field_names, offset=0, limit=None, order=None)
- 功能
查找满足搜索条件的记录,并将指定字段的数据加载到缓存中,以优化后续操作。 - 参数
- domain(list): 搜索条件。
- field_names(list): 要加载的字段名。
- 其他参数与 search() 类似。
- 返回值
- 满足条件的记录集,字段数据已缓存。
- 示例
records = self.env['model'].search_fetch([('field_name', '=', 'value')], ['field1', 'field2'])
5. Model.name_search(name='', args=None, operator='ilike', limit=100)
- 功能
搜索与 name 匹配的记录,主要用于部分值匹配的情况,如自动完成字段建议。 - 参数
- name(str): 要匹配的名称模式。
- args(list): 可选的额外搜索条件。
- operator(str): 匹配运算符(例如 'ilike', '=')。
- limit(int): 返回的最大记录数。
- 返回值
- 匹配记录的列表,每个元素是 (id, display_name) 的元组。
- 示例
results = self.env['model'].name_search(name='partial', limit=10)
6. Model.read([fields])
- 功能
读取记录的指定字段值,返回字段名与其值的映射字典。 - 参数
- fields(list): 要读取的字段名列表(默认读取所有字段)。
- 返回值
- 每条记录对应一个字典,字典键为字段名,值为字段值。
- 示例
data = self.env['model'].browse([1, 2]).read(['field1', 'field2'])
7. Model.read_group(domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True)
- 功能
根据指定的字段分组,并计算字段的聚合值(如计数、求和等)。 - 参数
- domain(list): 搜索条件。
- fields(list): 要读取的字段和聚合操作(如 'field:sum')。
- groupby(list): 分组字段,可指定时间字段的粒度(如 'date_field:month')。
- lazy(bool): 是否仅按第一个字段分组。
- 返回值
- 包含分组值和聚合值的字典列表。
- 示例
groups = self.env['model'].read_group([('field', '!=', False)], ['field:sum'], ['field'])
总结
- 记录集操作:
- browse():通过 ID 获取记录。
- search():通过条件查找记录。
- search_count():返回匹配记录的数量。
- 字段读取:
- read():读取记录字段值。
- search_fetch():优化字段读取,直接缓存数据。
- 分组与聚合:
- read_group():按字段分组并计算聚合值。
- 名称搜索:
- name_search():通过部分匹配查找记录,用于自动完成场景。
这些方法适用于从简单查询到复杂数据分组的各种场景,是 Odoo ORM 的核心操作工具。
字段相关操作与搜索域的解读
1. Model.fields_get([allfields][, attributes])
- 功能
返回模型中每个字段的定义。 - 返回值
返回一个以字段名称为键的字典,字典的值是另一个字典,包含字段的属性和值。 - 参数
- allfields(list,可选): 要查询的字段名称列表,默认为全部字段。
- attributes(list,可选): 要返回的字段属性列表,默认为返回所有属性。
- 示例
fields_info = self.env['model'].fields_get(['field1', 'field2'], ['string', 'type'])
2. 搜索域 (Search Domains)
- 搜索域是一个条件列表,其中每个条件是一个三元组或列表,格式如下
- (field_name, operator, value)
- field_name: 当前模型的字段名,或通过 Many2one 关系的点标记法访问的字段名(如 partner_id.country)。
- 对日期/时间字段,可指定粒度(如 field_name.year_number)。
- 支持的粒度包括:
- year_number, quarter_number, month_number, iso_week_number
- day_of_week, day_of_month, day_of_year
- hour_number, minute_number, second_number
- operator: 比较操作符,用于比较字段值和目标值,支持以下类型:
操作符 含义 = 等于 != 不等于 > 大于 >= 大于等于 < 小于 <= 小于等于 =? 未设置或等于(适用于值为 None 或 False 的字段) like 匹配模式 %value% not like 不匹配模式 %value% ilike 不区分大小写的 like not ilike 不区分大小写的 not like in 值在列表中 not in 值不在列表中 child_of 值是记录的子项(基于 _parent_name 关系字段) parent_of 值是记录的父项(基于 _parent_name 关系字段) any 关系字段的某条记录满足提供的域条件(适用于 Many2one、One2many、Many2many) not any 关系字段的所有记录都不满足提供的域条件 - value: 用于比较的值,类型需与字段匹配。
3. 搜索域的逻辑运算
- 逻辑操作符
- '&':逻辑与,默认的条件组合方式。
- '|':逻辑或。
- '!':逻辑非,用于否定条件。
- 使用示例
- 查找名称为 "ABC",且电话或手机号中包含 "7620" 的联系人:
[('name', '=', 'ABC'),'|', ('phone', 'ilike', '7620'), ('mobile', 'ilike', '7620')]
- 查找需开票的销售订单,其中至少有一行产品的库存不足:
[('invoice_status', '=', 'to invoice'),('order_line', 'any',
[('product_id.qty_available', '<=', 0)])] - 查找出生月份为 2 月的所有联系人:
[('birthday.month_number', '=', 2)]
- 查找名称为 "ABC",且电话或手机号中包含 "7620" 的联系人:
总结
- 字段定义 (fields_get):可以用来查看模型中字段的详细信息,包括名称、类型、帮助文本等。
- 搜索域:通过灵活的条件组合(字段、操作符和值)构建强大的查询,适用于复杂的业务需求。
- 逻辑操作符:支持条件的动态组合,轻松实现复杂的业务逻辑过滤。
这部分功能是 Odoo ORM 查询机制的基础,合理使用可极大提升开发效率和数据查询的精准性。
Odoo 模型方法与操作的详细解读
1. 删除记录:unlink
- 功能
删除当前 self 中的记录。 - 可能的异常
- AccessError: 当前用户没有权限删除这些记录。
- UserError: 这些记录是其他记录的默认属性,无法删除。
- 示例
record.unlink()
2. 记录集信息
- Model.ids
返回记录集中每个记录的实际 ID 列表。ids = recordset.ids
- odoo.models.env
返回当前记录集的运行环境 (Environment)。env = recordset.env
- Model.exists()
返回 self 中存在的记录子集,可用于检查记录是否仍然存在。if record.exists(): # 记录存在时执行逻辑
- Model.ensure_one()
确保当前记录集中仅包含一个记录,否则抛出异常。- 异常
- ValueError: 如果 len(self) != 1。
record.ensure_one()
- 异常
- Model.get_metadata()
获取记录的元数据。
返回值为字典列表,每个记录包含以下信息:- id: 记录 ID
- create_uid: 创建记录的用户
- create_date: 记录创建的日期
- write_uid: 最后修改记录的用户
- write_date: 最后修改的日期
- xmlid: 用于引用该记录的 XML ID(如存在)
- noupdate: 是否是不可更新的记录
metadata = record.get_metadata()
3. 记录集的集合操作
- 记录集不可变,但同模型的记录集可以通过集合操作组合,返回新的记录集。
操作符 含义 record in set 检查单一记录是否存在于记录集中。 set1 <= set2 set1 是否为 set2 的子集。 set1 & set2 返回两个记录集的交集。 set1 - set2 返回仅在 set1 中但不在 set2 中的记录。
4. 过滤、映射与排序
- Model.filtered(func)
根据条件过滤记录集,返回符合条件的记录子集。- 参数:
- func: 可调用对象或字段名字符串。
- 示例:
# 筛选公司为当前用户所在公司的记录 records.filtered(lambda r: r.company_id == user.company_id) # 筛选合作伙伴是公司的记录 records.filtered("partner_id.is_company")
- 参数:
- Model.filtered_domain(domain)
根据 Odoo 的搜索域筛选记录集。filtered_records = records.filtered_domain([('field', '=', value)])
- Model.mapped(func)
将函数或字段名字符串应用于记录集的每条记录,返回结果列表或记录集。- 示例:
# 获取名称列表 names = records.mapped('name') # 获取关联的所有合作伙伴 partners = records.mapped('partner_id')
- 示例:
- Model.sorted(key=None, reverse=False)
按指定的键排序记录集,返回排序后的记录集。- 参数:
- key: 可调用对象、字段名或 None(使用默认排序)。
- reverse: 是否降序排序。
- 示例:
sorted_records = records.sorted(key=lambda r: r.name, reverse=True)
- 参数:
5. 分组操作:grouped
- 功能
根据指定键对记录集进行分组,返回一个字典,键为分组结果,值为对应的记录集。 - 示例
grouped_records = records.grouped('partner_id') for partner, partner_records in grouped_records.items(): print(partner.name, partner_records)
- 注意
与 itertools.groupby 不同,grouped 不依赖输入顺序,但不支持惰性操作。
总结
上述方法为 Odoo ORM 提供了强大的记录集操作能力,包括:
- 数据筛选、排序与分组;
- 集合运算(如并集、交集等);
- 元数据提取与记录删除。
灵活使用这些方法可以有效提高开发效率和代码可读性,是开发者需要掌握的重要工具。
odoo-ORM2