summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-07-05 19:33:14 +0200
committerChristophe Simonis <chs@odoo.com>2018-07-05 19:33:14 +0200
commit8db9865c2d5bf09b735fa8d59bc11e511927619b (patch)
tree05da09c5735e8cfe74f36a15ef6f448f587007f2 /addons/web/static
parentceaa6562a585e4098c0981812e47d120d7c9f96e (diff)
parent2214bffefb26ce033ec145bce9edabacc342fe02 (diff)
[MERGE] forward port branch saas-11.3 up to 2214bffefb2
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js1
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js139
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js130
-rw-r--r--addons/web/static/tests/helpers/mock_server.js1
-rw-r--r--addons/web/static/tests/views/kanban_tests.js50
5 files changed, 256 insertions, 65 deletions
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index c86411b6cd9..deccd3d7709 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -1593,6 +1593,7 @@ var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
this.$('form.o_form_binary_form').submit();
this.$('.oe_fileupload').hide();
+ ev.target.value = "";
},
/**
* @private
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 9d509dd703c..4b4a4a86c16 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -825,7 +825,8 @@ var BasicModel = AbstractModel.extend({
return this.mutex.exec(this._applyChange.bind(this, record_id, changes, options));
},
/**
- * Reload all data for a given resource
+ * Reload all data for a given resource. At any time there is at most one
+ * reload operation active.
*
* @param {string} id local id for a resource
* @param {Object} [options]
@@ -834,64 +835,7 @@ var BasicModel = AbstractModel.extend({
* @returns {Deferred<string>} resolves to the id of the resource
*/
reload: function (id, options) {
- options = options || {};
- var element = this.localData[id];
-
- if (element.type === 'record') {
- if (!options.currentId && (('currentId' in options) || this.isNew(id))) {
- var params = {
- context: element.context,
- fieldsInfo: element.fieldsInfo,
- fields: element.fields,
- viewType: element.viewType,
- };
- return this._makeDefaultRecord(element.model, params);
- }
- if (!options.keepChanges) {
- this.discardChanges(id, {rollback: false});
- }
- } else if (element._changes) {
- delete element.tempLimitIncrement;
- _.each(element._changes, function (change) {
- delete change.isNew;
- });
- }
-
- if (options.context !== undefined) {
- element.context = options.context;
- }
- if (options.domain !== undefined) {
- element.domain = options.domain;
- }
- if (options.groupBy !== undefined) {
- element.groupedBy = options.groupBy;
- }
- if (options.limit !== undefined) {
- element.limit = options.limit;
- }
- if (options.offset !== undefined) {
- this._setOffset(element.id, options.offset);
- }
- if (options.loadMoreOffset !== undefined) {
- element.loadMoreOffset = options.loadMoreOffset;
- } else {
- // reset if not specified
- element.loadMoreOffset = 0;
- }
- if (options.currentId !== undefined) {
- element.res_id = options.currentId;
- }
- if (options.ids !== undefined) {
- element.res_ids = options.ids;
- element.count = element.res_ids.length;
- }
- if (element.type === 'record') {
- element.offset = _.indexOf(element.res_ids, element.res_id);
- }
- var loadOptions = _.pick(options, 'fieldNames', 'viewType');
- return this._load(element, loadOptions).then(function (result) {
- return result.id;
- });
+ return this.mutex.exec(this._reload.bind(this, id, options));
},
/**
* In some case, we may need to remove an element from a list, without going
@@ -2934,13 +2878,10 @@ var BasicModel = AbstractModel.extend({
continue;
}
changes = this._generateChanges(relRecord, options);
- if (changes.id) {
+ if (!this.isNew(relRecord.id)) {
// the subrecord already exists in db
+ commands[fieldName].push(x2ManyCommands.link_to(relRecord.res_id));
delete changes.id;
- if (this.isNew(record.id)) {
- // if the main record is new, link the subrecord to it
- commands[fieldName].push(x2ManyCommands.link_to(relRecord.res_id));
- }
if (!_.isEmpty(changes)) {
commands[fieldName].push(x2ManyCommands.update(relRecord.res_id, changes));
}
@@ -4046,6 +3987,76 @@ var BasicModel = AbstractModel.extend({
});
},
/**
+ * Reload all data for a given resource
+ *
+ * @private
+ * @param {string} id local id for a resource
+ * @param {Object} [options]
+ * @param {boolean} [options.keepChanges=false] if true, doesn't discard the
+ * changes on the record before reloading it
+ * @returns {Deferred<string>} resolves to the id of the resource
+ */
+ _reload: function (id, options) {
+ options = options || {};
+ var element = this.localData[id];
+
+ if (element.type === 'record') {
+ if (!options.currentId && (('currentId' in options) || this.isNew(id))) {
+ var params = {
+ context: element.context,
+ fieldsInfo: element.fieldsInfo,
+ fields: element.fields,
+ viewType: element.viewType,
+ };
+ return this._makeDefaultRecord(element.model, params);
+ }
+ if (!options.keepChanges) {
+ this.discardChanges(id, {rollback: false});
+ }
+ } else if (element._changes) {
+ delete element.tempLimitIncrement;
+ _.each(element._changes, function (change) {
+ delete change.isNew;
+ });
+ }
+
+ if (options.context !== undefined) {
+ element.context = options.context;
+ }
+ if (options.domain !== undefined) {
+ element.domain = options.domain;
+ }
+ if (options.groupBy !== undefined) {
+ element.groupedBy = options.groupBy;
+ }
+ if (options.limit !== undefined) {
+ element.limit = options.limit;
+ }
+ if (options.offset !== undefined) {
+ this._setOffset(element.id, options.offset);
+ }
+ if (options.loadMoreOffset !== undefined) {
+ element.loadMoreOffset = options.loadMoreOffset;
+ } else {
+ // reset if not specified
+ element.loadMoreOffset = 0;
+ }
+ if (options.currentId !== undefined) {
+ element.res_id = options.currentId;
+ }
+ if (options.ids !== undefined) {
+ element.res_ids = options.ids;
+ element.count = element.res_ids.length;
+ }
+ if (element.type === 'record') {
+ element.offset = _.indexOf(element.res_ids, element.res_id);
+ }
+ var loadOptions = _.pick(options, 'fieldNames', 'viewType');
+ return this._load(element, loadOptions).then(function (result) {
+ return result.id;
+ });
+ },
+ /**
* Allows to save a value in the specialData cache associated to a given
* record and fieldName. If the value in the cache was already the given
* one, nothing is done and the method indicates it by returning false
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 0cf7c43618e..b232a9bc4b9 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -3236,6 +3236,136 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('onchange for embedded one2many in a one2many with a second page', function (assert) {
+ assert.expect(1);
+
+ this.data.turtle.fields.partner_ids.type = 'one2many';
+ this.data.turtle.records[0].partner_ids = [1];
+ // we need a second page, so we set two records and only display one per page
+ this.data.partner.records[0].turtles = [1, 2];
+
+ this.data.partner.onchanges = {
+ turtles: function (obj) {
+ obj.turtles = [
+ [5],
+ [1, 1, {
+ turtle_foo: "hop",
+ partner_ids: [[5], [4, 1]],
+ }],
+ [1, 2, {
+ turtle_foo: "blip",
+ partner_ids: [[5], [4, 2], [4, 4]],
+ }],
+ ];
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="turtles">' +
+ '<tree editable="bottom" limit="1">' +
+ '<field name="turtle_foo"/>' +
+ '<field name="partner_ids" widget="many2many_tags"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ var expectedResultTurtles = [
+ [1, 1, {
+ turtle_foo: "hop",
+ }],
+ [1, 2, {
+ partner_ids: [[4, 2, false], [4, 4, false]],
+ turtle_foo: "blip",
+ }],
+ ];
+ assert.deepEqual(args.args[1].turtles, expectedResultTurtles,
+ "the right values should be written");
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_data_cell').eq(1).click();
+ var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]');
+ $cell.val("hop").trigger('change');
+ form.$buttons.find('.o_form_button_save').click();
+
+ form.destroy();
+ });
+
+ QUnit.test('onchange for embedded one2many in a one2many updated by server', function (assert) {
+ // here we test that after an onchange, the embedded one2many field has
+ // been updated by a new list of ids by the server response, to this new
+ // list should be correctly sent back at save time
+ assert.expect(3);
+
+ this.data.turtle.fields.partner_ids.type = 'one2many';
+ this.data.partner.records[0].turtles = [2];
+ this.data.turtle.records[1].partner_ids = [2];
+
+ this.data.partner.onchanges = {
+ turtles: function (obj) {
+ obj.turtles = [
+ [5],
+ [1, 2, {
+ turtle_foo: "hop",
+ partner_ids: [[5], [4, 2], [4, 4]],
+ }],
+ ];
+ },
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="turtles">' +
+ '<tree editable="bottom">' +
+ '<field name="turtle_foo"/>' +
+ '<field name="partner_ids" widget="many2many_tags"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/write') {
+ var expectedResultTurtles = [
+ [1, 2, {
+ partner_ids: [[4, 2, false], [4, 4, false]],
+ turtle_foo: "hop",
+ }],
+ ];
+ assert.deepEqual(args.args[1].turtles, expectedResultTurtles,
+ 'The right values should be written');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim(), "second record",
+ "the partner_ids should be as specified at initialization");
+
+ form.$buttons.find('.o_form_button_edit').click();
+ form.$('.o_data_cell').eq(1).click();
+ var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]');
+ $cell.val("hop").trigger("change");
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim().split(/\s+/),
+ [ "second", "record", "aaa" ],
+ 'The partner_ids should have been updated');
+
+ form.destroy();
+ });
+
QUnit.test('onchange for embedded one2many with handle widget', function (assert) {
assert.expect(2);
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 5afdc3dd447..5e82766b051 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -237,7 +237,6 @@ var MockServer = Class.extend({
if (attrs) {
attrs = pyeval.py_eval(attrs);
_.extend(modifiers, attrs);
- node.removeAttribute('attrs');
}
var states = node.getAttribute('states');
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index 04c1c603118..57dfd96e288 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -3633,6 +3633,56 @@ QUnit.module('Views', {
testUtils.unpatch(mixins.ParentedMixin);
});
+ QUnit.test('grouped kanban becomes ungrouped when clearing domain then clearing groupby', function (assert) {
+ // in this test, we simulate that clearing the domain is slow, so that
+ // clearing the groupby does not corrupt the data handled while
+ // reloading the kanban view.
+ assert.expect(4);
+
+ var def = $.Deferred();
+
+ var kanban = createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: '<kanban class="o_kanban_test">' +
+ '<field name="bar"/>' +
+ '<templates><t t-name="kanban-box">' +
+ '<div><field name="foo"/></div>' +
+ '</t></templates></kanban>',
+ domain: [['foo', '=', 'norecord']],
+ groupBy: ['bar'],
+ mockRPC: function (route, args) {
+ var result = this._super(route, args);
+ if (args.method === 'read_group') {
+ var isFirstUpdate = _.isEmpty(args.kwargs.domain) &&
+ args.kwargs.groupby &&
+ args.kwargs.groupby[0] === 'bar';
+ if (isFirstUpdate) {
+ return def.then(_.constant(result));
+ }
+ }
+ return result;
+ },
+ });
+
+ assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'),
+ "the kanban view should be grouped");
+ assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_ungrouped'),
+ "the kanban view should not be ungrouped");
+
+ kanban.update({domain: []}); // 1st update on kanban view
+ kanban.update({groupBy: false}); // 2n update on kanban view
+ def.resolve(); // simulate slow 1st update of kanban view
+
+ assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'),
+ "the kanban view should not longer be grouped");
+ assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_ungrouped'),
+ "the kanban view should have become ungrouped");
+
+ kanban.destroy();
+ });
+
QUnit.test('keyboard navigation on kanban basic rendering', function (assert) {
assert.expect(3);