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