添加自定义模型
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
unlink
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)