details:   https://code.tryton.org/tryton/commit/16057e0c1c93
branch:    default
user:      Cédric Krier <[email protected]>
date:      Thu Jun 18 23:24:06 2026 +0200
description:
        Prevent to modify attachments on model without write access

        When the user has no write access to the module, the "Add" button of the
        attachment menu is not displayed and the manage button opens a screen in
        readonly mode.

        Closes #14863
diffstat:

 sao/src/tab.js                         |  41 ++++++++++++++++++---------------
 sao/src/window.js                      |   2 +
 tryton/tryton/gui/window/attachment.py |   7 ++++-
 tryton/tryton/gui/window/form.py       |   8 ++++--
 4 files changed, 34 insertions(+), 24 deletions(-)

diffs (126 lines):

diff -r 9a7e061acd01 -r 16057e0c1c93 sao/src/tab.js
--- a/sao/src/tab.js    Thu Jan 08 01:06:16 2026 +0100
+++ b/sao/src/tab.js    Thu Jun 18 23:24:06 2026 +0200
@@ -1189,6 +1189,7 @@
             var record = this.screen.current_record;
             var menu = dropdown.find('.dropdown-menu');
             menu.empty();
+            let access = Sao.common.MODELACCESS.get(this.screen.model_name);
             return Sao.Window.Attachment.get_attachments(record)
                 .then(function(attachments) {
                     attachments.forEach(function(value) {
@@ -1215,25 +1216,27 @@
                     menu.append(jQuery('<li/>', {
                         'class': 'divider',
                     }));
-                    menu.append(jQuery('<li/>', {
-                        'role': 'presentation',
-                        'class': 'input-file',
-                    }).append(jQuery('<input/>', {
-                        'type': 'file',
-                        'role': 'menuitem',
-                        'multiple': true,
-                        'tabindex': -1,
-                    }).change(function() {
-                        var attachment = window_();
-                        Sao.common.get_input_data(
-                            jQuery(this), function(data, filename) {
-                                attachment.add_data(data, filename);
-                            });
-                    })).append(jQuery('<a/>', {
-                        'role': 'menuitem',
-                        'href': '#',
-                        'tabindex': -1,
-                    }).text(Sao.i18n.gettext('Add...'))));
+                    if (access.write) {
+                        menu.append(jQuery('<li/>', {
+                            'role': 'presentation',
+                            'class': 'input-file',
+                        }).append(jQuery('<input/>', {
+                            'type': 'file',
+                            'role': 'menuitem',
+                            'multiple': true,
+                            'tabindex': -1,
+                        }).change(function() {
+                            var attachment = window_();
+                            Sao.common.get_input_data(
+                                jQuery(this), function(data, filename) {
+                                    attachment.add_data(data, filename);
+                                });
+                        })).append(jQuery('<a/>', {
+                            'role': 'menuitem',
+                            'href': '#',
+                            'tabindex': -1,
+                        }).text(Sao.i18n.gettext('Add...'))));
+                    }
                     menu.append(jQuery('<li/>', {
                         'role': 'presentation',
                     }).append(jQuery('<a/>', {
diff -r 9a7e061acd01 -r 16057e0c1c93 sao/src/window.js
--- a/sao/src/window.js Thu Jan 08 01:06:16 2026 +0100
+++ b/sao/src/window.js Thu Jun 18 23:24:06 2026 +0200
@@ -668,10 +668,12 @@
             this.resource = record.model.name + ',' + record.id;
             this.attachment_callback = callback;
             var context = jQuery.extend({}, record.get_context());
+            let access = Sao.common.MODELACCESS.get(record.model.name);
             var screen = new Sao.Screen('ir.attachment', {
                 domain: [['resource', '=', this.resource]],
                 mode: ['tree', 'form'],
                 context: context,
+                readonly: !access.write,
             });
             var title = record.rec_name().then(function(rec_name) {
                 return Sao.i18n.gettext('Attachments (%1)', rec_name);
diff -r 9a7e061acd01 -r 16057e0c1c93 tryton/tryton/gui/window/attachment.py
--- a/tryton/tryton/gui/window/attachment.py    Thu Jan 08 01:06:16 2026 +0100
+++ b/tryton/tryton/gui/window/attachment.py    Thu Jun 18 23:24:06 2026 +0200
@@ -8,7 +8,7 @@
 from urllib.parse import unquote, urlparse
 
 from tryton.common import (
-    RPCException, RPCExecute, file_open, file_write, url_open)
+    MODELACCESS, RPCException, RPCExecute, file_open, file_write, url_open)
 from tryton.gui.window.view_form.screen import Screen
 from tryton.gui.window.win_form import WinForm
 
@@ -21,10 +21,13 @@
     def __init__(self, record, callback=None):
         self.resource = '%s,%s' % (record.model_name, record.id)
         self.attachment_callback = callback
+        access = MODELACCESS[record.model_name]
         title = _('Attachments (%s)') % (record.rec_name())
         screen = Screen('ir.attachment', domain=[
             ('resource', '=', self.resource),
-            ], mode=['tree', 'form'])
+            ],
+            mode=['tree', 'form'],
+            readonly=not access['write'])
         super().__init__(screen, self.callback,
             view_type='tree', title=title)
         screen.search_filter()
diff -r 9a7e061acd01 -r 16057e0c1c93 tryton/tryton/gui/window/form.py
--- a/tryton/tryton/gui/window/form.py  Thu Jan 08 01:06:16 2026 +0100
+++ b/tryton/tryton/gui/window/form.py  Thu Jun 18 23:24:06 2026 +0200
@@ -166,6 +166,7 @@
         def activate(widget, callback):
             callback()
 
+        access = common.MODELACCESS[self.model]
         button = self.buttons['attach']
         if widget != button:
             if button.props.sensitive:
@@ -178,9 +179,10 @@
             item.connect('activate', activate, callback)
             menu.add(item)
         menu.add(Gtk.SeparatorMenuItem())
-        add_item = Gtk.MenuItem(label=_("Add..."))
-        add_item.connect('activate', add_file)
-        menu.add(add_item)
+        if access['write']:
+            add_item = Gtk.MenuItem(label=_("Add..."))
+            add_item.connect('activate', add_file)
+            menu.add(add_item)
         preview_item = Gtk.CheckMenuItem(label=_("Preview"))
         preview_item.set_active(bool(
                 self.attachment_preview.get_children()))

Reply via email to