tryton 6.6.8__py3-none-any.whl → 6.8.1__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 (92) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/action/main.py +32 -43
  3. tryton/bus.py +2 -0
  4. tryton/client.py +3 -0
  5. tryton/common/button.py +3 -1
  6. tryton/common/common.py +55 -43
  7. tryton/common/datetime_.py +14 -2
  8. tryton/common/domain_inversion.py +10 -10
  9. tryton/common/domain_parser.py +5 -2
  10. tryton/common/popup_menu.py +7 -0
  11. tryton/common/selection.py +3 -1
  12. tryton/config.py +22 -5
  13. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  14. tryton/data/locale/bg/LC_MESSAGES/tryton.po +45 -39
  15. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  16. tryton/data/locale/ca/LC_MESSAGES/tryton.po +41 -35
  17. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  18. tryton/data/locale/cs/LC_MESSAGES/tryton.po +46 -39
  19. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  20. tryton/data/locale/de/LC_MESSAGES/tryton.po +41 -35
  21. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  22. tryton/data/locale/es/LC_MESSAGES/tryton.po +41 -35
  23. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  24. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +171 -167
  25. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  26. tryton/data/locale/et/LC_MESSAGES/tryton.po +47 -39
  27. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  28. tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -38
  29. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  30. tryton/data/locale/fi/LC_MESSAGES/tryton.po +38 -32
  31. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  32. tryton/data/locale/fr/LC_MESSAGES/tryton.po +42 -36
  33. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  34. tryton/data/locale/hu/LC_MESSAGES/tryton.po +44 -34
  35. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  36. tryton/data/locale/id/LC_MESSAGES/tryton.po +40 -34
  37. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  38. tryton/data/locale/it/LC_MESSAGES/tryton.po +44 -34
  39. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/lo/LC_MESSAGES/tryton.po +46 -38
  42. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  43. tryton/data/locale/lt/LC_MESSAGES/tryton.po +47 -37
  44. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  45. tryton/data/locale/nl/LC_MESSAGES/tryton.po +41 -35
  46. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  47. tryton/data/locale/pl/LC_MESSAGES/tryton.po +45 -35
  48. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  49. tryton/data/locale/pt/LC_MESSAGES/tryton.po +46 -38
  50. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  51. tryton/data/locale/ro/LC_MESSAGES/tryton.po +50 -48
  52. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  53. tryton/data/locale/ru/LC_MESSAGES/tryton.po +45 -39
  54. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  55. tryton/data/locale/sl/LC_MESSAGES/tryton.po +47 -38
  56. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  57. tryton/data/locale/tr/LC_MESSAGES/tryton.po +39 -33
  58. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  59. tryton/data/locale/uk/LC_MESSAGES/tryton.po +43 -35
  60. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  61. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +45 -35
  62. tryton/data/pixmaps/tryton/tryton-icon.svg +1 -0
  63. tryton/gui/main.py +54 -61
  64. tryton/gui/window/dblogin.py +27 -10
  65. tryton/gui/window/form.py +21 -53
  66. tryton/gui/window/infobar.py +9 -4
  67. tryton/gui/window/log.py +95 -0
  68. tryton/gui/window/view_board/action.py +0 -4
  69. tryton/gui/window/view_form/model/field.py +36 -14
  70. tryton/gui/window/view_form/model/record.py +22 -9
  71. tryton/gui/window/view_form/screen/screen.py +45 -76
  72. tryton/gui/window/view_form/view/calendar_.py +24 -11
  73. tryton/gui/window/view_form/view/calendar_gtk/toolbar.py +6 -5
  74. tryton/gui/window/view_form/view/form.py +14 -5
  75. tryton/gui/window/view_form/view/form_gtk/many2many.py +10 -1
  76. tryton/gui/window/view_form/view/form_gtk/many2one.py +1 -0
  77. tryton/gui/window/view_form/view/form_gtk/one2many.py +7 -7
  78. tryton/gui/window/view_form/view/form_gtk/textbox.py +0 -2
  79. tryton/gui/window/view_form/view/form_gtk/widget.py +8 -10
  80. tryton/gui/window/view_form/view/list_form.py +61 -5
  81. tryton/gui/window/view_form/view/list_gtk/editabletree.py +13 -3
  82. tryton/gui/window/view_form/view/list_gtk/widget.py +97 -27
  83. tryton/gui/window/win_form.py +6 -5
  84. tryton/rpc.py +13 -15
  85. tryton/tests/test_common.py +46 -0
  86. tryton/tests/test_common_domain_parser.py +24 -24
  87. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/METADATA +6 -6
  88. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/RECORD +92 -89
  89. {tryton-6.6.8.data → tryton-6.8.1.data}/scripts/tryton +0 -0
  90. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/LICENSE +0 -0
  91. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/WHEEL +0 -0
  92. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/top_level.txt +0 -0
