tryton 7.0.21__py3-none-any.whl → 7.2.0__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 (94) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +125 -69
  4. tryton/common/completion.py +2 -2
  5. tryton/common/domain_inversion.py +1 -2
  6. tryton/common/domain_parser.py +7 -17
  7. tryton/common/selection.py +6 -3
  8. tryton/common/tempfile.py +34 -0
  9. tryton/config.py +3 -2
  10. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  11. tryton/data/locale/bg/LC_MESSAGES/tryton.po +28 -3
  12. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  13. tryton/data/locale/ca/LC_MESSAGES/tryton.po +33 -5
  14. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  15. tryton/data/locale/cs/LC_MESSAGES/tryton.po +28 -3
  16. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  17. tryton/data/locale/de/LC_MESSAGES/tryton.po +32 -4
  18. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  19. tryton/data/locale/es/LC_MESSAGES/tryton.po +32 -4
  20. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  21. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +29 -4
  22. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  23. tryton/data/locale/et/LC_MESSAGES/tryton.po +32 -5
  24. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  25. tryton/data/locale/fa/LC_MESSAGES/tryton.po +32 -6
  26. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  27. tryton/data/locale/fi/LC_MESSAGES/tryton.po +28 -3
  28. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  29. tryton/data/locale/fr/LC_MESSAGES/tryton.po +32 -4
  30. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  31. tryton/data/locale/hu/LC_MESSAGES/tryton.po +32 -5
  32. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/id/LC_MESSAGES/tryton.po +30 -3
  34. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/it/LC_MESSAGES/tryton.po +31 -5
  36. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  38. tryton/data/locale/lo/LC_MESSAGES/tryton.po +31 -5
  39. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lt/LC_MESSAGES/tryton.po +32 -5
  41. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  42. tryton/data/locale/nl/LC_MESSAGES/tryton.po +32 -4
  43. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  44. tryton/data/locale/pl/LC_MESSAGES/tryton.po +32 -5
  45. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  46. tryton/data/locale/pt/LC_MESSAGES/tryton.po +31 -5
  47. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  48. tryton/data/locale/ro/LC_MESSAGES/tryton.po +43 -16
  49. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  50. tryton/data/locale/ru/LC_MESSAGES/tryton.po +29 -5
  51. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  52. tryton/data/locale/sl/LC_MESSAGES/tryton.po +32 -4
  53. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  54. tryton/data/locale/tr/LC_MESSAGES/tryton.po +28 -3
  55. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  56. tryton/data/locale/uk/LC_MESSAGES/tryton.po +32 -5
  57. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  58. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +32 -4
  59. tryton/device_cookie.py +1 -1
  60. tryton/gui/main.py +3 -2
  61. tryton/gui/window/about.py +1 -1
  62. tryton/gui/window/dblogin.py +2 -2
  63. tryton/gui/window/email_.py +1 -1
  64. tryton/gui/window/form.py +4 -3
  65. tryton/gui/window/log.py +24 -2
  66. tryton/gui/window/view_form/model/field.py +56 -62
  67. tryton/gui/window/view_form/model/group.py +3 -1
  68. tryton/gui/window/view_form/model/record.py +55 -16
  69. tryton/gui/window/view_form/screen/screen.py +22 -22
  70. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +7 -12
  71. tryton/gui/window/view_form/view/form.py +4 -14
  72. tryton/gui/window/view_form/view/form_gtk/binary.py +3 -3
  73. tryton/gui/window/view_form/view/form_gtk/dictionary.py +33 -27
  74. tryton/gui/window/view_form/view/form_gtk/document.py +10 -9
  75. tryton/gui/window/view_form/view/form_gtk/many2many.py +17 -7
  76. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  77. tryton/gui/window/view_form/view/form_gtk/one2many.py +25 -6
  78. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  79. tryton/gui/window/view_form/view/list.py +47 -56
  80. tryton/gui/window/view_form/view/list_gtk/widget.py +36 -23
  81. tryton/gui/window/view_form/view/screen_container.py +5 -3
  82. tryton/gui/window/win_export.py +1 -2
  83. tryton/gui/window/win_form.py +6 -8
  84. tryton/gui/window/wizard.py +11 -10
  85. tryton/jsonrpc.py +41 -27
  86. tryton/pyson.py +54 -4
  87. tryton/rpc.py +18 -0
  88. tryton/tests/test_common_domain_parser.py +0 -8
  89. {tryton-7.0.21.data → tryton-7.2.0.data}/scripts/tryton +1 -2
  90. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/METADATA +6 -20
  91. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/RECORD +94 -92
  92. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/WHEEL +1 -1
  93. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/LICENSE +0 -0
  94. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/top_level.txt +0 -0
