summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-07-26 23:35:06 +0200
committerChristophe Simonis <chs@odoo.com>2017-07-26 23:35:06 +0200
commit8a1311bca17bd93a87f94579698adddc416f78f0 (patch)
treebac0ac044fd643927466dc5eb32d0359d9b37bde /addons/web/static
parentfaf72250221e0eb2fc711b9168c75b1dc70120e7 (diff)
parent3d4238f71e48de14afd0072a8309f2bb6fc46eb6 (diff)
[MERGE] forward port branch saas-17 up to 3d4238f71e4
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/chrome/search_menus.js8
-rw-r--r--addons/web/static/src/js/chrome/search_view.js2
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js34
-rw-r--r--addons/web/static/src/js/services/data_manager.js2
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js36
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js15
-rw-r--r--addons/web/static/src/less/form_view.less21
-rw-r--r--addons/web/static/src/xml/base.xml49
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js72
-rw-r--r--addons/web/static/tests/helpers/mock_server.js2
-rw-r--r--addons/web/static/tests/views/basic_model_tests.js14
-rw-r--r--addons/web/static/tests/views/form_tests.js65
12 files changed, 260 insertions, 60 deletions
diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js
index 1da7800b049..eee7303ffbd 100644
--- a/addons/web/static/src/js/chrome/search_menus.js
+++ b/addons/web/static/src/js/chrome/search_menus.js
@@ -122,6 +122,7 @@ return Widget.extend({
model_id: this.target_model,
context: results.context,
domain: results.domain,
+ sort: JSON.stringify(this.searchview.dataset._sort),
is_default: default_filter,
action_id: this.action_id,
};
@@ -243,6 +244,13 @@ return Widget.extend({
}
this.query.reset([this.facet_for(filter)], {
preventSearch: preventSearch || false});
+
+ // Load sort settings on view
+ if (!_.isUndefined(filter.sort)){
+ var sort_items = JSON.parse(filter.sort);
+ this.searchview.dataset.set_sort(sort_items);
+ }
+
this.$filters[this.key_for(filter)].addClass('selected');
},
remove_filter: function (filter, $filter, key) {
diff --git a/addons/web/static/src/js/chrome/search_view.js b/addons/web/static/src/js/chrome/search_view.js
index 26305817e55..6c3adcafcca 100644
--- a/addons/web/static/src/js/chrome/search_view.js
+++ b/addons/web/static/src/js/chrome/search_view.js
@@ -232,7 +232,7 @@ var SearchView = Widget.extend({
'click .o_searchview_more': function (e) {
$(e.target).toggleClass('fa-search-plus fa-search-minus');
var visibleSearchMenu = this.call('local_storage', 'getItem', 'visible_search_menu');
- this.call('local_storage', 'setItem', visibleSearchMenu !== 'true');
+ this.call('local_storage', 'setItem', 'visible_search_menu', visibleSearchMenu !== 'true');
this.toggle_buttons();
},
'keydown .o_searchview_input, .o_searchview_facet': function (e) {
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 7522bb3bc37..1935e5804dd 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -99,7 +99,7 @@ var FieldMany2One = AbstractField.extend({
this.nodeOptions = _.defaults(this.nodeOptions, {
quick_create: true,
});
- this.m2o_value = field_utils.format.many2one(this.value);
+ this.m2o_value = this._formatValue(this.value);
// 'recordParams' is a dict of params used when calling functions
// 'getDomain' and 'getContext' on this.record
this.recordParams = {fieldName: this.name, viewType: this.viewType};
@@ -838,8 +838,10 @@ var FieldX2Many = AbstractField.extend({
*/
_onDeleteRecord: function (ev) {
ev.stopPropagation();
+ var shouldForget = this.attrs.widget === 'many2many' || this.field.type === 'many2many';
+ var operation = shouldForget ? 'FORGET' : 'DELETE';
this._setValue({
- operation: 'REMOVE',
+ operation: operation,
ids: [ev.data.id],
});
},
@@ -1290,7 +1292,7 @@ var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
var record = _.findWhere(this.value.data, {res_id: fileID});
if (record) {
this._setValue({
- operation: 'REMOVE',
+ operation: 'FORGET',
ids: [record.id],
});
var metadata = this.metadata[record.id];
@@ -1469,7 +1471,7 @@ var FieldMany2ManyTags = AbstractField.extend({
_removeTag: function (id) {
var record = _.findWhere(this.value.data, {res_id: id});
this._setValue({
- operation: 'REMOVE',
+ operation: 'FORGET',
ids: [record.id],
});
},
@@ -2029,7 +2031,20 @@ var FieldReference = FieldMany2One.extend({
// needs to be copied as it is an unmutable object
this.field = _.extend({}, this.field);
if (this.value) {
- this._setRelation(this.value.model);
+ var model = null;
+ // The value of a Reference field can either be a string in the form
+ // of model,record_id or an object
+ if (typeof this.value === "string") {
+ var value = this.value.split(',');
+ model = value[0];
+ this.value = {
+ res_id: parseInt(value[1]),
+ };
+ } else {
+ model = this.value.model;
+ }
+
+ this._setRelation(model);
}
},
/**
@@ -2045,6 +2060,15 @@ var FieldReference = FieldMany2One.extend({
//--------------------------------------------------------------------------
/**
+ * Get the encompassing record's display_name
+ *
+ * @override
+ */
+ _formatValue: function () {
+ return this.recordData.display_name;
+ },
+
+ /**
* Add a select in edit mode (for the model).
*
* @override
diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js
index c1e69a72ce3..999a9d8df01 100644
--- a/addons/web/static/src/js/services/data_manager.js
+++ b/addons/web/static/src/js/services/data_manager.js
@@ -349,7 +349,7 @@ return core.Class.extend({
}
if (attrs.Widget.prototype.fieldsToFetch) {
attrs.viewType = 'default';
- attrs.relatedFields = attrs.Widget.prototype.fieldsToFetch;
+ attrs.relatedFields = _.extend({}, attrs.Widget.prototype.fieldsToFetch);
attrs.fieldsInfo = {
default: _.mapObject(attrs.Widget.prototype.fieldsToFetch, function () {
return {};
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 75a0ad02971..8f679207f00 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -509,6 +509,7 @@ var BasicModel = AbstractModel.extend({
}
if (params.type === 'record' && params.res_id === undefined) {
+ params.allowWarning = true;
return this._makeDefaultRecord(params.modelName, params);
}
var dataPoint = this._makeDataPoint(params);
@@ -1294,9 +1295,12 @@ var BasicModel = AbstractModel.extend({
defs.push(this._applyChange(command.id, command.data));
}
break;
- case 'REMOVE':
+ case 'FORGET':
+ // Unlink the record of list.
+ list._forceM2MUnlink = true;
+ case 'DELETE':
// filter out existing operations involving the current
- // dataPoint, and add a 'REMOVE' operation only if there is
+ // dataPoint, and add a 'DELETE' or 'FORGET' operation only if there is
// no 'ADD' operation for that dataPoint, as it would mean
// that the record wasn't in the relation yet
var idsToRemove = command.ids;
@@ -1308,7 +1312,8 @@ var BasicModel = AbstractModel.extend({
return idInCommands;
});
_.each(idsToRemove, function (id) {
- list._changes.push({operation: 'REMOVE', id: id});
+ var operation = list._forceM2MUnlink ? 'FORGET': 'DELETE';
+ list._changes.push({operation: operation, id: id});
});
break;
case 'REPLACE_WITH':
@@ -1332,7 +1337,7 @@ var BasicModel = AbstractModel.extend({
return self.localData[localId];
});
removedDef = this._applyX2ManyChange(record, fieldName, {
- operation: 'REMOVE',
+ operation: 'DELETE',
ids: _.map(removedIds, function (resID) {
return _.findWhere(listData, {res_id: resID}).id;
}),
@@ -1349,7 +1354,7 @@ var BasicModel = AbstractModel.extend({
},
/**
* In dataPoints of type list for x2manys, the changes are stored as a list
- * of operations (being of type 'ADD', 'REMOVE', 'UPDATE' or 'REMOVE_ALL').
+ * of operations (being of type 'ADD', 'DELETE', 'FORGET', UPDATE' or 'REMOVE_ALL').
* This function applies the operation of such a dataPoint without altering
* the original dataPoint. It returns a copy of the dataPoint in which the
* 'count', 'data' and 'res_ids' keys have been updated.
@@ -1383,7 +1388,8 @@ var BasicModel = AbstractModel.extend({
list.res_ids.push(resID);
}
break;
- case 'REMOVE':
+ case 'FORGET':
+ case 'DELETE':
list.count--;
list.res_ids = _.without(list.res_ids, relRecord.res_id);
break;
@@ -2348,7 +2354,11 @@ var BasicModel = AbstractModel.extend({
}
// add delete commands
for (i = 0; i < removedIds.length; i++) {
- commands[fieldName].push(x2ManyCommands.delete(removedIds[i]));
+ if (list._forceM2MUnlink) {
+ commands[fieldName].push(x2ManyCommands.forget(removedIds[i]));
+ } else {
+ commands[fieldName].push(x2ManyCommands.delete(removedIds[i]));
+ }
}
}
}
@@ -2601,7 +2611,9 @@ var BasicModel = AbstractModel.extend({
var isValid = true;
var element = this.localData[id];
_.each(element._changes, function (command) {
- if (command.operation === 'REMOVE' || command.operation === 'REMOVE_ALL') {
+ if (command.operation === 'DELETE' ||
+ command.operation === 'FORGET' ||
+ command.operation === 'REMOVE_ALL') {
return;
}
var recordData = self.get(command.id, {raw: true}).data;
@@ -2735,6 +2747,8 @@ var BasicModel = AbstractModel.extend({
* @private
* @param {any} params
* @param {string} modelName model name
+ * @param {boolean} [params.allowWarning=false] if true, the default record
+ * operation can complete, even if a warning is raised
* @param {Object} params.context the context for the new record
* @param {Object} params.fieldsInfo contains the fieldInfo of each view,
* for each field
@@ -2887,7 +2901,11 @@ var BasicModel = AbstractModel.extend({
.then(function () {
return self._performOnChange(record, fields_key).then(function () {
if (record._warning) {
- return $.Deferred().reject();
+ if (params.allowWarning) {
+ delete record._warning;
+ } else {
+ return $.Deferred().reject();
+ }
}
});
})
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index 08b3fabd2e2..35194b313ad 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -336,12 +336,27 @@ var FormRenderer = BasicRenderer.extend({
* @returns {jQueryElement}
*/
_renderHeaderButton: function (node) {
+ var self = this;
var $button = $('<button>')
.text(node.attrs.string)
.addClass('btn btn-sm btn-default');
this._addOnClickAction($button, node);
this._handleAttributes($button, node);
this._registerModifiers(node, this.state, $button);
+
+ // Display tooltip
+ if (config.debug || node.attrs.help) {
+ $button.tooltip({
+ delay: { show: 1000, hide: 0 },
+ title: function () {
+ return qweb.render('WidgetButton.tooltip', {
+ debug: config.debug,
+ state: self.state,
+ node: node,
+ });
+ },
+ });
+ }
return $button;
},
/**
diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less
index 3127b2e4ea7..bcea630b152 100644
--- a/addons/web/static/src/less/form_view.less
+++ b/addons/web/static/src/less/form_view.less
@@ -86,18 +86,19 @@
display: inline-block;
}
- .o_form_uri { // FIXME maybe global?
- display: inline-block;
+ }
- color: @odoo-main-text-color;
+ .o_form_uri {
+ display: inline-block;
+
+ color: @odoo-main-text-color;
+ &:first-line {
+ color: @link-color;
+ }
+ &:hover {
+ color: darken(@odoo-main-text-color, 1%); // hack required on chrome
&:first-line {
- color: @link-color;
- }
- &:hover {
- color: darken(@odoo-main-text-color, 1%); // hack required on chrome
- &:first-line {
- color: darken(@link-color, 15%);
- }
+ color: darken(@link-color, 15%);
}
}
}
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index b9aa125c8de..f0a08b5dc62 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -69,36 +69,49 @@
</li>
</ul>
</t>
-<t t-name="WidgetButton.tooltip" t-extend="WidgetLabel.tooltip">
- <t t-jquery="div.oe_tooltip_string" t-operation="replace">
- <div class="oe_tooltip_string" t-if="debug || widget.string">
- <t t-if="debug">
- Button
- <t t-if="widget.string">: </t>
- <t t-if="!widget.string"> (no string)</t>
- </t>
- <t t-esc="widget.string"/>
- </div>
- </t>
- <t t-jquery="ul.oe_tooltip_technical" t-operation="append">
- <li t-if="widget.node.attrs.special" data-item="special">
+<t t-name="WidgetButton.tooltip">
+ <div class="oe_tooltip_string" t-if="debug || node.attrs.string">
+ <t t-if="debug">
+ Button
+ <t t-if="node.attrs.string">: </t>
+ <t t-if="!node.attrs.string"> (no string)</t>
+ </t>
+ <t t-esc="node.attrs.string"/>
+ </div>
+ <p t-if="node.attrs.help" class="oe_tooltip_help">
+ <t t-esc="node.attrs.help"/>
+ </p>
+ <ul t-if="debug" class="oe_tooltip_technical">
+ <li data-item="object">
+ <span class="oe_tooltip_technical_title">Object:</span>
+ <t t-esc="state.model"/>
+ </li>
+ <li t-if="node.attrs.context" data-item="context">
+ <span class="oe_tooltip_technical_title">Context:</span>
+ <t t-esc="node.attrs.context || widget.field.context"/>
+ </li>
+ <li t-if="node.attrs.modifiers and node.attrs.modifiers != '{}'" data-item="modifiers">
+ <span class="oe_tooltip_technical_title">Modifiers:</span>
+ <t t-esc="node.attrs.modifiers"/>
+ </li>
+ <li t-if="node.attrs.special" data-item="special">
<span class="oe_tooltip_technical_title">Special:</span>
- <t t-esc="widget.node.attrs.special"/>
+ <t t-esc="node.attrs.special"/>
</li>
- <t t-set="button_type" t-value="widget.node.attrs.type"/>
+ <t t-set="button_type" t-value="node.attrs.type"/>
<li t-if="button_type" data-item="button_type">
<span class="oe_tooltip_technical_title">Button Type:</span>
<t t-esc="button_type"/>
</li>
<li t-if="button_type === 'object'" data-item="button_method">
<span class="oe_tooltip_technical_title">Method:</span>
- <t t-esc="widget.node.attrs.name"/>
+ <t t-esc="node.attrs.name"/>
</li>
<li t-if="button_type === 'action'" data-item="button_action">
<span class="oe_tooltip_technical_title">Action ID:</span>
- <t t-esc="widget.node.attrs.name"/>
+ <t t-esc="node.attrs.name"/>
</li>
- </t>
+ </ul>
</t>
<t t-name="Notification">
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 1a7badd353f..68a0257b24c 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -2005,6 +2005,49 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('one2many list: unlink one record', function (assert) {
+ assert.expect(5);
+ this.data.partner.records[0].p = [2, 4];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="p" widget="many2many">' +
+ '<tree>' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/write') {
+ var commands = args.args[1].p;
+ assert.strictEqual(commands.length, 2,
+ 'should have generated two commands');
+ assert.ok(commands[0][0] === 4 && commands[0][1] === 4,
+ 'should have generated the command 4 (LINK_TO) with id 4');
+ assert.ok(commands[1][0] === 3 && commands[1][1] === 2,
+ 'should have generated the command 3 (UNLINK) with id 2');
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('td.o_list_record_delete span').length, 2,
+ "should have 2 delete buttons");
+
+ form.$('td.o_list_record_delete span').first().click();
+
+ assert.strictEqual(form.$('td.o_list_record_delete span').length, 1,
+ "should have 1 delete button (a record is supposed to have been unlinked)");
+
+ // save and check that the correct command has been generated
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+
QUnit.test('one2many list: deleting one record', function (assert) {
assert.expect(5);
this.data.partner.records[0].p = [2, 4];
@@ -6156,32 +6199,44 @@ QUnit.module('relational_fields', {
QUnit.module('FieldMany2ManyTags');
- QUnit.test('fieldmany2many tags without color', function (assert) {
- assert.expect(4);
+ QUnit.test('fieldmany2many tags with and without color', function (assert) {
+ assert.expect(5);
+ this.data.partner.fields.partner_ids = {string: "Partner", type: "many2many", relation: 'partner'};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
+ '<field name="partner_ids" widget="many2many_tags" options="{\'color_field\': \'color\'}"/>' +
'<field name="timmy" widget="many2many_tags"/>' +
'</form>',
mockRPC: function (route, args) {
if (args.method ==='read' && args.model === 'partner_type') {
assert.deepEqual(args.args , [[12], ['display_name']], "should not read any color field");
+ } else if (args.method ==='read' && args.model === 'partner') {
+ assert.deepEqual(args.args , [[1], ['display_name', 'color']], "should read color field");
}
return this._super.apply(this, arguments);
}
});
- var $input = form.$('.o_field_many2manytags input');
+
+ // add a tag on field partner_ids
+ var $input = form.$('.o_field_many2manytags[name="partner_ids"] input');
+ $input.click(); // opens the dropdown
+ $input.autocomplete('widget').find('li:first()').click(); // adds a tag
+
+ // add a tag on field timmy
+ $input = form.$('.o_field_many2manytags[name="timmy"] input');
$input.click(); // opens the dropdown
assert.strictEqual($input.autocomplete('widget').find('li').length, 3,
"autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')");
$input.autocomplete('widget').find('li:first()').click(); // adds a tag
- assert.strictEqual(form.$('.o_field_many2manytags > span').length, 1,
+ assert.strictEqual(form.$('.o_field_many2manytags[name="timmy"] > span').length, 1,
"should contain 1 tag");
- assert.ok(form.$('.o_field_many2manytags > span:contains("gold")').length,
+ assert.ok(form.$('.o_field_many2manytags[name="timmy"] > span:contains("gold")').length,
"should contain newly added tag 'gold'");
+
form.destroy();
});
@@ -6909,7 +6964,7 @@ QUnit.module('relational_fields', {
"should do a do_action with correct parameters");
});
- assert.strictEqual(form.$('a.o_form_uri:contains(xphone)').length, 1,
+ assert.strictEqual(form.$('a.o_form_uri:contains(first record)').length, 1,
"should contain a link");
form.$('a.o_form_uri').click(); // click on the link in readonly mode (should trigger do_action)
@@ -6921,7 +6976,7 @@ QUnit.module('relational_fields', {
"should contain one many2one");
assert.strictEqual(form.$('.o_field_widget select').val(), "product",
"widget should contain one select with the model");
- assert.strictEqual(form.$('.o_field_widget input').val(), "xphone",
+ assert.strictEqual(form.$('.o_field_widget input').val(), "first record",
"widget should contain one input with the record");
var options = _.map(form.$('.o_field_widget select > option'), function (el) {
@@ -6930,7 +6985,6 @@ QUnit.module('relational_fields', {
assert.deepEqual(options, ['', 'product', 'partner_type', 'partner'],
"the options should be correctly set");
-
form.$('.o_external_button').click(); // click on the external button (should do an RPC)
assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: custom label',
@@ -6947,7 +7001,7 @@ QUnit.module('relational_fields', {
$dropdown.find('li:first()').click();
form.$buttons.find('.o_form_button_save').click();
- assert.strictEqual(form.$('a.o_form_uri:contains(gold)').length, 1,
+ assert.strictEqual(form.$('a.o_form_uri').length, 1,
"should contain a link with the new value");
form.destroy();
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 06b85dd8f9e..3f97df1d4bf 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -947,6 +947,8 @@ var MockServer = Class.extend({
self._mockWrite(field.relation, [[command[1]], command[2]]);
} else if (command[0] === 2) { // DELETE
ids = _.without(ids, command[1]);
+ } else if (command[0] === 3) { // FORGET
+ ids = _.without(ids, command[1]);
} else if (command[0] === 4) { // LINK_TO
if (!_.contains(ids, command[1])) {
ids.push(command[1]);
diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js
index 7f7547262da..9e8e64edde6 100644
--- a/addons/web/static/tests/views/basic_model_tests.js
+++ b/addons/web/static/tests/views/basic_model_tests.js
@@ -361,27 +361,27 @@ QUnit.module('Views', {
assert.strictEqual(record.data.product_ids.count, 2,
"there should be two records in the relation");
- // trigger a 'REMOVE' command on the existing record
+ // trigger a 'DELETE' command on the existing record
var existingRecordID = record.data.product_ids.data[0].id;
- model.notifyChanges(resultID, {product_ids: {operation: 'REMOVE', ids: [existingRecordID]}});
+ model.notifyChanges(resultID, {product_ids: {operation: 'DELETE', ids: [existingRecordID]}});
assert.deepEqual(model.localData[x2mListID]._changes, [{
operation: 'ADD', id: newRecordID,
}, {
operation: 'UPDATE', id: newRecordID,
}, {
- operation: 'REMOVE', id: existingRecordID,
+ operation: 'DELETE', id: existingRecordID,
}],
"_changes should be correct");
record = model.get(resultID);
assert.strictEqual(record.data.product_ids.count, 1,
"there should be one record in the relation");
- // trigger a 'REMOVE' command on the new record
- model.notifyChanges(resultID, {product_ids: {operation: 'REMOVE', ids: [newRecordID]}});
+ // trigger a 'DELETE' command on the new record
+ model.notifyChanges(resultID, {product_ids: {operation: 'DELETE', ids: [newRecordID]}});
assert.deepEqual(model.localData[x2mListID]._changes, [{
- operation: 'REMOVE', id: existingRecordID,
+ operation: 'DELETE', id: existingRecordID,
}], "_changes should be correct");
record = model.get(resultID);
assert.strictEqual(record.data.product_ids.count, 0,
@@ -827,7 +827,7 @@ QUnit.module('Views', {
assert.ok(_.isObject(category_value), "category field should have been fetched");
assert.strictEqual(category_value.data.length, 1, "category field should contain one record");
model.notifyChanges(resultID, {category: {
- operation: 'REMOVE',
+ operation: 'DELETE',
ids: [category_value.data[0].id],
}});
assert.ok(model.isDirty(resultID), "record should be considered dirty");
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index 8a718ce01ec..4b499fe1c6f 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -2,6 +2,7 @@ odoo.define('web.form_tests', function (require) {
"use strict";
var concurrency = require('web.concurrency');
+var config = require('web.config');
var core = require('web.core');
var FormView = require('web.FormView');
var pyeval = require('web.pyeval');
@@ -2561,6 +2562,42 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('can create record even if onchange returns a warning', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.onchanges = { foo: true };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group><field name="foo"/><field name="int_field"/></group>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (args.method === 'onchange') {
+ return $.when({
+ value: { int_field: 10 },
+ warning: {
+ title: "Warning",
+ message: "You must first select a partner"
+ }
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ intercepts: {
+ warning: function (event) {
+ assert.step('warning');
+ },
+ },
+ });
+ assert.strictEqual(form.$('input[name="int_field"]').val(), "10",
+ "record should have been created and rendered");
+
+ form.destroy();
+ });
+
QUnit.test('do nothing if add a line in one2many result in a onchange with a warning', function (assert) {
assert.expect(2);
@@ -5389,6 +5426,34 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('display tooltips for buttons', function (assert) {
+ assert.expect(1);
+
+ var initialDebugMode = config.debug;
+ config.debug = true;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<header>' +
+ '<button name="some_method" class="oe_highlight" string="Button" type="object"/>' +
+ '</header>' +
+ '</form>',
+ });
+
+ var $button = form.$('.o_form_statusbar button');
+ $button.tooltip('show', false);
+ $button.trigger($.Event('mouseenter'));
+
+ assert.strictEqual($('.tooltip .oe_tooltip_string').length, 1,
+ "should have rendered a tooltip");
+
+ config.debug = initialDebugMode;
+ form.destroy();
+ });
+
QUnit.test('rainbowman attributes correctly passed on button click', function (assert) {
assert.expect(1);