+1
-1
@@ -788,7 +788,7 @@
|
||||
'method': 'model.' + this.attributes.relation +
|
||||
'.search_read',
|
||||
'params': [domain, 0, null, null, fields, context]
|
||||
}, record.model.session);
|
||||
}, record.model.session, true, false);
|
||||
prm.done(result => {
|
||||
var selection = [];
|
||||
for (const x of result) {
|
||||
|
||||
+6
-4
@@ -24,12 +24,14 @@
|
||||
}
|
||||
return added;
|
||||
},
|
||||
execute: function(method, params, context={}, async=true) {
|
||||
execute: function(
|
||||
method, params, context={}, async=true,
|
||||
process_exception=true) {
|
||||
var args = {
|
||||
'method': 'model.' + this.name + '.' + method,
|
||||
'params': params.concat(context)
|
||||
};
|
||||
return Sao.rpc(args, this.session, async);
|
||||
return Sao.rpc(args, this.session, async, process_exception);
|
||||
},
|
||||
copy: function(records, context) {
|
||||
if (jQuery.isEmptyObject(records)) {
|
||||
@@ -609,7 +611,7 @@
|
||||
is_loaded: function(name) {
|
||||
return ((this.id < 0) || (name in this._loaded));
|
||||
},
|
||||
load: function(name, async=true) {
|
||||
load: function(name, async=true, process_exception=true) {
|
||||
var fname;
|
||||
var prm;
|
||||
if (this.destroyed || this.is_loaded(name)) {
|
||||
@@ -748,7 +750,7 @@
|
||||
}
|
||||
var result = this.model.execute('read', [
|
||||
Object.keys(id2record).map( e => parseInt(e, 10)),
|
||||
fnames_to_fetch], context, async);
|
||||
fnames_to_fetch], context, async, process_exception);
|
||||
const succeed = (values, exception=false) => {
|
||||
var id2value = {};
|
||||
for (const e of values) {
|
||||
|
||||
+5
-2
@@ -3,7 +3,7 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
Sao.rpc = function(args, session=null, async=true) {
|
||||
Sao.rpc = function(args, session=null, async=true, process_exception=true) {
|
||||
var dfd = jQuery.Deferred(),
|
||||
result;
|
||||
if (!session) {
|
||||
@@ -32,7 +32,10 @@
|
||||
Sao.common.warning.run('',
|
||||
Sao.i18n.gettext('Unable to reach the server.'))
|
||||
.always(dfd.reject);
|
||||
} else if (data.error) {
|
||||
} else if (data.error && !process_exception) {
|
||||
console.debug(`RPC error calling ${args}: ${data.error[0]}: ${data.error[1]}.`);
|
||||
dfd.reject();
|
||||
} else if (data.error && process_exception) {
|
||||
var name, msg, description;
|
||||
if (data.error[0] == 'UserWarning') {
|
||||
name = data.error[1][0];
|
||||
|
||||
+33
-10
@@ -1389,7 +1389,7 @@
|
||||
if (field_name && !record.is_loaded(field_name)) {
|
||||
// Prefetch the first field to prevent promises in
|
||||
// Cell.render
|
||||
record.load(field_name).done(redraw);
|
||||
record.load(field_name, true, false).done(redraw);
|
||||
return;
|
||||
} else {
|
||||
row.redraw(selected, expanded);
|
||||
@@ -1686,7 +1686,8 @@
|
||||
}
|
||||
};
|
||||
if (!this.record.is_loaded(this.children_field)) {
|
||||
this.record.load(this.children_field).done(update_expander);
|
||||
this.record.load(this.children_field, true, false)
|
||||
.done(update_expander);
|
||||
} else {
|
||||
update_expander();
|
||||
}
|
||||
@@ -1761,7 +1762,8 @@
|
||||
this.rows = [];
|
||||
},
|
||||
expand_children: function(selected, expanded) {
|
||||
return this.record.load(this.children_field).done(() => {
|
||||
return this.record.load(
|
||||
this.children_field, true, false).done(() => {
|
||||
if (this.rows.length === 0) {
|
||||
var children = this.record.field_get_client(
|
||||
this.children_field);
|
||||
@@ -1957,7 +1959,8 @@
|
||||
if (widget) {
|
||||
var callback = display_callback(widget);
|
||||
if (!this.record.is_loaded(column.attributes.name)) {
|
||||
this.record.load(column.attributes.name).done(callback);
|
||||
this.record.load(column.attributes.name, true, false)
|
||||
.done(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -2304,7 +2307,7 @@
|
||||
}
|
||||
};
|
||||
if (!record.is_loaded(this.attributes.name)) {
|
||||
record.load(this.attributes.name).done(render);
|
||||
record.load(this.attributes.name, true, false).done(render);
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
@@ -2353,7 +2356,7 @@
|
||||
}
|
||||
};
|
||||
if (!record.is_loaded(this.attributes.name)) {
|
||||
record.load(this.attributes.name).done(render);
|
||||
record.load(this.attributes.name, true, false).done(render);
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
@@ -2405,10 +2408,21 @@
|
||||
cell.show();
|
||||
}
|
||||
};
|
||||
const render_error = () => {
|
||||
var text = Sao.i18n.gettext('#ERROR');
|
||||
cell.text(text).attr('title', text);
|
||||
};
|
||||
|
||||
if (!record.is_loaded(this.attributes.name)) {
|
||||
record.load(this.attributes.name).done(render);
|
||||
record.load(this.attributes.name, true, false)
|
||||
.done(render)
|
||||
.fail(render_error);
|
||||
} else {
|
||||
render();
|
||||
if (record.exception) {
|
||||
render_error();
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
}
|
||||
return cell;
|
||||
},
|
||||
@@ -2832,10 +2846,19 @@
|
||||
set_src(null);
|
||||
}
|
||||
};
|
||||
const render_error = () => {
|
||||
cell.attr('src', null);
|
||||
};
|
||||
if (!record.is_loaded(this.attributes.name)) {
|
||||
record.load(this.attributes.name).done(render);
|
||||
record.load(this.attributes.name, true, false)
|
||||
.done(render)
|
||||
.fail(render_error);
|
||||
} else {
|
||||
render();
|
||||
if (render.exception) {
|
||||
render_error();
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class SelectionMixin(object):
|
||||
try:
|
||||
result = RPCExecute('model', self.attrs['relation'],
|
||||
'search_read', domain, 0, None, None, fields,
|
||||
context=context)
|
||||
context=context, process_exception=False)
|
||||
except RPCException:
|
||||
result = False
|
||||
if isinstance(result, list):
|
||||
|
||||
@@ -44,6 +44,11 @@ class Record:
|
||||
self.destroyed = False
|
||||
|
||||
def __getitem__(self, name):
|
||||
self.load(name)
|
||||
if name != '*':
|
||||
return self.group.fields[name]
|
||||
|
||||
def load(self, name, process_exception=True):
|
||||
if not self.destroyed and self.id >= 0 and name not in self._loaded:
|
||||
id2record = {
|
||||
self.id: self,
|
||||
@@ -130,7 +135,8 @@ class Record:
|
||||
exception = False
|
||||
try:
|
||||
values = RPCExecute('model', self.model_name, 'read',
|
||||
list(id2record.keys()), fnames, context=ctx)
|
||||
list(id2record.keys()), fnames, context=ctx,
|
||||
process_exception=process_exception)
|
||||
except RPCException:
|
||||
values = [{'id': x} for x in id2record]
|
||||
default_values = dict((f, None) for f in fnames)
|
||||
@@ -146,8 +152,6 @@ class Record:
|
||||
for key in record.modified_fields:
|
||||
value.pop(key, None)
|
||||
record.set(value, modified=False)
|
||||
if name != '*':
|
||||
return self.group.fields[name]
|
||||
|
||||
def __repr__(self):
|
||||
return '<Record %s@%s at %s>' % (self.id, self.model_name, id(self))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import gettext
|
||||
import logging
|
||||
from itertools import chain, cycle, islice
|
||||
|
||||
from gi.repository import Gdk, GLib, Gtk
|
||||
@@ -9,6 +10,7 @@ from tryton.common import MODELACCESS
|
||||
from tryton.common.datetime_ import Date, Time
|
||||
|
||||
_ = gettext.gettext
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def focusable_cells(column, editable=True):
|
||||
@@ -51,7 +53,14 @@ class TreeView(Gtk.TreeView):
|
||||
if not column.name:
|
||||
continue
|
||||
widget = self.view.get_column_widget(column)
|
||||
field = record[column.name]
|
||||
try:
|
||||
record.load(column.name, process_exception=False)
|
||||
except Exception:
|
||||
logger.error(
|
||||
f"Error loading '{column.name}' for {record}",
|
||||
exc_info=True)
|
||||
return (None, None)
|
||||
field = record.group.fields[column.name]
|
||||
field.state_set(record, states=('readonly', 'invisible'))
|
||||
invisible = field.get_state_attrs(record).get('invisible', False)
|
||||
if not column.get_visible():
|
||||
@@ -83,7 +92,8 @@ class EditableTreeView(TreeView):
|
||||
|
||||
def on_quit_cell(
|
||||
self, current_record, column, renderer, value, callback=None):
|
||||
field = current_record[column.name]
|
||||
current_record.load(column.name, process_exception=False)
|
||||
field = current_record.group.fields[column.name]
|
||||
widget = self.view.get_column_widget(column)
|
||||
|
||||
# The value has not changed and is valid ... do nothing.
|
||||
@@ -271,7 +281,7 @@ class EditableTreeView(TreeView):
|
||||
create=(event.keyval == Gdk.KEY_F3), value=value,
|
||||
callback=callback)
|
||||
else:
|
||||
field = record[column.name]
|
||||
field = record.group.fields[column.name]
|
||||
if isinstance(entry, Gtk.Entry):
|
||||
entry.set_max_length(int(field.attrs.get('size', 0)))
|
||||
record.modified_fields.setdefault(column.name)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import datetime
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
import webbrowser
|
||||
from functools import partial, wraps
|
||||
@@ -31,6 +32,7 @@ from tryton.gui.window.win_form import WinForm
|
||||
from tryton.gui.window.win_search import WinSearch
|
||||
|
||||
_ = gettext.gettext
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
COLORS = {n: v for n, v in zip(
|
||||
['muted', 'success', 'warning', 'danger'],
|
||||
@@ -50,6 +52,23 @@ def send_keys(renderer, editable, position, treeview):
|
||||
editable.connect('changed', changed)
|
||||
|
||||
|
||||
def catch_errors(error_value=_('#ERROR')):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, record):
|
||||
if record.exception:
|
||||
return error_value
|
||||
try:
|
||||
return func(self, record)
|
||||
except Exception:
|
||||
logger.error(
|
||||
f"Error calling {func.__name__} for {self} with {record}",
|
||||
exc_info=True)
|
||||
return error_value
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def realized(func):
|
||||
|
||||
@wraps(func)
|
||||
@@ -143,7 +162,7 @@ class Cell(object):
|
||||
if not store:
|
||||
store = self.view.treeview.get_model()
|
||||
record = store.get_value(iter_, 0)
|
||||
field = record[self.attrs['name']]
|
||||
field = record.group.fields[self.attrs['name']]
|
||||
return record, field
|
||||
|
||||
def _set_visual(self, cell, record):
|
||||
@@ -330,9 +349,9 @@ class GenericText(Cell):
|
||||
callback=None):
|
||||
raise NotImplementedError
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
if not record:
|
||||
return ''
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
return record[self.attrs['name']].get_client(record)
|
||||
|
||||
def value_from_text(self, record, text, callback=None):
|
||||
@@ -396,9 +415,9 @@ class Int(GenericText):
|
||||
return [self.renderer_suffix]
|
||||
return []
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
if not record:
|
||||
return ''
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
return record[self.attrs['name']].get_client(
|
||||
record, factor=self.factor, grouping=self.grouping)
|
||||
|
||||
@@ -464,9 +483,9 @@ class Date(GenericText):
|
||||
else:
|
||||
return '%x'
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
if not record:
|
||||
return ''
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
value = record[self.attrs['name']].get_client(record)
|
||||
if value:
|
||||
return value.strftime(self.renderer.props.format)
|
||||
@@ -487,9 +506,9 @@ class Time(Date):
|
||||
else:
|
||||
return '%X'
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
if not record:
|
||||
return ''
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
value = record[self.attrs['name']].get_client(record)
|
||||
if value is not None:
|
||||
if isinstance(value, datetime.datetime):
|
||||
@@ -549,7 +568,9 @@ class Binary(GenericText):
|
||||
def suffixes(self):
|
||||
return [self.renderer_save, self.renderer_select]
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
if hasattr(field, 'get_size'):
|
||||
size = field.get_size(record)
|
||||
@@ -604,6 +625,11 @@ class _BinaryIcon(Cell):
|
||||
def view(self):
|
||||
return self.binary.view
|
||||
|
||||
@catch_errors(False)
|
||||
def _fetch_data(self, record):
|
||||
record.fetch(self.attrs['name'], process_exception=False)
|
||||
return True
|
||||
|
||||
|
||||
class _BinarySave(_BinaryIcon):
|
||||
icon_name = 'tryton-save'
|
||||
@@ -631,6 +657,9 @@ class _BinarySave(_BinaryIcon):
|
||||
@CellCache.cache
|
||||
def setter(self, column, cell, store, iter_, user_data=None):
|
||||
record, field = self._get_record_field_from_iter(iter_, store)
|
||||
if not self._fetch_data(record):
|
||||
cell.set_property('visible', False)
|
||||
return
|
||||
if hasattr(field, 'get_size'):
|
||||
size = field.get_size(record)
|
||||
else:
|
||||
@@ -671,6 +700,9 @@ class _BinarySelect(_BinaryIcon):
|
||||
@CellCache.cache
|
||||
def setter(self, column, cell, store, iter_, user_data=None):
|
||||
record, field = self._get_record_field_from_iter(iter_, store)
|
||||
if not self._fetch_data(record):
|
||||
cell.set_property('visible', False)
|
||||
return
|
||||
if hasattr(field, 'get_size'):
|
||||
size = field.get_size(record)
|
||||
else:
|
||||
@@ -710,6 +742,9 @@ class _BinaryOpen(_BinarySave):
|
||||
def setter(self, column, cell, store, iter_, user_data=None):
|
||||
super().setter(column, cell, store, iter_)
|
||||
record, field = self._get_record_field_from_iter(iter_, store)
|
||||
if not self._fetch_data(record):
|
||||
cell.set_property('visible', False)
|
||||
return
|
||||
filename_field = record.group.fields.get(self.attrs.get('filename'))
|
||||
filename = filename_field.get(record)
|
||||
if not filename:
|
||||
@@ -731,22 +766,33 @@ class Image(GenericText):
|
||||
@CellCache.cache
|
||||
def setter(self, column, cell, store, iter_, user_data=None):
|
||||
record, field = self._get_record_field_from_iter(iter_, store)
|
||||
value = field.get_client(record)
|
||||
if isinstance(value, int):
|
||||
if value > CONFIG['image.max_size']:
|
||||
value = None
|
||||
else:
|
||||
value = field.get_data(record)
|
||||
value = self._get_data(record)
|
||||
if value is None:
|
||||
cell.set_property('pixbuf', None)
|
||||
return
|
||||
pixbuf = data2pixbuf(value)
|
||||
if pixbuf:
|
||||
pixbuf = common.resize_pixbuf(pixbuf, self.width, self.height)
|
||||
cell.set_property('pixbuf', pixbuf)
|
||||
self._set_visual(cell, record)
|
||||
|
||||
@catch_errors(None)
|
||||
def _get_data(self, record):
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
value = field.get_client(record)
|
||||
if isinstance(value, int):
|
||||
if value > CONFIG['image.max_size']:
|
||||
value = None
|
||||
else:
|
||||
value = field.get_data(record)
|
||||
return value
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
if not record:
|
||||
return ''
|
||||
return str(record[self.attrs['name']].get_size(record))
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
return str(field.get_size(record))
|
||||
|
||||
|
||||
class M2O(GenericText):
|
||||
@@ -974,9 +1020,11 @@ class O2O(M2O):
|
||||
class O2M(GenericText):
|
||||
align = 0.5
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
return '( ' + str(len(record[self.attrs['name']]
|
||||
.get_eval(record))) + ' )'
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
return '( ' + str(len(field.get_eval(record))) + ' )'
|
||||
|
||||
def value_from_text(self, record, text, callback=None):
|
||||
if callback:
|
||||
@@ -1056,13 +1104,16 @@ class Selection(GenericText, SelectionMixin, PopdownMixin):
|
||||
def get_value(self, record, field):
|
||||
return field.get(record)
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
related = self.attrs['name'] + ':string'
|
||||
record.load(related, process_exception=False)
|
||||
if not self.view.editable and related in record.value:
|
||||
return record.value[related]
|
||||
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
self.update_selection(record, field)
|
||||
self.update_selection(record, field, process_exception=False)
|
||||
value = self.get_value(record, field)
|
||||
text = dict(self.selection).get(value, '')
|
||||
if value and not text:
|
||||
@@ -1138,13 +1189,16 @@ class MultiSelection(GenericText, SelectionMixin):
|
||||
if callback:
|
||||
callback()
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
related = self.attrs['name'] + ':string'
|
||||
record.load(related, process_exception=False)
|
||||
if not self.view.editable and related in record.value:
|
||||
return ";".join(record.value[related])
|
||||
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
self.update_selection(record, field)
|
||||
self.update_selection(record, field, process_exception=False)
|
||||
selection = dict(self.selection)
|
||||
values = []
|
||||
for value in field.get_eval(record):
|
||||
@@ -1191,6 +1245,7 @@ class Reference(M2O):
|
||||
_, value = value.split(',')
|
||||
return int(value)
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
value = super().get_textual_value(record)
|
||||
if value:
|
||||
@@ -1232,8 +1287,11 @@ class Dict(GenericText):
|
||||
super().setter(column, cell, store, iter_, user_data=None)
|
||||
cell.props.editable = False
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
return '(%s)' % len(record[self.attrs['name']].get_client(record))
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
return '(%s)' % len(field.get_client(record))
|
||||
|
||||
|
||||
class ProgressBar(Cell):
|
||||
@@ -1275,8 +1333,11 @@ class ProgressBar(Cell):
|
||||
callback=None):
|
||||
raise NotImplementedError
|
||||
|
||||
@catch_errors()
|
||||
def get_textual_value(self, record):
|
||||
return record[self.attrs['name']].get_client(record, factor=100) or ''
|
||||
record.load(self.attrs['name'], process_exception=False)
|
||||
field = record[self.attrs['name']]
|
||||
return field.get_client(record, factor=100) or ''
|
||||
|
||||
def value_from_text(self, record, text, callback=None):
|
||||
field = record[self.attrs['name']]
|
||||
|
||||
Reference in New Issue
Block a user