8. Single Cache
Instead of one cache per env / context
➔ Stored fields have a single value
➔ Computed, non-stored fields’ value
depends on specific keywords in the
context
➔ ex: translatable, properties
v12 cache structure:
➔ {env: {field: {id: value}}}
v13 cache:
➔ {field: {id: value}}
v13 cache, @depends_context fields:
➔ {field: {id: {context_keys: value}}}
# Odoo 12
self.field # SELECT
r = self.with_context(key=val) # new cache
r.field # SELECT
self.sudo().field # new cache + SELECT
# Odoo 13
self.field # SELECT
r = self.with_context(key=val)
r.field
self.sudo().field
9. Prefer In-Memory updates
Instead of SQL queries
➔ put in cache, instead of writing in DB
➔ env.all.towrite contains values to UPDATE
➔ nearly never invalidate cache, update it
instead (e.g. changing a many2one, will
update cached values of its one2many)
➔ flush([fields]):
◆ write in DB (minimize updates)
◆ recompute optional fields
◆ called at end of rpc
➔ create() does INSERT directly
# Odoo 12
self.name = 'test' # UPDATE + invalid cache
self.name = 'test2' # UPDATE + invalid cache
self.name # SELECT
# Odoo 13
self.name = 'test' # put in cache
self.name = 'test2' # put in cache
self.name # get from cache
self.flush() # UPDATE
10. Dependencies In-Memory
class order(Model):
discount = Float()
line_ids = One2many('order_id')
class order_line(Model):
order_id = Many2one('order')
price = Float()
subtotal = Float(compute="get_total")
@api.depends('price','order_id.discount')
def get_total(self):
self.subtotal = self.price * self.order_id.discount
# Odoo 12
for order in self:
order.discount = 10.0 # SELECT id WHERE order_id
# UPDATE order
# Odoo 13
for order in self:
order.discount = 10.0 # no SQL
self.flush() # UPDATE order WHERE id IN ()
Whenever possible
➔ instead of SQL query, get inverse field, ex:
to evaluate @depends(‘order_id’), check
value of line_ids in the cache
➔ usually avoids a SELECT as the
implementation of the compute will read the
same object
➔ if no inverse field, fall back to SQL query
(e.g. many2one without a one2many)
➔ when create(), set one2many to []
11. Delay computed field
As late as possible
➔ compute fields:
◆ only if you read it
◆ or if you need to flush in the DB
➔ as the old value stays in memory, we can
optimize to not recompute if the value do
not change
# odoo 12
for line in order:
line.product_qty = 1 # mark to recompute
# compute l.subtotal, o.total
line.discount = 10 # mark to recompute
# compute l.subtotal, o.total
line.price = 100 # mark to recompute
# compute l.subtotal, o.total
# odoo 13
for line in order:
line.product_qty = 1 # mark to recompute
line.discount = 10 # mark to recompute
line.price = 100 # mark to recompute
self.flush() # recompute all lines & orders
12. Delay SQL updates
As late as possible
➔ save all changes in a self.env.all.towrite
➔ optimize updates, to minimize queries:
update several records at once
update several fields at once
# odoo 12
for line in order:
line.state = 'draft' # UPDATE ... WHERE ID = x
# odoo 13
for line in order:
line.state = 'draft'
self.flush() # UPDATE ... WHERE ID IN (...)
13. Optimize dependency tree
to reduce python & sql computations
v12: recursive dependencies
change order.discount
→ order_line.subtotal depends on it
→ to recompute subtotal
→ order_id.total depends on it (for each line)
→ to recompute order.total
v13: optimized dependency trees
change order.discount
→ to recompute total
→ order_line.subtotal depends on it
→ to recompute order_line.subtotal
class order(Model):
discount = Float()
line_ids = One2many('order_id')
class order_line(Model):
order_id = Many2one('order')
price = Float()
subtotal = Float(compute="get_total")
@api.depends('price','order_id.discount')
def get_total(self):
self.subtotal = self.price * self.order_id.discount
# Impact of:
order.discount = 1
14. browse() optimization
Avoid multiple format conversion
➔ cache format similar to DB to avoid
conversion:
None instead of False
4 instead of (4,) for many2one
➔ don’t convert to read() format, for browse
records
# Odoo 12
self.field
→ prefetch
→ read
→ _read
SQL query
convert_to_cache
put in cache
→ get from cache (convert_to_record)
→ convert_to_read
# Odoo 13
self.field
→ prefetch
_read
SQL query
put in cache
15. And a lot of code cleanup,
and Python optimization.
18. Who can tell what
onchange_partner_id()
does on a sale.order?
(not me)
19. How is the sale.order
pricelist_id field computed?
And the delivery address?
(much easier to express)
20. Compute vs onchange
Computed Fields
✓ Work in Python module & JS client
✓ Clean dependency graph
✓ Express a business logic
✓ Separation of business concepts
⛌ Field readonly (but can be inverted)
onchange() method
⛌ Only work in JS client
⛌ No dependency graph: recursion
⛌ interface logic, not business logic
⛌ Business concepts aggregated
✓ Allow to edit the field
The new framework supports fields with:
compute=”...”, readonly=False
compute method might not set a value
22. Example: Definition
onchange() → compute()
# Odoo 12
pricelist_id = fields.Many2one()
delivery_id = fields.Many2one()
@api.onchange('partner_id')
def onchange_partner_id(self):
for order in self:
order.pricelist_id = order.partner_id.pricelist_id
order.delivery_id = order.partner_id.pricelist_id
# Odoo 13
pricelist_id = fields.Many2one(compute="comp_pl_id",
readonly=False)
delivery_id = fields.Many2one(compute="comp_delivery_id")
@api.depends('partner_id')
def compute_pricelist_id(self):
for order in self:
self.pricelist_id = order.partner_id.pricelist_id
@api.depends('partner_id')
def compute_delivery_id(self):
for order in self:
self.delivery_id = order.partner_id.pricelist_id
➔ reverse the definition: instead of telling
what field is impacted by a change, express
the business logic of each field (how is it
computed)
23. Example: Definition
onchange() → compute()
# Odoo 12
pricelist_id = fields.Many2one()
delivery_id = fields.Many2one()
@api.onchange('partner_id')
def onchange_partner_id(self):
for order in self:
order.pricelist_id = order.partner_id.pricelist_id
order.delivery_id = order.partner_id.pricelist_id
# Odoo 13
pricelist_id = fields.Many2one(
related="partner_id.pricelist_id",
depends="partner_id",
readonly=False,
store=True)
➔ reverse the definition: instead of telling
what field is impacted by a change, express
the business logic of each field (how is it
computed)
24. Advantages
onchange() → compute()
➔ compute() fields are tested automatically
➔ inheritances simplified
➔ business logic abstraction
➔ code simplification (no need to call
onchange() manually anymore)
Example
l10n_co adds a customer_type field on
account.invoice that depends on
fiscal_position_id?
→ compare onchange vs compute...
# Odoo 13, create an invoice
self.create({
'partner_id': 1,
'line_ids': ([0,0, {'product_id': 2}])
})
# Odoo 13, create another invoice
self.create({
'partner_id': 1,
'payment_term_id': 2,
'line_ids': ([0,0, {
'product_id': 2, 'price_unit': 100.0
}])
})