[FIX] web: handle 'domain' key returned by onchanges
Onchange RPCs return an object that may contain a 'domain' key. When it does, its value is an object whose keys are field names and values are the new domain for the corresponding field. Before this rev., the 'domain' key was totally ignored by the BasicModel. This feature is used for example in the account.payment form view (go to an open invoice, click on 'Register payment'): the domain of field payment_method_id is updated by the onchanges (e.g. when changing the journal_id). As the domain was ignored, it always displayed all possible payment methods.
This commit is contained in:
@@ -1785,15 +1785,7 @@ var FieldSelection = AbstractField.extend({
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.field.type === 'many2one') {
|
||||
this.values = this.record.specialData[this.name];
|
||||
this.formatType = 'many2one';
|
||||
} else {
|
||||
this.values = _.reject(this.field.selection, function (v) {
|
||||
return v[0] === false && v[1] === '';
|
||||
});
|
||||
}
|
||||
this.values = [[false, this.attrs.placeholder || '']].concat(this.values);
|
||||
this._setValues();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -1823,13 +1815,12 @@ var FieldSelection = AbstractField.extend({
|
||||
* @private
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
if (!this.$el.children().length) {
|
||||
for (var i = 0 ; i < this.values.length ; i++) {
|
||||
this.$el.append($('<option/>', {
|
||||
value: JSON.stringify(this.values[i][0]),
|
||||
html: this.values[i][1]
|
||||
}));
|
||||
}
|
||||
this.$el.empty();
|
||||
for (var i = 0 ; i < this.values.length ; i++) {
|
||||
this.$el.append($('<option/>', {
|
||||
value: JSON.stringify(this.values[i][0]),
|
||||
html: this.values[i][1]
|
||||
}));
|
||||
}
|
||||
var value = this.value;
|
||||
if (this.field.type === 'many2one' && value) {
|
||||
@@ -1844,6 +1835,31 @@ var FieldSelection = AbstractField.extend({
|
||||
_renderReadonly: function () {
|
||||
this.$el.empty().text(this._formatValue(this.value));
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_reset: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this._setValues();
|
||||
},
|
||||
/**
|
||||
* Sets the possible field values. If the field is a many2one, those values
|
||||
* may change during the lifecycle of the widget if the domain change (an
|
||||
* onchange may change the domain).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setValues: function () {
|
||||
if (this.field.type === 'many2one') {
|
||||
this.values = this.record.specialData[this.name];
|
||||
this.formatType = 'many2one';
|
||||
} else {
|
||||
this.values = _.reject(this.field.selection, function (v) {
|
||||
return v[0] === false && v[1] === '';
|
||||
});
|
||||
}
|
||||
this.values = [[false, this.attrs.placeholder || '']].concat(this.values);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
@@ -1869,7 +1885,9 @@ var FieldSelection = AbstractField.extend({
|
||||
});
|
||||
|
||||
var FieldRadio = FieldSelection.extend({
|
||||
template: 'FieldRadio',
|
||||
template: null,
|
||||
className: 'o_field_radio',
|
||||
tagName: 'span',
|
||||
specialData: "_fetchSpecialMany2ones",
|
||||
supportedFieldTypes: ['selection', 'many2one'],
|
||||
events: _.extend({}, AbstractField.prototype.events, {
|
||||
@@ -1880,14 +1898,12 @@ var FieldRadio = FieldSelection.extend({
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.field.type === 'selection') {
|
||||
this.values = this.field.selection || [];
|
||||
} else if (this.field.type === 'many2one') {
|
||||
this.values = _.map(this.record.specialData[this.name], function (val) {
|
||||
return [val.id, val.display_name];
|
||||
});
|
||||
if (this.mode === 'edit') {
|
||||
this.tagName = 'div';
|
||||
this.className += this.nodeOptions.horizontal ? 'o_horizontal' : 'o_vertical';
|
||||
}
|
||||
this.unique_id = _.uniqueId("radio");
|
||||
this._setValues();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -1911,17 +1927,45 @@ var FieldRadio = FieldSelection.extend({
|
||||
* @override
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
var value;
|
||||
var self = this;
|
||||
var currentValue;
|
||||
if (this.field.type === 'many2one') {
|
||||
value = this.value && this.value.data.id;
|
||||
currentValue = this.value && this.value.data.id;
|
||||
} else {
|
||||
value = this.value;
|
||||
currentValue = this.value;
|
||||
}
|
||||
var index = _.findIndex(this.values, function (option) {
|
||||
return option[0] === value;
|
||||
this.$el.empty();
|
||||
_.each(this.values, function (value, index) {
|
||||
self.$el.append(qweb.render('FieldRadio.button', {
|
||||
checked: value[0] === currentValue,
|
||||
id: self.unique_id + '_' + value[0],
|
||||
index: index,
|
||||
value: value,
|
||||
}));
|
||||
});
|
||||
this.$("input").prop("checked", false);
|
||||
this.$('input[data-index="' + index + '"]').prop('checked', true);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_reset: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this._setValues();
|
||||
},
|
||||
/**
|
||||
* Sets the possible field values. If the field is a many2one, those values
|
||||
* may change during the lifecycle of the widget if the domain change (an
|
||||
* onchange may change the domain).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setValues: function () {
|
||||
if (this.field.type === 'selection') {
|
||||
this.values = this.field.selection || [];
|
||||
} else if (this.field.type === 'many2one') {
|
||||
this.values = _.map(this.record.specialData[this.name], function (val) {
|
||||
return [val.id, val.display_name];
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -1791,7 +1791,9 @@ var BasicModel = AbstractModel.extend({
|
||||
var context = record.getContext({fieldName: fieldName});
|
||||
var domain = record.getDomain({fieldName: fieldName});
|
||||
if (domain.length) {
|
||||
var localID = record._changes && record._changes[fieldName] || record.data[fieldName];
|
||||
var localID = (record._changes && fieldName in record._changes) ?
|
||||
record._changes[fieldName] :
|
||||
record.data[fieldName];
|
||||
if (localID) {
|
||||
var element = this.localData[localID];
|
||||
domain = ["|", ["id", "=", element.data.id]].concat(domain);
|
||||
@@ -2994,6 +2996,12 @@ var BasicModel = AbstractModel.extend({
|
||||
});
|
||||
record._warning = true;
|
||||
}
|
||||
if (result.domain) {
|
||||
var fieldsInfo = record.fieldsInfo[viewType || record.viewType];
|
||||
for (var fieldName in result.domain) {
|
||||
fieldsInfo[fieldName].domain = result.domain[fieldName];
|
||||
}
|
||||
}
|
||||
return self._applyOnChange(result.value, record).then(function () {
|
||||
return result;
|
||||
});
|
||||
|
||||
@@ -724,15 +724,11 @@
|
||||
t-att-autofocus="widget.attrs.autofocus"
|
||||
t-att-id="widget.idForLabel"/>
|
||||
</t>
|
||||
<t t-name="FieldRadio">
|
||||
<span t-if="widget.mode === 'readonly'"/>
|
||||
|
||||
<div t-if="widget.mode !== 'readonly'" t-attf-class="o_field_radio #{widget.nodeOptions.horizontal ? 'o_horizontal' : 'o_vertical'}">
|
||||
<div t-foreach="widget.values" t-as="value" class="o_radio_item">
|
||||
<input type="radio" class="o_radio_input" t-att-id="widget.unique_id + '_' + value[0]"
|
||||
t-att-name="widget.unique_id" t-att-data-value="value[0]" t-att-data-index="value_index"/>
|
||||
<label t-if="!widget.nodeOptions.no_radiolabel" class="o_form_label" t-att-for="widget.unique_id + '_' + value[0]"><t t-esc="value[1]"/></label>
|
||||
</div>
|
||||
<t t-name="FieldRadio.button">
|
||||
<div class="o_radio_item">
|
||||
<input type="radio" class="o_radio_input" t-att-checked="checked ? true : undefined"
|
||||
t-att-data-value="value[0]" t-att-data-index="index" t-att-id="id"/>
|
||||
<label class="o_form_label" t-att-for="id"><t t-esc="value[1]"/></label>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="FieldMany2One">
|
||||
|
||||
@@ -1250,6 +1250,55 @@ QUnit.module('relational_fields', {
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('many2one: domain updated by an onchange', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
int_field: function () {},
|
||||
};
|
||||
|
||||
var domain = [];
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="int_field"/>' +
|
||||
'<field name="trululu"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'onchange') {
|
||||
domain = [['id', 'in', [10]]];
|
||||
return $.when({
|
||||
domain: {
|
||||
trululu: domain,
|
||||
}
|
||||
});
|
||||
}
|
||||
if (args.method === 'name_search') {
|
||||
assert.deepEqual(args.kwargs.args, domain,
|
||||
"sent domain should be correct");
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
});
|
||||
|
||||
// trigger a name_search (domain should be [])
|
||||
form.$('.o_field_widget[name=trululu] input').click();
|
||||
// close the dropdown
|
||||
form.$('.o_field_widget[name=trululu] input').click();
|
||||
// trigger an onchange that will update the domain
|
||||
form.$('.o_field_widget[name=int_field]').val(2).trigger('input');
|
||||
// trigger a name_search (domain should be [['id', 'in', [10]]])
|
||||
form.$('.o_field_widget[name=trululu] input').click();
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('FieldOne2Many');
|
||||
|
||||
QUnit.test('one2many basic properties', function (assert) {
|
||||
@@ -5884,6 +5933,55 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('widget selection on a many2one: domain updated by an onchange', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
int_field: function () {},
|
||||
};
|
||||
|
||||
var domain = [];
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="int_field"/>' +
|
||||
'<field name="trululu" widget="selection"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'onchange') {
|
||||
domain = [['id', 'in', [10]]];
|
||||
return $.when({
|
||||
domain: {
|
||||
trululu: domain,
|
||||
}
|
||||
});
|
||||
}
|
||||
if (args.method === 'name_search') {
|
||||
assert.deepEqual(args.args[1], domain,
|
||||
"sent domain should be correct");
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=trululu] option').length, 4,
|
||||
"should be 4 options in the selection");
|
||||
|
||||
// trigger an onchange that will update the domain
|
||||
form.$('.o_field_widget[name=int_field]').val(2).trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=trululu] option').length, 1,
|
||||
"should be 1 option in the selection");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('FieldMany2ManyTags');
|
||||
|
||||
QUnit.test('fieldmany2many tags: rendering and edition', function (assert) {
|
||||
@@ -6298,6 +6396,58 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('widget radio on a many2one: domain updated by an onchange', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
int_field: function () {},
|
||||
};
|
||||
|
||||
var domain = [];
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="int_field"/>' +
|
||||
'<field name="trululu" widget="radio"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'onchange') {
|
||||
domain = [['id', 'in', [10]]];
|
||||
return $.when({
|
||||
value: {
|
||||
trululu: false,
|
||||
},
|
||||
domain: {
|
||||
trululu: domain,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (args.method === 'search_read') {
|
||||
assert.deepEqual(args.kwargs.domain, domain,
|
||||
"sent domain should be correct");
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=trululu] .o_radio_item').length, 3,
|
||||
"should be 3 radio buttons");
|
||||
|
||||
// trigger an onchange that will update the domain
|
||||
form.$('.o_field_widget[name=int_field]').val(2).trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=trululu] .o_radio_item').length, 0,
|
||||
"should be no more radio button");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('FieldMany2ManyCheckBoxes');
|
||||
|
||||
QUnit.test('widget many2many_checkboxes', function (assert) {
|
||||
|
||||
Reference in New Issue
Block a user