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

简介: odoo ORM API学习总结兼orm学习教程

修改环境

  • 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方法,该方法如果被重写了,也可能会导致耗时的增加,总的来说,遵守一个原则,仅更新需要更新的字段)
  • 如果用户对请求的对象没有创建权限
  • 如果用户尝试绕过访问规则在请求的对象上创建
  • 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
  • 参数
    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")
Map
  • Model.mapped(func)[源代码]self中的所有记录应用func,并将结果作为列表或记录集返回(如果func返回记录集)。后者返回的记录集的顺序是任意的。
  • 参数
    func (可调用对象 或 str) – 一个函数或者点分字段名称序列
  • 返回
    如果funcFalse则返回self 作用于所有self中记录的func的返回结果
  • 返回类型
    list 或 recordset
# returns a list of summing two fields for each record in the set
records.mapped(lambda r: r.field1 + r.field2)
  • 提供的函数可以是获取字段值的字符串:
# returns a list of names
records.mapped('name')
# returns a recordset of partners
records.mapped('partner_id')
# returns the union of all partner banks, with duplicates removed
records.mapped('partner_id.bank_ids')

注解

V13开始, 支持多多关系字段访问,像mapped调用那样工作:

records.partner_id  # == records.mapped('partner_id')
records.partner_id.bank_ids  # == records.mapped('partner_id.bank_ids')
records.partner_id.mapped('name')  # == records.mapped('partner_id.name')
Sort
  • Model.sorted(key=None, reverse=False)[源代码]返回按key排序的记录集self
  • 参数
    key (可调用对象或者str 或者 None) – 一个参数的函数,为每个记录返回一个比较键,或字段名,或None,如果为None,记录按照默认模型的顺序排序
    reverse (bool) – 如果为True, 返回逆序排序的结果
# sort records by name
records.sorted(key=lambda r: r.name)

继承与扩展(Inheritance and extension)

Odoo提供三种不同的机制,以模块化方式扩展模型:

  • 从现有模型创建新模型,向副本中添加新信息,但保留原始模块
  • 扩展其他模块中定义的模型,替换以前的版本
  • 将模型的一些字段委派给它包含的记录

经典继承

当同时使用_inherit_name 属性时,Odoo使用现有模型(通过_inherit提供)作为base创建新模型。新模型从其base中获取所有字段、方法和元信息(默认值等)。

class Inheritance0(models.Model):
    _name = 'inheritance.0'
    _description = 'Inheritance Zero'
    name = fields.Char()
    def call(self):
        return self.check("model 0")
    def check(self, s):
        return "This is {} record {}".format(s, self.name)
class Inheritance1(models.Model):
    _name = 'inheritance.1'
    _inherit = 'inheritance.0'
    _description = 'Inheritance One'
    def call(self):
        return self.check("model 1")

使用它们:

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

输出:

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

第二个模型继承了第一个模型的check方法及其name字段,但重写了call方法,就像使用标准Python继承一样。

说明:

  • 以上为官方文档给出的案例,笔者实践发现是无法直接运行的。
  • 模型继承会继承父类中的所有属性,会拷贝字段、属性和方法。
  • 可以同时继承多个模型,比如:
_inherit = ['res.partner', 'md.status.mixin']

扩展

当使用_inherit但省略_name时,新模型将替换现有模型,实质上就是在原有模型上扩展。这对于将新字段或方法添加到现有模型(在其他模块中创建)或自定义或重新配置它们(例如更改其默认排序顺序)非常有用:

class Extension0(models.Model):
    _name = 'extension.0'
    _description = 'Extension zero'
    name = fields.Char(default="A")
    def func():
        print('test a')
class Extension1(models.Model):
    _inherit = 'extension.0'
    description = fields.Char(default="Extended")
    def func(): # 重写函数
        print('test b')
record = env['extension.0'].create({})
record.read()[0]

返回:

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

注解

它还会返回各种自动生成的字段,除非它们被禁用了。

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

返回:

test b

注意:

如果同时继承抽象模块和非抽象模块,并把_name配置为非抽象模块,抽象模块的字段也会添加到非抽象模块对应的表