tryton/gui/window/form.py CHANGED
@@ -2,7 +2,6 @@
2
2
  # this repository contains the full copyright notices and license terms.
3
3
  "Form"
4
4
  import csv
5
- import datetime
6
5
  import gettext
7
6
  import locale
8
7
  import os
@@ -14,8 +13,7 @@ from gi.repository import Gdk, GLib, Gtk
14
13
  import tryton.common as common
15
14
  from tryton import plugins
16
15
  from tryton.action import Action
17
- from tryton.common import (
18
- RPCException, RPCExecute, message, sur, sur_3b, timezoned_date)
16
+ from tryton.common import RPCException, RPCExecute, sur, sur_3b
19
17
  from tryton.common.common import selection as selection_
20
18
  from tryton.common.popup_menu import popup
21
19
  from tryton.common.underline import set_underline
@@ -23,6 +21,7 @@ from tryton.gui import Main
23
21
  from tryton.gui.window import Window
24
22
  from tryton.gui.window.attachment import Attachment
25
23
  from tryton.gui.window.email_ import Email
24
+ from tryton.gui.window.log import Log
26
25
  from tryton.gui.window.note import Note
27
26
  from tryton.gui.window.revision import Revision
28
27
  from tryton.gui.window.view_form.screen import Screen
@@ -75,7 +74,7 @@ class Form(TabContent):
75
74
  self.screen.search_filter()
76
75
 
77
76
  self.update_revision()
78
- self.activate_save()
77
+ self.set_buttons_sensitive()
79
78
 
80
79
  def get_toolbars(self):
81
80
  try:
@@ -295,37 +294,8 @@ class Form(TabContent):
295
294
  if not current_record or current_record.id < 0:
296
295
  self.info_bar_add(
297
296
  _('You have to select one record.'), Gtk.MessageType.INFO)
298
- return False
299
-
300
- fields = [
301
- ('id', _('ID:')),
302
- ('create_uid.rec_name', _('Created by:')),
303
- ('create_date', _('Created at:')),
304
- ('write_uid.rec_name', _('Edited by:')),
305
- ('write_date', _('Edited at:')),
306
- ]
307
-
308
- try:
309
- data = RPCExecute('model', self.model, 'read', [current_record.id],
310
- [x[0] for x in fields], context=self.screen.context)[0]
311
- except RPCException:
312
297
  return
313
- date_format = self.screen.context.get('date_format', '%x')
314
- datetime_format = date_format + ' %H:%M:%S.%f'
315
- message_str = ''
316
- for (key, label) in fields:
317
- value = data
318
- keys = key.split('.')
319
- name = keys.pop(-1)
320
- for key in keys:
321
- value = value.get(key + '.', {})
322
- value = (value or {}).get(name, '/')
323
- if isinstance(value, datetime.datetime):
324
- value = timezoned_date(value).strftime(datetime_format)
325
- message_str += '%s %s\n' % (label, value)
326
- message_str += _('Model:') + ' ' + self.model
327
- message(message_str)
328
- return True
298
+ Log(current_record)
329
299
 