@@ -29,6 +29,10 @@ msgstr "določi ime in vrata strežniškega gostitelja"
29
29
  msgid "disable thread usage"
30
30
  msgstr "onemogoči uporabo niti"
31
31
 
32
+ #, python-format
33
+ msgid "Invalid response id (%s) expected %s"
34
+ msgstr ""
35
+
32
36
  #, python-format
33
37
  msgid "Unable to set locale %s"
34
38
  msgstr "Ni možno nastaviti krajevnih nastavitev %s"
@@ -130,15 +134,27 @@ msgstr "Prenesi"
130
134
  msgid "Could not get a session."
131
135
  msgstr "Ni mogoče vzpostaviti seje."
132
136
 
133
- msgid "Too many requests. Try again later."
137
+ #, fuzzy, python-format
138
+ msgid "Error \"%s\". Try again later."
134
139
  msgstr "Preveč poslanih zahtev. Poskusite kasneje."
135
140
 
136
- msgid "Not found."
137
- msgstr "Ni najdeno."
141
+ msgid "Too many requests. Try again later."
142
+ msgstr "Preveč poslanih zahtev. Poskusite kasneje."
138
143
 
139
144
  msgid "Not Found."
140
145
  msgstr "Ni najdeno."
141
146
 
147
+ msgid "Reset forgotten password"
148
+ msgstr ""
149
+
150
+ msgid "Send you an email to reset your password."
151
+ msgstr ""
152
+
153
+ msgid ""
154
+ "A request to reset your password has been sent.\n"
155
+ "Please check your mailbox."
156
+ msgstr ""
157
+
142
158
  msgid "..."
143
159
  msgstr "..."
144
160
 
@@ -1085,7 +1101,19 @@ msgstr "Podoba je prevelika."
1085
1101
  msgid "Copy"
1086
1102
  msgstr "Kopiraj"
1087
1103
 
1088
- msgid "Paste"
1104
+ #, fuzzy
1105
+ msgid "Copy Row"
1106
+ msgstr "_Kopiraj URL"
1107
+
1108
+ #, fuzzy
1109
+ msgid "Copy Rows"
1110
+ msgstr "_Kopiraj URL"
1111
+
1112
+ msgid "Copy Column"
1113
+ msgstr ""
1114
+
1115
+ #, fuzzy
1116
+ msgid "Paste Rows"
1089
1117
  msgstr "Prilepi"
1090
1118
 
1091
1119
  msgid ".."
Binary file
@@ -29,6 +29,10 @@ msgstr "Server hostname belirle: port"
29
29
  msgid "disable thread usage"
30
30
  msgstr ""
31
31
 
32
+ #, python-format
33
+ msgid "Invalid response id (%s) expected %s"
34
+ msgstr ""
35
+
32
36
  #, python-format
33
37
  msgid "Unable to set locale %s"
34
38
  msgstr "Lokal %s ayarlanamadı"
@@ -131,15 +135,27 @@ msgstr ""
131
135
  msgid "Could not get a session."
132
136
  msgstr ""
133
137
 
134
- msgid "Too many requests. Try again later."
138
+ #, python-format
139
+ msgid "Error \"%s\". Try again later."
135
140
  msgstr ""
136
141
 
137
- msgid "Not found."
142
+ msgid "Too many requests. Try again later."
138
143
  msgstr ""
139
144
 
140
145
  msgid "Not Found."
141
146
  msgstr ""
142
147
 
148
+ msgid "Reset forgotten password"
149
+ msgstr ""
150
+
151
+ msgid "Send you an email to reset your password."
152
+ msgstr ""
153
+
154
+ msgid ""
155
+ "A request to reset your password has been sent.\n"
156
+ "Please check your mailbox."
157
+ msgstr ""
158
+
143
159
  msgid "..."
144
160
  msgstr "..."
145
161
 
@@ -1084,7 +1100,16 @@ msgstr ""
1084
1100
  msgid "Copy"
