接触过odoo开发的同学一定对Environment对象不陌生,但是你真的了解odoo中的Environment对象吗,不妨看看你能否回答出下面的问题:
-
我们都知道
sale_order_obj = self.env['sale.order']
可以获取到销售订单的模型,但它背后的原理是什么? -
我们可以通过self.env.user获取当前用户,self.env.company可以获取当前公司,那么还有什么其他的变量可以直接使用吗?
-
为什么self.env.ref可以获取到xmlid对应的对象?
-
self.env默认是当前公司,如果想要切换到另外一个公司,应该怎么做?如果你知道答案,那么我再问,当前版本(14.0)中那个方法还有效吗?
-
我们知道self.env.context是个不可变值,那么如果我们就是想要改变context中的某些值,应该怎么做?其内部原理是怎样的?
-
模型对象中的env变量(即我们常用的self.env)是什么时候进行初始化赋值的?
如果你对上述6个问题的都了然于胸,那么你肯定是odoo领域的大牛,原谅我在您面前班门弄斧了,下面的文章就不用浪费时间了。如果你对问题的答案摸棱两可或是一头雾水,那么你可以接着往下读,看看笔者能否给你一次讲个明白。
Environment是什么?
odoo中的environment对象是从旧版本API演化到新版本API的产物,它封装了旧版本中一些琐碎的变量,例如cr, uid, ids, context等等。它的主要作用是封装ORM的环境变量,提供新版API模型的快速访问方法,以及管理一些需要计算的数据结构。
不知道读者是否注意过,我们在定义一个模型的时候,只定义了模型的字段数据和必要的方法,缺从未对模型进行过实例化(如果你有过Python编程经验,应该知道类只有被实例之后才可以使用)。既然odoo不需要我们手动进行实例化,那么它是在什么时候进行实例化的呢?
答案是odoo在启动之后,即对所有记录在案(数据库)的模型进行了注册,将模型名称与模型类的对应关系维护起来,当我们需要的时候就通过叫名字的方法将模型类调出来供我们使用。这种理念有没有很熟悉?没错,它很像我们数据库操作中线程池的概念,早期的odoo版本使用的变量名就是pool,随着版本的迭代,pool在系统中已经不多见了,取而代之的就是我们今天的主角—Environment。
事实上,我们在使用self.env['sale.order']
方法获取到的即模型sale.order的实例,之后我们使用ORM的browse或者search方法对返回的记录集进行操作。
Environment对象的变量
我们知道可以通过self.env来或者模型实例,也可以获取当前用户,那么Environment对象有哪些常用的数量呢?
下面是笔者从代码中总结出来的常用的变量:
- self.cr: 数据库查询cursor
- self.uid: 当前用户的uid
- self.context: 当前用户的上下文
- self.su: 是否是超级用户
- self.env.user: 获取当前用户对象
- self.env.company: 获取当前用户的公司对象
- self.env.companies: 获取当前用户可访问的所有公司
- self.env.lang: 获取当前用户的语言代码
另外还有几个可以用来判断当前用户角色的方法:
- is_superuser(): 判断当前用户是否是超级用户
- is_admin(): 判断当前用户是否是管理员
- is_system(): 判断当前用户是否是系统管理员
Environment获取模型实例的原理
Envronment对象在实例化的时候会实例化一个Registry对象,Registry对象是odoo用来管理注册模型的类,Environment对象使用Registery的实例通过传入的模型名称获取模型的类名称,然后再实例化一个空的记录集返回给调用者使用。
ref方法的原理
Environment的ref方法实际上调用的是ir.model.data的xmlid_to_object方法,做了简单的封装,以方便我们进行调用。
多公司条件的下的环境变量
如果启用了多公司,那么我们的self.env默认为当前的用户所在的公司环境。假设我们系统中目前有两个公司A和B,如果当前公司是A,想要操作B公司的数据,应该如何处理?
在13.0版本中,我们可以使用with_context(force_company=B公司的id)的方式进行强制的公司环境切换。但是14.0中已经弃用了force_company,取而代之的是使用with_company方法。
那么问题来了,with_context是什么,有什么作用?
with_context
我们知道,self.env.context是个不可变的字典,我们不能直接修改它的内容。如果某个需求要求你必须将它的内容改掉,怎么办?
答案也很简单,既然你不让我改,那我再重新制造一份新的副本不就可以了。这就是with_context的使用场景。
with_context方法将传入的上下文变量,使用之前的记录ids,重新生成了一份新的记录集,返回给调用方使用。
Environment的加载机制
那么现在来讨论最后一个问题,self.env是何时赋值给self的?
这个问题的答案非常隐晦,笔者翻遍了model和api的源码也没找到BaseModel中给env变量赋值的语句。起初,笔者以为既然BaseModel中没有,那么它可能隐藏在元类MetaModel和Meta中,后来证明BaseModel和Meta中也没有。
实际上,odoo在启动过程中对数据库中的模型(ir.model)进行了初始化,而初始化这一步骤中实例化了一个env对象,但这里没有model.env = env
这种显式的赋值语句,实际上的赋值操作隐藏在了env[model_name]
这里。
env[model_name]方法内部将当前的env变量传给了model的实例,也因此,我们在后续的操作中可以使用self.env直接调用环境变量。
一般情况下,我们不需要操心env的加载时机,因为对于常规的开发而言,操作时env变量已经是就绪状态,直接调用即可。
更详细的内容请关注公众号: odoohub 开发指南第二部分第十二章