330
300
  def sig_revision(self, widget=None):
331
301
  if not self.modified_save():
@@ -374,24 +344,29 @@ class Form(TabContent):
374
344
  tooltip = self.name
375
345
  self.title.set_text(label)
376
346
  tooltips.set_tip(self.title, tooltip)
377
- self.set_buttons_sensitive(revision)
347
+ self.set_buttons_sensitive()
378
348
 
379
- def set_buttons_sensitive(self, revision=None):
349
+ def set_buttons_sensitive(self):
350
+ revision = self.screen.context.get('_datetime')
380
351
  if not revision:
381
352
  access = common.MODELACCESS[self.model]
353
+ modified = self.screen.modified()
382
354
  for name, sensitive in [
383
- ('new', access['create']),
384
- ('save', access['create'] or access['write']),
355
+ ('new', access['create'] and not modified),
356
+ ('save',
357
+ (access['create'] or access['write'])
358
+ and modified and not self.screen.readonly),
385
359
  ('remove', access['delete']),
386
360
  ('copy', access['create']),
387
361
  ('import', access['create']),
362
+ ('action', access['write'] and not self.screen.readonly),
388
363
  ]:
389
364
  if name in self.buttons:
390
365
  self.buttons[name].props.sensitive = sensitive
391
366
  if name in self.menu_buttons:
392
367
  self.menu_buttons[name].props.sensitive = sensitive
393
368
  else:
394
- for name in ['new', 'save', 'remove', 'copy', 'import']:
369
+ for name in ['new', 'save', 'remove', 'copy', 'import', 'action']:
395
370
  if name in self.buttons:
396
371
  self.buttons[name].props.sensitive = False
397
372
  if name in self.menu_buttons:
@@ -467,7 +442,7 @@ class Form(TabContent):
467
442
  return
468
443
  self.screen.new()
469
444
  self.info_bar_clear()
470
- self.activate_save()
445
+ self.set_buttons_sensitive()
471
446
 
472
447
  def sig_copy(self, widget=None):
473
448
  if not common.MODELACCESS[self.model]['create']:
@@ -502,14 +477,14 @@ class Form(TabContent):
502
477
  return
503
478
  self.screen.display_prev()
504
479
  self.info_bar_clear()
505
- self.activate_save()
480
+ self.set_buttons_sensitive()
506
481
 
507
482
  def sig_next(self, widget=None):
508
483
  if not self.modified_save():
509
484
  return
510
485
  self.screen.display_next()
511
486
  self.info_bar_clear()
512
- self.activate_save()
487
+ self.set_buttons_sensitive()
513
488
 
514
489
  def sig_reload(self, test_modified=True):
515
490
  if test_modified:
@@ -531,7 +506,6 @@ class Form(TabContent):
531
506
  self.screen.display(set_cursor=set_cursor)
532
507
  self.info_bar_clear()
533
508
  self.set_buttons_sensitive()
534
- self.activate_save()
535
509
  self.screen.count_tab_domain()
536
510
  return True
537
511
 
@@ -601,8 +575,7 @@ class Form(TabContent):
601
575
  selected = len(self.screen.selected_records)
602
576
  if selected > 1:
603
577
  name += '#%i' % selected
604
- for button_id in ('print', 'relate', 'email', 'open', 'save',
605
- 'attach'):
578
+ for button_id in ['print', 'relate', 'email', 'open', 'attach']:
606
579
  button = self.buttons[button_id]
607
580
  can_be_sensitive = getattr(button, '_can_be_sensitive', True)
608
581
  if button_id in {'print', 'relate', 'email', 'open'}:
@@ -612,8 +585,6 @@ class Form(TabContent):
612
585
  can_be_sensitive |= any(
613
586
  b.attrs.get('keyword', 'action') == action_type
614
587
  for b in self.screen.get_buttons())
