diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-03-14 14:13:47 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-03-14 14:13:47 +0100 |
| commit | 9a4e84ae6670a6ce7086174a3f33f2eee8ded0a3 (patch) | |
| tree | 6a89fa5e6afe9120abda03cfbd32e27cf6cf4768 /addons/web/static | |
| parent | 5625257991d8997c453aee26b5f05ef85925824e (diff) | |
| parent | f5ab04ce5098bb3c3e88ca18ba6b533e6139f8e5 (diff) | |
[MERGE] forward port branch saas-11.3 up to f5ab04ce509
Diffstat (limited to 'addons/web/static')
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_model.js | 6 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/field_manager_mixin.js | 15 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form/form_controller.js | 13 | ||||
| -rw-r--r-- | addons/web/static/src/scss/form_view.scss | 3 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/relational_fields_tests.js | 37 | ||||
| -rw-r--r-- | addons/web/static/tests/views/form_tests.js | 104 |
6 files changed, 177 insertions, 1 deletions
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 b21c4b55eff..cb48e09fe98 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -3492,6 +3492,7 @@ var BasicModel = AbstractModel.extend({ var res_id, value; var res_ids = params.res_ids || []; var data = params.data || (type === 'record' ? {} : []); + var context = params.context; if (type === 'record') { res_id = params.res_id || (params.data && params.data.id); if (res_id) { @@ -3499,6 +3500,9 @@ var BasicModel = AbstractModel.extend({ } else { res_id = _.uniqueId('virtual_'); } + // it doesn't make sense for a record datapoint to have those keys + // besides, it will mess up x2m and actions down the line + context = _.omit(context, ['orderedBy', 'group_by']); } else { var isValueArray = params.value instanceof Array; res_id = isValueArray ? params.value[0] : undefined; @@ -3516,7 +3520,7 @@ var BasicModel = AbstractModel.extend({ _domains: {}, _rawChanges: {}, aggregateValues: params.aggregateValues || {}, - context: params.context, + context: context, count: params.count || res_ids.length, data: data, domain: params.domain || [], diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js index bc61e92a89a..ab441b4f75a 100644 --- a/addons/web/static/src/js/views/field_manager_mixin.js +++ b/addons/web/static/src/js/views/field_manager_mixin.js @@ -52,6 +52,8 @@ var FieldManagerMixin = { if (event.data.force_save) { return self.model.save(dataPointID).then(function () { return self._confirmSave(dataPointID); + }).fail(function () { + return self._rejectSave(dataPointID); }); } else if (options.notifyChange !== false) { return self._confirmChange(dataPointID, result, event); @@ -84,6 +86,19 @@ var FieldManagerMixin = { _confirmSave: function (id) { return $.when(); }, + /** + * This method will be called whenever a save has been triggered by a change + * and has failed. For example, when a statusbar button is clicked in a + * readonly form view. + * + * @abstract + * @private + * @param {string} id The basicModel ID for the saved record + * @returns {Deferred} + */ + _rejectSave: function (id) { + return $.when(); + }, //-------------------------------------------------------------------------- // Handlers diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 45b68ce6bea..434e2095a8e 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -368,6 +368,19 @@ var FormController = BasicController.extend({ this._super(state); }, /** + * Overrides to reload the form when saving failed in readonly (e.g. after + * a change on a widget like priority or statusbar). + * + * @override + * @private + */ + _rejectSave: function () { + if (this.mode === 'readonly') { + return this.reload(); + } + return this._super.apply(this, arguments); + }, + /** * Calls unfreezeOrder when changing the mode. * Also, when there is a change of mode, the tracking of last activated * field is reset, so that the following field activation process starts diff --git a/addons/web/static/src/scss/form_view.scss b/addons/web/static/src/scss/form_view.scss index 812e6cbd6cc..99430bd0eed 100644 --- a/addons/web/static/src/scss/form_view.scss +++ b/addons/web/static/src/scss/form_view.scss @@ -570,6 +570,9 @@ &.o_form_readonly .o_field_widget .o_list_view .o_row_handle { display: none; // Hide the handler in non-edit mode } + .o_field_widget.o_readonly_modifier .o_list_view .o_row_handle { + display: none; // Hide the handler on readonly fields + } // Attachments .oe_attachment { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 640e02701c2..bae181fa7a6 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -3596,6 +3596,43 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('embedded readonly one2many with handle widget', function (assert) { + assert.expect(4); + + this.data.partner.records[0].turtles = [1, 2, 3]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'<form string="Partners">' + + '<sheet>' + + '<field name="turtles" readonly="1">' + + '<tree editable="top">' + + '<field name="turtle_int" widget="handle"/>' + + '<field name="turtle_foo"/>' + + '</tree>' + + '</field>' + + '</sheet>' + + '</form>', + res_id: 1, + }); + + assert.strictEqual(form.$('.o_row_handle').length, 3, + "there should be 3 handles (one for each row)"); + assert.strictEqual(form.$('.o_row_handle:visible').length, 0, + "the handles should be hidden in readonly mode"); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_row_handle').length, 3, + "the handles should still be there"); + assert.strictEqual(form.$('.o_row_handle:visible').length, 0, + "the handles should still be hidden (on readonly fields)"); + + form.destroy(); + }); + QUnit.test('onchange for embedded one2many in a one2many with a second page', function (assert) { assert.expect(1); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index ebb579edd00..bd3b775aa16 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -6215,6 +6215,66 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('form view is not broken if save failed in readonly mode on field changed', function (assert) { + assert.expect(10); + + var failFlag = false; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<header><field name="trululu" widget="statusbar" clickable="True"/></header>' + + '</form>', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.step('write'); + if (failFlag) { + return $.Deferred().reject(); + } + } else if (args.method === 'read') { + assert.step('read'); + } + return this._super.apply(this, arguments); + }, + }); + + var $selectedState = form.$('.o_statusbar_status button[data-value="4"]'); + assert.ok($selectedState.hasClass('btn-primary') && $selectedState.hasClass('disabled'), + "selected status should be btn-primary and disabled"); + + failFlag = true; + var $clickableState = form.$('.o_statusbar_status button[data-value="1"]'); + $clickableState.click(); + + var $lastActiveState = form.$('.o_statusbar_status button[data-value="4"]'); + $selectedState = form.$('.o_statusbar_status button.btn-primary'); + assert.strictEqual($selectedState[0], $lastActiveState[0], + "selected status is AAA record after save fail"); + + failFlag = false; + $clickableState = form.$('.o_statusbar_status button[data-value="1"]'); + $clickableState.click(); + + var $lastClickedState = form.$('.o_statusbar_status button[data-value="1"]'); + $selectedState = form.$('.o_statusbar_status button.btn-primary'); + assert.strictEqual($selectedState[0], $lastClickedState[0], + "last clicked status should be active"); + + assert.verifySteps([ + 'read', + 'write', // fails + 'read', // must reload when saving fails + 'write', // works + 'read', // must reload when saving works + 'read', // fixme: this read should not be necessary + ]); + + form.destroy(); + }); + QUnit.test('support password attribute', function (assert) { assert.expect(3); @@ -7005,6 +7065,50 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('Form view from ordered, grouped list view correct context', function (assert) { + assert.expect(10); + this.data.partner.records[0].timmy = [12]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<field name="foo"/>' + + '<field name="timmy"/>' + + '</form>', + archs: { + 'partner_type,false,list': + '<list>' + + '<field name="name"/>' + + '</list>', + }, + viewOptions: { + // Simulates coming from a list view with a groupby and filter + context: { + orderedBy: [{name: 'foo', asc:true}], + group_by: ['foo'], + } + }, + res_id: 1, + mockRPC: function (route, args) { + assert.step(args.model + ":" + args.method); + if (args.method === 'read') { + assert.ok(args.kwargs.context, 'context is present'); + assert.notOk('orderedBy' in args.kwargs.context, + 'orderedBy not in context'); + assert.notOk('group_by' in args.kwargs.context, + 'group_by not in context'); + } + return this._super.apply(this, arguments); + } + }); + + assert.verifySteps(['partner_type:load_views', 'partner:read', 'partner_type:read']); + + form.destroy(); + }); + QUnit.test('edition in form view on a "noCache" model', function (assert) { assert.expect(4); |