1085
1101
  msgstr ""
1086
1102
 
1087
- msgid "Paste"
1103
+ msgid "Copy Row"
1104
+ msgstr ""
1105
+
1106
+ msgid "Copy Rows"
1107
+ msgstr ""
1108
+
1109
+ msgid "Copy Column"
1110
+ msgstr ""
1111
+
1112
+ msgid "Paste Rows"
1088
1113
  msgstr ""
1089
1114
 
1090
1115
  msgid ".."
Binary file
@@ -26,6 +26,10 @@ msgstr "вкажіть ім'я сервера:порт"
26
26
  msgid "disable thread usage"
27
27
  msgstr ""
28
28
 
29
+ #, python-format
30
+ msgid "Invalid response id (%s) expected %s"
31
+ msgstr ""
32
+
29
33
  #, python-format
30
34
  msgid "Unable to set locale %s"
31
35
  msgstr "Неможливо встановити локалізацію %s"
@@ -127,17 +131,28 @@ msgstr "Завантажити"
127
131
  msgid "Could not get a session."
128
132
  msgstr "Не вдалося підключитися до сервера."
129
133
 
130
- msgid "Too many requests. Try again later."
134
+ #, fuzzy, python-format
135
+ msgid "Error \"%s\". Try again later."
131
136
  msgstr "Дуже багато запитів. Спробуйте ще раз пізніше."
132
137
 
133
- #, fuzzy
134
- msgid "Not found."
135
- msgstr "Нічого не знайдено."
138
+ msgid "Too many requests. Try again later."
139
+ msgstr "Дуже багато запитів. Спробуйте ще раз пізніше."
136
140
 
137
141
  #, fuzzy
138
142
  msgid "Not Found."
139
143
  msgstr "Нічого не знайдено."
140
144
 
145
+ msgid "Reset forgotten password"
146
+ msgstr ""
147
+
148
+ msgid "Send you an email to reset your password."
149
+ msgstr ""
150
+
151
+ msgid ""
152
+ "A request to reset your password has been sent.\n"
153
+ "Please check your mailbox."
154
+ msgstr ""
155
+
141
156
  msgid "..."
142
157
  msgstr "..."
143
158
 
@@ -1090,7 +1105,19 @@ msgstr "Розмір зображення завеликий."
1090
1105
  msgid "Copy"
1091
1106
  msgstr "Копіювати"
1092
1107
 
1093
- msgid "Paste"
1108
+ #, fuzzy
1109
+ msgid "Copy Row"
1110
+ msgstr "_Копіювати URL"
1111
+
1112
+ #, fuzzy
1113
+ msgid "Copy Rows"
1114
+ msgstr "_Копіювати URL"
1115
+
1116
+ msgid "Copy Column"
1117
+ msgstr ""
1118
+
1119
+ #, fuzzy
1120
+ msgid "Paste Rows"
1094
1121
  msgstr "Вставити"
1095
1122
 
1096
1123
  msgid ".."
@@ -29,6 +29,10 @@ msgstr "设置服务器主机名:端口号"
29
29
  msgid "disable thread usage"
30
30
  msgstr "禁用线程"
31
31
 
32
+ #, python-format
33
+ msgid "Invalid response id (%s) expected %s"
34
+ msgstr ""
35
+
32
36
  #, python-format
33
37
  msgid "Unable to set locale %s"
34
38
  msgstr "无法设置 locale %s"
@@ -129,15 +133,27 @@ msgstr "下载"
129
133
  msgid "Could not get a session."
130
134
  msgstr "无法获取会话。"
131
135
 
132
- msgid "Too many requests. Try again later."
136
+ #, fuzzy, python-format
137
+ msgid "Error \"%s\". Try again later."
133
138
  msgstr "请求太多,请稍后重试."
134
139
 
135
- msgid "Not found."
136
- msgstr "无符合条件的数据."
140
+ msgid "Too many requests. Try again later."
141
+ msgstr "请求太多,请稍后重试."
137
142
 
138
143
  msgid "Not Found."
139
144
  msgstr "无符合条件的数据."
140
145
 
146
+ msgid "Reset forgotten password"
147
+ msgstr ""
148
+
149
+ msgid "Send you an email to reset your password."
150
+ msgstr ""
151
+
152
+ msgid ""
153
+ "A request to reset your password has been sent.\n"
154
+ "Please check your mailbox."
155
+ msgstr ""
156
+
141
157
  msgid "..."