615
- elif button_id == 'save':
616
- can_be_sensitive &= not self.screen.readonly
617
588
  set_sensitive(button_id, bool(position) and can_be_sensitive)
618
589
  set_sensitive('switch', self.screen.number_of_views > 1)
619
590
  set_sensitive('remove', self.screen.deletable)
@@ -629,7 +600,7 @@ class Form(TabContent):
629
600
  msg = "%s/%s" % (name, common.humanize(size))
630
601
  self.status_label.set_text(msg)
631
602
  self.info_bar_clear()
632
- self.activate_save()
603
+ self.set_buttons_sensitive()
633
604
  self.refresh_attachment_preview()
634
605
 
635
606
  def record_modified(self):
@@ -637,12 +608,12 @@ class Form(TabContent):
637
608
  # As it is called via idle_add, the form could have been destroyed
638
609
  # in the meantime.
639
610
  if self.widget_get().props.window:
640
- self.activate_save()
611
+ self.set_buttons_sensitive()
641
612
  GLib.idle_add(_record_modified)
642
613
  self.info_bar_refresh()
643
614
 
644
615
  def record_saved(self):
645
- self.activate_save()
616
+ self.set_buttons_sensitive()
646
617
  self.refresh_resources()
647
618
 
648
619
  def modified_save(self):
@@ -693,9 +664,6 @@ class Form(TabContent):
693
664
  }
694
665
  Action.execute(action, data, context=self.screen.local_context)
695
666
 
696
- def activate_save(self):
697
- self.buttons['save'].props.sensitive = self.screen.modified()
698
-
699
667
  def sig_win_close(self, widget):
700
668
  Main().sig_win_close(widget)
701
669
 
@@ -12,9 +12,10 @@ class InfoBar(object):
12
12
  self.__box = Gtk.VBox()
13
13
  self.__box.show()
14
14
  self.__messages = set()
15
+ self.__kinds = {}
15
16
  return self.__box
16
17
 
17
- def info_bar_add(self, message, type_=Gtk.MessageType.ERROR):
18
+ def info_bar_add(self, message, type_=Gtk.MessageType.ERROR, kind=None):
18
19
  if not message:
19
20
  return
20
21
  key = (message, type_)
@@ -27,15 +28,19 @@ class InfoBar(object):
27
28
  info_bar.connect('response', self.__response, key)
28
29
  info_bar.set_message_type(type_)
29
30
  info_bar.show_all()
31
+ self.__kinds[info_bar] = kind
30
32
 
31
33
  def __response(self, widget, response, key):
32
34
  self.__messages.add(key)
33
35
  self.__box.remove(widget)
34
36
 
35
- def info_bar_refresh(self):
37
+ def info_bar_refresh(self, kind=None):
36
38
  for child in self.__box.get_children():
37
- self.__box.remove(child)
39
+ if self.__kinds[child] == kind:
40
+ self.__box.remove(child)
41
+ del self.__kinds[child]
38
42
 
39
43
  def info_bar_clear(self):
40
- self.info_bar_refresh()
44
+ for kind in set(self.__kinds.values()):
45
+ self.info_bar_refresh(kind=kind)
41
46
  self.__messages.clear()
