[FIX] web: grouped Kanban: traceback on click during reload
Have a grouped Kanban view with an x2many field. Interact with the
searchview, and then click anywhere.
The fetch of the x2many relational data is batched for the whole view
a single RPC for all groups) since e10fd346.
If the click occurs before all search_read are done (i.e. before the
fetch of relational data is started), there is a crash.
This is due to a call to get({raw: true}) to obtain the current model's
state, which is currently being reloaded and in an intermediate state.
Ideally, this shouldn't happen, but with the sync API of get, we can't
change that easily. So at most, we can ensure that it doesn't crash.
Moreover, sensitive functions requesting the state for the data can do
that in the mutex to ensure that the state isn't currently being reloaded.
Related to Issue: 1937149
closes odoo/odoo#32587
Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
Co-authored-by: Mohammed Shekha <msh@openerp.com>
Co-authored-by: Aaron Bohy <aab@odoo.com>
This commit is contained in:
committed by
Aaron Bohy
co-authored by
Mohammed Shekha
Aaron Bohy
parent
11b24c6b45
commit
8bcb17b674
@@ -563,9 +563,16 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
} else if (field.type === 'one2many' || field.type === 'many2many') {
|
||||
if (options.raw) {
|
||||
relDataPoint = this.localData[data[fieldName]];
|
||||
relDataPoint = this._applyX2ManyOperations(relDataPoint);
|
||||
data[fieldName] = relDataPoint.res_ids;
|
||||
if (typeof data[fieldName] === 'string') {
|
||||
relDataPoint = this.localData[data[fieldName]];
|
||||
relDataPoint = this._applyX2ManyOperations(relDataPoint);
|
||||
data[fieldName] = relDataPoint.res_ids;
|
||||
} else {
|
||||
// no datapoint has been created yet (because the loading of relational
|
||||
// data has been batched, and hasn't started yet), so the value is still
|
||||
// the list of ids in the relation
|
||||
data[fieldName] = data[fieldName] || [];
|
||||
}
|
||||
} else {
|
||||
data[fieldName] = this.get(data[fieldName]) || [];
|
||||
}
|
||||
|
||||
@@ -293,6 +293,70 @@ QUnit.module('Views', {
|
||||
|
||||
model.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('call get (raw: true) before loading x2many data', function (assert) {
|
||||
// Sometimes, get can be called on a datapoint that is currently being
|
||||
// reloaded, and thus in a partially updated state (e.g. in a kanban
|
||||
// view, the user interacts with the searchview, and before the view is
|
||||
// fully reloaded, it clicks on CREATE). Ideally, this shouldn't happen,
|
||||
// but with the sync API of get, we can't change that easily. So at most,
|
||||
// we can ensure that it doesn't crash. Moreover, sensitive functions
|
||||
// requesting the state for more precise information that, e.g., the
|
||||
// count, can do that in the mutex to ensure that the state isn't
|
||||
// currently being reloaded.
|
||||
// In this test, we have a grouped kanban view with a one2many, whose
|
||||
// relational data is loaded in batch, once for all groups. We call get
|
||||
// when the search_read for the first group has returned, but not the
|
||||
// second (and thus, the read of the one2many hasn't started yet).
|
||||
// Note: this test can be removed as soon as search_reads are performed
|
||||
// alongside read_group.
|
||||
assert.expect(2);
|
||||
|
||||
this.data.partner.records[1].product_ids = [37, 41];
|
||||
this.params.fieldsInfo = {
|
||||
kanban: {
|
||||
product_ids: {
|
||||
fieldsInfo: {
|
||||
default: { display_name: {}, color: {} },
|
||||
},
|
||||
relatedFields: this.data.product.fields,
|
||||
viewType: 'default',
|
||||
},
|
||||
},
|
||||
};
|
||||
this.params.viewType = 'kanban';
|
||||
this.params.groupedBy = ['foo'];
|
||||
|
||||
var block;
|
||||
var def = $.Deferred();
|
||||
var model = createModel({
|
||||
Model: KanbanModel,
|
||||
data: this.data,
|
||||
mockRPC: function (route) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (route === '/web/dataset/search_read' && block) {
|
||||
block = false;
|
||||
return $.when(def).then(_.constant(result));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
model.load(this.params).then(function (handle) {
|
||||
block = true;
|
||||
model.reload(handle, {});
|
||||
|
||||
var state = model.get(handle, {raw: true});
|
||||
assert.strictEqual(state.count, 2);
|
||||
|
||||
def.resolve();
|
||||
|
||||
state = model.get(handle, {raw: true});
|
||||
assert.strictEqual(state.count, 2);
|
||||
});
|
||||
|
||||
model.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user