Join GitHub today
Compute with "readonly=False" as an alternative to onchange #32784
Support for compute, with readonly=False as an alternative to onchange & conditional defaults.
Most onchange includes business logic (ex: changing a partner on an invoice sets the
Compute fields are usually orthogonal between each others, thus simpler to implement and
USE CASE 1:
If onchanges are replaced by compute fields, the code to create an invoice, or automated tests
-> the compute fields will compute the right fiscal position, taxes, customer address, etc. It's
This simplifies creation of records in the code, but also import. (you import only some data, and you can let the business logic do the rest: import customers of your invoice, and the system will setup the journal, currency, fiscal position, taxes, etx)
USE CASE 2:
Let's say the module l10n_mx want to add a TAXCODE field on an invoice,
or, the new approach:
The onchange approach creates a big issue: you will have to manage this logic by creating
With the new approach, the TAXCODE is delegated to the invoice, and handled automatically
Having some data that are computed downstream and others upstream, and some backend and others for the interface only is, in my opinion, a big design flaw in Odoo's framework. It is much easier if everything is computed in the same direction, with only one dependency tree, and one concept. (imagine computed fields are a like an excel cell, whose formula depends on others cell; imagine the mess if Excel would have introduced "onchange")
The main change is to evaluate computed fields only when we need it, instead of "at every write & create". Thus, computed fields are evaluated at the end of the transaction (commit), when you read a field that is marked as "to recompute", or when you search on a field that have record marked as "to recompute".
As opposed to the current version, the value in cache is usually not invalidated. Instead, we use the existing "todo" concept of the framework to mark fields to be recomputed. Except that we don't process the "todo" at each write() / create() anymore.
That allows multiple speed improvements:
So, the following code will evaluate invoice's computed fields only once (e.g., the subtotal & taxes):
But the triggers to evaluate compute fields' dependencies are still called once every create/write. Thus, it's still a good optimization to create all the lines at once to avoid the triggers. (but I think the triggers cost can be reduced a lot, see bellow)
This version of the ORM is mostly compatible with the current version in master (all the tests of the base module passed without modifications). But there are subtles impacts that requires to fix some business modules. The main ones are:
Performances seem much better with this refactoring. The following code is 2.32x faster with demo data (0.0086s vs 0.0037s):
Writing and creating complex records (with related fields, computed fields) seems faster too, but I still have to do the benchmark on real business modules. The method testme() in this branch, that create a complex object with one2many, multiple related and computed fields is:
Stack trace is shorter when crashing in computed fields.
This branch will also allow to remove computed fields' triggers using inverse fields instead or hard SQL queries. I expect this to speed up most write() / create() by reducing SQL queries by an extra ~35%, which should translates into an extra speed improvement.
It's a quick POC written in a few evenings; it still requires a huge cleanup.
Once these are done, ideas for future improvements: https://pad.odoo.com/p/r.896af7a5c2a2fc86575f8c5b4d306419
Onchange will remain supported in v13, but should be limited to pure UI changes, without business logic. (so only a few; pretty much everything should be implemented with computed fields instead)