diff --git a/onchange_helper/README.rst b/onchange_helper/README.rst new file mode 100644 index 000000000..622fbe8dd --- /dev/null +++ b/onchange_helper/README.rst @@ -0,0 +1,63 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +=============== +Onchange Helper +=============== + +This is a technical module. Its goal is to ease the play of onchange method directly called from Python code. + +Usage +===== + +To use this module, you need to: + +* depend on this module +* call `yourmodel.play_onchanges(values, ['field'])` + +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 = self.env['sale.order'].play_onchanges(vals, ['partner_id'])` + +Then, `vals` will be updated with partner_invoice_id, partner_shipping_id, pricelist_id, etc... + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Guewen Baconnier +* Florian da Costa +* Andrea Stirpe + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/onchange_helper/__init__.py b/onchange_helper/__init__.py new file mode 100644 index 000000000..31660d6a9 --- /dev/null +++ b/onchange_helper/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/onchange_helper/__manifest__.py b/onchange_helper/__manifest__.py new file mode 100644 index 000000000..386ae237e --- /dev/null +++ b/onchange_helper/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2016-2017 Akretion (http://www.akretion.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + 'name': 'Onchange Helper', + 'version': '12.0.1.0.0', + 'summary': 'Technical module that ease execution' + ' of onchange in Python code', + 'author': 'Akretion,Camptocamp,Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/server-tools', + 'license': 'AGPL-3', + 'category': 'Generic Modules', + 'depends': ['base'], + 'installable': True, +} diff --git a/onchange_helper/i18n/cs_CZ.po b/onchange_helper/i18n/cs_CZ.po new file mode 100644 index 000000000..cadc8d529 --- /dev/null +++ b/onchange_helper/i18n/cs_CZ.po @@ -0,0 +1,25 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * onchange_helper +# +# Translators: +# Lukáš Spurný , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-03 10:08+0000\n" +"PO-Revision-Date: 2018-03-03 10:08+0000\n" +"Last-Translator: Lukáš Spurný , 2018\n" +"Language-Team: Czech (Czech Republic) (https://www.transifex.com/oca/" +"teams/23907/cs_CZ/)\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#. module: onchange_helper +#: model:ir.model,name:onchange_helper.model_base +msgid "base" +msgstr "základny" diff --git a/onchange_helper/i18n/de.po b/onchange_helper/i18n/de.po new file mode 100644 index 000000000..b68ea6efa --- /dev/null +++ b/onchange_helper/i18n/de.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * onchange_helper +# +# Translators: +# Niki Waibel , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-01 14:59+0000\n" +"PO-Revision-Date: 2017-06-01 14:59+0000\n" +"Last-Translator: Niki Waibel , 2017\n" +"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: onchange_helper +#: model:ir.model,name:onchange_helper.model_base +msgid "base" +msgstr "" + +#~ msgid "ir.rule" +#~ msgstr "ir.rule" diff --git a/onchange_helper/i18n/es.po b/onchange_helper/i18n/es.po new file mode 100644 index 000000000..36b629cc9 --- /dev/null +++ b/onchange_helper/i18n/es.po @@ -0,0 +1,24 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * onchange_helper +# +# Translators: +# enjolras , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-03 10:08+0000\n" +"PO-Revision-Date: 2018-03-03 10:08+0000\n" +"Last-Translator: enjolras , 2018\n" +"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: onchange_helper +#: model:ir.model,name:onchange_helper.model_base +msgid "base" +msgstr "base" diff --git a/onchange_helper/i18n/fr.po b/onchange_helper/i18n/fr.po new file mode 100644 index 000000000..56625a21d --- /dev/null +++ b/onchange_helper/i18n/fr.po @@ -0,0 +1,24 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * onchange_helper +# +# Translators: +# Quentin THEURET , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-03 10:08+0000\n" +"PO-Revision-Date: 2018-03-03 10:08+0000\n" +"Last-Translator: Quentin THEURET , 2018\n" +"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: onchange_helper +#: model:ir.model,name:onchange_helper.model_base +msgid "base" +msgstr "base" diff --git a/onchange_helper/i18n/hr.po b/onchange_helper/i18n/hr.po new file mode 100644 index 000000000..f05aade44 --- /dev/null +++ b/onchange_helper/i18n/hr.po @@ -0,0 +1,28 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * onchange_helper +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-01 14:59+0000\n" +"PO-Revision-Date: 2017-06-01 14:59+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Croatian (https://www.transifex.com/oca/teams/23907/hr/)\n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: onchange_helper +#: model:ir.model,name:onchange_helper.model_base +msgid "base" +msgstr "" + +#~ msgid "ir.rule" +#~ msgstr "ir.rule" diff --git a/onchange_helper/i18n/onchange_helper.pot b/onchange_helper/i18n/onchange_helper.pot new file mode 100644 index 000000000..720754925 --- /dev/null +++ b/onchange_helper/i18n/onchange_helper.pot @@ -0,0 +1,20 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * onchange_helper +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: onchange_helper +#: model:ir.model,name:onchange_helper.model_base +msgid "base" +msgstr "" + diff --git a/onchange_helper/i18n/sl.po b/onchange_helper/i18n/sl.po new file mode 100644 index 000000000..dafa549a7 --- /dev/null +++ b/onchange_helper/i18n/sl.po @@ -0,0 +1,28 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * onchange_helper +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-01 14:59+0000\n" +"PO-Revision-Date: 2017-06-01 14:59+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n" +"Language: sl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" +"%100==4 ? 2 : 3);\n" + +#. module: onchange_helper +#: model:ir.model,name:onchange_helper.model_base +msgid "base" +msgstr "" + +#~ msgid "ir.rule" +#~ msgstr "ir.rule" diff --git a/onchange_helper/models/__init__.py b/onchange_helper/models/__init__.py new file mode 100644 index 000000000..08f5a5618 --- /dev/null +++ b/onchange_helper/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import base diff --git a/onchange_helper/models/base.py b/onchange_helper/models/base.py new file mode 100644 index 000000000..4cc24ca2b --- /dev/null +++ b/onchange_helper/models/base.py @@ -0,0 +1,69 @@ +# Copyright 2016-2017 Akretion (http://www.akretion.com) +# Copyright 2016-2017 Camptocamp (http://www.camptocamp.com/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +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.items(): + 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 + + @api.model + def play_onchanges(self, values, onchange_fields): + """ + :param values: dict of input value that + :param onchange_fields: fields for which onchange methods will be + played + Order in onchange_fields is very important as onchanges methods will + be played in that order. + :return: changed values + """ + # _onchange_spec() will return onchange fields from the default view + # we need all fields in the dict even the empty ones + # otherwise 'onchange()' will not apply changes to them + onchange_specs = { + field_name: '1' for field_name, field in self._fields.items() + } + 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._convert_to_write( + { + field_name: self[field_name] + for field_name, field in self._fields.items() + } + ) + else: + # We get default values, they may be used in onchange + record_values = self.default_get(self._fields.keys()) + 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.items() + if not (self._fields[f].compute and not self._fields[f].inverse) + and (f in values or f in new_values or f in onchange_fields) + } diff --git a/onchange_helper/readme/CONTRIBUTORS.rst b/onchange_helper/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..47a0cfe24 --- /dev/null +++ b/onchange_helper/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Guewen Baconnier +* Florian da Costa +* Andrea Stirpe +* Souheil Bejaoui diff --git a/onchange_helper/readme/DESCRIPTION.rst b/onchange_helper/readme/DESCRIPTION.rst new file mode 100644 index 000000000..23c01a8ef --- /dev/null +++ b/onchange_helper/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This is a technical module. Its goal is to ease the play of onchange method directly called from Python code. diff --git a/onchange_helper/readme/USAGE.rst b/onchange_helper/readme/USAGE.rst new file mode 100644 index 000000000..52da81375 --- /dev/null +++ b/onchange_helper/readme/USAGE.rst @@ -0,0 +1,39 @@ +To use this module, you need to: + +* depend on this module +* call `yourmodel.play_onchanges(values, ['field'])` + +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 = self.env['sale.order'].play_onchanges(vals, ['partner_id'])` + +Then, `vals` will be updated with partner_invoice_id, partner_shipping_id, pricelist_id, etc... + +Default values will be used to process onchange methods, if respective fields are not set in `vals`. +You can get them if you pass fields name in the list of fields. + + + `vals = {'partner_id': 1}` + + `vals = self.env['sale.order'].play_onchanges(vals, ['partner_id', 'date_order'])` + +`vals` will contain, in addition to the changed values, the default value for `date_order` + + +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.. + +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 + + +Notes: + +- Order in onchange_fields is very important as onchanges methods will be played in that order. +- If you use memory object in `vals`, be award that onchange method in base model call `self.invalidate_cache()` that reset it. diff --git a/onchange_helper/tests/__init__.py b/onchange_helper/tests/__init__.py new file mode 100644 index 000000000..806c1a0d6 --- /dev/null +++ b/onchange_helper/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_onchange_helper diff --git a/onchange_helper/tests/test_onchange_helper.py b/onchange_helper/tests/test_onchange_helper.py new file mode 100644 index 000000000..9e746b6b1 --- /dev/null +++ b/onchange_helper/tests/test_onchange_helper.py @@ -0,0 +1,36 @@ +# Copyright 2017 Onestein () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestOnchangeHelper(TransactionCase): + def test01_partner_parent(self): + main_partner = self.env.ref('base.main_partner') + input_vals = dict(parent_id=main_partner.id, type='contact') + updated_vals = self.env['res.partner'].play_onchanges( + input_vals, ['parent_id'] + ) + self.assertIn('country_id', updated_vals) + self.assertIn('state_id', updated_vals) + self.assertIn('street', updated_vals) + self.assertIn('zip', updated_vals) + + self.assertEqual( + updated_vals['country_id'], main_partner.country_id.id + ) + self.assertEqual(updated_vals['state_id'], main_partner.state_id.id) + self.assertEqual(updated_vals['street'], main_partner.street) + self.assertEqual(updated_vals['zip'], main_partner.zip) + + def test02_partner_country(self): + partner_demo = self.env.ref('base.partner_demo') + input_vals = {'country_id': self.env.ref('base.us').id} + updated_vals = partner_demo.play_onchanges(input_vals, ['country_id']) + self.assertIn('country_id', updated_vals) + + 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) diff --git a/setup/onchange_helper/odoo/addons/onchange_helper b/setup/onchange_helper/odoo/addons/onchange_helper new file mode 120000 index 000000000..edc6c529e --- /dev/null +++ b/setup/onchange_helper/odoo/addons/onchange_helper @@ -0,0 +1 @@ +../../../../onchange_helper \ No newline at end of file diff --git a/setup/onchange_helper/setup.py b/setup/onchange_helper/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/onchange_helper/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)