odoo-orm

添加自定义模型

https://www.odoogo.com/manual/odoo-dev-doc/4d400c71

1.在models文件夹新建文件
2.在文件里面编写内容
class classroom(models.Model):
    _name = 'classroom.zxmodles'

    name = fields.Char()
3.在__init__.py声明

模型编写

模型属性
_name odoo模型的标识,必须唯一

_description 展示给用户看的标题

_order 默认的排序字段规则

_rec_name 可以指定一个字段作为该记录的描述,由于展示和搜索

_table 指定后台数据库的表名

https://alanhou.org/odoo12-structuring-data/

段与字段属性

name = fields.Char()

required=True 必填

string 界面label的值

help 界面tooltip

index 是否创建数据库索引

简单字段类型Boolean, Integer,Float,Monetary, Char,Text,Html,Date,Datetime,Binary(二进制),Selection(选择框),Reference 不可再分 保存单一数据

保留字段 id, create_date, create_uid, write_date, write_uid 会随模型一起在表中建立(请勿重复定义)
注意
1.__name__不能乱改,实在要改的话,就卸载再改,一般名称为模块名.表名
2.模块里面新建模型需要重启服务器进行更新
3.一开始不设置required=true,如果表中有数据了在设置,可能会出现数据的冲突

临时模型

临时模型继承models.TransientModel类,用于向导式的用户交互。这类数据会存储在数据库中,但仅是临时性的。会定时运行清空 job 来清除这些表中的老数据。比如Settings > Translations菜单下的Load a Language对话窗口,就使用了临时模型来存储用户选择并实现向导逻辑

抽象模型

抽象模型继承models.AbstractModel类,它不带有数据存储。抽象模型用作可复用的功能集,与使用 Odoo 继承功能的其它模型配合使用。

继承

https://www.jianshu.com/p/365696a5b823

_inherit:单一继承。值为所继承父类_name标识。如子类不定义_name属性,则在父类中增加该子类下的字段或方法,不创建新对象;如子类定义_name属性,则创建新对象,新对象拥有父类所有的字段或方法,父类不受影响。

_inherits:多重继承。子类通过关联字段与父类关联,子类不拥有父类的字段或方法,但是可以直接操作父类的字段或方法。

计算字段和视图装饰器

https://www.odoogo.com/manual/odoo-dev-doc/701ffd98

https://segmentfault.com/a/1190000016052104

# models.py
is_expired = fields.Boolean(u'已过期', compute='_compute_is_expired')

@api.depends('deadline')
@api.multi
def _compute_is_expired(self):
	#self相当于所以的记录,用for来遍历记录
    for record in self:
        if record.deadline:
            record.is_expired = record.deadline < fields.Datetime.now()
        else:
            record.is_expired = False

一对多

https://ruterly.com/2018/08/19/Odoo-basic-tutorial-04/

https://www.odoogo.com/manual/odoo-dev-doc/ee9a55e8

#odoo的一对多比较特殊,可以在两个modles里面都设置相互关联的字段,这样其实就是为了查数据更方便
一对多 外键多方

多方
category_id = fields.Many2one('todo.category', string=u'分类')
Many2one 有一个必填的属性 comodel_name 表示要关联的模型的 _name,这个字段的值可能是 0 个或 1 个所关联对象的记录集,我们可以通过这个字段直接获取到所关联的数据对象,而不需要自己去查找对应的实例。

一方
需要以Many2one作为依靠,可以在视图显示多方的数据
task_ids = fields.One2many('todo.task', 'category_id', string=u'待办事项')
One2many 同样有必填的属性 comodel_name,同时还有一个 inverse_name 属性,表示的是与当前模型所关联的模型(comodel_name 所指的模型)的 Many2one 字段的字段名,在此例中即 category_id,通过 One2many 字段我们可以直接获取到所有关联了当前记录的数据集。在这个例子中,假设我们有一个分类是「工作」,也就是说我们可以通过工作这个分类的 task_ids 这个字段获取到所有待办事项中 category_id 所关联的分类是「工作」的所有待办事项。

多对多

attendee_ids = fields.Many2many('res.partner', string="学生")

层级关联

class BookCategory(models.Model):
    _name = 'library.book.category'
    _description = 'Book Category'
    _parent_store = True

    name = fields.Char(translate=True, required=True)
    # Hierarchy fields
    parent_id = fields.Many2one(
        'library.book.category',
        'Parent Category',
        ondelete='restrict')
    parent_path = fields.Char(index=True)

    # Optional but good to have:
    highlighted_id = fields.Reference(
        [('library.book', 'Book'), ('res.partner', 'Author')],
        'Category Highlight'
    )
    child_ids = fields.One2many(
        'library.book.category',
        'parent_id',
        'Subcategories')

