details: https://code.tryton.org/tryton/commit/b7df39d1d09e
branch: 8.0
user: Cédric Krier <[email protected]>
date: Tue Aug 25 13:09:25 2026 +0200
description:
Ensure it is still the same record when executing the display callback
of a widget
When a widget display is asynchronous, we must ensure that it is still
the same
record that is being displayed when the callback is finally executed.
Otherwise
it may render the data of a different record in the HTML.
Closes #15041
(grafted from dbd215ffb07150c2f476922bd7c66dc896ed2665)
diffstat:
sao/src/view/form.js | 82 +++++++++++++++++++++++++++++++++++++--------------
1 files changed, 59 insertions(+), 23 deletions(-)
diffs (189 lines):
diff -r e07927ab586e -r b7df39d1d09e sao/src/view/form.js
--- a/sao/src/view/form.js Wed Sep 02 13:57:21 2026 +0200
+++ b/sao/src/view/form.js Tue Aug 25 13:09:25 2026 +0200
@@ -2278,14 +2278,22 @@
Sao.common.selection_mixin.init_selection.call(this, key,
this.set_selection.bind(this));
},
- update_selection: function(record, field, callbak) {
+ update_selection: function(record, field, callback) {
+ let deferred = jQuery.Deferred();
Sao.common.selection_mixin.update_selection.call(this, record,
field, (selection, help) => {
this.set_selection(selection, help);
- if (callbak) {
- callbak(selection, help);
+ let prm;
+ if (callback) {
+ prm = callback(selection, help);
+ }
+ if (prm) {
+ prm.always(deferred.resolve);
+ } else {
+ deferred.resolve();
}
});
+ return deferred;
},
set_selection: function(selection, help) {
var select = this.select;
@@ -2299,9 +2307,13 @@
}
},
display_update_selection: function() {
- var record = this.record;
- var field = this.field;
- this.update_selection(record, field, (selection, help) => {
+ let record = this.record,
+ field = this.field;
+ return this.update_selection(record, field, (selection, help) => {
+ if (record !== this.record) {
+ return;
+ }
+
if (!field) {
this.select.val('');
return;
@@ -2317,7 +2329,7 @@
if (!found) {
prm = Sao.common.selection_mixin.get_inactive_selection
.call(this, value);
- prm.done(inactive => {
+ prm = prm.then(inactive => {
this.select.append(jQuery('<option/>', {
value: JSON.stringify(inactive[0]),
text: inactive[1],
@@ -2327,7 +2339,7 @@
} else {
prm = jQuery.when();
}
- prm.done(() => {
+ return prm.then(() => {
this.select.val(JSON.stringify(value));
var title = help[value] || null;
if (this.attributes.help && title) {
@@ -2339,7 +2351,7 @@
},
display: function() {
Sao.View.Form.Selection._super.display.call(this);
- this.display_update_selection();
+ return this.display_update_selection();
},
focus: function() {
this.select.focus();
@@ -3142,13 +3154,21 @@
this.set_selection.bind(this));
},
update_selection: function(record, field, callback) {
+ let deferred = jQuery.Deferred();
Sao.common.selection_mixin.update_selection.call(this, record,
field, (selection, help) => {
this.set_selection(selection, help);
+ let prm;
if (callback) {
- callback();
+ prm = callback(help);
+ }
+ if (prm) {
+ prm.always(deferred.resolve);
+ } else {
+ deferred.resolve();
}
});
+ return deferred;
},
set_selection: function(selection, help) {
var select = this.select;
@@ -3271,7 +3291,11 @@
}
},
display: function() {
- this.update_selection(this.record, this.field, () => {
+ let record = this.record;
+ return this.update_selection(this.record, this.field, () => {
+ if (record !== this.record) {
+ return;
+ }
Sao.View.Form.Reference._super.display.call(this);
});
},
@@ -3637,13 +3661,17 @@
display: function() {
Sao.View.Form.One2Many._super.display.call(this);
- let display = function() {
+ let record = this.record,
+ field = this.field;
+
+ let display = () => {
+ if (record !== this.record) {
+ return;
+ }
+
this._set_button_sensitive();
- var record = this.record;
- var field = this.field;
-
- if (!field) {
+ if (!field) {
this.screen.new_group();
this.screen.current_record = null;
this.screen.group.parent = null;
@@ -3684,7 +3712,7 @@
.css('max-height', this.attributes.height + 'px');
}
return this.screen.display();
- }.bind(this);
+ };
if (this.prm.state() == 'pending') {
return this.prm.then(() => display());
@@ -4219,9 +4247,13 @@
display: function() {
Sao.View.Form.Many2Many._super.display.call(this);
- let display = function() {
- var record = this.record;
- var field = this.field;
+ let record = this.record,
+ field = this.field;
+
+ let display = () => {
+ if (record !== this.record) {
+ return;
+ }
if (!field) {
this.screen.new_group();
@@ -4241,7 +4273,7 @@
.css('max-height', this.attributes.height + 'px');
}
return this.screen.display();
- }.bind(this);
+ };
if (this.prm.state() == 'pending') {
return this.prm.then(() => display());
@@ -5338,8 +5370,8 @@
_display: function() {
Sao.View.Form.Dict._super.display.call(this);
- var record = this.record;
- var field = this.field;
+ let record = this.record,
+ field = this.field;
if (!field) {
return;
}
@@ -5365,6 +5397,10 @@
prm = jQuery.when();
}
prm.then(() => {
+ if (record !== this.record) {
+ return;
+ }
+
var i, len, key;
var keys = Object.keys(value)
.filter(function(key) {