142
158
  msgstr "..."
143
159
 
@@ -1083,7 +1099,19 @@ msgstr "图片尺寸过大."
1083
1099
  msgid "Copy"
1084
1100
  msgstr "复制"
1085
1101
 
1086
- msgid "Paste"
1102
+ #, fuzzy
1103
+ msgid "Copy Row"
1104
+ msgstr "复制(_C)URL"
1105
+
1106
+ #, fuzzy
1107
+ msgid "Copy Rows"
1108
+ msgstr "复制(_C)URL"
1109
+
1110
+ msgid "Copy Column"
1111
+ msgstr ""
1112
+
1113
+ #, fuzzy
1114
+ msgid "Paste Rows"
1087
1115
  msgstr "粘贴"
1088
1116
 
1089
1117
  msgid ".."
tryton/device_cookie.py CHANGED
@@ -20,7 +20,7 @@ def renew():
20
20
  def set_cookie(new_cookie):
21
21
  try:
22
22
  new_cookie = new_cookie()
23
- except Exception:
23
+ except common.RPCException:
24
24
  logger.error("Cannot renew device cookie", exc_info=True)
25
25
  else:
26
26
  _set(new_cookie)
tryton/gui/main.py CHANGED
@@ -401,7 +401,8 @@ class Main(Gtk.Application):
401
401
 
402
402
  RPCExecute('model', 'ir.model', 'global_search', search_text,
403
403
  CONFIG['client.limit'], self.menu_screen.model_name,
404
- context=self.menu_screen.context, callback=set_result)
404
+ context=self.menu_screen.context, callback=set_result,
405
+ process_exception=False)
405
406
  return False
406
407
 
407
408
  def changed(widget):
@@ -470,7 +471,7 @@ class Main(Gtk.Application):
470
471
  favorites = RPCExecute('model',
471
472
  self.menu_screen.model_name + '.favorite', 'get',
472
473
  process_exception=False)
473
- except Exception:
474
+ except RPCException:
474
475
  return False
475
476
  for id_, name, icon in favorites:
476
477
  menuitem = Gtk.MenuItem(label=name)
@@ -11,7 +11,7 @@ from tryton.common import get_toplevel_window
11
11
  from tryton.config import CONFIG, PIXMAPS_DIR
12
12
 
13
13
  COPYRIGHT = '''\
14
- Copyright (C) 2004-2025 Tryton.
14
+ Copyright (C) 2004-2024 Tryton.
15
15
  '''
