Odoo开发之记录集 – 使用模型数据

Odoo 12开发之记录集 – 使用模型数据

一·启动服务链接数据库

# 1.启动服务,链接数据库
 	./odoo-bin shell -c debian/odoo.conf  -d library_db
    
# 2. self   当前操作的对象
	res.users(1,)
    
# 3. self._name  # 当前模块名称 ,获得记录集模型名
	'res.users'
    
# 4. self.login  # 记录值,至于怎么用.未知
	'__system__'
    
# 5. self.name     #  name 值为OdooBot
	OdooBot

二·执行环境

环境属性

# 1. self.env , 获得当前环境
	<odoo.api.Environment object at 0x7f78a26026a0>
    
# 2. self.env中的执行环境属性
	# env.cr  正在使用的数据库游标(cursor)
    	<odoo.sql_db.Cursor object at 0x7f9df38efc50>
    # env.user 是当前用户的记录
    	res.users(1,)
    # env.uid 是会话用户的id
    	1 
    # env.context 是会话上下文的不可变字典    
    	{'lang': 'en_US', 'tz': 'Europe/Brussels'}
        # 由于是 frozendict 由odoo 自行封装的字典,不可变
           env.context['test']='test'
           错误: NotImplementedError: '__setitem__' not supported on frozendic
                
     # search([('name','like','Ad')]) 搜索查询,search和domain表达式结合使用
    	self.env['res.partner'].search([('name','like','Ad')])
     
     # browse() 也是搜索查询,browse放id列表如[1,2,3,4]
        self.env['res.partner'].browse([1,2,3,4])      

环境上下文

### 环境上下是带有会话数据的字典. 
### 作用:用于客户端和服务端ORM和业务逻辑中. 可以将信息从一个视图中传递到另一个视图中.
	# 比如:上一个视图中活跃的id,通过点击链接或者按钮.将默认值带入到下一个视图中.
    # 形式:	
		{'lang': 'en_US', 'tz': 'Europe/Brussels', 'uid': 2}
# 1. 查看上下文命令
	self.context_get()
	self.env.context
	# lang :用户语言 tz:时区信息 uid:当前用户的id

修改记录集执行环境

### 记录集执行环境是不可变的.不可被修改,因此需要创建一个新的环境来修改
	# 1. env.with_context({diciionary}) # 替换原上下文为新的上下文
    # 2. env.with_context(key=value,...) # 修改当前上下文,为一些键设置值
    # 3. env.sudo(user) # 传入一条用户记录并返回该用户的环境.如果未传用户,则使用	__system__超级用户. 可绕过安全规则执行指定操作
    
# env.ref()函数 , 传入一个外部标识符字符串并返回它的记录
	self.env.ref('base.user_root') 
    res.users(1,)
    

三· 记录集和作用域domain查询数据

创建记录

### search方法 接收一个域表达式并返回符合条件的记录集. 空域[]将返回所有的记录

    # 关键字参数:
        # order是一个数据库查询语句种的 order by使用的字符串.通常是一个逗号分隔的字段名列表。每个字段都可接DESC关键字,用于表示倒序排列。
        # limit 设置获取记录的最大条数
        # offset 忽略钱n条记录,配合limit使用
    # 用法:
    	self.env['res.partner'].search([('name', 'like', 'Pac')])
        # 结果
        res.partner(42, 62)
 	   
    
### search_count()方法 返回记录条数. 先知道条数,节约内存
	# 用法
    	self.env['res.partner'].search_count([])
        #结果
        681
### browse()方法接收一个ID列表或单个ID并返回这些记录的记录集
	# 用法
    	self.env['res.partner'].browse([42, 62])
        # 结果
        res.partner(42, 62)

域表达式

