details: https://code.tryton.org/tryton/commit/9a7e061acd01
branch: default
user: Nicolas Évrard <[email protected]>
date: Thu Jan 08 01:06:16 2026 +0100
description:
Allow to store empty files in Binary fields
Closes #14476
diffstat:
sao/src/model.js | 2 +-
sao/src/view/form.js | 11 ++++++--
sao/src/view/tree.js | 3 +-
tryton/tryton/gui/window/view_form/model/field.py | 4 +-
tryton/tryton/gui/window/view_form/view/form_gtk/binary.py | 7 +++--
tryton/tryton/gui/window/view_form/view/list_gtk/widget.py | 6 ++--
trytond/trytond/model/fields/binary.py | 10 ++++---
trytond/trytond/tests/test_field_binary.py | 18 ++++++++++---
8 files changed, 40 insertions(+), 21 deletions(-)
diffs (201 lines):
diff -r f62e20c70717 -r 9a7e061acd01 sao/src/model.js
--- a/sao/src/model.js Thu Jun 18 15:49:10 2026 +0200
+++ b/sao/src/model.js Thu Jan 08 01:06:16 2026 +0100
@@ -2863,7 +2863,7 @@
return previous != value;
},
get_size: function(record) {
- var data = record._values[this.name] || 0;
+ var data = record._values[this.name];
if ((data instanceof Uint8Array) ||
(typeof(data) == 'string')) {
return data.length;
diff -r f62e20c70717 -r 9a7e061acd01 sao/src/view/form.js
--- a/sao/src/view/form.js Thu Jun 18 15:49:10 2026 +0200
+++ b/sao/src/view/form.js Thu Jan 08 01:06:16 2026 +0100
@@ -4768,17 +4768,22 @@
} else {
size = field.get(record).length;
}
- this.size.val(Sao.common.humanize(size, 'B'));
+ let is_empty = ((size === undefined) || (size === null));
+ if (!is_empty) {
+ this.size.val(Sao.common.humanize(size, 'B'));
+ } else {
+ this.size.val('');
+ }
if (this.text) {
this.text.val(this.filename_field.get(record) || '');
- if (size) {
+ if (!is_empty) {
this.but_open.parent().sao_show();
} else {
this.but_open.parent().sao_hide();
}
}
- this.update_buttons(Boolean(size));
+ this.update_buttons(!is_empty);
},
key_press: function(evt) {
var editable = !this.text.prop('readonly');
diff -r f62e20c70717 -r 9a7e061acd01 sao/src/view/tree.js
--- a/sao/src/view/tree.js Thu Jun 18 15:49:10 2026 +0200
+++ b/sao/src/view/tree.js Thu Jan 08 01:06:16 2026 +0100
@@ -3086,7 +3086,8 @@
} else {
size = this.field.get(record).length;
}
- return size? Sao.common.humanize(size, 'B') : '';
+ let file_exist = !((size === undefined) || (size === null));
+ return file_exist ? Sao.common.humanize(size, 'B') : '';
},
update_text: function(cell, record) {
var text = this.get_textual_value(record);
diff -r f62e20c70717 -r 9a7e061acd01
tryton/tryton/gui/window/view_form/model/field.py
--- a/tryton/tryton/gui/window/view_form/model/field.py Thu Jun 18 15:49:10
2026 +0200
+++ b/tryton/tryton/gui/window/view_form/model/field.py Thu Jan 08 01:06:16
2026 +0100
@@ -1140,13 +1140,13 @@
return self.get(record)
def set_client(self, record, value, force_change=False):
- self._set_file_cache(record, value or b'')
+ self._set_file_cache(record, value)
self.sig_changed(record)
record.validate(softvalidation=True)
record.set_modified(self.name)
def get_size(self, record):
- result = record.value.get(self.name) or 0
+ result = record.value.get(self.name)
if isinstance(result, _FileCache):
result = os.stat(result.path).st_size
elif isinstance(result, (str, bytes)):
diff -r f62e20c70717 -r 9a7e061acd01
tryton/tryton/gui/window/view_form/view/form_gtk/binary.py
--- a/tryton/tryton/gui/window/view_form/view/form_gtk/binary.py Thu Jun
18 15:49:10 2026 +0200
+++ b/tryton/tryton/gui/window/view_form/view/form_gtk/binary.py Thu Jan
08 01:06:16 2026 +0100
@@ -233,12 +233,13 @@
size = self.field.get_size(self.record)
else:
size = len(self.field.get(self.record))
- self.wid_size.set_text(common.humanize(size or 0, 'B'))
+ self.wid_size.set_text(
+ common.humanize(size, 'B') if size is not None else '')
reset_position(self.wid_size)
if self.wid_text:
self.wid_text.set_text(self.filename_field.get(self.record) or '')
reset_position(self.wid_text)
- if size:
+ if size is not None:
icon, tooltip = 'tryton-open', _("Open...")
else:
icon, tooltip = None, ''
@@ -250,7 +251,7 @@
pixbuf = None
self.wid_text.set_icon_from_pixbuf(pos, pixbuf)
self.wid_text.set_icon_tooltip_text(pos, tooltip)
- self.update_buttons(bool(size))
+ self.update_buttons(size is not None)
return True
def set_value(self):
diff -r f62e20c70717 -r 9a7e061acd01
tryton/tryton/gui/window/view_form/view/list_gtk/widget.py
--- a/tryton/tryton/gui/window/view_form/view/list_gtk/widget.py Thu Jun
18 15:49:10 2026 +0200
+++ b/tryton/tryton/gui/window/view_form/view/list_gtk/widget.py Thu Jan
08 01:06:16 2026 +0100
@@ -604,7 +604,7 @@
size = field.get_size(record)
else:
size = len(field.get(record))
- return common.humanize(size, 'B') if size else ''
+ return common.humanize(size, 'B') if size is not None else ''
def value_from_text(self, record, text, callback=None):
if callback:
@@ -694,7 +694,7 @@
size = len(field.get(record))
field.state_set(record, states=['invisible'])
invisible = field.get_state_attrs(record).get('invisible', False)
- cell.set_property('visible', not invisible and size)
+ cell.set_property('visible', not invisible and size is not None)
self._set_visual(cell, record)
@@ -763,7 +763,7 @@
invisible = field.get_state_attrs(record).get('invisible', False)
readonly = self.attrs.get('readonly',
field.get_state_attrs(record).get('readonly', False))
- if readonly or size:
+ if readonly or size is not None:
cell.set_property('visible', False)
else:
cell.set_property('visible', not invisible)
diff -r f62e20c70717 -r 9a7e061acd01 trytond/trytond/model/fields/binary.py
--- a/trytond/trytond/model/fields/binary.py Thu Jun 18 15:49:10 2026 +0200
+++ b/trytond/trytond/model/fields/binary.py Thu Jan 08 01:06:16 2026 +0100
@@ -70,7 +70,6 @@
'%s.%s' % (model.__name__, name), '')
if format_ == 'size':
converter = len
- default = 0
result = defaultdict(type(default))
if self.file_id:
@@ -104,7 +103,7 @@
if i['id'] in result:
continue
value = i[name]
- if value:
+ if value is not None:
value = converter(value)
else:
value = default
@@ -152,9 +151,12 @@
for ids, value in zip(args, args):
self.queue_for_removal(Model, name, ids)
if self.file_id:
+ if value is not None:
+ file_name = filestore.set(value, prefix)
+ else:
+ file_name = None
columns = [Column(table, self.file_id), Column(table, name)]
- values = [
- filestore.set(value, prefix) if value else None, None]
+ values = [file_name, None]
else:
columns = [Column(table, name)]
values = [self.sql_format(value)]
diff -r f62e20c70717 -r 9a7e061acd01 trytond/trytond/tests/test_field_binary.py
--- a/trytond/trytond/tests/test_field_binary.py Thu Jun 18 15:49:10
2026 +0200
+++ b/trytond/trytond/tests/test_field_binary.py Thu Jan 08 01:06:16
2026 +0100
@@ -90,10 +90,9 @@
"Test create binary with empty"
Binary = Pool().get('test.binary_required')
- with self.assertRaises(RequiredValidationError):
- binary, = Binary.create([{
- 'binary': cast(b''),
- }])
+ binary, = Binary.create([{
+ 'binary': cast(b''),
+ }])
@with_transaction()
def test_create_required_with_invalid_sql_constraint(self):
@@ -157,6 +156,17 @@
binary.binary = Literal('foo')
@with_transaction()
+ def test_read_empty(self):
+ "Test reading an empty binary field"
+ Binary = Pool().get('test.binary')
+
+ binary, = Binary.create([{
+ 'binary': cast(b''),
+ }])
+
+ self.assertEqual(binary.binary, b'')
+
+ @with_transaction()
def test_read_size(self):
"Test read binary size"
Binary = Pool().get('test.binary')