16
16
  AUTHORS = [
17
17
  'Bertrand Chenal <bertrand.chenal@b2ck.com>',
@@ -7,8 +7,8 @@ import logging
7
7
  import os
8
8
  import re
9
9
  import shutil
10
- import tempfile
11
10
  import threading
11
+ from tempfile import NamedTemporaryFile
12
12
 
13
13
  from gi.repository import GLib, GObject, Gtk
14
14
 
@@ -501,7 +501,7 @@ class DBLogin(object):
501
501
  # reset self.profiles as parsing errors may leave wrong data in
502
502
  # the parser
503
503
  self.profiles = configparser.ConfigParser()
504
- with tempfile.NamedTemporaryFile(
504
+ with NamedTemporaryFile(
505
505
  delete=False, prefix='profiles_', suffix='.cfg',
506
506
  dir=config_dir) as temp_file:
507
507
  temp_name = temp_file.name
@@ -74,7 +74,7 @@ class EmailEntry(Gtk.Entry):
74
74
  RPCExecute(
75
75
  'model', 'ir.email', 'complete', text, CONFIG['client.limit'],
76
76
  process_exception=False, callback=callback)
77
- except Exception:
77
+ except RPCException:
78
78
  logger.warning(
79
79
  _("Unable to complete email entry"), exc_info=True)
80
80
  return False
tryton/gui/window/form.py CHANGED
@@ -5,7 +5,6 @@ import csv
5
5
  import gettext
6
6
  import locale
7
7
  import os
8
- import tempfile
9
8
  from itertools import zip_longest
10
9
 
11
10
  from gi.repository import Gdk, GLib, Gtk
@@ -13,7 +12,7 @@ from gi.repository import Gdk, GLib, Gtk
13
12
  import tryton.common as common
14
13
  from tryton import plugins
15
14
  from tryton.action import Action
16
- from tryton.common import RPCException, RPCExecute, sur, sur_3b
15
+ from tryton.common import RPCException, RPCExecute, sur, sur_3b, tempfile
17
16
  from tryton.common.common import selection as selection_
18
17
  from tryton.common.popup_menu import popup
19
18
  from tryton.common.underline import set_underline
@@ -106,7 +105,9 @@ class Form(TabContent):
106
105
  def compare(self, model, attributes):
107
106
  if not attributes:
108
107
  return False
109
- return (self.model == model
108
+ return (
109
+ self.screen.view_index == 0
110
+ and self.model == model
110
111
  and self.res_id == attributes.get('res_id')
111
112
  and self.attributes.get('domain') == attributes.get('domain')
112
113
  and self.attributes.get('view_ids') == attributes.get('view_ids')
tryton/gui/window/log.py CHANGED
@@ -24,7 +24,8 @@ class Log(WinForm):
24
24
  log, = RPCExecute(
25
25
  'model', record.model_name, 'read', [record.id],
26
26
  ['create_uid.rec_name', 'create_date',
27
- 'write_uid.rec_name', 'write_date'], context=context)
27
+ 'write_uid.rec_name', 'write_date',
28
+ 'xml_id'], context=context)
28
29
  except RPCException:
29
30
  return
30
31
 
@@ -53,11 +54,32 @@ class Log(WinForm):
53
54
  label_id.set_mnemonic_widget(entry_id)
54
55
  grid.attach(label_id, 2, 1, 1, 1)
55
56
 
57
+ if log.get('xml_id'):
58
+ module, xml_id = log['xml_id'].split('.', 1)
59
+
60
+ entry_module = Gtk.Entry(editable=False)
61
+ entry_module.set_text(module)
62
+ grid.attach(entry_module, 1, 2, 1, 1)
63
+ label_module = Gtk.Label(
64
+ label=set_underline(_("Module:")),
65
+ use_underline=True, halign=Gtk.Align.END)
66
+ label_module.set_mnemonic_widget(entry_module)
67
+ grid.attach(label_module, 0, 2, 1, 1)
68
+
69
+ entry_xml_id = Gtk.Entry(editable=False)
70
+ entry_xml_id.set_text(xml_id)
71
+ grid.attach(entry_xml_id, 3, 2, 1, 1)
72
+ label_xml_id = Gtk.Label(
73
+ label=set_underline(_("XML ID:")),
74
+ use_underline=True, halign=Gtk.Align.END)
75
+ label_xml_id.set_mnemonic_widget(entry_xml_id)
76
+ grid.attach(label_xml_id, 2, 2, 1, 1)
77
+
56
78
  for i, (user, user_label, date, date_label) in enumerate([
57
79
  ('create_uid.', _("Created by:"),
58
80
  'create_date', _("Created at:")),
59
81
  ('write_uid.', _("Last Modified by:"),
60
- 'write_date', _("Last Modified at:"))], 2):
82
+ 'write_date', _("Last Modified at:"))], 3):
61
83
  entry_user = Gtk.Entry(editable=False, width_chars=50)
62
84
  user = log.get(user)
63
85
  if user:
@@ -2,20 +2,21 @@
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
12
- from pathlib import Path
13
13
 
14
14
  import tryton.common as common
15
15
  from tryton.common import (
16
16
  EvalEnvironment, RPCException, RPCExecute, concat, domain_inversion,
17
17
  eval_domain, extract_reference_models, filter_leaf, inverse_leaf,
18
- localize_domain, merge, prepare_reference_domain, simplify, unique_value)
18
+ localize_domain, merge, prepare_reference_domain, simplify, tempfile,
19
+ unique_value)
19
20
  from tryton.common.htmltextbuffer import guess_decode
20
21
  from tryton.config import CONFIG
21
22
  from tryton.pyson import PYSONDecoder
@@ -619,10 +620,12 @@ class O2MField(Field):
619
620
  from .group import Group
620
621
  parent_name = self.attrs.get('relation_field', '')
621
622
  fields = fields or {}
623
+ 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,
628
+ context=context,
626
629
  parent_datetime_field=self.attrs.get('datetime_field'))
627
630
  if not fields and record.model_name == self.attrs['relation']:
