diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index b80f4890233..58626c1066e 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -107,7 +107,7 @@ class AccountInvoice(models.Model): def _compute_residual(self): residual = 0.0 residual_company_signed = 0.0 - sign = self.type in ['in_invoice', 'out_refund'] and -1 or 1 + sign = self.type in ['in_refund', 'out_refund'] and -1 or 1 for line in self.sudo().move_id.line_ids: if line.account_id == self.account_id: residual_company_signed += line.amount_residual diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 646974d952e..dccf457a22a 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -198,7 +198,7 @@ class account_abstract_payment(models.AbstractModel): total = 0.0 groups = groupby(invoices, lambda i: i.currency_id) for payment_currency, payment_invoices in groups: - amount_total = sum(i.residual_signed for i in payment_invoices) + amount_total = sum([MAP_INVOICE_TYPE_PAYMENT_SIGN[i.type] * i.residual_signed for i in payment_invoices]) if payment_currency == currency: total += amount_total else: diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py index 67883604d18..67350534347 100644 --- a/addons/account/tests/test_payment.py +++ b/addons/account/tests/test_payment.py @@ -464,31 +464,6 @@ class TestPayment(AccountingTestCase): #check the invoice status self.assertEqual(invoice.state, 'paid') - def test_payment_and_writeoff_out_refund(self): - # Use case: - # Company is in EUR, create a credit note for 100 EUR and register payment of 90. - # Mark invoice as fully paid with a write_off - # Check that all the aml are correctly created. - invoice = self.create_invoice(amount=100, type='out_refund', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) - # register payment on invoice - payment = self.payment_model.create({'payment_type': 'outbound', - 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, - 'partner_type': 'customer', - 'partner_id': self.partner_agrolait.id, - 'amount': 90, - 'payment_date': time.strftime('%Y') + '-07-15', - 'payment_difference_handling': 'reconcile', - 'writeoff_account_id': self.account_payable.id, - 'journal_id': self.bank_journal_euro.id, - 'invoice_ids': [(4, invoice.id, None)] - }) - payment.post() - self.check_journal_items(payment.move_line_ids, [ - {'account_id': self.account_eur.id, 'debit': 0.0, 'credit': 90.0, 'amount_currency': 0.0, 'currency_id': False}, - {'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 10.0, 'amount_currency': 0.0, 'currency_id': False}, - {'account_id': self.account_receivable.id, 'debit': 100.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False}, - ]) - self.assertEqual(invoice.state, 'paid') def test_payment_and_writeoff_in_other_currency_3(self): # Use case related in revision 20935462a0cabeb45480ce70114ff2f4e91eaf79 @@ -537,4 +512,4 @@ class TestPayment(AccountingTestCase): # Check the invoice status and the full reconciliation: the difference on the receivable account # should have been completed by an exchange rate difference entry self.assertEqual(invoice.state, 'paid') - self.assertTrue(invoice.move_id.line_ids.filtered(lambda l: l.account_id == self.account_receivable)[0].full_reconcile_id) + self.assertTrue(invoice.move_id.line_ids.filtered(lambda l: l.account_id == self.account_receivable)[0].full_reconcile_id) \ No newline at end of file diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py index 8e3d9d4949d..90e9be09048 100644 --- a/addons/l10n_ch/models/res_bank.py +++ b/addons/l10n_ch/models/res_bank.py @@ -10,8 +10,12 @@ from odoo.tools.misc import mod10r def _is_l10n_ch_postal(account_ref): """ Returns True iff the string account_ref is a valid postal account number, i.e. it only contains ciphers and is last cipher is the result of a recursive - modulo 10 operation ran over the rest of it. + modulo 10 operation ran over the rest of it. Shorten form with - is also accepted. """ + if re.match('^[0-9]{2}-[0-9]{1,6}-[0-9]$', account_ref or ''): + ref_subparts = account_ref.split('-') + account_ref = ref_subparts[0] + ref_subparts[1].rjust(6,'0') + ref_subparts[2] + if re.match('\d+$', account_ref or ''): account_ref_without_check = account_ref[:-1] return mod10r(account_ref_without_check) == account_ref diff --git a/addons/l10n_ro/data/l10n_ro_chart_data.xml b/addons/l10n_ro/data/l10n_ro_chart_data.xml index c5cf25a68d5..0f7b3c646a8 100644 --- a/addons/l10n_ro/data/l10n_ro_chart_data.xml +++ b/addons/l10n_ro/data/l10n_ro_chart_data.xml @@ -1807,7 +1807,7 @@ - >Datorii din operaţiuni de fiducie + Datorii din operaţiuni de fiducie 4661 diff --git a/addons/l10n_vn/data/l10n_vn_chart_data.xml b/addons/l10n_vn/data/l10n_vn_chart_data.xml index 0748dd1531e..c643d8a38e5 100644 --- a/addons/l10n_vn/data/l10n_vn_chart_data.xml +++ b/addons/l10n_vn/data/l10n_vn_chart_data.xml @@ -81,8 +81,7 @@ - Thuế GTGT được khấu trừ của hàng hóa, dịch vụ - + Thuế GTGT được khấu trừ của hàng hóa, dịch vụ 1332 @@ -140,7 +139,7 @@ 1388 - + Phải thu khác @@ -149,7 +148,7 @@ 141 - + Tạm ứng @@ -322,8 +321,7 @@ - Cây lâu năm, súc vật làm việc và cho sản phẩm - + Cây lâu năm, súc vật làm việc và cho sản phẩm 2118 @@ -449,7 +447,7 @@ - >Đầu tư vào công ty liên doanh + Đầu tư vào công ty liên doanh @@ -459,7 +457,7 @@ - >Đầu tư góp vốn vào đơn vị khác + Đầu tư góp vốn vào đơn vị khác @@ -651,14 +649,14 @@ 3341 - + Phải trả công nhân viên 3348 - + Phải trả người lao động khác @@ -677,7 +675,7 @@ - Phải trả nội bộ về vốn kinh doanh + Phải trả nội bộ về vốn kinh doanh @@ -763,7 +761,7 @@ 3388 - + Phải trả, phải nộp khác @@ -790,7 +788,7 @@ - Trái phiếu + Trái phiếu 34311 diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js index aecf41b1d4f..70db9c29ed5 100644 --- a/addons/mail/static/src/js/services/chat_manager.js +++ b/addons/mail/static/src/js/services/chat_manager.js @@ -516,7 +516,9 @@ var ChatManager = AbstractService.extend({ if ('ids' in options) { // get messages from their ids (chatter is the main use case) return this._fetchDocumentMessages(options.ids, options).then(function (result) { - self.markAsRead(options.ids); + if (options.shouldMarkAsRead) { + self.markAsRead(options.ids); + } return result; }); } diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js index 6d036208eb2..bffa6817cf7 100644 --- a/addons/mail/static/src/js/thread_field.js +++ b/addons/mail/static/src/js/thread_field.js @@ -19,6 +19,7 @@ var ThreadField = AbstractField.extend({ init: function () { this._super.apply(this, arguments); this.msgIDs = this.value.res_ids; + this._markAsReadOnRender = true; // DO NOT FORWARD-PORT }, willStart: function () { return this.alive(this.call('chat_manager', 'isReady')); @@ -68,6 +69,7 @@ var ThreadField = AbstractField.extend({ this.msgIDs = this.value.res_ids; // the mail widgets being persistent, one need to update the res_id on reset this.res_id = record.res_id; + this._markAsReadOnRender = true; // DO NOT FORWARD-PORT }, //-------------------------------------------------------------------------- @@ -123,6 +125,10 @@ var ThreadField = AbstractField.extend({ var self = this; options = options || {}; options.ids = ids; + if (this._markAsReadOnRender) { + options.shouldMarkAsRead = true; + this._markAsReadOnRender = false; + } var fetch_def = this.dp.add(this.call('chat_manager', 'getMessages', options)); return fetch_def.then(function (raw_messages) { var isCreateMode = false; diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index c6575303342..59621521a0a 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -3606,11 +3606,12 @@ var BasicModel = AbstractModel.extend({ */ _postprocess: function (record, options) { var self = this; + var viewType = options && options.viewType || record.viewType; var defs = []; _.each(record.getFieldNames(options), function (name) { var field = record.fields[name]; - var fieldInfo = record.fieldsInfo[record.viewType][name] || {}; + var fieldInfo = record.fieldsInfo[viewType][name] || {}; var options = fieldInfo.options || {}; if (options.always_reload) { if (record.fields[name].type === 'many2one' && record.data[name]) { @@ -3619,7 +3620,7 @@ var BasicModel = AbstractModel.extend({ model: field.relation, method: 'name_get', args: [element.data.id], - context: self._getContext(record, {fieldName: name}), + context: self._getContext(record, {fieldName: name, viewType: viewType}), }) .then(function (result) { element.data.display_name = result[0][1]; diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 7ab9ba0547d..f49ac63514b 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -150,6 +150,14 @@ var BasicView = AbstractView.extend({ } } } + // Many2one: context is not the same between the different views + // this means the result of a name_get could differ + if (fieldType === 'many2one') { + if (JSON.stringify(record.data[name].context) !== + JSON.stringify(fieldInfo.context)) { + fieldNames.push(name); + } + } } }); diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index b577bf0a10f..6b574efde1a 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -268,7 +268,7 @@ var KanbanController = BasicController.extend({ _onButtonNew: function () { var state = this.model.get(this.handle, {raw: true}); var quickCreateEnabled = this.quickCreateEnabled && viewUtils.isQuickCreateEnabled(state); - if (this.on_create === 'quick_create' && quickCreateEnabled) { + if (this.on_create === 'quick_create' && quickCreateEnabled && state.data.length) { // Activate the quick create in the first column this.renderer.addQuickCreate(); } else if (this.on_create && this.on_create !== 'quick_create') { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 20008a92c21..e9f08195ec1 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -380,6 +380,93 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('show_address works in a view embedded in a view of another type', function (assert) { + assert.expect(1); + + this.data.turtle.records[1].turtle_trululu = 2; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + res_id: 1, + archs: { + "turtle,false,form": '
' + + '' + + '' + + '', + "turtle,false,list": '' + + '' + + '', + }, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + return this._super(route, args).then(function (result) { + if (args.model === 'partner' && args.kwargs.context.show_address) { + result[0][1] += '\nrue morgue\nparis 75013'; + } + return result; + }); + } + return this._super(route, args); + }, + }); + // click the turtle field, opens a modal with the turtle form view + form.$('.o_data_row:first td.o_data_cell').click(); + + assert.strictEqual($('[name="turtle_trululu"]').text(), "second recordrue morgueparis 75013", + "The partner's address should be displayed"); + form.destroy(); + }); + + QUnit.test('many2one data is reloaded if there is a context to take into account', function (assert) { + assert.expect(1); + + this.data.turtle.records[1].turtle_trululu = 2; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + res_id: 1, + archs: { + "turtle,false,form": '
' + + '' + + '' + + '', + "turtle,false,list": '' + + '' + + '' + + '', + }, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + return this._super(route, args).then(function (result) { + if (args.model === 'partner' && args.kwargs.context.show_address) { + result[0][1] += '\nrue morgue\nparis 75013'; + } + return result; + }); + } + return this._super(route, args); + }, + }); + // click the turtle field, opens a modal with the turtle form view + form.$('.o_data_row:first').click(); + + assert.strictEqual($('.modal [name=turtle_trululu]').text(), "second recordrue morgueparis 75013", + "The partner's address should be displayed"); + form.destroy(); + }); + QUnit.test('many2ones in form views with search more', function (assert) { assert.expect(3); this.data.partner.records.push({ diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index d153d63ea2b..00a1f29cedc 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -4210,6 +4210,30 @@ QUnit.module('Views', { kanban.destroy(); }); + QUnit.test('quick_create on grouped kanban without column', function (assert) { + assert.expect(1); + this.data.partner.records = []; + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '' + + '
' + + '' + + '
' + + '
', + groupBy: ['product_id'], + + intercepts: { + switch_view: function (event) { + assert.ok(true, "switch_view was called instead of quick_create"); + }, + }, + }); + kanban.$buttons.find('.o-kanban-button-new').click(); + kanban.destroy(); + }); + QUnit.test('keyboard navigation on kanban basic rendering', function (assert) { assert.expect(3); diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 0b7e73e84af..92185307bae 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -72,6 +72,11 @@ class Website(models.Model): homepage_id = fields.Many2one('website.page', string='Homepage') favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.") + @api.onchange('language_ids') + def _onchange_language_ids(self): + if self.language_ids and self.default_lang_id not in self.language_ids: + self.default_lang_id = self.language_ids[0] + @api.multi def _compute_menu(self): Menu = self.env['website.menu'] diff --git a/doc/webservices/iap.rst b/doc/webservices/iap.rst index 23467c267c3..003fe2cf07f 100644 --- a/doc/webservices/iap.rst +++ b/doc/webservices/iap.rst @@ -81,8 +81,11 @@ Overview The Credits +.. note:: The credits went from integer to float value starting **October 2018** + Integer values are still supported. + Every service provided through the In-App platform can be used by the - clients with tokens or *credits*. The credits are an integer unit and + clients with tokens or *credits*. The credits are an float unit and their monetary value depends on the service and is decided by the provider. This could be: @@ -157,7 +160,8 @@ The first step is to register your service on the IAP endpoint (production and/or test) before you can actually query user accounts. To create a service, go to your *Portal Account* on the IAP endpoint (https://iap.odoo.com for production, https://iap-sandbox.odoo.com for testing, the endpoints are -*independent* and *not synchronized*). +*independent* and *not synchronized*). Alternatively, you can go to your portal +on Odoo (https://iap.odoo.com/my/home) and select *In-App Services*. .. note:: @@ -166,33 +170,45 @@ production, https://iap-sandbox.odoo.com for testing, the endpoints are on sandbox to ease the tests. Log in then go to :menuselection:`My Account --> Your In-App Services`, click -Create and provide the name of your service. +Create and provide the informations of your service. -The now created service has *two* important fields: +The service has *four* important fields: * :samp:`name` - :class:`ServiceName`: this will identify your service in the - client's :ref:`app ` communicates directly with IAP. -* :samp:`key` - :class:`ServiceKey`: the developer key that identifies you in - IAP (see :ref:`your service `) and allows to draw credits from - the client's account. - + client's :ref:`app ` communicates directly with IAP, choose it + carefully! .. warning:: The :class:`ServiceName` is unique and should usually match the name of your - Odoo App. + Odoo Client App. +* :samp:`Icon` - :class:`Icon`: A generic icon that will serve as default for your + :ref:`packages ` + +* :samp:`key` - :class:`ServiceKey`: the developer key that identifies you in + IAP (see :ref:`your service `) and allows to draw credits from + the client's account. It will be shown only once upon creation of the service + and can be regenerated at will. .. danger:: Your :class:`ServiceKey` *is a secret*, leaking your service key allows other application developers to draw credits bought for your service(s). -.. image:: images/service_select.png +* :samp:`privacy policy` - :class:`PrivacyPolicy`: This is an url to the privacy + policy of your service. This should explicitly mention the **information you collect**, + How you **use it, its relevance** to make your service work and inform the + client on how they can **access, update or delete their personal information**. + +.. image:: images/menu.png :align: center -.. image:: images/service_create.png +.. image:: images/service_list.png :align: center -.. image:: images/service_packs.png +.. image:: images/creating_service.png + :align: center + +.. image:: images/service_created.png :align: center You can then create *credit packs* which clients can purchase in order to @@ -203,9 +219,10 @@ use your service. Packages -------- -The credit packages are essentially a product with 4 characteristics. +The credit packages are essentially a product with 5 characteristics. * Name: the name of the package, +* Icon: A specific icon for the package. If not provided, it will fallback on the service icon * Description: details on the package that will appear on the shop page as well as the invoice, * Credits: the amount of credits the client is entitled to when buying the package, @@ -362,7 +379,6 @@ parameters we can use to make things clearer to the end-user: .. patch:: - .. TODO:: how do you test your service? JSON-RPC2_ Transaction API @@ -394,13 +410,13 @@ Authorize :param ServiceKey key: :param UserToken account_token: - :param int credit: + :param float credit: :param str description: optional, helps users identify the reason for charges on their accounts. :returns: :class:`TransactionToken` if the authorization succeeded. :raises: :class:`~odoo.exceptions.AccessError` if the service token is invalid :raises: :class:`~odoo.addons.iap.models.iap.InsufficientCreditError` if the account does - :raises: ``TypeError`` if the ``credit`` value is not an integer + :raises: ``TypeError`` if the ``credit`` value is not an integer or a float .. code-block:: python @@ -434,7 +450,7 @@ Capture :param TransactionToken token: :param ServiceKey key: - :param int credit_to_capture: (new - 15 Jan 2018) optional parameter to capture a smaller amount of credits than authorized + :param float credit_to_capture: (new - 15 Jan 2018) optional parameter to capture a smaller amount of credits than authorized :raises: :class:`~odoo.exceptions.AccessError` .. code-block:: python @@ -534,6 +550,33 @@ care how they are implemented Raised by any unexpeted behaviour at the discretion of the App developer (*you*). + +Test the API +------------ + +In order to test the developped app, we propose a sandbox platform that allows you to: + +1. Test the whole flow from the client's point of view - Actual services and transactions + that can be consulted. (again this requires to change the endpoint, see the danger note + in :ref:`Service `) +2. Test the API. + +The latter consists in specific tokens that will work on **IAP-Sandbox only**. + +* token ``000000``: represents a non-existing account. Returns + an :class:`~odoo.addons.iap.models.iap.InsufficientCreditError` on authorize attempt. +* token ``000111``: Represents an account without sufficient credits to perform any service. + Returns an :class:`~odoo.addons.iap.models.iap.InsufficientCreditError` on authorize attempt. +* token ``111111``: Represents an account with enough credits to perform any service. + An authorize attempt will return a dummy transacion token that is processed by the capture + and cancel routes. + +.. note:: + + * Those tokens are only active on the IAP-Sanbox server. + * The service key is completely ignored with this flow, If you want to run a robust test + of your service, you should ignore these tokens. + Odoo Helpers ============ @@ -548,7 +591,7 @@ Charging .. note:: A new functionality was introduced to capture a different amount of credits than reserved. - As this patch was added on the 15th of January 2018, you will need to upgrade your ``iap`` module in order to use it. + As this patch was added on the **15th of January 2018**, you will need to upgrade your ``iap`` module in order to use it. The specifics of the new functionality are highlighted in the code. .. class:: odoo.addons.iap.models.iap.charge(env, key, account_token, credit[, description, credit_template]) @@ -567,12 +610,12 @@ Charging configuration key :param ServiceKey key: :param UserToken token: - :param int credit: + :param float credit: :param str description: :param Qweb template credit_template: .. code-block:: python - :emphasize-lines: 10,13,14,15 + :emphasize-lines: 11,13,14,15 @route('/deathstar/superlaser', type='json') def superlaser(self, user_account, @@ -583,7 +626,8 @@ Charging 0.0 is none, 1.0 is full power """ credits = int(MAXIMUM_POWER * factor) - with charge(request.env, SERVICE_KEY, user_account, credits) as transaction: + description = "We will demonstrate the power of this station on your home planet of Alderaan." + with charge(request.env, SERVICE_KEY, user_account, credits, description) as transaction: # TODO: allow other targets transaction.credit = max(credits, 2) # Sales ongoing one the energy price, diff --git a/doc/webservices/images/creating_service.png b/doc/webservices/images/creating_service.png new file mode 100644 index 00000000000..dec807ade45 Binary files /dev/null and b/doc/webservices/images/creating_service.png differ diff --git a/doc/webservices/images/menu.png b/doc/webservices/images/menu.png new file mode 100644 index 00000000000..50e1ed0267a Binary files /dev/null and b/doc/webservices/images/menu.png differ diff --git a/doc/webservices/images/package.png b/doc/webservices/images/package.png index a1627f9aecd..aca5959f7db 100644 Binary files a/doc/webservices/images/package.png and b/doc/webservices/images/package.png differ diff --git a/doc/webservices/images/service_create.png b/doc/webservices/images/service_create.png deleted file mode 100644 index 2163259ed84..00000000000 Binary files a/doc/webservices/images/service_create.png and /dev/null differ diff --git a/doc/webservices/images/service_created.png b/doc/webservices/images/service_created.png new file mode 100644 index 00000000000..1cb7a78c921 Binary files /dev/null and b/doc/webservices/images/service_created.png differ diff --git a/doc/webservices/images/service_list.png b/doc/webservices/images/service_list.png new file mode 100644 index 00000000000..d2b37f6255c Binary files /dev/null and b/doc/webservices/images/service_list.png differ diff --git a/doc/webservices/images/service_packs.png b/doc/webservices/images/service_packs.png deleted file mode 100644 index f86e97888be..00000000000 Binary files a/doc/webservices/images/service_packs.png and /dev/null differ diff --git a/doc/webservices/images/service_select.png b/doc/webservices/images/service_select.png deleted file mode 100644 index fb5878cd384..00000000000 Binary files a/doc/webservices/images/service_select.png and /dev/null differ diff --git a/odoo/addons/base/models/res_config.py b/odoo/addons/base/models/res_config.py index 6b84f4f17ae..36fbf6f21f8 100644 --- a/odoo/addons/base/models/res_config.py +++ b/odoo/addons/base/models/res_config.py @@ -384,8 +384,6 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) def copy(self, values): raise UserError(_("Cannot duplicate configuration!"), "") - # TODO: Find replacement for 'onchange' attribute in view with dynamic - # api.onchange(...) and migrate the onchange_module(...) accordingly. @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): @@ -407,9 +405,6 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) modifiers = json.loads(node.get("modifiers")) modifiers['readonly'] = True node.set("modifiers", json.dumps(modifiers)) - if 'on_change' not in node.attrib: - node.set("on_change", - "onchange_module(%s, '%s')" % (field, field)) ret_val['arch'] = etree.tostring(doc, encoding='unicode') return ret_val @@ -433,6 +428,16 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) } return {} + def _register_hook(self): + """ Add an onchange method for each module field. """ + def make_method(name): + return lambda self: self.onchange_module(self[name], name) + + for name in self._fields: + if name.startswith('module_'): + method = make_method(name) + self._onchange_methods[name].append(method) + @api.model def _get_classified_fields(self): """ return a dictionary with the fields classified by category:: diff --git a/requirements.txt b/requirements.txt index 530f569a83f..6035fb929e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ PyYAML==3.12 ; python_version < '3.7' PyYAML==3.13 ; python_version >= '3.7' qrcode==5.3 reportlab==3.3.0 -requests==2.11.1 +requests==2.20.0 suds-jurko==0.6 vatnumber==1.2 vobject==0.9.3 diff --git a/setup/win32/winpy_requirements.txt b/setup/win32/winpy_requirements.txt index 4a615ad1818..f998d871af2 100644 --- a/setup/win32/winpy_requirements.txt +++ b/setup/win32/winpy_requirements.txt @@ -28,7 +28,7 @@ pyusb==1.0.0 PyYAML==3.12 qrcode==5.3 reportlab>=3.3.0 -requests==2.11.1 +requests==2.20.0 six==1.10.0 suds-jurko==0.6 vatnumber==1.2