@@ -0,0 +1,95 @@
1
+ # This file is part of Tryton. The COPYRIGHT file at the top level of
2
+ # this repository contains the full copyright notices and license terms.
3
+
4
+ import gettext
5
+
6
+ from gi.repository import Gtk
7
+
8
+ from tryton.common import RPCException, RPCExecute, timezoned_date
9
+ from tryton.common.underline import set_underline
10
+ from tryton.gui.window.view_form.screen import Screen
11
+ from tryton.gui.window.win_form import WinForm
12
+
13
+ _ = gettext.gettext
14
+
15
+
16
+ class Log(WinForm):
17
+
18
+ def __init__(self, record):
19
+ self.resource = '%s,%s' % (record.model_name, record.id)
20
+ title = _("Logs (%s)") % record.rec_name()
21
+
22
+ context = record.get_context()
23
+ try:
24
+ log, = RPCExecute(
25
+ 'model', record.model_name, 'read', [record.id],
26
+ ['create_uid.rec_name', 'create_date',
27
+ 'write_uid.rec_name', 'write_date'], context=context)
28
+ except RPCException:
29
+ return
30
+
31
+ date_format = context.get('date_format', '%x')
32
+ datetime_format = date_format + ' %H:%M:%S.%f'
33
+
34
+ grid = Gtk.Grid(
35
+ column_spacing=3, row_spacing=3, border_width=3)
36
+
37
+ entry_model = Gtk.Entry(editable=False)
38
+ entry_model.set_text(record.model_name)
39
+ grid.attach(entry_model, 1, 1, 1, 1)
40
+ label_model = Gtk.Label(
41
+ label=set_underline(_("Model:")),
42
+ use_underline=True, halign=Gtk.Align.END)
43
+ label_model.set_mnemonic_widget(entry_model)
44
+ grid.attach(label_model, 0, 1, 1, 1)
45
+
46
+ entry_id = Gtk.Entry(editable=False)
47
+ entry_id.set_alignment(1)
48
+ entry_id.set_text(str(record.id))
49
+ grid.attach(entry_id, 3, 1, 1, 1)
50
+ label_id = Gtk.Label(
51
+ label=set_underline(_("ID:")),
52
+ use_underline=True, halign=Gtk.Align.END)
53
+ label_id.set_mnemonic_widget(entry_id)
54
+ grid.attach(label_id, 2, 1, 1, 1)
55
+
56
+ for i, (user, user_label, date, date_label) in enumerate([
57
+ ('create_uid.', _("Created by:"),
58
+ 'create_date', _("Created at:")),
59
+ ('write_uid.', _("Last Modified by:"),
60
+ 'write_date', _("Last Modified at:"))], 2):
61
+ entry_user = Gtk.Entry(editable=False, width_chars=50)
62
+ user = log.get(user)
63
+ if user:
64
+ user = user.get('rec_name', '')
65
+ entry_user.set_text(user or '')
66
+ grid.attach(entry_user, 1, i, 1, 1)
67
+ label_user = Gtk.Label(
68
+ label=set_underline(user_label),
69
+ use_underline=True, halign=Gtk.Align.END)
70
+ label_user.set_mnemonic_widget(entry_user)
71
+ grid.attach(label_user, 0, i, 1, 1)
72
+
73
+ entry_date = Gtk.Entry(editable=False)
74
+ date = log.get(date)
75
+ if date:
76
+ date = timezoned_date(date).strftime(datetime_format)
77
+ entry_date.set_width_chars(len(date or ''))
78
+ entry_date.set_text(date or '')
79
+ grid.attach(entry_date, 3, i, 1, 1)
80
+ label_date = Gtk.Label(
81
+ label=set_underline(date_label),
82
+ use_underline=True, halign=Gtk.Align.END)
83
+ label_date.set_mnemonic_widget(entry_date)
84
+ grid.attach(label_date, 2, i, 1, 1)
85
+
86
+ grid.show_all()
87
+
88
+ screen = Screen('ir.model.log', domain=[
89
+ ('resource', '=', self.resource),
90
+ ], mode=['tree', 'form'])
91
+ super().__init__(screen, view_type='tree', title=title)
92
+ screen.search_filter()
93
+
94
+ self.win.vbox.pack_start(grid, expand=False, fill=True, padding=0)
95
+ self.win.vbox.reorder_child(grid, 2)
@@ -122,10 +122,6 @@ class Action:
122
122
  self.screen.current_record.cancel()
123
123
  WinForm(self.screen, callback, title=self.title.get_text())
124
124
 
125
- def set_value(self, mode, model_field):
126
- self.screen.current_view.set_value()
127
- return True
128
-
129
125
  def display(self):
130
126
  self.screen.search_filter(self.screen.screen_container.get_text())
131
127
 