628
631
  group.fields = record.group.fields
@@ -698,7 +701,8 @@ class O2MField(Field):
698
701
  skip={self.attrs.get('relation_field', '')}))
699
702
  return result
700
703
 
701
- def _set_value(self, record, value, default=False, modified=False):
704
+ def _set_value(
705
+ self, record, value, default=False, modified=False, data=None):
702
706
  self._set_default_value(record)
703
707
  group = record.value[self.name]
704
708
  if value is None:
@@ -708,23 +712,42 @@ class O2MField(Field):
708
712
  else:
709
713
  mode = 'list values'
710
714
 
711
- if mode == 'list values':
715
+ if mode == 'list values' or data:
712
716
  context = self.get_context(record)
713
- field_names = set(f for v in value for f in v
714
- if f not in group.fields and '.' not in f)
715
- if field_names:
717
+ if mode == 'list values':
718
+ fields = set(f for v in value for f in v)
719
+ else:
720
+ fields = functools.reduce(
721
+ operator.or_, (d.keys() for d in data), set())
722
+ field_names = {f for f in fields
723
+ if (f not in group.fields
724
+ and '.' not in f
725
+ and ':' not in f
726
+ and not f.startswith('_'))}
727
+ attr_fields = functools.reduce(
728
+ operator.or_,
729
+ (v['fields'] for v in self.attrs.get('views', {}).values()),
730
+ {})
731
+ fields = {n: attr_fields[n]
732
+ for n in field_names
733
+ if n in attr_fields}
734
+ if to_fetch := (field_names - attr_fields.keys()):
716
735
  try:
717
- fields = RPCExecute('model', self.attrs['relation'],
718
- 'fields_get', list(field_names), context=context)
736
+ fields |= RPCExecute('model', self.attrs['relation'],
737
+ 'fields_get', list(to_fetch), context=context)
719
738
  except RPCException:
720
739
  return
740
+
741
+ if fields:
721
742
  group.load_fields(fields)
722
743
 
723
744
  if mode == 'list ids':
724
745
  records_to_remove = [r for r in group if r.id not in value]
725
746
  for record_to_remove in records_to_remove:
726
747
  group.remove(record_to_remove, remove=True, modified=False)
727
- group.load(value, modified=modified or default)
748
+ group.load(
749
+ value, modified=modified or default,
750
+ preloaded={v['id']: v for v in (data or [])})
728
751
  else:
729
752
  for vals in value:
730
753
  if 'id' in vals:
@@ -745,7 +768,7 @@ class O2MField(Field):
745
768
  # Trigger modified only once
746
769
  group.record_modified()
747
770
 
748
- def set(self, record, value, _default=False):
771
+ def set(self, record, value, _default=False, preloaded=None):
749
772
  group = record.value.get(self.name)
750
773
  fields = {}
751
774
  if group is not None:
@@ -765,7 +788,7 @@ class O2MField(Field):
765
788
 
766
789
  # Prevent to trigger group-cleared
767
790
  group.parent = None
768
- self._set_value(record, value, default=_default)
791
+ self._set_value(record, value, default=_default, data=preloaded)
769
792
  group.parent = record
770
793
 
771
794
  def set_client(self, record, value, force_change=False):
@@ -1047,56 +1070,26 @@ class ReferenceField(Field):
1047
1070
 
1048
1071
 
1049
1072
  class _FileCache(object):
1050
- def __init__(self, data=None):
1051
- _, filename = tempfile.mkstemp(prefix='tryton_')
1052
- self.path = Path(filename)
1053
- self.suffixes = {}
1054
- if data:
1055
- with open(self.path, 'wb') as fp:
1056
- fp.write(data)
1057
-
1058
- @property
1059
- def data(self):
1060
- with open(self.path, 'rb') as fp:
1061
- return fp.read()
1073
+ def __init__(self, path):
1074
+ self.path = path
1062
1075
 
1063
1076
  def __del__(self):
1064
1077
  try:
1065
1078
  os.remove(self.path)
1066
1079
  except IOError:
1067
1080
  pass