### domain用于过滤数据记录. 使用的是特殊语法来供Odoo ORM解析	
	# 写法:
		(‘字段名’, ‘运算符’, ‘值’) 组成的元组
    # 字段名:是一个有待过滤的字段,可用点号标记,来表示关联模型种的字段
    # 值: 分为两种运行上下文: 
    	 ① 窗口操作或字段属性等客户端,使用原生字段值,不能使用点标记符
         ② 服务端可以是一个对象
    # 运算符
    	# 比较: <,>,<=,>=,=,!=
        # =like 和 =ilike 
        # like匹配 '%value%'模式,ilike与其相似.
        # not like 和 not ilike
        # child of 支持层级关联的模型中 查找层级关系种的子级值
        # in 和 not in 在和不在
        
	 # 多条件,即包含多个元祖  默认是 AND 运算符
    	# 例子: 此例子为编造
        self.env['res.partner'].search([('message_follower_ids', 'in', [user.partner_id.id]),('user_id', '=', user.id)])
     
     # 显式逻辑运算符 ‘&‘符号表示 AND 运算符(默认值),管道运算符’|‘表示OR运算符
    	# 例子:
            ['|',('message_follower_ids', 'in', [user.partner_id.id]),'|',('user_id', '=', user.id),('user_id', '=', False)]
 

四·记录集中访问数据

访问记录中数据

# 1.记录集仅有一条记录时,称为单例
	self.name  --> OdooBot
### 注意:
	尝试访问有多条记录的记录集字段值会产生错误,所以在不确定操作的是否为单例数据集时就会产生问题。对于设计仅操作单例的方法,
    可在开头处使用 self.ensure_one(),如果 self 不是单例时将抛出错误。

访问关联字段

### 包含:many-to-one, one-to-many和many-to-many
    # 对于many-to-one,其值可以是单例或空记录集
        self.company_id ---> res.company(1,)
        self.company_id.name  ---> 'YourCompany'
        self.company_id.currency_id   ---> res.currency(1,)
        self.company_id.currency_id.name   ---> 'EUR'

    # 空记录可像单例一样操作,访问其字段值不会返回错误而是返回 False 
    	self.company_id.parent_id --->  res.company()
        self.company_id.parent_id.name ---> False

访问时间和日期值

# 1.查询上次admin用户登录日期
	self.browse(2).login_date ---> datetime.datetime(2019, 1, 8, 9, 2, 54, 45546)

#  2. 可以利用python的datetime模块进行时间计算

Odoo 还在odoo.tools.date_utils模块中提供额外的便利函数
    # start_of(value, granularity)是某个特定刻度时间区间的开始时间,这些刻度有year, quarter, month, week, day或hour
    #  end_of(value, granularity)是某个特定刻度时间区间的结束时间
	#  add(value, **kwargs)为指定值加上一个时间间隔。**kwargs参数由一个relativedelta对象来定义时间间隔。这些参数可以是years, months, weeks, days, hours, minutes等等
    #  subtract(value, **kwargs)为指定值减去一个时间间隔 
    
from odoo.tools import date_utils
from datetime import datetime

# 范围时间当前时间和指定时间参数:week,本周周一那天.如今天是2020年5月27,一周前是2020年5月25	
# 输入: date_utils.start_of(datetime.now(), 'week')
	    datetime.datetime(2020, 5, 25, 0, 0)
    
# end_of 同理 末尾时间
# 输入:date_utils.end_of(datetime.now(), 'week')
	  datetime.datetime(2020, 5, 31, 23, 59, 59, 999999)
    
# add 指定添加多长时间
# 输入: date_utils.add(datetime.today(), months=2)
		datetime.datetime(2020, 7, 27, 2, 40, 55, 424214)
    
# subtract 指定减去多长时间
# 输入: date_utils.subtract(datetime.today(), months=2)
		datetime.datetime(2020, 3, 27, 2, 42, 21, 390561)
odoo.fields.Date和the odoo.fields.Datetime
# 1. fields.Date.today()   # 返回当前日期,类型是datetime
	 datetime.date(2020, 5, 27)
# 2. fields.Datetime.now() # 返回当前日期+时间,类型是datetime
	 datetime.datetime(2020, 5, 27, 2, 46, 10)
