修改环境
Model.with_context([context][, **overrides])
-> records[源代码]
返回附加到扩展上下文的此记录集的新版本。
扩展上下文是提供的合并了overrides
的context
,或者是合并了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中使用CREATE
、UPDATE
或DELETE
,但不使用SELECT
(只读取数据库)时,必须清除缓存。
注解
可以使用 invalidate_cache()
执行缓存的清理
Model.invalidate_cache(fnames=None, ids=None)
[源代码]
修改某些记录后,使记录缓存无效。如果fnames
和ids
都为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
值为1
,bar
为"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.Date
和odoo.fields.Datetime
字段使用字符串作为值(写入和读取),而不是date
或datetime
。这些日期字符串仅为UTC格式,并根据odoo.tools.misc.DEFAULT_SERVER_DATE_FORMAT
和odoo.tools.miisc.DEFAULT_SERVER _DATETIME_FORMAT
进行格式化 odoo.fields.One2many
和odoo.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
- 参数
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)
列表
- 参数
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”。目前,唯一支持的函数是day
、week
、month
、quarter
或year
,它们只适用于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_uid
,create_date
,write_date
,write_uid
,id
字段/视图(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_id
为None
的话(‘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_name
与value
的运算符。有效运算符为:
=
等于!=
不等于>
大于>=
大于等于<
小于<=
小于等于=?
未设置或者等于(如果value
为None
或者False
则返回True
,否则与=
一样)=like
将field_name
同value
模式匹配。模式中的下划线_
匹配任何单个字符;百分号%
匹配任何零个或多个字符的字符串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)
unlink
Model.unlink()
[源代码]
删除当前记录集中的记录
引发
AccessError –
如果用户没有所请求对象的unlink权限
如果用户尝试绕过访问规则对请求对象执行unlink
UserError –如果记录为其它记录的默认属性
记录(集)信息
Model.ids
返回与self
对应的真实记录IDodoo.models.env
返回给定记录集的环境。类型Environment
Model.exists()
→ records[源代码]
返回self中存在的记录子集并将删除的记录标记为缓存中的记录. 可用作对记录的测试:
if record.exists(): ...
- 按约定,将新记录作为现有记录返回
Model.ensure_one()
[源代码]
验证当前记录集只拥有一条记录
引发odoo.exceptions.ValueError –len(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
andset1 > set2
返回set1
是否是set2
的超集set1 | set2
返回两个记录集的并集。一个包含出现在两个源记录集中的所有记录的记录集set1 & set2
返回两个记录集的交集。一个只包含同时存在两个源记录集中的记录的记录集。set1 - set2
返回一个包含仅出现在set1
中的记录的记录集
记录集是可迭代的,因此通常的Python工具可用于转换(map()
,sorted()
,ifilter()
,…),然后这些函数返回list
或iterator
,删除对结果调用方法或使用集合操作的能力。
因此,记录集提供以下返回记录集本身的操作(如果可能):
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) – 一个函数或者点分字段名称序列 - 返回
如果func
为False
则返回self
作用于所有self
中记录的func
的返回结果 - 返回类型
list 或 recordset
# returns a list of summing two fields for each record in the set records.mapped(lambda r: r.field1 + r.field2)
- 提供的函数可以是获取字段值的字符串:
# returns a list of names records.mapped('name') # returns a recordset of partners records.mapped('partner_id') # returns the union of all partner banks, with duplicates removed records.mapped('partner_id.bank_ids')
注解
V13开始, 支持多多关系字段访问,像mapped调用那样工作:
records.partner_id # == records.mapped('partner_id') records.partner_id.bank_ids # == records.mapped('partner_id.bank_ids') records.partner_id.mapped('name') # == records.mapped('partner_id.name')
Sort
Model.sorted(key=None, reverse=False)
[源代码]返回按key
排序的记录集self
- 参数
key (可调用对象或者str 或者None
) – 一个参数的函数,为每个记录返回一个比较键,或字段名,或None
,如果为None
,记录按照默认模型的顺序排序
reverse (bool) – 如果为True
, 返回逆序排序的结果
# sort records by name records.sorted(key=lambda r: r.name)
继承与扩展(Inheritance and extension)
Odoo提供三种不同的机制,以模块化方式扩展模型:
- 从现有模型创建新模型,向副本中添加新信息,但保留原始模块
- 扩展其他模块中定义的模型,替换以前的版本
- 将模型的一些字段委派给它包含的记录
经典继承
当同时使用_inherit
和 _name
属性时,Odoo使用现有模型(通过_inherit
提供)作为base创建新模型。新模型从其base中获取所有字段、方法和元信息(默认值等)。
class Inheritance0(models.Model): _name = 'inheritance.0' _description = 'Inheritance Zero' name = fields.Char() def call(self): return self.check("model 0") def check(self, s): return "This is {} record {}".format(s, self.name) class Inheritance1(models.Model): _name = 'inheritance.1' _inherit = 'inheritance.0' _description = 'Inheritance One' def call(self): return self.check("model 1")
使用它们:
a = env['inheritance.0'].create({'name': 'A'}) b = env['inheritance.1'].create({'name': 'B'}) a.call() b.call()
输出:
“This is model 0 record A” “This is model 1 record B”
第二个模型继承了第一个模型的check
方法及其name
字段,但重写了call
方法,就像使用标准Python继承一样。
说明:
- 以上为官方文档给出的案例,笔者实践发现是无法直接运行的。
- 模型继承会继承父类中的所有属性,会拷贝字段、属性和方法。
- 可以同时继承多个模型,比如:
_inherit = ['res.partner', 'md.status.mixin']
扩展
当使用_inherit
但省略_name
时,新模型将替换现有模型,实质上就是在原有模型上扩展。这对于将新字段或方法添加到现有模型(在其他模块中创建)或自定义或重新配置它们(例如更改其默认排序顺序)非常有用:
class Extension0(models.Model): _name = 'extension.0' _description = 'Extension zero' name = fields.Char(default="A") def func(): print('test a') class Extension1(models.Model): _inherit = 'extension.0' description = fields.Char(default="Extended") def func(): # 重写函数 print('test b')
record = env['extension.0'].create({}) record.read()[0]
返回:
{'name': "A", 'description': "Extended"}
注解
它还会返回各种自动生成的字段,除非它们被禁用了。
env['extension.0'].func({})
返回:
test b
注意:
如果同时继承抽象模块和非抽象模块,并把_name
配置为非抽象模块,抽象模块的字段也会添加到非抽象模块对应的表
委托(Delegation)
第三种继承机制提供了更大的灵活性(可以在运行时更改),但威力更小:使用_inherits
模型,将当前模型中未找到的任何字段的查找委托给“children”模型。委托通过Reference
执行在父模型上自动设置的字段。
主要区别在于意义。使用委托时,模型has one而不是is one,从而将关系转换为组合而不是继承:
class Screen(models.Model): _name = 'delegation.screen' _description = 'Screen' size = fields.Float(string='Screen Size in inches') class Keyboard(models.Model): _name = 'delegation.keyboard' _description = 'Keyboard' layout = fields.Char(string='Layout') class Laptop(models.Model): _name = 'delegation.laptop' _description = 'Laptop' _inherits = { 'delegation.screen': 'screen_id', 'delegation.keyboard': 'keyboard_id', } name = fields.Char(string='Name') maker = fields.Char(string='Maker') # a Laptop has a screen screen_id = fields.Many2one('delegation.screen', required=True, ondelete="cascade") # a Laptop has a keyboard keyboard_id = fields.Many2one('delegation.keyboard', required=True, ondelete="cascade")
record = env['delegation.laptop'].create({ 'screen_id': env['delegation.screen'].create({'size': 13.0}).id, 'keyboard_id': env['delegation.keyboard'].create({'layout': 'QWERTY'}).id, }) record.size record.layout
将产生结果:
13.0 'QWERTY'
可以直接修改委托字段:
record.write({'size': 14.0})
警告
使用委托继承时,方法不是被继承的,只有字段
警告
_inherits
或多或少已实现,如果可以的话避免用它(_inherits
is more or less implemented, avoid it if you can)- 链式的
_inherits
基本上没有实现,我们不对最终行为做任何保证。(chained_inherits
is essentially not implemented, we cannot guarantee anything on the final behavior)
字段增量定义
字段定义为模型类的类属性。如果扩展了模型,还可以通过在子类上重新定义具有相同名称和类型的字段来扩展字段定义。在这种情况下,字段的属性取自父类,并由子类中给定的属性覆盖。
例如,下面的第二个类仅在state
字段上添加工具提示:
class First(models.Model): _name = 'foo' state = fields.Selection([...], required=True) class Second(models.Model): _inherit = 'foo' state = fields.Selection(help="Blah blah blah")
入门实践
模型定义
odoo14\custom\estate\models\estate_property_tag.py
#!/usr/bin/env python # -*- coding:utf-8 -*- from odoo import models, fields class EstatePropertyTag(models.Model): _name = 'estate.property.tag' _description = 'estate property tag' _order = 'name' name = fields.Char(string='tag', required=True) color = fields.Integer(string='Color')
odoo14\custom\estate\models\estate_property_offer.py
#!/usr/bin/env python # -*- coding:utf-8 -*- from odoo import models, fields class EstatePropertyOffer(models.Model): _name = 'estate.property.offer' _description = 'estate property offer' property_id = fields.Many2one('estate.property', required=True) price = fields.Integer()
odoo14\custom\estate\models\estate_property_type.py
#!/usr/bin/env python # -*- coding:utf-8 -*- from odoo import models, fields class EstatePropertyType(models.Model): _name = 'estate.property.type' _description = 'estate property type' name = fields.Char(string='name', required=True)
odoo14\custom\estate\models\estate_property.py
#!/usr/bin/env python # -*- coding: utf-8 -*- from odoo import models, fields class EstateProperty(models.Model): _name = 'estate.property' _description = 'estate property table' _order = 'id desc' name = fields.Char(required=True) property_type_id = fields.Many2one("estate.property.type", string="PropertyType") tag_ids = fields.Many2many("estate.property.tag") offer_ids = fields.One2many("estate.property.offer", "property_id", string="PropertyOffer")
ORM操作实践
>>> self.env['estate.property.type'] estate.property.type() # 创建单条记录 >>> self.env['estate.property.type'].create({'name':'house'}) estate.property.type(1,) # 按id查询记录 >>> self.env['estate.property.type'].browse([1]) estate.property.type(1,) # 未给定id列表,或者未提供参数的情况下,返回空记录集 >>> self.env['estate.property.type'].browse() estate.property.type() >>> self.env['estate.property.type'].browse([]) estate.property.type() # 复制记录 >>> self.env['estate.property.type'].browse([1]).copy({'name':'garden'}) estate.property.type(2,) # 针对仅获取单条记录的记录集,可通过 records.fieldName 的方式引用对应字段(读取字段值,或者给字段赋值) >>> self.env['estate.property.type'].browse([2]).name 'garden' # 更新记录 >>> self.env['estate.property.type'].browse([1]).name 'house' >>> self.env['estate.property.type'].browse([1]).write({'name':'garden'}) True >>> self.env['estate.property.type'].browse([1]).name 'garden' # 针对仅获取单条记录的记录集,可通过 records.fieldName 的方式引用对应字段(读取字段值,或者给字段赋值) >>> self.env['estate.property.type'].browse([1]).name = 'house' >>> self.env['estate.property.type'].browse([1]).name 'house' # 不能直接通过以下方式,试图在write函数指定id的方式来更新记录 # 不会修改任何记录,也未新增任何记录 >>> self.env['estate.property.type'].write({'id':1, 'name':'apartment'}) True >>> self.env['estate.property.type'].browse([1]).name 'house' # 通过search api查询记录集 >>> self.env['estate.property.type'].search([]) estate.property.type(1, 2) # 批量创建记录 # 创建测试用数据 >>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag1', 'color': 2}, {'name': 'tag1', 'color': 3}]) estate.property.tag(1, 2, 3) # 注意:Many2one类型字段的值,必须设置为对应记录的主键id >>> self.env['estate.property'].create({'name': 'house in beijing', 'property_type_id': 1, 'tag_ids':[(0,0, {'name': 'tag1', 'color': 3})]}) estate.property(1,) >>> self.env['estate.property'].search([]) estate.property(1,) # 查询关系字段值 >>> self.env['estate.property'].browse([1]).property_type_id # Many2one estate.property.type(1,) >>> self.env['estate.property'].browse([1]).tag_ids # Many2many estate.property.tag(4,) # 更新Many2many关系字段值 >>> self.env['estate.property'].browse([1]).tag_ids.write({'name': 'tag4', 'color': 4}) True >>> self.env['estate.property'].browse([1]).tag_ids.color 4 >>> self.env['estate.property.tag'].search([]) estate.property.tag(1, 2, 3, 4) # 查询关系字段值 >>> self.env['estate.property'].browse([1]).offer_ids # One2many estate.property.offer() ## 更新One2many关系字段值 # 为关系字段创建关联记录 # (0, 0, values) # 从提供的`values`字典创建新记录。 >>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1})] >>> self.env['estate.property'].browse([1]).offer_ids estate.property.offer(1,) >>> self.env['estate.property'].browse([1]).offer_ids.property_id estate.property(1,) # 更新关系字段所代表记录对象的属性值 # (1, id, values) # 使用 values 字典中的值更新id值为给定 id 值的现有记录。不能在create()中使用。 >>> self.env['estate.property'].browse([1]).offer_ids = [(1, 1, {'price': 30000})] >>> self.env['estate.property'].browse([1]).offer_ids.price 30000 # 删除关系字段关联记录 # (3, id, 0) # 从记录集中删除id为id的记录,但不从数据库中删除它,可以理解为仅解除关联。不能在create()中使用。 >>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0)] >>> self.env['estate.property'].browse([1]).offer_ids estate.property.offer() # 将已存在记录同关系字段关联 # (4, id, 0) # 添加一条id为id已存在记录到记录集 >>> self.env['estate.property.offer'].browse([1]) estate.property.offer(1,) >>> self.env['estate.property'].browse([1]).offer_ids = [(4,1,0)] >>> self.env['estate.property'].browse([1]).offer_ids estate.property.offer(1,) # 为关系字段一次创建多条关联记录 >>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1, 'price': 100000}),(0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 300000})] >>> self.env['estate.property'].browse([1]).offer_ids estate.property.offer(1, 2, 3, 4, 5) # 替换关系字段关联的记录 # (6, 0, ids) # 根据ids列表,替换所有已存在记录, 等价于使用命令(5, 0, 0),随后对ids中的每个id使用命令(4, id, 0)。 >>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0),(3,2,0)] >>> self.env['estate.property'].browse([1]).offer_ids estate.property.offer(3, 4, 5) >>> self.env['estate.property'].browse([1]).offer_ids = [(6, 0, [1,2])] # 报错, 因为ID 1,2 对应的记录,其Many2one字段值为null # 为Many2many关系字段创建多条关联记录 >>> self.env['estate.property'].create({'name': 'house in shanghai'}) estate.property(2,) >>> self.env['estate.property'].browse([2]) estate.property(2,) >>> self.env['estate.property'].browse([2]).tag_ids estate.property.tag() >>> self.env['estate.property'].browse([2]).tag_ids = [(0, 0, {'name': 'tag5', 'color': 5}), (0, 0, {'name': 'tag6', 'color': 6}), (0, 0, {'name': 'tag7', 'color': 7})] >>> self.env['estate.property'].browse([2]).tag_ids estate.property.tag(5, 6, 7) # 删除关系字段关联的记录 # (2, id, 0) # 从记录集中删除id为id的记录,然后(从数据库中)删除它,不能在create()中使用 >>> self.env['estate.property'].browse([2]).tag_ids = [(2, 5, 0)] 2023-01-29 08:48:25,491 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [5] >>> print( self.env['estate.property.tag'].browse([5]).exists()) estate.property.tag() >>> if self.env['estate.property.tag'].browse([5]).exists(): ... print('exists record with id equal 5') ... >>> # 创建测试用数据 >>> self.env['estate.property.tag'].create({'name': 'tag8', 'color': 8}) estate.property.tag(8,) >>> self.env['estate.property.tag'].create({'name': 'tag9', 'color': 9}) estate.property.tag(9,) >>> self.env['estate.property'].browse([2]) estate.property(2,) # 替换关系字段关联的记录 # (6, 0, ids) # 根据ids列表,替换所有已存在记录, 等价于使用命令(5, 0, 0),随后对ids中的每个id使用命令(4, id, 0)。 >>> self.env['estate.property'].browse([2]).tag_ids estate.property.tag(6, 7) >>> self.env['estate.property'].browse([2]).tag_ids = [(6, 0 , [8, 9])] >>> self.env['estate.property'].browse([2]).tag_ids estate.property.tag(8, 9) >>> # 通过mapped获取记录字段值(关联记录的属性值)列表 >>> self.env['estate.property'].browse([2]).tag_ids.mapped('name') ['tag8', 'tag9'] >>> self.env['estate.property'].browse([2]).mapped('tag_ids') estate.property.tag(8, 9) >>> self.env['estate.property'].browse([2]).mapped('tag_ids').mapped('id')) [8, 9] # search api 应用 # 搜索域 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]) estate.property.tag(6, 7, 8, 9) # 偏移 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1) estate.property.tag(7, 8, 9) # 限制返回记录集中的最大记录数 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2) estate.property.tag(7, 8) # 返回记录集中的记录排序 # 降序 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id desc') estate.property.tag(8, 7) # 升序 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id') estate.property.tag(7, 8) >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id asc') estate.property.tag(7, 8) # 仅返回记录数 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], count=True) 4 # 利用search_count api实现等价效果 >>> self.env['estate.property.tag'].search_count(args=[('id', '>', 5)]) 4 # 搜索域条件组合 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5),('color', '<', 8)]) estate.property.tag(6, 7) # 获取记录(集)信息 # ids >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).ids [6, 7, 8, 9] # env >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).env <odoo.api.Environment object at 0x0000020E31C80080> # name_get api 使用 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).name_get() [(6, 'tag6'), (7, 'tag7'), (8, 'tag8'), (9, 'tag9')] # get_metadata api 使用 >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).get_metadata() [{'id': 6, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8,41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 7, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 8, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023,1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 9, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}] # 利用 read_group 实现按组读取 >>> self.env['estate.property.tag'].create({'name': 'tag10', 'color': 9}) estate.property.tag(10,) >>> self.env['estate.property.tag'].read_group([], fields=['color'], groupby=['color']) [{'color_count': 1, 'color': 6, '__domain': [('color', '=', 6)]}, {'color_count': 1, 'color': 7, '__domain': [('color', '=', 7)]}, {'color_count': 1, 'color': 8, '__domain': [('color', '=', 8)]}, {'color_count': 2, 'color': 9, '__domain': [('color', '=', 9)]}] # 获取字段定义 >>> self.env['estate.property.tag'].fields_get(['name']) {'name': {'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': True, 'searchable': True, 'sortable': True , 'store': True, 'string': 'tag', 'translate': False, 'trim': True}} # 回滚 >>> self.env.cr.rollback() >>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id') estate.property.tag() # 执行 sql self.env.cr.execute('TRUNCATE TABLE estate_property_tag_test CASCADE;') self.env.cr.commit() # 重置自增主键ID 为1(每个表的主键ID存在名为 tableName_id_seq 的序列中) self.env.cr.execute('ALTER SEQUENCE estate_property_tag_test_id_seq RESTART WITH 1;') self.env.cr.commit() >>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag2', 'color': 2}, {'name': 'tag3', 'color': 3}]) estate.property.tag(1, 2, 3) # 批量更新记录字段值 #记录集存在多条记录的情况下,不能通过 records.fieldName = 目标值 实现批量更新 >>> self.env['estate.property.tag'].browse([1,3]).write({'color':1}) True >>> self.env['estate.property.tag'].browse([1,3]).mapped('color') [1, 1] # 修改查询记录集context >>> self.env['estate.property.tag'].browse([]).env.context {'lang': 'en_US', 'tz': 'Europe/Brussels'} >>> self.env['estate.property.tag'].with_context(is_sync=False).browse([]).env.context {'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False} # with_context和sudo共存时的使用方式 >>> self.env['estate.property.tag'].with_context(is_sync=False).sudo().browse([]).env.context {'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False} >>> self.env['estate.property.tag'].sudo().with_context(is_sync=False).browse([]).env.context {'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False} # 修改创建记录时返回记录的context(更新记录(write)也是一样的用法) # 如此,可以通过重写对应模型的create或者write方法,并在方法中通过self.env.context获取目标key值,进而执行需求实现需要采取的动作,参见下文 >>> self.env['estate.property.tag'].with_context(is_sync=False).create({'name': 'tag4', 'color': 4}).env.context {'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False} # 删除记录 >>> self.env['estate.property.tag'].search([]) estate.property.tag(1, 2, 3, 4) >>> self.env['estate.property.tag'].search([('id', '>', 2)]).unlink() 2023-01-29 09:55:47,796 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [3, 4] True # 遍历记录集 >>> for record_set in self. self.env['estate.property.tag.test'].search([]): ... print(record_set) ... estate.property.tag.test(1,) estate.property.tag.test(2,)
获取context上下文目标key值示例
#!/usr/bin/env python # -*- coding:utf-8 -*- from odoo import models, fields,api class EstatePropertyTag(models.Model): _name = 'estate.property.tag' _description = 'estate property tag' _order = 'name' name = fields.Char(string='tag', required=True) color = fields.Integer(string='Color') @api.model def create(self, vals_list): res = super(EstatePropertyTag, self).create(vals_list) # 获取上下文目标key值 if not self.env.context.get('is_sync', True): # do something you need return res
参考连接
https://www.odoo.com/documentation/14.0/zh_CN/developer/reference/addons/orm.html#