1068
- for path in self.suffixes.values():
1069
- try:
1070
- os.remove(path)
1071
- except IOError:
1072
- pass
1073
-
1074
- def with_suffix(self, suffix):
1075
- if suffix in self.suffixes:
1076
- return self.suffixes[suffix]
1077
- _, filename = tempfile.mkstemp(prefix='tryton_', suffix=suffix)
1078
- self.suffixes[suffix] = path = Path(filename)
1079
- with open(path, 'wb') as fp:
1080
- fp.write(self.data)
1081
- return path
1082
1081
 
1083
1082
 
1084
1083
  class BinaryField(Field):
1085
1084
 
1086
1085
  _default = None
1087
1086
 
1088
- def _set_file_cache(self, record, data):
1089
- if isinstance(data, str):
1090
- data = data.encode('utf-8')
1091
- file_cache = _FileCache(data)
1092
- self.set(record, file_cache)
1093
- return file_cache
1094
-
1095
1087
  def get(self, record):
1096
1088
  result = record.value.get(self.name, self._default)
1097
1089
  if isinstance(result, _FileCache):
1098
1090
  try:
1099
- result = result.data
1091
+ with open(result.path, 'rb') as fp:
1092
+ result = fp.read()
1100
1093
  except IOError:
1101
1094
  result = self.get_data(record)
1102
1095
  return result
@@ -1105,7 +1098,13 @@ class BinaryField(Field):
1105
1098
  return self.get(record)
1106
1099
 
1107
1100
  def set_client(self, record, value, force_change=False):
1108
- self._set_file_cache(record, value or b'')
1101
+ _, filename = tempfile.mkstemp(prefix='tryton_')
1102
+ data = value or b''
1103
+ if isinstance(data, str):
1104
+ data = data.encode('utf-8')
1105
+ with open(filename, 'wb') as fp:
1106
+ fp.write(data)
1107
+ self.set(record, _FileCache(filename))
1109
1108
  self.sig_changed(record)
1110
1109
  record.validate(softvalidation=True)
1111
1110
  record.set_modified(self.name)
@@ -1129,20 +1128,15 @@ class BinaryField(Field):
1129
1128
  [record.id], [self.name], context=context)
1130
1129
  except RPCException:
1131
1130
  return b''
1132
- self._set_file_cache(record, values[self.name] or b'')
1131
+ _, filename = tempfile.mkstemp(prefix='tryton_')
1132
+ data = values[self.name] or b''
1133
+ if isinstance(data, str):
1134
+ data = data.encode('utf-8')
1135
+ with open(filename, 'wb') as fp:
1136
+ fp.write(data)
1137
+ self.set(record, _FileCache(filename))
1133
1138
  return self.get(record)
1134
1139
 
1135
- def get_filename(self, record, suffix=None):
1136
- data = self.get_data(record)
1137
- file_cache = record.value.get(self.name)
1138
- if not isinstance(file_cache, _FileCache):
1139
- file_cache = self._set_file_cache(record, data)
1140
- if suffix:
1141
- filename = file_cache.with_suffix(suffix)
1142
- else:
1143
- filename = file_cache.path
1144
- return filename
1145
-
1146
1140
 
1147
1141
  class DictField(Field):
1148
1142
 
@@ -1154,10 +1148,10 @@ class DictField(Field):
1154
1148
  self.keys = {}
1155
1149
 
1156
1150
  def get(self, record):
1157
- return (super().get(record) or self._default).copy()
1151
+ return super(DictField, self).get(record) or self._default
1158
1152
 
1159
1153
  def get_client(self, record):
1160
- return super().get_client(record)
1154
+ return super(DictField, self).get_client(record) or self._default
1161
1155
 
1162
1156
  def validation_domains(self, record, pre_validate=None):
1163
1157
  screen_domain, attr_domain = self.domains_get(record, pre_validate)
@@ -239,7 +239,7 @@ class Group(list):
239
239
  return []
240
240
  return list({}.fromkeys(res))
241
241
 
242
- def load(self, ids, modified=False, position=-1):
242
+ def load(self, ids, modified=False, position=-1, preloaded=None):
243
243
  if not ids:
244
244
  return True
245
245
 
@@ -256,6 +256,8 @@ class Group(list):
256
256
  else:
257
257
  self.insert(position, new_record)
258
258
  position += 1
259
+ if preloaded and (already_loaded := preloaded.get(id)):
260
+ new_record.set(already_loaded, modified=False, validate=False)
259
261
  new_records.append(new_record)
260
262
 
261
263
  # Remove previously removed or deleted records