# 3. fields.Date.context_today(record, timestamp=None)
   fields.Date.context_today(self, timestamp=None)
     datetime.date(2020, 5, 27)

	 # 在会话上下文中返回带有当前日期的字符串。时间从记录上下文中获取。可选项timestamp参数是一个datetime对象,如果传入将不使用当前时间,而使用传入值
 
# 4. fields.Datetime.context_timestamp(record, timestamp)
	 # 将原生的datetime值(无时区)转换为具体时区的datetime。时区从记录上下文中提取,因此使了前述函数名。

转换文本形式的日期和时间

# fields.Date和fields.Datetime都提供了如下函数
# 1. to_date将字符串转换为date对象
# 2. to_datetime(value)将字符串转换为datetime对象
# 3. to_string(value)将 date和datetime对象转换成字符串格式

# 4. Odoo 预设文本格式默认值:
    odoo.tools.DEFAULT_SERVER_DATE_FORMAT ---> %Y-%m-%d
    odoo.tools.DEFAULT_SERVER_DATETIME_FORMAT ---> Y-%m-%d %H:%M:%S
            
from odoo import fields
# 输入: fields.Datetime.to_datetime('2019-01-12 13:48:50')
		datetime.datetime(2019, 1, 12, 13, 48, 50)  
# 注意:
	其他格式时间字符串转换成日期类型需要使用 datetime.strptime单独转换
    from datetime import datetime
	# 输入: datetime.strptime('1/1/2019', '%d/%m/%Y')
	datetime.datetime(2019, 1, 1, 0, 0)

五·记录中写入

# odoo写入分为两种模式:
	# 1. 使用对象形式直接分配 . 简单但一次只能操作一条记录,效率较低
	# 2. 使用write() 方法 .  写入关联字段时使用特殊语法,但每条命令可写入多个字段和记录,记录计算更为高效  

对象形式分配值写入

# 查询一条数据
	root = self.env['res.users'].browse(1)
# 更改root对象name值
	root.name = 'Superuser'
# root.name
	Superuser
 
### 

通过 write()方法写入

# 用write()方法来同时更新多条记录中的多个字段,仅需一条数据库命令
# write() 接收一个字典来进行字段和值的映射

# 得到一个空对象
	Partner = self.env['res.partner']
# 查询值
	recs = Partner.search( [('name', 'ilike', 'Azure')] )
# 修改值, 修改成功为True
	recs.write({'comment': 'Hello!'})

    
### 在写入many-to-one字段时,写入的值必须是关联记录的ID。
	例如,我们不用self.write({‘user_id’: self.env.user}),而应使用self.write({‘user_id’: self.env.user.id})   
    
### 写入to-many字段时,写入的值必须使用和 XML 数据文件相同的特殊语法
	比如,我们设置图书作者列表为author1和author2,这是两条 Partner 记录。| 管道运算符可拼接记录来创建一个记录集,因此使用对象形式的分配可以这么写
    publisher.child_ids = author1 | author2
    # 使用write()方法
    book.write( { 'child_ids': [(6, 0, [author1.id, author2.id])] } )
    ### (4, id, _)添加一条记录
    ### (6, _, [ids])替换关联记录列表为所传入的列表

写入日期和时间值

### 可以使用文本形式值写入日期和时间:
# 如:
	# 搜索到一个对象
	demo = self.search([('login', '=', 'demo')])
    # 更改demo.login_date值
	demo.login_date = '2019-01-01 09:00:00'
    # 查看
    demo.login_date ---> datetime.datetime(2019, 1, 1, 9, 0)

创建和删除记录

#创建和删除记录 通过 create()和unlink()模型方法实现

### create方法
    # 得到一个空的对象 ,这个对象仅被加载到了内存种,没有实际保存到数据库种
        Partner = self.env['res.partner']
    # 创建一个对象,此时就被保存到数据库种
        new = Partner.create({'name': 'ACME', 'is_company': True})
        res.partner(64,)
    # 查看cutomer属性值
        print(new.customer) # customer标记默认为 True
