odoo ORM 笔记

528 阅读7分钟

💡 这里将记录我再odoo学习和探索中的收获

业精于勤荒于嬉;行成于思毁于随!

(持续更新中) Last write 2023/6/30


1. env 环境

在 Odoo 中,self.env 是一个重要的环境对象,它是 Odoo ORM 中的核心对象之一。

self.env 是一个 Odoo 环境,它提供了访问 Odoo 数据库的 API。在 self.env 中,您可以使用 Odoo ORM 操作数据库中的数据,例如创建、读取、更新和删除记录。

下面是 self.env 中一些常用的方法:

  1. search(): 使用条件从数据库中检索记录,返回符合条件的记录 ID 列表
  2. browse(): 根据 ID 列表获取记录,返回一个记录集。
  3. create(): 创建新记录,并将其写入数据库。
  4. write(): 更新指定 ID 的记录,并将更改写入数据库。
  5. unlink(): 根据指定 ID 删除数据库中的记录。
  6. read(): 从数据库中读取指定 ID 的记录。
  7. execute(): 执行原始 SQL 查询,并返回查询结果。

用法如下:

# 获取所有状态为'open'的sale.order记录
orders = self.env['sale.order'].search([('state', '=', 'open')])

# 获取id为1的sale.order记录
order = self.env['sale.order'].browse(1)
# 获取order对应的partner记录
partner = order.partner_id

# 获取id为1的sale.order记录的name和partner_id字段的值
order_data = self.env['sale.order'].browse(1).read(['name', 'partner_id'])

# 创建一条新的sale.order记录
new_order = self.env['sale.order'].create({'name': 'New Order', 'partner_id': 1})

# 修改id为1的sale.order记录的name字段的值为'Updated Order'
self.env['sale.order'].browse(1).write({'name': 'Updated Order'})

# 删除id为1的sale.order记录
self.env['sale.order'].browse(1).unlink()

# 以超级用户的身份获取id为1的sale.order记录
order = self.env['sale.order'].sudo().browse(1)

# 定义SQL语句   execute遍底层不建议二开使用
sql = "SELECT * FROM %s WHERE name LIKE '%%s%%'"
# 执行SQL语句
self.env.cr.execute(sql % (table_name, 'odoo'))
# 获取查询结果
result = self.env.cr.fetchall()

除了上述常用的方法,self.env 还提供了其他一些方法,例如 name_search()、search_count()、exists()、mapped() 等等。其中,name_search() 可以通过名称搜索记录,search_count() 可以获取符合条件的记录数量,exists() 可以检查记录是否存在,mapped() 可以将一个记录集映射到一个字典中。

  1. name_search(name='', args=None, operator='ilike', limit=100) 方法用于在模型的名称字段(默认为 name 字段)上进行查询,返回匹配指定名称(name)的记录 ID 和名称(name)列表。
  2. search_count(domain=None) 方法用于计算符合给定域(domain)条件的记录数。
  3. exists() 方法用于检查是否存在符合给定条件的记录。
  4. mapped(function, *args, **kwargs) 方法将指定函数应用于查询结果的每个记录,并返回一个字典,其中键是记录 ID,值是函数的结果。
  5. filtered(function) 方法将查出来的结果记录集过滤
result = self.env['res.partner'].name_search(name='Acme')

在上面的示例中,name_search() 方法会在 res.partner 模型的名称字段上查找包含 "Acme" 的名称,返回匹配的记录 ID 和名称列表。可以通过传递参数 args 来添加其他筛选条件。可以通过传递参数 limit 来指定返回结果的最大数量。


count = self.env['res.partner'].search_count([('is_company', '=', True)])

在上面的示例中,search_count() 方法会计算符合 is_company=True 条件的 res.partner 模型的记录数。


exists = self.env['res.partner'].exists([('is_company', '=', True)])

在上面的示例中,exists() 方法会检查是否存在 is_company=True 条件的 res.partner 模型的记录。如果存在,返回 True,否则返回 False。


result = self.env['res.partner'].search([('is_company', '=', True)]).mapped('name')

