tryton 7.0.7__py3-none-any.whl → 7.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tryton might be problematic. Click here for more details.
- tryton/__init__.py +1 -1
- tryton/cache.py +34 -0
- tryton/common/common.py +149 -73
- tryton/common/completion.py +2 -2
- tryton/common/datetime_.py +3 -1
- tryton/common/domain_inversion.py +2 -1
- tryton/common/domain_parser.py +22 -11
- tryton/common/popup_menu.py +1 -1
- tryton/common/selection.py +6 -3
- tryton/common/tempfile.py +34 -0
- tryton/config.py +4 -5
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +69 -20
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +70 -25
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +68 -21
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +71 -26
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +68 -23
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +72 -22
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +73 -23
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +74 -25
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +63 -20
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +73 -28
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +75 -23
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +72 -23
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +73 -24
- tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.po +74 -25
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +73 -23
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +72 -27
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +113 -78
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +73 -24
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +87 -36
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +72 -25
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +77 -26
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +64 -20
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +75 -23
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +76 -24
- tryton/device_cookie.py +1 -1
- tryton/gui/main.py +14 -12
- tryton/gui/window/about.py +1 -1
- tryton/gui/window/dblogin.py +2 -2
- tryton/gui/window/email_.py +2 -2
- tryton/gui/window/form.py +10 -5
- tryton/gui/window/log.py +24 -2
- tryton/gui/window/tabcontent.py +2 -2
- tryton/gui/window/view_form/model/field.py +84 -34
- tryton/gui/window/view_form/model/group.py +7 -2
- tryton/gui/window/view_form/model/record.py +70 -31
- tryton/gui/window/view_form/screen/screen.py +98 -47
- tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
- tryton/gui/window/view_form/view/form.py +6 -12
- tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
- tryton/gui/window/view_form/view/form_gtk/dictionary.py +49 -29
- tryton/gui/window/view_form/view/form_gtk/document.py +15 -10
- tryton/gui/window/view_form/view/form_gtk/many2many.py +49 -7
- tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
- tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
- tryton/gui/window/view_form/view/form_gtk/one2many.py +42 -10
- tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
- tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
- tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
- tryton/gui/window/view_form/view/list.py +116 -48
- tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
- tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
- tryton/gui/window/view_form/view/screen_container.py +3 -5
- tryton/gui/window/win_csv.py +6 -12
- tryton/gui/window/win_export.py +49 -26
- tryton/gui/window/win_form.py +9 -7
- tryton/gui/window/win_import.py +45 -15
- tryton/gui/window/wizard.py +13 -10
- tryton/jsonrpc.py +75 -34
- tryton/plugins/__init__.py +5 -3
- tryton/pyson.py +57 -6
- tryton/rpc.py +18 -0
- tryton/tests/test_common_domain_parser.py +31 -2
- tryton/translate.py +5 -2
- {tryton-7.0.7.data → tryton-7.4.4.data}/scripts/tryton +8 -7
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/METADATA +6 -6
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/RECORD +105 -103
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/WHEEL +1 -1
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/top_level.txt +0 -0
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
3
|
import datetime
|
|
4
4
|
import decimal
|
|
5
|
+
import functools
|
|
5
6
|
import locale
|
|
6
7
|
import logging
|
|
7
8
|
import math
|
|
9
|
+
import operator
|
|
8
10
|
import os
|
|
9
|
-
import tempfile
|
|
10
11
|
from decimal import Decimal
|
|
11
12
|
from itertools import chain
|
|
13
|
+
from pathlib import Path
|
|
12
14
|
|
|
13
15
|
import tryton.common as common
|
|
14
16
|
from tryton.common import (
|
|
15
17
|
EvalEnvironment, RPCException, RPCExecute, concat, domain_inversion,
|
|
16
18
|
eval_domain, extract_reference_models, filter_leaf, inverse_leaf,
|
|
17
|
-
localize_domain, merge, prepare_reference_domain, simplify,
|
|
19
|
+
localize_domain, merge, prepare_reference_domain, simplify, tempfile,
|
|
20
|
+
unique_value)
|
|
18
21
|
from tryton.common.htmltextbuffer import guess_decode
|
|
19
22
|
from tryton.config import CONFIG
|
|
20
23
|
from tryton.pyson import PYSONDecoder
|
|
@@ -618,12 +621,10 @@ class O2MField(Field):
|
|
|
618
621
|
from .group import Group
|
|
619
622
|
parent_name = self.attrs.get('relation_field', '')
|
|
620
623
|
fields = fields or {}
|
|
621
|
-
context = record.expr_eval(self.attrs.get('context', {}))
|
|
622
624
|
group = Group(self.attrs['relation'], fields,
|
|
623
625
|
parent=record,
|
|
624
626
|
parent_name=parent_name,
|
|
625
627
|
child_name=self.name,
|
|
626
|
-
context=context,
|
|
627
628
|
parent_datetime_field=self.attrs.get('datetime_field'))
|
|
628
629
|
if not fields and record.model_name == self.attrs['relation']:
|
|
629
630
|
group.fields = record.group.fields
|
|
@@ -699,7 +700,8 @@ class O2MField(Field):
|
|
|
699
700
|
skip={self.attrs.get('relation_field', '')}))
|
|
700
701
|
return result
|
|
701
702
|
|
|
702
|
-
def _set_value(
|
|
703
|
+
def _set_value(
|
|
704
|
+
self, record, value, default=False, modified=False, data=None):
|
|
703
705
|
self._set_default_value(record)
|
|
704
706
|
group = record.value[self.name]
|
|
705
707
|
if value is None:
|
|
@@ -709,23 +711,42 @@ class O2MField(Field):
|
|
|
709
711
|
else:
|
|
710
712
|
mode = 'list values'
|
|
711
713
|
|
|
712
|
-
if mode == 'list values':
|
|
714
|
+
if mode == 'list values' or data:
|
|
713
715
|
context = self.get_context(record)
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
716
|
+
if mode == 'list values':
|
|
717
|
+
fields = set(f for v in value for f in v)
|
|
718
|
+
else:
|
|
719
|
+
fields = functools.reduce(
|
|
720
|
+
operator.or_, (d.keys() for d in data), set())
|
|
721
|
+
field_names = {f for f in fields
|
|
722
|
+
if (f not in group.fields
|
|
723
|
+
and '.' not in f
|
|
724
|
+
and ':' not in f
|
|
725
|
+
and not f.startswith('_'))}
|
|
726
|
+
attr_fields = functools.reduce(
|
|
727
|
+
operator.or_,
|
|
728
|
+
(v['fields'] for v in self.attrs.get('views', {}).values()),
|
|
729
|
+
{})
|
|
730
|
+
fields = {n: attr_fields[n]
|
|
731
|
+
for n in field_names
|
|
732
|
+
if n in attr_fields}
|
|
733
|
+
if to_fetch := (field_names - attr_fields.keys()):
|
|
717
734
|
try:
|
|
718
|
-
fields
|
|
719
|
-
'fields_get', list(
|
|
735
|
+
fields |= RPCExecute('model', self.attrs['relation'],
|
|
736
|
+
'fields_get', list(to_fetch), context=context)
|
|
720
737
|
except RPCException:
|
|
721
738
|
return
|
|
739
|
+
|
|
740
|
+
if fields:
|
|
722
741
|
group.load_fields(fields)
|
|
723
742
|
|
|
724
743
|
if mode == 'list ids':
|
|
725
744
|
records_to_remove = [r for r in group if r.id not in value]
|
|
726
745
|
for record_to_remove in records_to_remove:
|
|
727
746
|
group.remove(record_to_remove, remove=True, modified=False)
|
|
728
|
-
group.load(
|
|
747
|
+
group.load(
|
|
748
|
+
value, modified=modified or default,
|
|
749
|
+
preloaded={v['id']: v for v in (data or [])})
|
|
729
750
|
else:
|
|
730
751
|
for vals in value:
|
|
731
752
|
if 'id' in vals:
|
|
@@ -746,7 +767,7 @@ class O2MField(Field):
|
|
|
746
767
|
# Trigger modified only once
|
|
747
768
|
group.record_modified()
|
|
748
769
|
|
|
749
|
-
def set(self, record, value, _default=False):
|
|
770
|
+
def set(self, record, value, _default=False, preloaded=None):
|
|
750
771
|
group = record.value.get(self.name)
|
|
751
772
|
fields = {}
|
|
752
773
|
if group is not None:
|
|
@@ -766,7 +787,7 @@ class O2MField(Field):
|
|
|
766
787
|
|
|
767
788
|
# Prevent to trigger group-cleared
|
|
768
789
|
group.parent = None
|
|
769
|
-
self._set_value(record, value, default=_default)
|
|
790
|
+
self._set_value(record, value, default=_default, data=preloaded)
|
|
770
791
|
group.parent = record
|
|
771
792
|
|
|
772
793
|
def set_client(self, record, value, force_change=False):
|
|
@@ -1048,26 +1069,56 @@ class ReferenceField(Field):
|
|
|
1048
1069
|
|
|
1049
1070
|
|
|
1050
1071
|
class _FileCache(object):
|
|
1051
|
-
def __init__(self,
|
|
1052
|
-
|
|
1072
|
+
def __init__(self, data=None):
|
|
1073
|
+
_, filename = tempfile.mkstemp(prefix='tryton_')
|
|
1074
|
+
self.path = Path(filename)
|
|
1075
|
+
self.suffixes = {}
|
|
1076
|
+
if data:
|
|
1077
|
+
with open(self.path, 'wb') as fp:
|
|
1078
|
+
fp.write(data)
|
|
1079
|
+
|
|
1080
|
+
@property
|
|
1081
|
+
def data(self):
|
|
1082
|
+
with open(self.path, 'rb') as fp:
|
|
1083
|
+
return fp.read()
|
|
1053
1084
|
|
|
1054
1085
|
def __del__(self):
|
|
1055
1086
|
try:
|
|
1056
1087
|
os.remove(self.path)
|
|
1057
1088
|
except IOError:
|
|
1058
1089
|
pass
|
|
1090
|
+
for path in self.suffixes.values():
|
|
1091
|
+
try:
|
|
1092
|
+
os.remove(path)
|
|
1093
|
+
except IOError:
|
|
1094
|
+
pass
|
|
1095
|
+
|
|
1096
|
+
def with_suffix(self, suffix):
|
|
1097
|
+
if suffix in self.suffixes:
|
|
1098
|
+
return self.suffixes[suffix]
|
|
1099
|
+
_, filename = tempfile.mkstemp(prefix='tryton_', suffix=suffix)
|
|
1100
|
+
self.suffixes[suffix] = path = Path(filename)
|
|
1101
|
+
with open(path, 'wb') as fp:
|
|
1102
|
+
fp.write(self.data)
|
|
1103
|
+
return path
|
|
1059
1104
|
|
|
1060
1105
|
|
|
1061
1106
|
class BinaryField(Field):
|
|
1062
1107
|
|
|
1063
1108
|
_default = None
|
|
1064
1109
|
|
|
1110
|
+
def _set_file_cache(self, record, data):
|
|
1111
|
+
if isinstance(data, str):
|
|
1112
|
+
data = data.encode('utf-8')
|
|
1113
|
+
file_cache = _FileCache(data)
|
|
1114
|
+
self.set(record, file_cache)
|
|
1115
|
+
return file_cache
|
|
1116
|
+
|
|
1065
1117
|
def get(self, record):
|
|
1066
1118
|
result = record.value.get(self.name, self._default)
|
|
1067
1119
|
if isinstance(result, _FileCache):
|
|
1068
1120
|
try:
|
|
1069
|
-
|
|
1070
|
-
result = fp.read()
|
|
1121
|
+
result = result.data
|
|
1071
1122
|
except IOError:
|
|
1072
1123
|
result = self.get_data(record)
|
|
1073
1124
|
return result
|
|
@@ -1076,13 +1127,7 @@ class BinaryField(Field):
|
|
|
1076
1127
|
return self.get(record)
|
|
1077
1128
|
|
|
1078
1129
|
def set_client(self, record, value, force_change=False):
|
|
1079
|
-
|
|
1080
|
-
data = value or b''
|
|
1081
|
-
if isinstance(data, str):
|
|
1082
|
-
data = data.encode('utf-8')
|
|
1083
|
-
with open(filename, 'wb') as fp:
|
|
1084
|
-
fp.write(data)
|
|
1085
|
-
self.set(record, _FileCache(filename))
|
|
1130
|
+
self._set_file_cache(record, value or b'')
|
|
1086
1131
|
self.sig_changed(record)
|
|
1087
1132
|
record.validate(softvalidation=True)
|
|
1088
1133
|
record.set_modified(self.name)
|
|
@@ -1106,15 +1151,20 @@ class BinaryField(Field):
|
|
|
1106
1151
|
[record.id], [self.name], context=context)
|
|
1107
1152
|
except RPCException:
|
|
1108
1153
|
return b''
|
|
1109
|
-
|
|
1110
|
-
data = values[self.name] or b''
|
|
1111
|
-
if isinstance(data, str):
|
|
1112
|
-
data = data.encode('utf-8')
|
|
1113
|
-
with open(filename, 'wb') as fp:
|
|
1114
|
-
fp.write(data)
|
|
1115
|
-
self.set(record, _FileCache(filename))
|
|
1154
|
+
self._set_file_cache(record, values[self.name] or b'')
|
|
1116
1155
|
return self.get(record)
|
|
1117
1156
|
|
|
1157
|
+
def get_filename(self, record, suffix=None):
|
|
1158
|
+
data = self.get_data(record)
|
|
1159
|
+
file_cache = record.value.get(self.name)
|
|
1160
|
+
if not isinstance(file_cache, _FileCache):
|
|
1161
|
+
file_cache = self._set_file_cache(record, data)
|
|
1162
|
+
if suffix:
|
|
1163
|
+
filename = file_cache.with_suffix(suffix)
|
|
1164
|
+
else:
|
|
1165
|
+
filename = file_cache.path
|
|
1166
|
+
return filename
|
|
1167
|
+
|
|
1118
1168
|
|
|
1119
1169
|
class DictField(Field):
|
|
1120
1170
|
|
|
@@ -1126,10 +1176,10 @@ class DictField(Field):
|
|
|
1126
1176
|
self.keys = {}
|
|
1127
1177
|
|
|
1128
1178
|
def get(self, record):
|
|
1129
|
-
return super(
|
|
1179
|
+
return (super().get(record) or self._default).copy()
|
|
1130
1180
|
|
|
1131
1181
|
def get_client(self, record):
|
|
1132
|
-
return super(
|
|
1182
|
+
return super().get_client(record)
|
|
1133
1183
|
|
|
1134
1184
|
def validation_domains(self, record, pre_validate=None):
|
|
1135
1185
|
screen_domain, attr_domain = self.domains_get(record, pre_validate)
|
|
@@ -128,6 +128,7 @@ class Group(list):
|
|
|
128
128
|
self._group_list_changed('record-removed', record, idx)
|
|
129
129
|
super(Group, self).remove(record)
|
|
130
130
|
del self.__id2record[record.id]
|
|
131
|
+
record.destroy()
|
|
131
132
|
|
|
132
133
|
def clear(self):
|
|
133
134
|
# Use reversed order to minimize the cursor reposition as the cursor
|
|
@@ -239,7 +240,7 @@ class Group(list):
|
|
|
239
240
|
return []
|
|
240
241
|
return list({}.fromkeys(res))
|
|
241
242
|
|
|
242
|
-
def load(self, ids, modified=False, position=-1):
|
|
243
|
+
def load(self, ids, modified=False, position=-1, preloaded=None):
|
|
243
244
|
if not ids:
|
|
244
245
|
return True
|
|
245
246
|
|
|
@@ -256,6 +257,8 @@ class Group(list):
|
|
|
256
257
|
else:
|
|
257
258
|
self.insert(position, new_record)
|
|
258
259
|
position += 1
|
|
260
|
+
if preloaded and (already_loaded := preloaded.get(id)):
|
|
261
|
+
new_record.set(already_loaded, modified=False, validate=False)
|
|
259
262
|
new_records.append(new_record)
|
|
260
263
|
|
|
261
264
|
# Remove previously removed or deleted records
|
|
@@ -406,7 +409,9 @@ class Group(list):
|
|
|
406
409
|
if record not in self.record_deleted:
|
|
407
410
|
self.record_deleted.append(record)
|
|
408
411
|
record.modified_fields.setdefault('id')
|
|
409
|
-
if record.id < 0
|
|
412
|
+
if (record.id < 0
|
|
413
|
+
or (self.parent and self.parent.id < 0)
|
|
414
|
+
or force_remove):
|
|
410
415
|
self._remove(record)
|
|
411
416
|
|
|
412
417
|
if len(self):
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
import functools
|
|
3
4
|
import logging
|
|
5
|
+
import operator
|
|
4
6
|
|
|
5
7
|
import tryton.common as common
|
|
6
8
|
from tryton.common import RPCException, RPCExecute
|
|
@@ -12,6 +14,30 @@ from . import field as fields
|
|
|
12
14
|
logger = logging.getLogger(__name__)
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
def get_x2m_sub_fields(f_attrs, prefix):
|
|
18
|
+
if f_attrs.get('visible') and f_attrs.get('views'):
|
|
19
|
+
sub_fields = functools.reduce(
|
|
20
|
+
operator.or_,
|
|
21
|
+
(v.get('fields', {}) for v in f_attrs['views'].values()),
|
|
22
|
+
{})
|
|
23
|
+
x2m_sub_fields = []
|
|
24
|
+
for s_field, f_def in sub_fields.items():
|
|
25
|
+
x2m_sub_fields.append(f"{prefix}.{s_field}")
|
|
26
|
+
if f_def['type'] in {'many2one', 'one2one', 'reference'}:
|
|
27
|
+
x2m_sub_fields.append(f"{prefix}.{s_field}.rec_name")
|
|
28
|
+
elif f_def['type'] in {'selection', 'multiselection'}:
|
|
29
|
+
x2m_sub_fields.append(f"{prefix}.{s_field}:string")
|
|
30
|
+
elif f_def['type'] in {'one2many', 'many2many'}:
|
|
31
|
+
x2m_sub_fields.extend(
|
|
32
|
+
get_x2m_sub_fields(f_def, f"{prefix}.{s_field}"))
|
|
33
|
+
x2m_sub_fields.extend(
|
|
34
|
+
f"{prefix}.{f}"
|
|
35
|
+
for f in ['_timestamp', '_write', '_delete'])
|
|
36
|
+
return x2m_sub_fields
|
|
37
|
+
else:
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
|
|
15
41
|
class Record:
|
|
16
42
|
|
|
17
43
|
id = -1
|
|
@@ -73,12 +99,15 @@ class Record:
|
|
|
73
99
|
fields = ((fname, field)
|
|
74
100
|
for fname, field in self.group.fields.items()
|
|
75
101
|
if field.attrs.get('loading', 'eager') == 'eager')
|
|
102
|
+
views_operator = set.issubset
|
|
76
103
|
else:
|
|
77
104
|
fields = self.group.fields.items()
|
|
105
|
+
views_operator = set.intersection
|
|
78
106
|
|
|
79
107
|
fnames = [fname for fname, field in fields
|
|
80
108
|
if fname not in self._loaded
|
|
81
|
-
and (not views or (views
|
|
109
|
+
and (not views or views_operator(views, field.views))]
|
|
110
|
+
related_read_limit = 0
|
|
82
111
|
for fname in list(fnames):
|
|
83
112
|
f_attrs = self.group.fields[fname].attrs
|
|
84
113
|
if f_attrs['type'] in {'many2one', 'one2one', 'reference'}:
|
|
@@ -86,6 +115,11 @@ class Record:
|
|
|
86
115
|
elif (f_attrs['type'] in {'selection', 'multiselection'}
|
|
87
116
|
and f_attrs.get('loading', 'lazy') == 'eager'):
|
|
88
117
|
fnames.append('%s:string' % fname)
|
|
118
|
+
elif f_attrs['type'] in {'many2many', 'one2many'}:
|
|
119
|
+
sub_fields = get_x2m_sub_fields(f_attrs, fname)
|
|
120
|
+
fnames.extend(sub_fields)
|
|
121
|
+
if sub_fields:
|
|
122
|
+
related_read_limit += len(sub_fields)
|
|
89
123
|
if 'rec_name' not in fnames:
|
|
90
124
|
fnames.append('rec_name')
|
|
91
125
|
fnames.extend(['_timestamp', '_write', '_delete'])
|
|
@@ -132,21 +166,25 @@ class Record:
|
|
|
132
166
|
ctx.update(dict(('%s.%s' % (self.model_name, fname), 'size')
|
|
133
167
|
for fname, field in self.group.fields.items()
|
|
134
168
|
if field.attrs['type'] == 'binary' and fname in fnames))
|
|
135
|
-
|
|
169
|
+
if related_read_limit:
|
|
170
|
+
ctx['related_read_limit'] = (
|
|
171
|
+
CONFIG['client.limit'] // min(related_read_limit, 10))
|
|
136
172
|
try:
|
|
137
173
|
values = RPCExecute('model', self.model_name, 'read',
|
|
138
174
|
list(id2record.keys()), fnames, context=ctx,
|
|
139
175
|
process_exception=process_exception)
|
|
140
176
|
except RPCException:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
177
|
+
for record in id2record.values():
|
|
178
|
+
record.exception = True
|
|
179
|
+
if process_exception:
|
|
180
|
+
values = []
|
|
181
|
+
default_values = {f: None for f in fnames if f != 'id'}
|
|
182
|
+
for id in id2record:
|
|
183
|
+
values.append({'id': id, **default_values})
|
|
184
|
+
else:
|
|
185
|
+
raise
|
|
146
186
|
id2value = dict((value['id'], value) for value in values)
|
|
147
187
|
for id, record in id2record.items():
|
|
148
|
-
if not record.exception:
|
|
149
|
-
record.exception = exception
|
|
150
188
|
value = id2value.get(id)
|
|
151
189
|
if record and not record.destroyed and value:
|
|
152
190
|
for key in record.modified_fields:
|
|
@@ -261,20 +299,13 @@ class Record:
|
|
|
261
299
|
return self.group.fields
|
|
262
300
|
|
|
263
301
|
def _check_load(self, fields=None):
|
|
264
|
-
if
|
|
265
|
-
|
|
266
|
-
self.reload(fields)
|
|
267
|
-
return True
|
|
268
|
-
return False
|
|
269
|
-
if not self.loaded:
|
|
270
|
-
self.reload()
|
|
271
|
-
return True
|
|
272
|
-
return False
|
|
302
|
+
if not self.get_loaded(fields):
|
|
303
|
+
self.reload(fields)
|
|
273
304
|
|
|
274
305
|
def get_loaded(self, fields=None):
|
|
275
|
-
if fields:
|
|
276
|
-
|
|
277
|
-
return set(
|
|
306
|
+
if fields is None:
|
|
307
|
+
fields = self.group.fields.keys()
|
|
308
|
+
return set(fields) <= (self._loaded | set(self.modified_fields))
|
|
278
309
|
|
|
279
310
|
loaded = property(get_loaded)
|
|
280
311
|
|
|
@@ -375,6 +406,7 @@ class Record:
|
|
|
375
406
|
return self.id
|
|
376
407
|
|
|
377
408
|
def default_get(self, defaults=None):
|
|
409
|
+
vals = {}
|
|
378
410
|
if len(self.group.fields):
|
|
379
411
|
context = self.get_context()
|
|
380
412
|
if defaults is not None:
|
|
@@ -384,7 +416,7 @@ class Record:
|
|
|
384
416
|
vals = RPCExecute('model', self.model_name, 'default_get',
|
|
385
417
|
list(self.group.fields.keys()), context=context)
|
|
386
418
|
except RPCException:
|
|
387
|
-
return
|
|
419
|
+
return vals
|
|
388
420
|
if (self.parent
|
|
389
421
|
and self.parent_name in self.group.fields):
|
|
390
422
|
parent_field = self.group.fields[self.parent_name]
|
|
@@ -405,10 +437,7 @@ class Record:
|
|
|
405
437
|
return ''
|
|
406
438
|
|
|
407
439
|
def validate(self, fields=None, softvalidation=False, pre_validate=None):
|
|
408
|
-
|
|
409
|
-
self._check_load(fields)
|
|
410
|
-
elif fields is None:
|
|
411
|
-
self._check_load()
|
|
440
|
+
self._check_load(fields)
|
|
412
441
|
res = True
|
|
413
442
|
for field_name, field in list(self.group.fields.items()):
|
|
414
443
|
if fields is not None and field_name not in fields:
|
|
@@ -496,7 +525,9 @@ class Record:
|
|
|
496
525
|
self._loaded.add(fieldname)
|
|
497
526
|
fieldnames.append(fieldname)
|
|
498
527
|
for fieldname, value in later.items():
|
|
499
|
-
|
|
528
|
+
if isinstance(
|
|
529
|
+
field := self.group.fields[fieldname], fields.O2MField):
|
|
530
|
+
field.set(self, value, preloaded=val.get(f"{fieldname}."))
|
|
500
531
|
self._loaded.add(fieldname)
|
|
501
532
|
fieldnames.append(fieldname)
|
|
502
533
|
if validate:
|
|
@@ -576,6 +607,7 @@ class Record:
|
|
|
576
607
|
values.update(self._get_on_change_args(on_change))
|
|
577
608
|
|
|
578
609
|
if values:
|
|
610
|
+
values['id'] = self.id
|
|
579
611
|
try:
|
|
580
612
|
if len(fieldnames) == 1 or 'id' not in values:
|
|
581
613
|
changes = []
|
|
@@ -637,6 +669,7 @@ class Record:
|
|
|
637
669
|
'on_change_with_' + fieldname,
|
|
638
670
|
values, context=self.get_context()))
|
|
639
671
|
else:
|
|
672
|
+
values['id'] = self.id
|
|
640
673
|
changed = RPCExecute(
|
|
641
674
|
'model', self.model_name, 'on_change_with',
|
|
642
675
|
values, list(fieldnames), context=self.get_context())
|
|
@@ -659,6 +692,7 @@ class Record:
|
|
|
659
692
|
'on_change_with_' + fieldname,
|
|
660
693
|
values, context=self.get_context()))
|
|
661
694
|
else:
|
|
695
|
+
values['id'] = self.id
|
|
662
696
|
changed = RPCExecute(
|
|
663
697
|
'model', self.model_name, 'on_change_with',
|
|
664
698
|
values, list(later), context=self.get_context())
|
|
@@ -680,7 +714,8 @@ class Record:
|
|
|
680
714
|
try:
|
|
681
715
|
res = RPCExecute(
|
|
682
716
|
'model', self.model_name,
|
|
683
|
-
'autocomplete_' + fieldname, args, context=self.get_context()
|
|
717
|
+
'autocomplete_' + fieldname, args, context=self.get_context(),
|
|
718
|
+
process_exception=False)
|
|
684
719
|
except RPCException:
|
|
685
720
|
# ensure res is a list
|
|
686
721
|
res = []
|
|
@@ -689,9 +724,13 @@ class Record:
|
|
|
689
724
|
def on_scan_code(self, code, depends):
|
|
690
725
|
depends = self.expr_eval(depends)
|
|
691
726
|
values = self._get_on_change_args(depends)
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
727
|
+
values['id'] = self.id
|
|
728
|
+
try:
|
|
729
|
+
changes = RPCExecute(
|
|
730
|
+
'model', self.model_name, 'on_scan_code', values, code,
|
|
731
|
+
context=self.get_context(), process_exception=False)
|
|
732
|
+
except RPCException:
|
|
733
|
+
changes = []
|
|
695
734
|
self.set_on_change(changes)
|
|
696
735
|
self.set_modified()
|
|
697
736
|
return bool(changes)
|