From 9ceecee4f19e319c656c5618557a50fe9e594a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Tue, 27 Feb 2018 00:49:53 +0100 Subject: [PATCH 1/5] [IMP] make posible to call the onchange on an existing record and avoid returning computed value --- onchange_helper/README.rst | 9 +++++++++ onchange_helper/models/ir_rule.py | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/onchange_helper/README.rst b/onchange_helper/README.rst index 48cca771e..5f75d895d 100644 --- a/onchange_helper/README.rst +++ b/onchange_helper/README.rst @@ -24,6 +24,15 @@ Example if you want to create a sale order and you want to get the values relati Then, `vals` will be updated with partner_invoice_id, partner_shipping_id, pricelist_id, etc... +You can also use it on existing record for example: + + `vals = {'partner_shipping_id: 1'}` + + `vals = sale.play_onchanges(vals, ['partner_shipping_id'])` + +Then the onchange will be played with the vals passed and the existing vals of the sale. `vals` will be updated with partner_invoice_id, pricelist_id, etc... + + Bug Tracker =========== diff --git a/onchange_helper/models/ir_rule.py b/onchange_helper/models/ir_rule.py index b4f67af16..d904a72ab 100644 --- a/onchange_helper/models/ir_rule.py +++ b/onchange_helper/models/ir_rule.py @@ -17,7 +17,6 @@ def get_new_values(model, record, on_change_result): new_values[fieldname] = value return new_values - @api.model def play_onchanges(self, values, onchange_fields): onchange_specs = self._onchange_spec() @@ -26,7 +25,10 @@ def play_onchanges(self, values, onchange_fields): all_values = values.copy() for field in self._fields: if field not in all_values: - all_values[field] = False + # If self is a record (play onchange on existing record) + # we take the value of the field + # If self is an empty record we will have an empty value + all_values[field] = self[field] # we work on a temporary record new_record = self.new(all_values) @@ -39,7 +41,7 @@ def play_onchanges(self, values, onchange_fields): all_values.update(new_values) res = {f: v for f, v in all_values.iteritems() - if f in values or f in new_values} + if not self._fields[f].compute and (f in values or f in new_values)} return res From 4fbc65636b782cee169d64736cf153ece750b2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Tue, 22 May 2018 17:56:01 +0200 Subject: [PATCH 2/5] [FIX] fix pep8 --- onchange_helper/models/ir_rule.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onchange_helper/models/ir_rule.py b/onchange_helper/models/ir_rule.py index d904a72ab..675cd6435 100644 --- a/onchange_helper/models/ir_rule.py +++ b/onchange_helper/models/ir_rule.py @@ -17,6 +17,7 @@ def get_new_values(model, record, on_change_result): new_values[fieldname] = value return new_values + @api.model def play_onchanges(self, values, onchange_fields): onchange_specs = self._onchange_spec() From 7e5db103b6fe3e19140f4f8a486d637eb31b921e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Tue, 12 Jun 2018 00:06:37 +0200 Subject: [PATCH 3/5] [REF] migrate to new api, inherit base class instead of patching, add test and make improve play_onchange --- onchange_helper/models/__init__.py | 2 +- onchange_helper/models/ir_rule.py | 55 -------------------------- onchange_helper/models/models.py | 51 ++++++++++++++++++++++++ onchange_helper/tests/__init__.py | 6 +++ onchange_helper/tests/test_onchange.py | 24 +++++++++++ 5 files changed, 82 insertions(+), 56 deletions(-) delete mode 100644 onchange_helper/models/ir_rule.py create mode 100644 onchange_helper/models/models.py create mode 100644 onchange_helper/tests/__init__.py create mode 100644 onchange_helper/tests/test_onchange.py diff --git a/onchange_helper/models/__init__.py b/onchange_helper/models/__init__.py index 0f0f860f3..cde864bae 100644 --- a/onchange_helper/models/__init__.py +++ b/onchange_helper/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from . import ir_rule +from . import models diff --git a/onchange_helper/models/ir_rule.py b/onchange_helper/models/ir_rule.py deleted file mode 100644 index 675cd6435..000000000 --- a/onchange_helper/models/ir_rule.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# © 2016-2017 Akretion (http://www.akretion.com) -# © 2016-2017 Camptocamp (http://www.camptocamp.com/) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import api, models - - -def get_new_values(model, record, on_change_result): - vals = on_change_result.get('value', {}) - new_values = {} - for fieldname, value in vals.iteritems(): - if fieldname not in record: - column = model._fields[fieldname] - if value and column.type == 'many2one': - value = value[0] # many2one are tuple (id, name) - new_values[fieldname] = value - return new_values - - -@api.model -def play_onchanges(self, values, onchange_fields): - onchange_specs = self._onchange_spec() - # we need all fields in the dict even the empty ones - # otherwise 'onchange()' will not apply changes to them - all_values = values.copy() - for field in self._fields: - if field not in all_values: - # If self is a record (play onchange on existing record) - # we take the value of the field - # If self is an empty record we will have an empty value - all_values[field] = self[field] - - # we work on a temporary record - new_record = self.new(all_values) - - new_values = {} - for field in onchange_fields: - onchange_values = new_record.onchange(all_values, - field, onchange_specs) - new_values.update(get_new_values(self, values, onchange_values)) - all_values.update(new_values) - - res = {f: v for f, v in all_values.iteritems() - if not self._fields[f].compute and (f in values or f in new_values)} - return res - - -class IrRule(models.Model): - _inherit = 'ir.rule' - - def _setup_complete(self): - if not hasattr(models.BaseModel, 'play_onchanges'): - setattr(models.BaseModel, 'play_onchanges', play_onchanges) - return super(IrRule, self)._setup_complete() diff --git a/onchange_helper/models/models.py b/onchange_helper/models/models.py new file mode 100644 index 000000000..f66a5716e --- /dev/null +++ b/onchange_helper/models/models.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# © 2016-2017 Akretion (http://www.akretion.com) +# © 2016-2017 Camptocamp (http://www.camptocamp.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class Base(models.AbstractModel): + _inherit = 'base' + + @api.model + def _get_new_values(self, record, on_change_result): + vals = on_change_result.get('value', {}) + new_values = {} + for fieldname, value in vals.iteritems(): + if fieldname not in record: + column = self._fields[fieldname] + if value and column.type == 'many2one': + value = value[0] # many2one are tuple (id, name) + new_values[fieldname] = value + return new_values + + def play_onchanges(self, values, onchange_fields): + onchange_specs = self._onchange_spec() + # we need all fields in the dict even the empty ones + # otherwise 'onchange()' will not apply changes to them + all_values = values.copy() + + # If self is a record (play onchange on existing record) + # we take the value of the field + # If self is an empty record we will have an empty value + if self: + self.ensure_one() + record_values = self.read()[0] + else: + record_values = {} + for field in self._fields: + if field not in all_values: + all_values[field] = record_values.get(field, False) + + new_values = {} + for field in onchange_fields: + onchange_values = self.onchange(all_values, field, onchange_specs) + new_values.update(self._get_new_values(values, onchange_values)) + all_values.update(new_values) + + return { + f: v for f, v in all_values.iteritems() + if not self._fields[f].compute + and (f in values or f in new_values)} diff --git a/onchange_helper/tests/__init__.py b/onchange_helper/tests/__init__.py new file mode 100644 index 000000000..27ca25d37 --- /dev/null +++ b/onchange_helper/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_onchange diff --git a/onchange_helper/tests/test_onchange.py b/onchange_helper/tests/test_onchange.py new file mode 100644 index 000000000..d52b6a57d --- /dev/null +++ b/onchange_helper/tests/test_onchange.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import openerp.tests.common as common + + +class TestOnchange(common.TransactionCase): + + def test_playing_onchange_on_model(self): + result = self.env['res.partner'].play_onchanges({ + 'company_type': 'company', + }, ['company_type']) + self.assertEqual(result['is_company'], True) + + def test_playing_onchange_on_record(self): + result = self.env.ref('base.main_company').play_onchanges({ + 'email': 'contact@akretion.com'}, + ['email']) + self.assertEqual( + result['rml_footer'], + u'Phone: +1 555 123 8069 | Email: contact@akretion.com | ' + u'Website: http://www.example.com') From 9ee929674ff31da03624c55e479d9afa8597542e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Tue, 12 Jun 2018 08:15:59 +0200 Subject: [PATCH 4/5] [IMP] use convert to write to pass clean/formated value, add test to check that record have been not modify --- onchange_helper/README.rst | 4 ++-- onchange_helper/models/models.py | 2 +- onchange_helper/tests/test_onchange.py | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/onchange_helper/README.rst b/onchange_helper/README.rst index 5f75d895d..f7753dcf3 100644 --- a/onchange_helper/README.rst +++ b/onchange_helper/README.rst @@ -18,7 +18,7 @@ To use this module, you need to: Example if you want to create a sale order and you want to get the values relative to partner_id field (as if you fill the field from UI) - `vals = {'partner_id: 1'}` + `vals = {'partner_id': 1}` `vals = self.env['sale.order'].play_onchanges(vals, ['partner_id'])` @@ -26,7 +26,7 @@ Then, `vals` will be updated with partner_invoice_id, partner_shipping_id, price You can also use it on existing record for example: - `vals = {'partner_shipping_id: 1'}` + `vals = {'partner_shipping_id': 1}` `vals = sale.play_onchanges(vals, ['partner_shipping_id'])` diff --git a/onchange_helper/models/models.py b/onchange_helper/models/models.py index f66a5716e..10ccee084 100644 --- a/onchange_helper/models/models.py +++ b/onchange_helper/models/models.py @@ -32,7 +32,7 @@ class Base(models.AbstractModel): # If self is an empty record we will have an empty value if self: self.ensure_one() - record_values = self.read()[0] + record_values = self._convert_to_write(self.read()[0]) else: record_values = {} for field in self._fields: diff --git a/onchange_helper/tests/test_onchange.py b/onchange_helper/tests/test_onchange.py index d52b6a57d..22e155294 100644 --- a/onchange_helper/tests/test_onchange.py +++ b/onchange_helper/tests/test_onchange.py @@ -15,10 +15,12 @@ class TestOnchange(common.TransactionCase): self.assertEqual(result['is_company'], True) def test_playing_onchange_on_record(self): - result = self.env.ref('base.main_company').play_onchanges({ + company = self.env.ref('base.main_company') + result = company.play_onchanges({ 'email': 'contact@akretion.com'}, ['email']) self.assertEqual( result['rml_footer'], u'Phone: +1 555 123 8069 | Email: contact@akretion.com | ' u'Website: http://www.example.com') + self.assertEqual(company.email, u'info@yourcompany.example.com') From d2449dbdd09154ca0c8b3e3427b6dff5683b7343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Tue, 12 Jun 2018 11:53:31 +0200 Subject: [PATCH 5/5] [IMP] update readme --- onchange_helper/README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/onchange_helper/README.rst b/onchange_helper/README.rst index f7753dcf3..c30d33249 100644 --- a/onchange_helper/README.rst +++ b/onchange_helper/README.rst @@ -30,9 +30,11 @@ You can also use it on existing record for example: `vals = sale.play_onchanges(vals, ['partner_shipping_id'])` -Then the onchange will be played with the vals passed and the existing vals of the sale. `vals` will be updated with partner_invoice_id, pricelist_id, etc... +Then the onchange will be played with the vals passed and the existing vals of the sale. `vals` will be updated with partner_invoice_id, pricelist_id, etc.. +Behind the scene, `play_onchanges` will execute **all the methods** registered for the list of changed fields, so you do not have to call manually each onchange. To avoid performance issue when the method is called on a record, the record will be transformed into a memory record before calling the registered methods to avoid to trigger SQL updates command when values are assigned to the record by the onchange + Bug Tracker ===========