在上面的示例中,mapped() 方法会筛选出所有 is_company=True 条件的 res.partner 模型的记录,并将 name 字段的值应用于每个记录,最终返回一个字典,其中键是记录 ID,值是 name 字段的值。


def my_method(self):
    # 应用筛选器并返回过滤后的结果 
    filtered_records = self.filtered(lambda r: r.field_name == 'value') 
    return filtered_records

也可以是search出来的结果记录集去过滤筛选。


在二次开发时,我们经常会用到 self.env 对象,例如创建自定义的业务逻辑、修改数据结构、添加字段等等。另外,在二次开发时,您也可以使用 self.env.context 来访问 Odoo 上下文,它是一个字典,可以在模型中存储上下文信息。


2. Models

其实我一直觉得odoo的模块设计很精妙,它的继承和扩展机制,使得开发人员在使用的时候,有更多的可能性;还支持多继承,继承父类重写父类的行为动作等操作。它的一些特定的变量,使得上手很快,减少很多重复造轮子的工作。

首先odoo的所有类都来源一个BaseModel(源代码)

关于模块的所有能执行的方法和属性都在这里;所以odoo在模块的基类就已经定义好了,模块可以做哪些事情,有哪些官方给的接口,和哪些属性的默认状态等

然后就说三种常见的可选择继承的模块了:

- Model

Model是常规数据库持久化 Odoo 模型的主要超类。一般情况下再定义一个模块的时候,需要pg数据库真实存储的表,就继承这个,所以这个是继承最常见的。定义好后升级和安装模块,系统会自动去pg生成数据库,字段就是python这边定义的field字段。

(由于odoo的ORM机制,所以odoo的模型和pg数据库是深度绑定的,一般情况都是操作模块,千万别手动去数据库操作数据库表。)

- AbstractModel

其实看过我上面的源码会发现,AbstractModel就是BaseModel,为什么这么多此一举,我猜想可能有两个原因1.为后续更高版本预留的;2.不让用户直接继承BaseModel和让odoo整体的ORM模块更有结构性。

继承AbstractModel大多是那种不需要生成真实存在的物理表,做一个抽象类出来,做一些汇总计算啥的。

- TransientModel

这个其实继承于Model,而Model继承于AbstractModel。它是所有瞬态模型的超类,生成真实的数据库表。什么样的模型才有可能继承它?一般那种短期存储,定时清理的数据的模块会继承它。

TransientModel 具有简化的访问权限管理,所有用户都可以创建新记录,并且只能访问他们创建的记录。超级用户可以不受限制地访问所有 TransientModel 记录。

image.png

- 属性

_name = None

定义模块名称,以点相连。eg:用户表 _name = 'res.user' 。在生成数据库的时候odoo会把'.'换成 ' _'如res_user

3. 模块的继承与扩展

odoo提供了三种机制来以模块化方式扩展模型,并支持模块的多继承

3.1.1. 经典继承

当同时使用 _inherit 和 _name 属性时,Odoo 会使用现有模型(通过 _inherit 提供)作为基础创建一个新模型。新模型从其基础中获取所有字段、方法和元信息(默认值等)。如果方法重名则遵循python的覆盖重写机制

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")
3.1.2. 扩展

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

class Extension0(models.Model):
_name = 'extension.0'
_description = 'Extension zero'

name = fields.Char(default="A")

class Extension1(models.Model):
_inherit = 'extension.0'

description = fields.Char(default="Extended")
3.1.3. 委托

第三种继承机制提供了更大的灵活性,但是方法不会被利用,只有字段会被利用上,更像是一种组合关系。

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")
字段的增量定义

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

class First(models.Model):
    _name = 'foo'
    state = fields.Selection([...], required=True)

class Second(models.Model):
    _inherit = 'foo'
    state = fields.Selection(help="Blah blah blah")
多继承

继承多个和一个逻辑一样,获取基础模块的字段和方法等信息

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','delegation.keyboard']

    name = fields.Char(string='Name')
    maker = fields.Char(string='Maker')