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.

Files changed (105) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +149 -73
  4. tryton/common/completion.py +2 -2
  5. tryton/common/datetime_.py +3 -1
  6. tryton/common/domain_inversion.py +2 -1
  7. tryton/common/domain_parser.py +22 -11
  8. tryton/common/popup_menu.py +1 -1
  9. tryton/common/selection.py +6 -3
  10. tryton/common/tempfile.py +34 -0
  11. tryton/config.py +4 -5
  12. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  13. tryton/data/locale/bg/LC_MESSAGES/tryton.po +69 -20
  14. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  15. tryton/data/locale/ca/LC_MESSAGES/tryton.po +70 -25
  16. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  17. tryton/data/locale/cs/LC_MESSAGES/tryton.po +68 -21
  18. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  19. tryton/data/locale/de/LC_MESSAGES/tryton.po +71 -26
  20. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  21. tryton/data/locale/es/LC_MESSAGES/tryton.po +68 -23
  22. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  23. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +72 -22
  24. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  25. tryton/data/locale/et/LC_MESSAGES/tryton.po +73 -23
  26. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  27. tryton/data/locale/fa/LC_MESSAGES/tryton.po +74 -25
  28. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  29. tryton/data/locale/fi/LC_MESSAGES/tryton.po +63 -20
  30. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  31. tryton/data/locale/fr/LC_MESSAGES/tryton.po +73 -28
  32. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/hu/LC_MESSAGES/tryton.po +75 -23
  34. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/id/LC_MESSAGES/tryton.po +72 -23
  36. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/it/LC_MESSAGES/tryton.po +73 -24
  38. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lo/LC_MESSAGES/tryton.po +74 -25
  41. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  42. tryton/data/locale/lt/LC_MESSAGES/tryton.po +73 -23
  43. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  44. tryton/data/locale/nl/LC_MESSAGES/tryton.po +72 -27
  45. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  46. tryton/data/locale/pl/LC_MESSAGES/tryton.po +113 -78
  47. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  48. tryton/data/locale/pt/LC_MESSAGES/tryton.po +73 -24
  49. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  50. tryton/data/locale/ro/LC_MESSAGES/tryton.po +87 -36
  51. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  52. tryton/data/locale/ru/LC_MESSAGES/tryton.po +72 -25
  53. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  54. tryton/data/locale/sl/LC_MESSAGES/tryton.po +77 -26
  55. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  56. tryton/data/locale/tr/LC_MESSAGES/tryton.po +64 -20
  57. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  58. tryton/data/locale/uk/LC_MESSAGES/tryton.po +75 -23
  59. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  60. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +76 -24
  61. tryton/device_cookie.py +1 -1
  62. tryton/gui/main.py +14 -12
  63. tryton/gui/window/about.py +1 -1
  64. tryton/gui/window/dblogin.py +2 -2
  65. tryton/gui/window/email_.py +2 -2
  66. tryton/gui/window/form.py +10 -5
  67. tryton/gui/window/log.py +24 -2
  68. tryton/gui/window/tabcontent.py +2 -2
  69. tryton/gui/window/view_form/model/field.py +84 -34
  70. tryton/gui/window/view_form/model/group.py +7 -2
  71. tryton/gui/window/view_form/model/record.py +70 -31
  72. tryton/gui/window/view_form/screen/screen.py +98 -47
  73. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
  74. tryton/gui/window/view_form/view/form.py +6 -12
  75. tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
  76. tryton/gui/window/view_form/view/form_gtk/dictionary.py +49 -29
  77. tryton/gui/window/view_form/view/form_gtk/document.py +15 -10
  78. tryton/gui/window/view_form/view/form_gtk/many2many.py +49 -7
  79. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  80. tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
  81. tryton/gui/window/view_form/view/form_gtk/one2many.py +42 -10
  82. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  83. tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
  84. tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
  85. tryton/gui/window/view_form/view/list.py +116 -48
  86. tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
  87. tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
  88. tryton/gui/window/view_form/view/screen_container.py +3 -5
  89. tryton/gui/window/win_csv.py +6 -12
  90. tryton/gui/window/win_export.py +49 -26
  91. tryton/gui/window/win_form.py +9 -7
  92. tryton/gui/window/win_import.py +45 -15
  93. tryton/gui/window/wizard.py +13 -10
  94. tryton/jsonrpc.py +75 -34
  95. tryton/plugins/__init__.py +5 -3
  96. tryton/pyson.py +57 -6
  97. tryton/rpc.py +18 -0
  98. tryton/tests/test_common_domain_parser.py +31 -2
  99. tryton/translate.py +5 -2
  100. {tryton-7.0.7.data → tryton-7.4.4.data}/scripts/tryton +8 -7
  101. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/METADATA +6 -6
  102. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/RECORD +105 -103
  103. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/WHEEL +1 -1
  104. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
  105. {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, unique_value)
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(self, record, value, default=False, modified=False):
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
- field_names = set(f for v in value for f in v
715
- if f not in group.fields and '.' not in f)
716
- if field_names:
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 = RPCExecute('model', self.attrs['relation'],
719
- 'fields_get', list(field_names), context=context)
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(value, modified=modified or default)
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, path):
1052
- self.path = path
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
- with open(result.path, 'rb') as fp:
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
- _, filename = tempfile.mkstemp(prefix='tryton_')
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
- _, filename = tempfile.mkstemp(prefix='tryton_')
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(DictField, self).get(record) or self._default
1179
+ return (super().get(record) or self._default).copy()
1130
1180
 
1131
1181
  def get_client(self, record):
1132
- return super(DictField, self).get_client(record) or self._default
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 or force_remove:
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 & field.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
- exception = False
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
- values = [{'id': x} for x in id2record]
142
- default_values = dict((f, None) for f in fnames)
143
- for value in values:
144
- value.update(default_values)
145
- self.exception = exception = True
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 fields is not None:
265
- if not self.get_loaded(fields):
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
- return set(fields) <= (self._loaded | set(self.modified_fields))
277
- return set(self.group.fields.keys()) == self._loaded
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
- if isinstance(fields, list) and fields:
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
- self.group.fields[fieldname].set(self, value)
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
- changes = RPCExecute(
693
- 'model', self.model_name, 'on_scan_code', values, code,
694
- context=self.get_context(), process_exception=False)
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)