### unlink()方法会删除记录
	# 搜索到某条记录
    rec = Partner.search([('name', '=', 'ACME')])
    rec.unlink() # 删除 partner 关联字段的串联删除

拷贝模型记录值

### copy() 方法,会自动创建一条记录.这条记录会被保存到数据库中

# 查询出一条记录 
	demo = self.env.ref('base.user_demo')
# 拷贝
	new = demo.copy({'name': 'Daniel', 'login': 'daniel', 'email': ''})

六·重构记录集

# 1. recordset.ids 返回记录集元素的ID列表
# 2. recordset.ensure_one()检查是否为单条记录(单例);若不是,则抛出ValueError异常
# 3. recordset.filtered(func)返回一个过滤了的记录集,func可以是一个函数或一个点号分隔的表达式来表示字段路径,可参见下面的示例。
# 4. recordset.mapped(func)返回一个映射值列表。除函数外,还可使用文本字符串作为映射的字段名。
# 5. recordset.sorted(func)返回一个排好序的记录值。除函数外,文本字符串可用作排序的字段名。reverse=True是其可选参数。
>>> rs0 = self.env['res.partner'].search([])
>>> len(rs0)
48

>>> starts_A = lambda r: r.name.startswith('A')
>>> rs1 = rs0.filtered(starts_A)
>>> print(rs1)
res.partner(63, 59, 14, 35)

>>> rs1.sorted(key=lambda r: r.id, reverse=True)
res.partner(63, 59, 35, 14)

>>> rs2 = rs1.filtered('is_company')
>>> print(rs2)
res.partner(14,)

>>> rs2.mapped('name')
['Azure Interior']

>>> rs2.mapped(lambda r: (r.id, r.name))
[(14, 'Azure Interior')]
rs1 | rs2是一个集合的并运算,会生成一个包含两个记录集所有元素的记录集
rs1 + rs2是集合加法运算,会将两个记录集拼接为一个记录集,这可能会带来集合中有重复记录
rs1 & rs2是集合的交集运算,会生成一个仅在两个记录集中同时出现元素组成的数据集
rs1 – rs2是集合的差集运算,会生成在rs1中有但rs2中没有的元素组成的数据集

# 还可以使用分片标记,例如:
    rs[0]和rs[-1]分别返回第一个和最后一个元素
    rs[1:]返回除第一元素外的记录集拷贝。其结果和rs – rs[0]相同,但保留了排序
    
# 删除或添加元素来修改记录集
	self.author_ids |= author1:如果不存在author1,它会将author1加入记录集
	self.author_ids -= author1:如果author1存在于记录集中,会进行删除
	self.author_ids = self.author_ids[:-1]删除最后一条记录

七·底层 SQL 和数据库事务

# 数据库引入运算在一个数据库事务上下文中执行
# 通过数据库游标self.env.cr

# 执行事务缓冲的写运算 ,提交数据

	self.env.cr.commit()
    
# 取消上次 commit之后的写运算,如果尚未 commit,则回滚所有操作
	self.env.cr.rollback()
    
# execute() 方法 执行SQL语法 self.env.cr.execute()
	参数一: 运行的SQL 语句,
    可选参数:一个用作 SQL 参数值的元组或列表。这些值会用在%s占位符之处。
    
    # 用法: 
	self.env.cr.execute("SELECT id, login FROM res_users WHERE login=%s OR id=%s", ('demo',1)
    self.env.cr.execute("SELECT id, login FROM res_users WHERE login=%s OR id=%s", ('demo',1))

### select 查询记录
	# fetchall() 函数以元组列表的形式获取所有行
    	# 输入:self.env.cr.fetchall()
        [(1, '__system__'), (6, 'demo')]
    
    # dictfetchall()则以字典列表的形式获取
    	# 输入: self.env.cr.dictfetchall()
        [{'id': 1, 'login': '__system__'}, {'id': 6, 'login': 'demo'}]

### 修改DML操纵语言
 self.env.cache.invalidate()
原文地址:https://www.cnblogs.com/dengz/p/12971517.html