@@ -95,8 +95,11 @@ class Field(object):
95
95
  def validate(self, record, softvalidation=False, pre_validate=None):
96
96
  if self.attrs.get('readonly'):
97
97
  return True
98
+ state_attrs = self.get_state_attrs(record)
99
+ is_required = bool(int(state_attrs.get('required') or 0))
100
+ is_invisible = bool(int(state_attrs.get('invisible') or 0))
98
101
  invalid = False
99
- self.get_state_attrs(record)['domain_readonly'] = False
102
+ state_attrs['domain_readonly'] = False
100
103
  domain = simplify(self.validation_domains(record, pre_validate))
101
104
  if not softvalidation:
102
105
  if not self.check_required(record):
@@ -107,8 +110,15 @@ class Field(object):
107
110
  elif domain == [('id', '=', None)]:
108
111
  invalid = 'domain'
109
112
  else:
113
+ screen_domain, _ = self.domains_get(record, pre_validate)
110
114
  unique, leftpart, value = unique_value(domain)
111
- if unique:
115
+ unique_from_screen, _, _ = unique_value(screen_domain)
116
+ if (self._is_empty(record)
117
+ and not is_required
118
+ and not is_invisible
119
+ and not unique_from_screen):
120
+ pass
121
+ elif unique:
112
122
  # If the inverted domain is so constraint that only one value
113
123
  # is possible we should use it. But we must also pay attention
114
124
  # to the fact that the original domain might be a 'OR' domain
@@ -132,11 +142,10 @@ class Field(object):
132
142
  setdefault = False
133
143
  if setdefault and not pre_validate:
134
144
  self.set_client(record, value)
135
- self.get_state_attrs(record)['domain_readonly'] = (
136
- domain_readonly)
145
+ state_attrs['domain_readonly'] = domain_readonly
137
146
  if not eval_domain(domain, EvalEnvironment(record)):
138
147
  invalid = domain
139
- self.get_state_attrs(record)['invalid'] = invalid
148
+ state_attrs['invalid'] = invalid
140
149
  return not invalid
141
150
 
142
151
  def set(self, record, value):
@@ -354,6 +363,11 @@ class TimeDeltaField(Field):
354
363
  class FloatField(Field):
355
364
  _default = None
356
365
 
366
+ def __init__(self, attrs):
367
+ super().__init__(attrs)
368
+ self._digits = {}
369
+ self._symbol = {}
370
+
357
371
  def _is_empty(self, record):
358
372
  return self.get(record) is None
359
373
 
@@ -369,14 +383,18 @@ class FloatField(Field):
369
383
  digits_name = digits_field.attrs.get('relation')
370
384
  digits_id = digits_field.get(record)
371
385
  if digits_name and digits_id is not None and digits_id >= 0:
372
- try:
373
- digits = RPCExecute(
374
- 'model', digits_name, 'get_digits', digits_id)
375
- except RPCException:
376
- logger.warn(
377
- "Fail to fetch digits for %s,%s",
378
- digits_name, digits_id)
379
- return
386
+ if digits_id in self._digits:
387
+ digits = self._digits[digits_id]
388
+ else:
389
+ try:
390
+ digits = RPCExecute(
391
+ 'model', digits_name, 'get_digits', digits_id)
392
+ except RPCException:
393
+ logger.warn(
394
+ "Fail to fetch digits for %s,%s",
395
+ digits_name, digits_id)
396
+ return
397
+ self._digits[digits_id] = digits
380
398
  else:
381
399
  return
382
400
  if not digits or any(d is None for d in digits):
@@ -396,10 +414,14 @@ class FloatField(Field):
396
414
  symbol_name = symbol_field.attrs.get('relation')
397
415
  symbol_id = symbol_field.get(record)
398
416
  if symbol_name and symbol_id is not None and symbol_id >= 0:
417
+ if symbol_id in self._symbol:
418
+ return self._symbol[symbol_id]
399
419
  try:
400
- return RPCExecute(
420
+ result = RPCExecute(
401
421
  'model', symbol_name, 'get_symbol', symbol_id, sign,
402
422
  context=record.get_context())
423
+ self._symbol[symbol_id] = result
424
+ return result
403
425
  except RPCException:
404
426
  logger.warn(
405
427
  "Fail to fetch symbol for %s,%s",
@@ -44,6 +44,11 @@ class Record:
44
44
  self.destroyed = False
45
45
 
46
46
  def __getitem__(self, name):
47
+ self.load(name)
48
+ if name != '*':
49
+ return self.group.fields[name]
50
+
51
+ def load(self, name, process_exception=True):
47
52
  if not self.destroyed and self.id >= 0 and name not in self._loaded:
48
53
  id2record = {
49
54
  self.id: self,
@@ -74,9 +79,13 @@ class Record:
74
79
  fnames = [fname for fname, field in fields
75
80
  if fname not in self._loaded
76
81
  and (not views or (views & field.views))]
77
- fnames.extend(('%s.rec_name' % fname for fname in fnames[:]
78
- if self.group.fields[fname].attrs['type']
79
- in ('many2one', 'one2one', 'reference')))
82
+ for fname in list(fnames):
83
+ f_attrs = self.group.fields[fname].attrs
84
+ if f_attrs['type'] in {'many2one', 'one2one', 'reference'}:
85
+ fnames.append('%s.rec_name' % fname)
86
+ elif (f_attrs['type'] == 'selection'
87
+ and f_attrs.get('loading', 'lazy') == 'eager'):
88
+ fnames.append('%s:string' % fname)
80
89
  if 'rec_name' not in fnames:
81
90
  fnames.append('rec_name')
82
91
  fnames.extend(['_timestamp', '_write', '_delete'])
@@ -126,7 +135,8 @@ class Record:
126
135
  exception = False
127
136
  try:
128
137
  values = RPCExecute('model', self.model_name, 'read',
129
- list(id2record.keys()), fnames, context=ctx)
138
+ list(id2record.keys()), fnames, context=ctx,
139
+ process_exception=process_exception)
130
140
  except RPCException:
131
141
  values = [{'id': x} for x in id2record]
132
142
  default_values = dict((f, None) for f in fnames)
@@ -142,8 +152,6 @@ class Record:
142
152
  for key in record.modified_fields:
143
153
  value.pop(key, None)
144
154
  record.set(value, modified=False)
145
- if name != '*':
146
- return self.group.fields[name]
147
155
 
148
156
  def __repr__(self):
149
157
  return '<Record %s@%s at %s>' % (self.id, self.model_name, id(self))
@@ -240,6 +248,7 @@ class Record:
240
248
  return (self.deleted
241
249
  or self.removed
242
250
  or self.exception
251
+ or self.group.readonly
243
252
  or not self._write)
244
253
 
245
254
  readonly = property(get_readonly)
@@ -464,13 +473,17 @@ class Record:
464
473
  if fieldname == 'rec_name':
465
474
  self.value['rec_name'] = value
466
475
  continue
467
- if isinstance(self.group.fields[fieldname], fields.O2MField):
476
+ field = self.group.fields[fieldname]
477
+ if isinstance(field, fields.O2MField):
468
478
  later[fieldname] = value
469
479
  continue
470
- if isinstance(self.group.fields[fieldname], (fields.M2OField,
471
- fields.ReferenceField)):
480
+ if isinstance(field, (fields.M2OField, fields.ReferenceField)):
472
481
  related = fieldname + '.'
473
482
  self.value[related] = val.get(related) or {}
483
+ elif (isinstance(field, fields.SelectionField)
484
+ and fieldname + ':string' in val):
485
+ related = fieldname + ':string'
486
+ self.value[related] = val[related]
474
487
  self.group.fields[fieldname].set(self, value)
475
488
  self._loaded.add(fieldname)
476
489
  fieldnames.append(fieldname)