summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-03-14 14:13:47 +0100
committerChristophe Simonis <chs@odoo.com>2019-03-14 14:13:47 +0100
commit9a4e84ae6670a6ce7086174a3f33f2eee8ded0a3 (patch)
tree6a89fa5e6afe9120abda03cfbd32e27cf6cf4768 /addons/web/static
parent5625257991d8997c453aee26b5f05ef85925824e (diff)
parentf5ab04ce5098bb3c3e88ca18ba6b533e6139f8e5 (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.js6
-rw-r--r--addons/web/static/src/js/views/field_manager_mixin.js15
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js13
-rw-r--r--addons/web/static/src/scss/form_view.scss3
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js37
-rw-r--r--addons/web/static/tests/views/form_tests.js104
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);