diff --git a/sao/CHANGELOG b/sao/CHANGELOG
index 5ccac21f45..74d8d89d7e 100644
--- a/sao/CHANGELOG
+++ b/sao/CHANGELOG
@@ -1,3 +1,4 @@
+* Display XML ID on Log window
* Use operators when converting PYSON to string
* Add DnD on binary and image widgets
* Allow resetting forgotten password
diff --git a/sao/src/window.js b/sao/src/window.js
index 979cc58931..23d6e9c761 100644
--- a/sao/src/window.js
+++ b/sao/src/window.js
@@ -767,7 +767,8 @@
record.model.execute(
'read', [[record.id],
['create_uid.rec_name', 'create_date',
- 'write_uid.rec_name', 'write_date']], context
+ 'write_uid.rec_name', 'write_date',
+ 'xml_id']], context
).then(([log]) => {
var row, cell, label, input;
@@ -802,6 +803,39 @@
input.css('text-align', 'end');
input.uniqueId();
+ if (log.xml_id) {
+ const [module, xml_id] = log.xml_id.split('.', 2);
+
+ row = jQuery('
').appendTo(body);
+ cell = jQuery(' | ').css('text-align', 'end').appendTo(row);
+ label = jQuery('', {
+ 'class': 'form-label',
+ 'text': Sao.i18n.gettext("Module:"),
+ }).appendTo(cell);
+ label.uniqueId();
+ cell = jQuery(' | ').appendTo(row);
+ input = jQuery('', {
+ 'readonly': true,
+ 'class': 'form-control input-sm',
+ 'aria-labelledby': label.attr('id'),
+ }).val(module).appendTo(cell);
+ input.uniqueId();
+
+ cell = jQuery(' | ').css('text-align', 'end').appendTo(row);
+ label = jQuery('', {
+ 'class': 'form-label',
+ 'text': Sao.i18n.gettext("XML ID:"),
+ }).appendTo(cell);
+ label.uniqueId();
+ cell = jQuery(' | ').appendTo(row);
+ input = jQuery('', {
+ 'readonly': true,
+ 'class': 'form-control input-sm',
+ 'aria-labelledby': label.attr('id'),
+ }).val(xml_id).appendTo(cell);
+ input.uniqueId();
+ }
+
[['create_uid.', Sao.i18n.gettext("Created by:"),
'create_date', Sao.i18n.gettext("Created at:")],
['write_uid.', Sao.i18n.gettext("Last Modified by:"),
diff --git a/tryton/CHANGELOG b/tryton/CHANGELOG
index bebbedae29..09475654d1 100644
--- a/tryton/CHANGELOG
+++ b/tryton/CHANGELOG
@@ -1,3 +1,4 @@
+* Display XML ID on Log window
* Use operators when converting PYSON to string
* Add contextual menu to copy cell and column
* Allow resetting forgotten password
diff --git a/tryton/tryton/gui/window/log.py b/tryton/tryton/gui/window/log.py
index 04bdb4c2fe..1e0700dbdf 100644
--- a/tryton/tryton/gui/window/log.py
+++ b/tryton/tryton/gui/window/log.py
@@ -24,7 +24,8 @@ class Log(WinForm):
log, = RPCExecute(
'model', record.model_name, 'read', [record.id],
['create_uid.rec_name', 'create_date',
- 'write_uid.rec_name', 'write_date'], context=context)
+ 'write_uid.rec_name', 'write_date',
+ 'xml_id'], context=context)
except RPCException:
return
@@ -53,11 +54,32 @@ class Log(WinForm):
label_id.set_mnemonic_widget(entry_id)
grid.attach(label_id, 2, 1, 1, 1)
+ if log.get('xml_id'):
+ module, xml_id = log['xml_id'].split('.', 1)
+
+ entry_module = Gtk.Entry(editable=False)
+ entry_module.set_text(module)
+ grid.attach(entry_module, 1, 2, 1, 1)
+ label_module = Gtk.Label(
+ label=set_underline(_("Module:")),
+ use_underline=True, halign=Gtk.Align.END)
+ label_module.set_mnemonic_widget(entry_module)
+ grid.attach(label_module, 0, 2, 1, 1)
+
+ entry_xml_id = Gtk.Entry(editable=False)
+ entry_xml_id.set_text(xml_id)
+ grid.attach(entry_xml_id, 3, 2, 1, 1)
+ label_xml_id = Gtk.Label(
+ label=set_underline(_("XML ID:")),
+ use_underline=True, halign=Gtk.Align.END)
+ label_xml_id.set_mnemonic_widget(entry_xml_id)
+ grid.attach(label_xml_id, 2, 2, 1, 1)
+
for i, (user, user_label, date, date_label) in enumerate([
('create_uid.', _("Created by:"),
'create_date', _("Created at:")),
('write_uid.', _("Last Modified by:"),
- 'write_date', _("Last Modified at:"))], 2):
+ 'write_date', _("Last Modified at:"))], 3):
entry_user = Gtk.Entry(editable=False, width_chars=50)
user = log.get(user)
if user:
diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG
index ddd88dcf73..8717868df1 100644
--- a/trytond/CHANGELOG
+++ b/trytond/CHANGELOG
@@ -1,3 +1,4 @@
+* Add xml_id field on ModelStorage
* Add send_message methods
* Store model on ir.rule.group
* Store model on ir.model.button
diff --git a/trytond/trytond/ir/message.xml b/trytond/trytond/ir/message.xml
index 64d87b19ee..7f603fd0bd 100644
--- a/trytond/trytond/ir/message.xml
+++ b/trytond/trytond/ir/message.xml
@@ -52,6 +52,9 @@ this repository contains the full copyright notices and license terms. -->
Record Name
+
+ XML ID
+
Active
diff --git a/trytond/trytond/model/modelstorage.py b/trytond/trytond/model/modelstorage.py
index 532a30a4e1..5072788226 100644
--- a/trytond/trytond/model/modelstorage.py
+++ b/trytond/trytond/model/modelstorage.py
@@ -119,6 +119,8 @@ class ModelStorage(Model):
rec_name = fields.Function(
fields.Char(lazy_gettext('ir.msg_record_name')), 'get_rec_name',
searcher='search_rec_name')
+ xml_id = fields.Function(
+ fields.Char(lazy_gettext('ir.msg_xml_id')), 'get_xml_id')
_count_cache = Cache(
'modelstorage.count', duration=_cache_count_timeout, context=False)
_log = None
@@ -685,6 +687,21 @@ class ModelStorage(Model):
return []
return [(rec_name,) + tuple(clause[1:])]
+ @classmethod
+ def get_xml_id(cls, records, name):
+ pool = Pool()
+ ModelData = pool.get('ir.model.data')
+
+ ids = dict.fromkeys(map(int, records))
+ with without_check_access():
+ for sub_records in grouped_slice(records):
+ for record in ModelData.search([
+ ('model', '=', cls.__name__),
+ ('db_id', 'in', [r.id for r in sub_records]),
+ ]):
+ ids[record.db_id] = f'{record.module}.{record.fs_id}'
+ return ids
+
@classmethod
def search_global(cls, text):
'''