模型案例

# models.py
class TodoTask(models.Model):
    _name = 'todo.task'
    _description = '待办事项'

    name = fields.Char('描述', required=True)
    is_done = fields.Boolean('已完成?')
    priority = fields.Selection([
        ('todo', '待办'),
        ('normal', '普通'),
        ('urgency', '紧急')
    ], default='todo', string='紧急程度')

模型继承

详细

https://www.odoogo.com/manual/odoo-dev-doc/10068063

传统继承
允许子类修改父类的方法,字段,也允许新增
委派
保留父类的字段和方法

直接继承

class Book(models.Model):
    _inherit = 'library.book'
    is_available = fields.Boolean('Is Available?')
    isbn = fields.Char(help="Use a valid ISBN-13 or ISBN-10.")
    publisher_id = fields.Many2one(index=True)

可以增加和修改原模型的字段_name没有使用的情况下
如果使用了,就会新建一个独立的模型

模型约束

sql约束
SQL约束加在数据表定义中,并由PostgreSQL直接执行。它由_sql_constraints类属性来定义。这是一个元组组成的列表,并且每个元组的格式为(name, code, error):

name是约束标识名
code是约束的PostgreSQL语法
error是在约束验证未通过时向用户显示的错误消息
我们将向图书模型添加两个SQL约束。一条是唯一性约束,用于通过标题和出版日期是否相同来确保没有重复的图书;另一条是检查出版日期是否为未出版:
class Book(models.Model):
...
    _sql_constraints = [
        ('library_book_name_date_uq', # 约束唯一标识符
        'UNIQUE (name, date_published)', # 约束 SQL 语法
        'Book title and publication date must be unique'), # 消息
        ('library_book_check_date',
        'CHECK (date_published <= current_date)',
        'Publication date must not be in the future.'),
python约束
from odoo.exceptions import ValidationError
 
class Book(models.Model):
...
    @api.constrains('isbn')
    def _constrain_isbn_valid(self):
        for book in self:
            if book.isbn and not book._check_isbn():
                raise ValidationError('%s is an invalid ISBN' % book.isbn)

进入ORM环境

https://alanhou.org/odoo12-recordsets/

python odoo-bin shell -d odoo12(数据库名字)
self.env 环境属性
env.cr是正在使用的数据库游标(cursor)
env.user是当前用户的记录
env.uid是会话用户 id,与env.user.id相同
env.context是会话上下文的不可变字典
通过外部标识寻找数据
self.env.ref('base.module_bug_manage')

事物

self.env.cr.savepoint() 设置事物保存点
self.env.cr.execute()   执行原生sql
self.env.cr.commit()    提交事物
self.env.cr.rollback()  回滚事物

domain

domain想当于过滤数据
每个条件都是一个(‘字段名’, ‘运算符’, ‘值’)组成的元组,例如,[(‘is_done’,’=’,False)]是仅带有一个条件的有效域表达式
作为search中的搜索条件

#使用波兰表示法
+1, 1

& (AND)  接2个操作数
| (OR) 接2个操作数
! (NOT) 接1个操作数

['|',('product_type', '=', 'service'),'!', '&',('unit_price', '>=', 1000),('unit_price', '<', 2000)]

看待顺序
'&',('unit_price', '>=', 1000),('unit_price', '<', 2000)
!
|
从后开始
下拉列表过滤数据
#下拉列表过滤是老师的人
instructor_id = fields.Many2one('res.partner', string="Instructor",domain=[('instructor', '=', True)])

search-browse

#通过条件找数据
self.env['res.partner'].search([('name','like','Ag')])

#通过id找数据
self.env['res.partner'].browse([9,31])
快速入门

https://www.jianshu.com/p/3bea01aa8a17

#快速指定模型表
self.env['res.partner']

ORM-常用api装饰器

计算字段和视图装饰器

https://www.odoogo.com/manual/odoo-dev-doc/701ffd98

https://www.odoogo.com/manual/odoo-dev-doc/a0fbb9e7

https://segmentfault.com/a/1190000016052104

# models.py
is_expired = fields.Boolean(u'已过期', compute='_compute_is_expired')

@api.depends('deadline')
@api.multi
def _compute_is_expired(self):
	#self相当于所以的记录,用for来遍历记录
    for record in self:
        if record.deadline:
            record.is_expired = record.deadline < fields.Datetime.now()
        else:
            record.is_expired = False
计算字段
class Book(models.Model):
...
    publisher_country_id = fields.Many2one(
        'res.country', string='Publisher Country',
        # store = False, # 默认不在数据库中存储
        compute='_compute_publisher_country'
    )

    @api.depends('publisher_id.country_id')
    def _compute_publisher_country(self):
        for book in self:
            book.publisher_country_id = book.publisher_id.country_id

出版社的国家就是书籍的国家信息

#注意
#计算字段默认是不写入数据库的
# store = False, # 默认不在数据库中存储
写入计算字段
#注意:前提是当前用户有权限
def _inverse_publisher_country(self):
        for book in self:
            book.publisher_id.country_id = book.publisher_country_id
搜索字段
#可以被拿来作为搜索的字段
    def _search_publisher_country(self, opearator, value):
        return [('publisher_id.country_id', operator, value)]
关联字段
#本质上关联字段仅仅是快捷实现 search 和 inverse 方法的计算字段。也就是说可以直接对其进行搜索和写入,而无需书写额外的代码。默认关联字段是只读的,因inverse写操作不可用,可通过readonly=False字段属性来开启写操作。
publisher_country_id = fields.Many2one(
        'res.country', string='Publisher Country',
        related='publisher_id.country_id',
    )
multi
    name = fields.Char(compute='_compute_name')

#multi代表数据集,每次展示数据都会执行一次
    @api.multi
    def _compute_name(self):
        for record in self:
            record.name = str(random.randint(1, 1e6))

当每次调用rec.name时,都会调用compute方法来计算字段的值
depends
class Book(models.Model):
...
    publisher_country_id = fields.Many2one(
        'res.country', string='Publisher Country',
        # store = False, # 默认不在数据库中存储
        compute='_compute_publisher_country'
    )

    @api.depends('publisher_id.country_id')
    def _compute_publisher_country(self):
        for book in self:
            book.publisher_country_id = book.publisher_id.country_id

出版社的国家就是书籍的国家信息
计算字段默认是不写入数据库的
# store = False, # 默认不在数据库中存储
onchange
当记录的字段发生变化,则会触发函数,一般来说onchange可以非常方便的根据一些字段变化对其他字段进行进一步的赋值
# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
    # set auto-changing field
    self.price = self.amount * self.unit_price
    # Can optionally return a warning and domains
    return {
        'warning': {
            'title': "Something bad happened",
            'message': "It was very bad indeed",
        }
    }

这个方法有几点需要注意
1 self是单条记录 在一个form里展示  不能用在list view
2 方法里改变self里的字段 会更新到未保存的form里
3 通过onchange里参数字段 触发更新与上一节depands类似
4 onchange方法不针对某个特定字段

可以直接抛出异常,动态关联的数据就会回退
raise UserError('不生效')

ORM内置方法

create
user=self.env['res.partner']
new_one=user.create({'name':'zx'})

重写创建
    @api.model
    def create(self, vals):
        # Code before create: should use the `vals` dict
        if 'stage_id' in vals:
            Stage = self.env['library.checkout.stage']
            new_state = Stage.browse(vals['stage_id']).state
            if new_state == 'open':
                vals['checkout_date'] = fields.Date.today()
        new_record = super().create(vals)
        # Code after create: can use the `new_record` created
        if new_record.state == 'done':
            raise exceptions.UserError(
                'Not allowed to create a checkout in the done state.')
        return new_record
write
Partner = self.env['res.partner']
recs = Partner.search( [('name', 'ilike', 'Azure')] )
recs.write({'comment': 'Hello!'})

#重写修改 
    @api.multi
    def write(self, vals):
        # Code before write: can use `self`, with the old values
        if 'stage_id' in vals:
            Stage = self.env['library.checkout.stage']
            new_state = Stage.browse(vals['stage_id']).state
            if new_state == 'open' and self.state != 'open':
                vals['checkout_date'] = fields.Date.today()
            if new_state == 'done' and self.state != 'done':
                vals['closed_date'] = fields.Date.today()
        super().write(vals)
        # Code after write: can use `self`, with the updated values
        return True
Partner = self.env['res.partner']
recs = Partner.search( [('name', 'ilike', 'Azure')] )
recs.unlink()
案例
>>> self.env['res.partner'].search([('name', 'like', 'Ad')])
res.partner(10, 35, 3)
上例中返回的res.partner模型记录集包含三条记录,id 分别为10, 35和3。记录集并没有按 id 排序,因为使用了相应模型的默认排序。就 partner 模型而言,默认的_order为display_name。

>>> self.env['res.partner'].search([('name', 'like', 'Pac')])
res.partner(42, 62)
>>> self.env['res.partner'].browse([42, 62])
res.partner(42, 62)
原文地址:https://www.cnblogs.com/zx125/p/13153932.html