委托(Delegation)

第三种继承机制提供了更大的灵活性(可以在运行时更改),但威力更小:使用_inherits模型,将当前模型中未找到的任何字段的查找委托给“children”模型。委托通过Reference执行在父模型上自动设置的字段。

主要区别在于意义。使用委托时,模型has one而不是is one,从而将关系转换为组合而不是继承:

class Screen(models.Model):
    _name = 'delegation.screen'
    _description = 'Screen'
    size = fields.Float(string='Screen Size in inches')
class Keyboard(models.Model):
    _name = 'delegation.keyboard'
    _description = 'Keyboard'
    layout = fields.Char(string='Layout')
class Laptop(models.Model):
    _name = 'delegation.laptop'
    _description = 'Laptop'
    _inherits = {
        'delegation.screen': 'screen_id',
        'delegation.keyboard': 'keyboard_id',
    }
    name = fields.Char(string='Name')
    maker = fields.Char(string='Maker')
    # a Laptop has a screen
    screen_id = fields.Many2one('delegation.screen', required=True, ondelete="cascade")
    # a Laptop has a keyboard
    keyboard_id = fields.Many2one('delegation.keyboard', required=True, ondelete="cascade")
record = env['delegation.laptop'].create({
    'screen_id': env['delegation.screen'].create({'size': 13.0}).id,
    'keyboard_id': env['delegation.keyboard'].create({'layout': 'QWERTY'}).id,
})
record.size
record.layout

将产生结果:

13.0
'QWERTY'

可以直接修改委托字段:

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

警告

使用委托继承时,方法不是被继承的,只有字段

警告

  • _inherits 或多或少已实现,如果可以的话避免用它(_inherits is more or less implemented, avoid it if you can)
  • 链式的_inherits基本上没有实现,我们不对最终行为做任何保证。(chained _inherits is essentially not implemented, we cannot guarantee anything on the final behavior)

字段增量定义

字段定义为模型类的类属性。如果扩展了模型,还可以通过在子类上重新定义具有相同名称和类型的字段来扩展字段定义。在这种情况下,字段的属性取自父类,并由子类中给定的属性覆盖。

例如,下面的第二个类仅在state字段上添加工具提示:

class First(models.Model):
    _name = 'foo'
    state = fields.Selection([...], required=True)
class Second(models.Model):
    _inherit = 'foo'
    state = fields.Selection(help="Blah blah blah")

入门实践

模型定义

odoo14\custom\estate\models\estate_property_tag.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyTag(models.Model):
    _name = 'estate.property.tag'
    _description = 'estate property tag'
    _order = 'name'
    name = fields.Char(string='tag', required=True)
    color = fields.Integer(string='Color')

odoo14\custom\estate\models\estate_property_offer.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyOffer(models.Model):
    _name = 'estate.property.offer'
    _description = 'estate property offer'
    property_id = fields.Many2one('estate.property', required=True)
    price = fields.Integer()

odoo14\custom\estate\models\estate_property_type.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyType(models.Model):
    _name = 'estate.property.type'
    _description = 'estate property type'
    name = fields.Char(string='name', required=True)

odoo14\custom\estate\models\estate_property.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from odoo import models, fields
class EstateProperty(models.Model):
    _name = 'estate.property'
    _description = 'estate property table'
    _order = 'id desc'
    name = fields.Char(required=True)
    property_type_id = fields.Many2one("estate.property.type", string="PropertyType")
    tag_ids = fields.Many2many("estate.property.tag")
    offer_ids = fields.One2many("estate.property.offer", "property_id", string="PropertyOffer")

ORM操作实践

