From ef58bc6b828469d002f76a8b1f4de88aec895c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 6 Aug 2026 14:53:51 +0200 Subject: [PATCH] Edit selected One2Many rows at once Closes #14997 --- sao/CHANGELOG | 1 + sao/src/view/form.js | 27 ++++++++++++++----- sao/src/window.js | 4 +++ tryton/CHANGELOG | 1 + .../view_form/view/form_gtk/one2many.py | 22 +++++++++++---- tryton/tryton/gui/window/win_form.py | 6 ++++- 6 files changed, 48 insertions(+), 13 deletions(-) diff --git a/sao/CHANGELOG b/sao/CHANGELOG index 4540e0db59..af7cc0057c 100644 --- a/sao/CHANGELOG +++ b/sao/CHANGELOG @@ -1,3 +1,4 @@ +* Edit the selected One2Many rows sequentially * Support filename extension for Binary field * Use record name as fallback for filename of Binary field * Support filters attribute for binary and image widgets diff --git a/sao/src/view/form.js b/sao/src/view/form.js index 124f22a0a7..738562bc1a 100644 --- a/sao/src/view/form.js +++ b/sao/src/view/form.js @@ -4010,16 +4010,29 @@ function eval_pyson(value){ return; } return this.validate().then(() => { - var record = this.screen.current_record; - if (record) { + let records = Array.from(this.screen.selected_records); + let size = records.length; + if (size) { if (this._popup) { return; - } else { - this._popup = true; } - new Sao.Window.Form(this.screen, () => { - this._popup = false; - }); + let edit_next_record = () => { + if (records.length) { + this.screen.current_record = records.shift(); + this._popup = true; + new Sao.Window.Form(this.screen, (result) => { + this._popup = false; + if (!result) { + records.length = 0; + } + }, { + title: (size > 1 ? + `(${size - records.length}/${size})` : ''), + on_destroy: edit_next_record, + }); + } + }; + edit_next_record(); } }); }, diff --git a/sao/src/window.js b/sao/src/window.js index 64c1cd28d4..d0b715eddc 100644 --- a/sao/src/window.js +++ b/sao/src/window.js @@ -124,6 +124,7 @@ this.domain = kwargs.domain || null; this.context = kwargs.context || null; this.save_current = kwargs.save_current; + this.on_destroy = kwargs.on_destroy; var title_prm = jQuery.when(kwargs.title || '').then( title => { if (screen.breadcrumb.length) { @@ -683,6 +684,9 @@ this.screen.switch_view(this.prev_view.view_type); } this.el.modal('hide'); + if (this.on_destroy) { + this.el.on('hidden.bs.modal', () => this.on_destroy()); + } } }); diff --git a/tryton/CHANGELOG b/tryton/CHANGELOG index e3829c0553..3ad387c871 100644 --- a/tryton/CHANGELOG +++ b/tryton/CHANGELOG @@ -1,3 +1,4 @@ +* Edit the selected One2Many rows sequentially * Support filename extension for Binary field * Use record name as fallback for filename of Binary field * Support filters attribute for binary and image widgets diff --git a/tryton/tryton/gui/window/view_form/view/form_gtk/one2many.py b/tryton/tryton/gui/window/view_form/view/form_gtk/one2many.py index dd389231e8..8ad0461b89 100644 --- a/tryton/tryton/gui/window/view_form/view/form_gtk/one2many.py +++ b/tryton/tryton/gui/window/view_form/view/form_gtk/one2many.py @@ -464,16 +464,28 @@ class One2Many(Widget): return if not self._validate(): return - record = self.screen.current_record - if record: + records = list(self.screen.selected_records) + size = len(records) + if records: if self._popup: return - else: - self._popup = True def callback(result): self._popup = False - WinForm(self.screen, callback, prev_view=self.screen.current_view) + if not result: + records.clear() + + def edit_next_record(): + if records: + self.screen.current_record = records.pop(0) + self._popup = True + WinForm( + self.screen, callback, + title=('(%s/%s)' % (size - len(records), size) + if size > 1 else ''), + prev_view=self.screen.current_view, + on_destroy=edit_next_record) + edit_next_record() def _sig_next(self, widget): if not self._validate(): diff --git a/tryton/tryton/gui/window/win_form.py b/tryton/tryton/gui/window/win_form.py index a5252960d4..7cfc8cd3a4 100644 --- a/tryton/tryton/gui/window/win_form.py +++ b/tryton/tryton/gui/window/win_form.py @@ -24,7 +24,8 @@ class WinForm(NoModal, InfoBar): def __init__(self, screen, callback=None, view_type='form', new=False, many=0, domain=None, context=None, - save_current=False, title='', defaults=None, prev_view=SENTINEL): + save_current=False, title='', defaults=None, prev_view=SENTINEL, + on_destroy=None): tooltips = common.Tooltips() NoModal.__init__(self) @@ -37,6 +38,7 @@ class WinForm(NoModal, InfoBar): self.domain = domain self.context = context self.save_current = save_current + self.on_destroy = on_destroy if screen.breadcrumb: breadcrumb = list(screen.breadcrumb) if title: @@ -528,6 +530,8 @@ class WinForm(NoModal, InfoBar): if getattr(self, 'win', None): self.win.destroy() NoModal.destroy(self) + if self.on_destroy: + self.on_destroy() def show(self): self.win.show()