转载:http://book.odoomommy.com/chapter2/README19.html
第十二章 上下文对象
严格地说,上下文并不是一类对象,它只是一个字典。但是它却在数据传递过程中有着非常重要的作用。
环境变量中的上下文对象
上下文在环境变量的创建过程中就被赋值了,不过传入的上下文被frozendict化了,因此环境变量中的上下文是不可变的,不可以通过赋值的方式试图去改变它的值。
def __new__(cls, cr, uid, context, su=False):
...
# otherwise create environment, and add it in the set
self = object.__new__(cls)
args = (cr, uid, frozendict(context), su)
self.cr, self.uid, self.context, self.su = self.args = args
...
从代码中我们可以看出,self.context是一个不可变的字典,即我们不能通过赋值的方式改变它的值。那么,如果我们希望在某些条件上切换用户的上下文变量,该如何做呢?
这个时候,就到with_context方法上场的时候了。
既然Environment对象的context上下文不能被改变,那么我们就把整个Environment对象换掉好了。我们来看with_context方法是怎么做的:
def with_context(self, *args, **kwargs):
""" with_context([context][, **overrides]) -> records
Returns a new version of this recordset attached to an extended
context.
The extended context is either the provided ``context`` in which
``overrides`` are merged or the *current* context in which
``overrides`` are merged e.g.::
# current context is {'key1': True}
r2 = records.with_context({}, key2=True)
# -> r2._context is {'key2': True}
r2 = records.with_context(key2=True)
# -> r2._context is {'key1': True, 'key2': True}
.. note:
The returned recordset has the same prefetch object as ``self``.
"""
if (args and 'force_company' in args[0]) or 'force_company' in kwargs:
_logger.warning(
"Context key 'force_company' is no longer supported. "
"Use with_company(company) instead.",
stack_info=True,
)
if (args and 'company' in args[0]) or 'company' in kwargs:
_logger.warning(
"Context key 'company' is not recommended, because "
"of its special meaning in @depends_context.",
stack_info=True,
)
context = dict(args[0] if args else self._context, **kwargs)
if 'allowed_company_ids' not in context and 'allowed_company_ids' in self._context:
# Force 'allowed_company_ids' to be kept when context is overridden
# without 'allowed_company_ids'
context['allowed_company_ids'] = self._context['allowed_company_ids']
return self.with_env(self.env(context=context))
从中可以看到,forece_company在当前版本(14.0)中已经被弃用了。with_context内部调用了with_env方法返回了一个使用了新的Environment对象的记录。而with_env又是何方神圣?
def with_env(self, env):
"""Return a new version of this recordset attached to the provided environment.
:param env:
:type env: :class:`~odoo.api.Environment`
.. warning::
The new environment will not benefit from the current
environment's data cache, so later data access may incur extra
delays while re-fetching from the database.
The returned recordset has the same prefetch object as ``self``.
"""
return self._browse(env, self._ids, self._prefetch_ids)
这就清楚了,with_env内部调用了低阶的_browse方法获取一条新的记录,而_browse方法第一个参数就是env变量,当我们传入了新的env对象和旧的记录id时,就完成了对之前记录的context上下文的替换。
搜索归档的记录
第一章提到过的search方法,有一个默认的行为,就是只搜索active值为True的记录。有些时候,我们希望能够搜索到active为False的记录,只在domain中添加 active = False 是不够的。正确的写法是利用with_context:
product_obj.with_context(active_test=False).search([
('default_code', '=', data["Cpcode"]),
('barcode', '=', data["barcode"]), ('active', '=', False)], limit=1)
强制使用某公司
多公司环境下,如果我们想要代码在某单一公司的环境下运行,可以使用下面的方法:
self.sudo().with_context(force_company=company_id)
force_company意味着强制使本行代码在company_id公司下运行,比较典型的使用场景是,查询某个公司的库存。如果当前用户拥有多个公司,默认情况下返回的是所有公司库存的总和。
多公司环境下的使用
当前用户的公司: self.env.user.company_id 当前环境下的公司: self.env.company (注意这里是company 而不是company_id)
向导下的Many2one字段的过滤
使用Env来解决动态domain的问题是一个非常有效的方法。由于odoo系统本身功能的限制,我们并不能很容易地在视图中动态设置domain的值,这对于某些场景下显得非常尴尬,例如,笔者的一个客户项目中,需要在向导中对产品进行限定,限定的值又是另外一个对象的One2many中的产品ID列表,由于Odoo并没有一个列表类型的字段,因此我们不能使用关联字段的方式来进行过滤,这就成了一个很棘手的问题。
这时候,我们可以通过env来曲线地完成这个任务,首先我们在product_id字段地属性context中添加当前环境地模型和id:
<tree editable="bottom">
<field name="product_id" context="{'active_id':active_id,'active_model':active_model}"/>
<field name="quantity"/>
<field name="inventory_qty"/>
<field name="location_id"/>
</tree>
然后我们在product_id的多对一对象product.product的name_search方法中对active_model进行判断,如果是符合我们的要求的模型,就使用我们的自定义逻辑:
_inherit = "product.product"
@api.model
def name_search(self, name, args, operator, limit):
model = self.env.context.get("active_model")
if model == "juhui.repair.workorder":
active_id = self.env.context.get("active_id")
workorder = self.env[model].browse(active_id)
ids = [line.product_id.id for line in workorder.consume_lines]
args = [('id','in',ids)]
return super(product_product, self).name_search(
name, args, operator, limit)
这样 我们就完美地实现了向导中Many2one字段的过滤。