>>> self.env['estate.property.type']
estate.property.type()
# 创建单条记录
>>> self.env['estate.property.type'].create({'name':'house'})
estate.property.type(1,)
# 按id查询记录
>>> self.env['estate.property.type'].browse([1])
estate.property.type(1,)
# 未给定id列表,或者未提供参数的情况下,返回空记录集
>>> self.env['estate.property.type'].browse()
estate.property.type()
>>> self.env['estate.property.type'].browse([])
estate.property.type()
# 复制记录
>>> self.env['estate.property.type'].browse([1]).copy({'name':'garden'})
estate.property.type(2,)
# 针对仅获取单条记录的记录集,可通过 records.fieldName 的方式引用对应字段(读取字段值,或者给字段赋值)
>>> self.env['estate.property.type'].browse([2]).name 
'garden'
# 更新记录
>>> self.env['estate.property.type'].browse([1]).name
'house'
>>> self.env['estate.property.type'].browse([1]).write({'name':'garden'})
True
>>> self.env['estate.property.type'].browse([1]).name
'garden'
# 针对仅获取单条记录的记录集,可通过 records.fieldName 的方式引用对应字段(读取字段值,或者给字段赋值)
>>> self.env['estate.property.type'].browse([1]).name = 'house'
>>> self.env['estate.property.type'].browse([1]).name
'house'
# 不能直接通过以下方式,试图在write函数指定id的方式来更新记录 # 不会修改任何记录,也未新增任何记录
>>> self.env['estate.property.type'].write({'id':1, 'name':'apartment'}) 
True
>>> self.env['estate.property.type'].browse([1]).name
'house'
# 通过search api查询记录集
>>> self.env['estate.property.type'].search([])
estate.property.type(1, 2)
# 批量创建记录
# 创建测试用数据
>>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag1', 'color': 2}, {'name': 'tag1', 'color': 3}])
estate.property.tag(1, 2, 3)
# 注意:Many2one类型字段的值,必须设置为对应记录的主键id
>>> self.env['estate.property'].create({'name': 'house in beijing', 'property_type_id': 1, 'tag_ids':[(0,0, {'name': 'tag1', 'color': 3})]})
estate.property(1,)
>>> self.env['estate.property'].search([]) 
estate.property(1,)
# 查询关系字段值
>>> self.env['estate.property'].browse([1]).property_type_id # Many2one
estate.property.type(1,)
>>> self.env['estate.property'].browse([1]).tag_ids  # Many2many
estate.property.tag(4,)
# 更新Many2many关系字段值
>>> self.env['estate.property'].browse([1]).tag_ids.write({'name': 'tag4', 'color': 4})
True
>>> self.env['estate.property'].browse([1]).tag_ids.color
4
>>> self.env['estate.property.tag'].search([])
estate.property.tag(1, 2, 3, 4)
# 查询关系字段值
>>> self.env['estate.property'].browse([1]).offer_ids # One2many
estate.property.offer()
## 更新One2many关系字段值
# 为关系字段创建关联记录
# (0, 0, values)
# 从提供的`values`字典创建新记录。
>>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1})]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1,)
>>> self.env['estate.property'].browse([1]).offer_ids.property_id
estate.property(1,)
# 更新关系字段所代表记录对象的属性值
# (1, id, values)
# 使用 values 字典中的值更新id值为给定 id 值的现有记录。不能在create()中使用。
>>> self.env['estate.property'].browse([1]).offer_ids = [(1, 1, {'price': 30000})]
>>> self.env['estate.property'].browse([1]).offer_ids.price
30000
# 删除关系字段关联记录
# (3, id, 0)
# 从记录集中删除id为id的记录,但不从数据库中删除它,可以理解为仅解除关联。不能在create()中使用。
>>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer()
# 将已存在记录同关系字段关联
# (4, id, 0)
# 添加一条id为id已存在记录到记录集
>>> self.env['estate.property.offer'].browse([1])
estate.property.offer(1,)
>>> self.env['estate.property'].browse([1]).offer_ids = [(4,1,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1,)
# 为关系字段一次创建多条关联记录
>>> self.env['estate.property'].browse([1]).offer_ids = [(0, 0, {'property_id':1, 'price': 100000}),(0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 200000}), (0, 0, {'property_id':1, 'price': 300000})]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(1, 2, 3, 4, 5)
# 替换关系字段关联的记录
# (6, 0, ids) 
# 根据ids列表,替换所有已存在记录, 等价于使用命令(5, 0, 0),随后对ids中的每个id使用命令(4, id, 0)。
>>> self.env['estate.property'].browse([1]).offer_ids = [(3,1,0),(3,2,0)]
>>> self.env['estate.property'].browse([1]).offer_ids
estate.property.offer(3, 4, 5)
>>> self.env['estate.property'].browse([1]).offer_ids = [(6, 0, [1,2])] # 报错, 因为ID 1,2 对应的记录,其Many2one字段值为null
# 为Many2many关系字段创建多条关联记录
>>> self.env['estate.property'].create({'name': 'house in shanghai'})
estate.property(2,)
>>> self.env['estate.property'].browse([2])
estate.property(2,)
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag()
>>> self.env['estate.property'].browse([2]).tag_ids = [(0, 0, {'name': 'tag5', 'color': 5}), (0, 0, {'name': 'tag6', 'color': 6}), (0, 0, {'name': 'tag7', 'color': 7})]
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(5, 6, 7)
# 删除关系字段关联的记录
# (2, id, 0)
# 从记录集中删除id为id的记录,然后(从数据库中)删除它,不能在create()中使用
>>> self.env['estate.property'].browse([2]).tag_ids = [(2, 5, 0)]
2023-01-29 08:48:25,491 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [5]
>>> print( self.env['estate.property.tag'].browse([5]).exists())
estate.property.tag()
>>> if self.env['estate.property.tag'].browse([5]).exists():
...     print('exists record with id equal 5')
...
>>>
# 创建测试用数据
>>> self.env['estate.property.tag'].create({'name': 'tag8', 'color': 8})
estate.property.tag(8,)
>>> self.env['estate.property.tag'].create({'name': 'tag9', 'color': 9})
estate.property.tag(9,)
>>> self.env['estate.property'].browse([2])
estate.property(2,)
# 替换关系字段关联的记录
# (6, 0, ids) 
# 根据ids列表,替换所有已存在记录, 等价于使用命令(5, 0, 0),随后对ids中的每个id使用命令(4, id, 0)。
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(6, 7)
>>> self.env['estate.property'].browse([2]).tag_ids = [(6, 0 , [8, 9])]
>>> self.env['estate.property'].browse([2]).tag_ids
estate.property.tag(8, 9)
>>>
# 通过mapped获取记录字段值(关联记录的属性值)列表
>>> self.env['estate.property'].browse([2]).tag_ids.mapped('name')
['tag8', 'tag9']
>>> self.env['estate.property'].browse([2]).mapped('tag_ids')
estate.property.tag(8, 9)
>>> self.env['estate.property'].browse([2]).mapped('tag_ids').mapped('id')) 
[8, 9]
# search api 应用
# 搜索域
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)])
estate.property.tag(6, 7, 8, 9)
# 偏移
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1)
estate.property.tag(7, 8, 9)
# 限制返回记录集中的最大记录数
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2)
estate.property.tag(7, 8)
# 返回记录集中的记录排序
# 降序
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id desc')
estate.property.tag(8, 7)
# 升序
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id')
estate.property.tag(7, 8)
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id asc')
estate.property.tag(7, 8)
# 仅返回记录数
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], count=True)
4
# 利用search_count api实现等价效果
>>> self.env['estate.property.tag'].search_count(args=[('id', '>', 5)])
4
# 搜索域条件组合
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5),('color', '<', 8)])
estate.property.tag(6, 7)
# 获取记录(集)信息
# ids
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).ids
[6, 7, 8, 9]
# env
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).env
<odoo.api.Environment object at 0x0000020E31C80080>
# name_get api 使用
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).name_get()
[(6, 'tag6'), (7, 'tag7'), (8, 'tag8'), (9, 'tag9')]
# get_metadata api 使用
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)]).get_metadata()
[{'id': 6, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8,41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 7, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 8, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023,1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}, {'id': 9, 'create_uid': (1, 'OdooBot'), 'create_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'write_uid': (1, 'OdooBot'), 'write_date': datetime.datetime(2023, 1, 29, 8, 41, 10, 551001), 'xmlid': False, 'noupdate': False}]
# 利用 read_group 实现按组读取
>>> self.env['estate.property.tag'].create({'name': 'tag10', 'color': 9})
estate.property.tag(10,)
>>> self.env['estate.property.tag'].read_group([], fields=['color'], groupby=['color'])
[{'color_count': 1, 'color': 6, '__domain': [('color', '=', 6)]}, {'color_count': 1, 'color': 7, '__domain': [('color', '=', 7)]}, {'color_count': 1, 'color': 8, '__domain': [('color', '=', 8)]}, {'color_count': 2, 'color': 9, '__domain': [('color', '=', 9)]}]
# 获取字段定义
>>> self.env['estate.property.tag'].fields_get(['name'])
{'name': {'type': 'char', 'change_default': False, 'company_dependent': False, 'depends': (), 'manual': False, 'readonly': False, 'required': True, 'searchable': True, 'sortable': True
, 'store': True, 'string': 'tag', 'translate': False, 'trim': True}}
# 回滚
>>> self.env.cr.rollback()
>>> self.env['estate.property.tag'].search(args=[('id', '>', 5)], offset=1, limit=2, order = 'id')
estate.property.tag()
# 执行 sql
self.env.cr.execute('TRUNCATE TABLE estate_property_tag_test CASCADE;')
self.env.cr.commit()
# 重置自增主键ID 为1(每个表的主键ID存在名为 tableName_id_seq 的序列中)
self.env.cr.execute('ALTER SEQUENCE estate_property_tag_test_id_seq RESTART WITH 1;')
self.env.cr.commit()
>>> self.env['estate.property.tag'].create([{'name': 'tag1', 'color': 1}, {'name': 'tag2', 'color': 2}, {'name': 'tag3', 'color': 3}])
estate.property.tag(1, 2, 3)
# 批量更新记录字段值 #记录集存在多条记录的情况下,不能通过 records.fieldName = 目标值 实现批量更新
>>> self.env['estate.property.tag'].browse([1,3]).write({'color':1})  
True
>>> self.env['estate.property.tag'].browse([1,3]).mapped('color')
[1, 1]
# 修改查询记录集context
>>> self.env['estate.property.tag'].browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels'}
>>> self.env['estate.property.tag'].with_context(is_sync=False).browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
# with_context和sudo共存时的使用方式
>>> self.env['estate.property.tag'].with_context(is_sync=False).sudo().browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
>>> self.env['estate.property.tag'].sudo().with_context(is_sync=False).browse([]).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
# 修改创建记录时返回记录的context(更新记录(write)也是一样的用法)
# 如此,可以通过重写对应模型的create或者write方法,并在方法中通过self.env.context获取目标key值,进而执行需求实现需要采取的动作,参见下文
>>> self.env['estate.property.tag'].with_context(is_sync=False).create({'name': 'tag4', 'color': 4}).env.context
{'lang': 'en_US', 'tz': 'Europe/Brussels', 'is_sync': False}
# 删除记录
>>> self.env['estate.property.tag'].search([])
estate.property.tag(1, 2, 3, 4)
>>> self.env['estate.property.tag'].search([('id', '>', 2)]).unlink()
2023-01-29 09:55:47,796 15984 INFO odoo odoo.models.unlink: User #1 deleted estate.property.tag records with IDs: [3, 4]
True
# 遍历记录集
>>> for record_set in self. self.env['estate.property.tag.test'].search([]):
...     print(record_set)
...
estate.property.tag.test(1,)
estate.property.tag.test(2,)

获取context上下文目标key值示例

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields,api
class EstatePropertyTag(models.Model):
    _name = 'estate.property.tag'
    _description = 'estate property tag'
    _order = 'name'
    name = fields.Char(string='tag', required=True)
    color = fields.Integer(string='Color')
    @api.model
    def create(self, vals_list):
        res = super(EstatePropertyTag, self).create(vals_list)
        # 获取上下文目标key值
        if not self.env.context.get('is_sync', True):
            # do something you need
        return res

参考连接

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

目录
相关文章
|
21小时前
|
API 数据安全/隐私保护 UED
探索鸿蒙的蓝牙A2DP与访问API:从学习到实现的开发之旅
在掌握了鸿蒙系统的开发基础后,我挑战了蓝牙功能的开发。通过Bluetooth A2DP和Access API,实现了蓝牙音频流传输、设备连接和权限管理。具体步骤包括:理解API作用、配置环境与权限、扫描并连接设备、实现音频流控制及动态切换设备。最终,我构建了一个简单的蓝牙音频播放器,具备设备扫描、连接、音频播放与停止、切换输出设备等功能。这次开发让我对蓝牙技术有了更深的理解,也为未来的复杂项目打下了坚实的基础。
77 58
探索鸿蒙的蓝牙A2DP与访问API:从学习到实现的开发之旅
|
23天前
|
网络协议 API
检测指定TCP端口开放状态免费API接口教程
此API用于检测指定TCP端口是否开放,支持POST/GET请求。需提供用户ID、KEY、目标主机,可选指定端口(默认80)和地区(默认国内)。返回状态码、信息提示、检测主机、端口及状态(开放或关闭)。示例中ID和KEY为公共测试用,建议使用个人ID和KEY以享受更高调用频率。
42 14
|
24天前
|
API
获取网页状态码[可指定地域]免费API接口教程
该接口用于获取指定网址的访问状态码,支持从国内、香港、美国等地域节点访问。通过POST或GET请求,需提供用户ID、KEY及目标网址等参数。返回结果包括状态码和信息提示。 示例:https://cn.apihz.cn/api/wangzhan/getcode.php?id=88888888&key=88888888&type=1&url=www.apihz.cn。
|
24天前
|
缓存 算法 API
查询域名WHOIS信息免费API接口教程
该API用于查询顶级域名的WHOIS信息,不支持国别域名和中文域名。通过POST或GET请求,需提供用户ID、KEY及待查询域名。返回信息包括域名状态、注册商、时间等详细数据。示例与文档见官网。
|
24天前
|
API
icp备案查询免费API接口教程
该接口用于查询指定域名的ICP备案信息,支持POST或GET请求方式。请求时需提供用户ID、用户KEY及待查询的域名,可选参数为查询通道。响应中包含状态码、消息内容、备案号、备案主体、域名及审核时间等信息。示例中提供了GET和POST请求方式及返回数据样例。
|
24天前
|
API 区块链
获取指定网页基础信息【TDK】免费API接口教程
该接口用于从标准网页中提取标题、关键词、描述和图标等信息。支持POST/GET请求,需提供用户ID、KEY及目标网址等参数,可选指定访问节点。返回状态码、信息提示及提取的内容。示例与详细文档见官网。
|
26天前
|
API 数据格式
关帝灵签免费API接口教程
接口简介:提供随机获取一枝关帝灵签的服务,共100签。通过POST或GET请求,需提交用户ID和KEY。返回内容包括状态码、消息内容及灵签详情,如序号、吉凶、诗文等。示例请求与响应展示了使用方法和数据格式。
|
24天前
|
前端开发 JavaScript API
提取网页所有链接免费API接口教程
此API用于提取指定网页内的所有链接信息并进行分类,支持POST和GET请求方式。需提供用户ID、KEY及目标网址等参数,可选指定访问节点。返回结果包括状态码、信息提示及各类链接集合,如图片、视频、文档等。示例中展示了请求格式与返回数据结构。
|
12天前
|
人工智能 自然语言处理 API
Multimodal Live API:谷歌推出新的 AI 接口,支持多模态交互和低延迟实时互动
谷歌推出的Multimodal Live API是一个支持多模态交互、低延迟实时互动的AI接口,能够处理文本、音频和视频输入,提供自然流畅的对话体验,适用于多种应用场景。
61 3
Multimodal Live API:谷歌推出新的 AI 接口,支持多模态交互和低延迟实时互动
|
8天前
|
前端开发 API 数据库
Next 编写接口api
Next 编写接口api