The Odoo Environment (env)
The env object is the gateway to everything in Odoo's ORM. Every model, every recordset, every method call happens through an environment. Understanding env is fundamental to Odoo development.
# Accessing env from a model method
def action_process(self):
env = self.env
env.uid # current user ID
env.user # current user recordset (res.users)
env.company # current company recordset
env.companies # all companies the user has access to
env.cr # database cursor
env.context # context dictionary
env.su # True if running in sudo mode
env.lang # current language code
env.ref('xml_id') # resolve XML ID to record
Accessing Models via env
# Access any model
partners = self.env['res.partner'].search([])
products = self.env['product.product'].browse([1, 2, 3])
# Create records
new_record = self.env['my.model'].create({'name': 'Test'})
# Access current user's partner
partner = self.env.user.partner_id
The Context Dictionary
The context is an immutable dictionary that flows through ORM operations, affecting behavior at every level:
# Reading context
lang = self.env.context.get('lang', 'en_US')
tz = self.env.context.get('tz', 'UTC')
active_id = self.env.context.get('active_id')
active_ids = self.env.context.get('active_ids', [])
# Default values from context
# When creating records, fields check for 'default_fieldname' in context
# context = {'default_partner_id': 5}
# -> new record gets partner_id = 5
Modifying Context with with_context()
Since context is immutable, you create a new environment with modified context:
# Add to existing context
records = self.with_context(custom_flag=True).search([])
# Replace entire context
records = self.with_context({'lang': 'fr_FR'}).search([])
# Common patterns
# Force language for translations
name_fr = product.with_context(lang='fr_FR').name
# Set defaults for record creation
new_line = self.env['order.line'].with_context(
default_order_id=order.id,
default_currency_id=order.currency_id.id,
).create({'product_id': product.id})
# Pass data to computed fields
records = self.with_context(
date_from=start_date,
date_to=end_date,
).mapped('computed_balance')
Important Context Keys
| Key | Purpose |
|---|---|
lang | Language for translations |
tz | Timezone for date display |
uid | Current user ID |
active_id | ID of the record that triggered the action |
active_ids | IDs of selected records (multi-select) |
active_model | Model of the triggering record |
default_* | Default values for new records |
search_default_* | Pre-activate search filters |
group_by | Default grouping |
tracking_disable | Disable mail tracking |
mail_create_nolog | Skip creation log message |
force_company | Override company context |
Company Context
Odoo's multi-company system uses the environment to determine which company's data to access:
# Current company
company = self.env.company # res.company recordset
# All allowed companies
companies = self.env.companies # recordset of all user's companies
# Switch company context
def _process_for_company(self, company):
return self.with_company(company)._compute_values()
# with_company changes both env.company and allowed companies
record = self.with_company(other_company)
record.env.company # other_company
with_user()
Switch the current user:
# Switch to a specific user
record_as_admin = self.with_user(admin_user)
# Switch to superuser (bypasses access rights)
record_su = self.with_user(SUPERUSER_ID)
# Equivalent to sudo() but more explicit
record_sudo = self.sudo()
Context in Computed Fields
Context flows into computed field methods, which can cause subtle bugs:
# This computed field depends on context!
@api.depends_context('date_from')
def _compute_balance(self):
date_from = self.env.context.get('date_from')
for record in self:
if date_from:
record.balance = record._get_balance_at(date_from)
else:
record.balance = record._get_current_balance()
Use @api.depends_context('key') to declare that a computed field depends on a context key. Without this, Odoo may cache the result from a different context.
Context in XML Views
<!-- Set context on action -->
<record id="action_tickets" model="ir.actions.act_window">
<field name="context">{
'default_priority': '2',
'search_default_filter_my': 1,
'group_by': 'state',
}</field>
</record>
<!-- Set context on a relational field -->
<field name="line_ids"
context="{'default_date': date_order,
'default_partner_id': partner_id}"/>
<!-- Set context on a button -->
<button name="action_process" type="object"
context="{'process_mode': 'quick'}"/>
env.ref() for XML ID Resolution
# Resolve an XML ID to a record
template = self.env.ref('module.email_template_id')
group = self.env.ref('base.group_system')
# With raise_if_not_found
record = self.env.ref('module.xml_id', raise_if_not_found=False)
if record:
# record exists
pass
Environment Immutability
The environment is immutable. Methods like with_context(), with_company(), with_user(), and sudo() all return new recordsets with new environments. The original recordset is unchanged:
original = self.env['res.partner'].search([])
elevated = original.sudo()
french = original.with_context(lang='fr_FR')
# All three are different environments
original.env.su # False
elevated.env.su # True
french.env.context.get('lang') # 'fr_FR'
Common Patterns
# Disable mail tracking for bulk operations
records.with_context(tracking_disable=True).write({
'state': 'done'})
# Skip mail create notification
self.with_context(mail_create_nolog=True).create(vals)
# Force a specific language for a report
def _get_report_data(self):
return self.with_context(
lang=self.partner_id.lang or 'en_US'
).read(['name', 'description'])