tryton 7.4.8__py3-none-any.whl → 7.6.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 (122) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/bus.py +113 -69
  3. tryton/chat.py +179 -0
  4. tryton/client.py +7 -0
  5. tryton/common/__init__.py +15 -11
  6. tryton/common/button.py +1 -1
  7. tryton/common/cellrendererfloat.py +1 -1
  8. tryton/common/cellrenderertext.py +2 -2
  9. tryton/common/common.py +91 -19
  10. tryton/common/domain_parser.py +8 -6
  11. tryton/common/environment.py +2 -2
  12. tryton/common/number_entry.py +12 -6
  13. tryton/common/selection.py +1 -1
  14. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  15. tryton/data/locale/bg/LC_MESSAGES/tryton.po +26 -16
  16. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  17. tryton/data/locale/ca/LC_MESSAGES/tryton.po +29 -18
  18. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  19. tryton/data/locale/cs/LC_MESSAGES/tryton.po +28 -16
  20. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  21. tryton/data/locale/de/LC_MESSAGES/tryton.po +27 -18
  22. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  23. tryton/data/locale/es/LC_MESSAGES/tryton.po +25 -16
  24. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  25. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +26 -16
  26. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  27. tryton/data/locale/et/LC_MESSAGES/tryton.po +28 -18
  28. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  29. tryton/data/locale/fa/LC_MESSAGES/tryton.po +28 -18
  30. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  31. tryton/data/locale/fi/LC_MESSAGES/tryton.po +25 -16
  32. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/fr/LC_MESSAGES/tryton.po +25 -16
  34. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/hu/LC_MESSAGES/tryton.po +28 -18
  36. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/id/LC_MESSAGES/tryton.po +23 -16
  38. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/it/LC_MESSAGES/tryton.po +29 -18
  40. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  42. tryton/data/locale/lo/LC_MESSAGES/tryton.po +26 -18
  43. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  44. tryton/data/locale/lt/LC_MESSAGES/tryton.po +30 -18
  45. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  46. tryton/data/locale/nl/LC_MESSAGES/tryton.po +25 -16
  47. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  48. tryton/data/locale/pl/LC_MESSAGES/tryton.po +31 -18
  49. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  50. tryton/data/locale/pt/LC_MESSAGES/tryton.po +148 -177
  51. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  52. tryton/data/locale/ro/LC_MESSAGES/tryton.po +32 -19
  53. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  54. tryton/data/locale/ru/LC_MESSAGES/tryton.po +28 -16
  55. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  56. tryton/data/locale/sl/LC_MESSAGES/tryton.po +33 -18
  57. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  58. tryton/data/locale/tr/LC_MESSAGES/tryton.po +25 -16
  59. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  60. tryton/data/locale/uk/LC_MESSAGES/tryton.po +31 -18
  61. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  62. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +27 -18
  63. tryton/data/pixmaps/tryton/tryton-chat.svg +1 -0
  64. tryton/data/pixmaps/tryton/tryton-note.svg +1 -4
  65. tryton/gui/window/attachment.py +2 -2
  66. tryton/gui/window/board.py +1 -1
  67. tryton/gui/window/form.py +57 -10
  68. tryton/gui/window/note.py +2 -2
  69. tryton/gui/window/tabcontent.py +8 -1
  70. tryton/gui/window/view_board/action.py +1 -1
  71. tryton/gui/window/view_form/model/field.py +38 -29
  72. tryton/gui/window/view_form/model/group.py +4 -4
  73. tryton/gui/window/view_form/model/record.py +17 -4
  74. tryton/gui/window/view_form/screen/screen.py +24 -6
  75. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +1 -1
  76. tryton/gui/window/view_form/view/calendar_gtk/toolbar.py +1 -1
  77. tryton/gui/window/view_form/view/form.py +2 -1
  78. tryton/gui/window/view_form/view/form_gtk/binary.py +3 -3
  79. tryton/gui/window/view_form/view/form_gtk/calendar_.py +4 -4
  80. tryton/gui/window/view_form/view/form_gtk/char.py +42 -5
  81. tryton/gui/window/view_form/view/form_gtk/checkbox.py +3 -3
  82. tryton/gui/window/view_form/view/form_gtk/dictionary.py +53 -15
  83. tryton/gui/window/view_form/view/form_gtk/float.py +3 -7
  84. tryton/gui/window/view_form/view/form_gtk/image.py +4 -4
  85. tryton/gui/window/view_form/view/form_gtk/integer.py +1 -1
  86. tryton/gui/window/view_form/view/form_gtk/many2many.py +3 -4
  87. tryton/gui/window/view_form/view/form_gtk/many2one.py +2 -2
  88. tryton/gui/window/view_form/view/form_gtk/multiselection.py +3 -3
  89. tryton/gui/window/view_form/view/form_gtk/one2many.py +11 -8
  90. tryton/gui/window/view_form/view/form_gtk/progressbar.py +2 -2
  91. tryton/gui/window/view_form/view/form_gtk/pyson.py +3 -3
  92. tryton/gui/window/view_form/view/form_gtk/reference.py +4 -4
  93. tryton/gui/window/view_form/view/form_gtk/richtextbox.py +5 -5
  94. tryton/gui/window/view_form/view/form_gtk/selection.py +3 -3
  95. tryton/gui/window/view_form/view/form_gtk/state_widget.py +8 -6
  96. tryton/gui/window/view_form/view/form_gtk/textbox.py +4 -4
  97. tryton/gui/window/view_form/view/form_gtk/timedelta.py +3 -3
  98. tryton/gui/window/view_form/view/form_gtk/url.py +2 -2
  99. tryton/gui/window/view_form/view/form_gtk/widget.py +1 -1
  100. tryton/gui/window/view_form/view/graph_gtk/bar.py +7 -7
  101. tryton/gui/window/view_form/view/graph_gtk/graph.py +2 -2
  102. tryton/gui/window/view_form/view/graph_gtk/line.py +5 -5
  103. tryton/gui/window/view_form/view/graph_gtk/pie.py +2 -2
  104. tryton/gui/window/view_form/view/list.py +110 -52
  105. tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -2
  106. tryton/gui/window/view_form/view/list_gtk/widget.py +22 -20
  107. tryton/gui/window/view_form/view/screen_container.py +13 -1
  108. tryton/gui/window/win_csv.py +2 -2
  109. tryton/gui/window/win_export.py +9 -7
  110. tryton/gui/window/win_form.py +73 -39
  111. tryton/gui/window/win_import.py +5 -6
  112. tryton/gui/window/wizard.py +11 -11
  113. tryton/jsonrpc.py +2 -2
  114. tryton/plugins/__init__.py +0 -1
  115. tryton/pyson.py +18 -18
  116. tryton/rpc.py +7 -5
  117. {tryton-7.4.8.dist-info → tryton-7.6.0.dist-info}/METADATA +6 -6
  118. {tryton-7.4.8.dist-info → tryton-7.6.0.dist-info}/RECORD +122 -120
  119. {tryton-7.4.8.data → tryton-7.6.0.data}/scripts/tryton +0 -0
  120. {tryton-7.4.8.dist-info → tryton-7.6.0.dist-info}/WHEEL +0 -0
  121. {tryton-7.4.8.dist-info → tryton-7.6.0.dist-info}/licenses/LICENSE +0 -0
  122. {tryton-7.4.8.dist-info → tryton-7.6.0.dist-info}/top_level.txt +0 -0
tryton/gui/window/form.py CHANGED
@@ -12,6 +12,7 @@ from gi.repository import Gdk, GLib, Gtk
12
12
  import tryton.common as common
13
13
  from tryton import plugins
14
14
  from tryton.action import Action
15
+ from tryton.chat import Chat
15
16
  from tryton.common import RPCException, RPCExecute, sur, sur_3b, tempfile
16
17
  from tryton.common.common import selection as selection_
17
18
  from tryton.common.popup_menu import popup
@@ -36,7 +37,7 @@ class Form(TabContent):
36
37
  "Form"
37
38
 
38
39
  def __init__(self, model, res_id=None, name='', **attributes):
39
- super(Form, self).__init__(**attributes)
40
+ super().__init__(**attributes)
40
41
 
41
42
  self.model = model
42
43
  self.res_id = res_id
@@ -88,6 +89,10 @@ class Form(TabContent):
88
89
  def create_tabcontent(self):
89
90
  super().create_tabcontent()
90
91
 
92
+ self.sidebar = Gtk.VBox()
93
+ self.sidebar.show()
94
+ self.main.pack2(self.sidebar, resize=False, shrink=True)
95
+
91
96
  self.attachment_preview = Gtk.Viewport()
92
97
  self.attachment_preview.set_shadow_type(Gtk.ShadowType.NONE)
93
98
  self.attachment_preview.show()
@@ -97,7 +102,13 @@ class Form(TabContent):
97
102
  Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
98
103
  scrolledwindow.add(self.attachment_preview)
99
104
  scrolledwindow.set_size_request(300, -1)
100
- self.main.pack2(scrolledwindow, resize=False, shrink=True)
105
+ self.sidebar.pack_start(
106
+ scrolledwindow, expand=True, fill=True, padding=0)
107
+
108
+ self.chat = Gtk.HBox()
109
+ self.chat.props.width_request = 300
110
+ self._chat = None
111
+ self.sidebar.pack_start(self.chat, expand=True, fill=True, padding=0)
101
112
 
102
113
  def widget_get(self):
103
114
  return self.screen.widget
@@ -186,7 +197,7 @@ class Form(TabContent):
186
197
  vbox.set_margin_start(4)
187
198
  hbox = Gtk.HBox(homogeneous=False, spacing=0)
188
199
  hbox.set_halign(Gtk.Align.CENTER)
189
- vbox.pack_start(hbox, expand=False, fill=True, padding=0)
200
+ vbox.pack_end(hbox, expand=False, fill=True, padding=0)
190
201
  hbox.set_border_width(2)
191
202
  tooltips = common.Tooltips()
192
203
 
@@ -247,6 +258,24 @@ class Form(TabContent):
247
258
  self.attachment_screen.current_record = group[0]
248
259
  self.attachment_screen.display()
249
260
 
261
+ def sig_chat(self, widget=None):
262
+ button = self.buttons['chat']
263
+ if widget != button:
264
+ if button.props.sensitive:
265
+ button.props.active = True
266
+ return
267
+
268
+ if button.get_active():
269
+ self._chat = Chat(self.screen.current_reference)
270
+ self.chat.pack_start(self._chat.widget, True, True, padding=3)
271
+ self.chat.show_all()
272
+ self._chat.refresh()
273
+ else:
274
+ self.chat.hide()
275
+ self.chat.remove(self._chat.widget)
276
+ self._chat.unregister()
277
+ self._chat = None
278
+
250
279
  def sig_note(self, widget=None):
251
280
  record = self.screen.current_record
252
281
  if not record or record.id < 0:
@@ -556,9 +585,11 @@ class Form(TabContent):
556
585
  self.buttons['copy_url'].props.active = True
557
586
 
558
587
  def sig_search(self, widget):
559
- search_container = self.screen.screen_container
560
- if hasattr(search_container, 'search_entry'):
561
- search_container.search_entry.grab_focus()
588
+ if not self.modified_save():
589
+ return
590
+ self.screen.switch_view(searchable=True, display=False)
591
+ self.screen.display()
592
+ self.screen.screen_container.grab_focus()
562
593
 
563
594
  def action_popup(self, widget):
564
595
  button, = widget.get_children()
@@ -583,8 +614,11 @@ class Form(TabContent):
583
614
  has_views = self.screen.number_of_views > 1
584
615
  if selected > 1:
585
616
  name += '#%i' % selected
586
- for button_id in ['print', 'relate', 'email', 'open', 'attach']:
587
- button = self.buttons[button_id]
617
+ for button_id in [
618
+ 'print', 'relate', 'email', 'open', 'attach', 'chat']:
619
+ button = self.buttons.get(button_id)
620
+ if not button:
621
+ continue
588
622
  can_be_sensitive = getattr(button, '_can_be_sensitive', True)
589
623
  if button_id in {'print', 'relate', 'email', 'open'}:
590
624
  action_type = button_id
@@ -613,6 +647,14 @@ class Form(TabContent):
613
647
  self.info_bar_clear()
614
648
  self.set_buttons_sensitive()
615
649
  self.refresh_attachment_preview()
650
+ if self._chat:
651
+ self._chat.unregister()
652
+ self.chat.remove(self._chat.widget)
653
+ if self.screen.current_reference:
654
+ self._chat = Chat(self.screen.current_reference)
655
+ self.chat.add(self._chat.widget)
656
+ self.chat.show_all()
657
+ self._chat.refresh()
616
658
 
617
659
  def record_modified(self):
618
660
  def _record_modified():
@@ -650,7 +692,12 @@ class Form(TabContent):
650
692
  for dialog in reversed(self.dialogs[:]):
651
693
  dialog.destroy()
652
694
  modified_save = self.modified_save()
653
- return True if modified_save is None else modified_save
695
+ can_close = True if modified_save is None else modified_save
696
+ if can_close and self._chat:
697
+ self._chat.unregister()
698
+ self.chat.remove(self._chat.widget)
699
+ self._chat = None
700
+ return can_close
654
701
 
655
702
  def _action(self, action, atype):
656
703
  if not self.modified_save():
@@ -679,7 +726,7 @@ class Form(TabContent):
679
726
  Main().sig_win_close(widget)
680
727
 
681
728
  def create_toolbar(self, toolbars):
682
- gtktoolbar = super(Form, self).create_toolbar(toolbars)
729
+ gtktoolbar = super().create_toolbar(toolbars)
683
730
 
684
731
  attach_btn = self.buttons['attach']
685
732
  attach_btn.drag_dest_set(
tryton/gui/window/note.py CHANGED
@@ -18,13 +18,13 @@ class Note(WinForm):
18
18
  screen = Screen('ir.note', domain=[
19
19
  ('resource', '=', self.resource),
20
20
  ], mode=['tree', 'form'])
21
- super(Note, self).__init__(screen, self.callback, view_type='tree',
21
+ super().__init__(screen, self.callback, view_type='tree',
22
22
  title=title)
23
23
  screen.search_filter()
24
24
 
25
25
  def destroy(self):
26
26
  self.prev_view.save_width()
27
- super(Note, self).destroy()
27
+ super().destroy()
28
28
 
29
29
  def callback(self, result):
30
30
  if result:
@@ -37,7 +37,7 @@ class ToolbarItem(object):
37
37
  class TabContent(InfoBar):
38
38
 
39
39
  def __init__(self, **attributes):
40
- super(TabContent, self).__init__()
40
+ super().__init__()
41
41
  self.attributes = attributes.copy()
42
42
 
43
43
  @property
@@ -118,6 +118,13 @@ class TabContent(InfoBar):
118
118
  tooltip=_("Add a note to the record"),
119
119
  icon_name='tryton-note',
120
120
  accel_path='<tryton>/Form/Notes'),
121
+ ToolbarItem(
122
+ id='chat' if self.model in common.MODELCHAT else None,
123
+ label=_("C_hat"),
124
+ tooltip=_("Chat on the record"),
125
+ icon_name='tryton-chat',
126
+ accel_path='<tryton>/Form/Chat',
127
+ toggle=True),
121
128
  ToolbarItem(
122
129
  id='action',
123
130
  label=_("_Actions..."),
@@ -19,7 +19,7 @@ _ = gettext.gettext
19
19
  class Action:
20
20
 
21
21
  def __init__(self, view, attrs=None):
22
- super(Action, self).__init__()
22
+ super().__init__()
23
23
  self.name = attrs['name']
24
24
  self.view = view
25
25
 
@@ -228,7 +228,7 @@ class CharField(Field):
228
228
  super().set(record, value)
229
229
 
230
230
  def get(self, record):
231
- return super(CharField, self).get(record) or self._default
231
+ return super().get(record) or self._default
232
232
 
233
233
  def set_client(self, record, value, force_change=False):
234
234
  if isinstance(value, bytes):
@@ -301,11 +301,11 @@ class DateTimeField(Field):
301
301
  value = datetime.datetime.combine(value, time)
302
302
  if value:
303
303
  value = common.untimezoned_date(value)
304
- super(DateTimeField, self).set_client(record, value,
304
+ super().set_client(record, value,
305
305
  force_change=force_change)
306
306
 
307
307
  def get_client(self, record):
308
- value = super(DateTimeField, self).get_client(record)
308
+ value = super().get_client(record)
309
309
  if value:
310
310
  return common.timezoned_date(value)
311
311
 
@@ -325,7 +325,7 @@ class DateField(Field):
325
325
  if isinstance(value, datetime.datetime):
326
326
  assert value.time() == datetime.time()
327
327
  value = value.date()
328
- super(DateField, self).set_client(record, value,
328
+ super().set_client(record, value,
329
329
  force_change=force_change)
330
330
 
331
331
  def date_format(self, record):
@@ -343,7 +343,7 @@ class TimeField(Field):
343
343
  def set_client(self, record, value, force_change=False):
344
344
  if isinstance(value, datetime.datetime):
345
345
  value = value.time()
346
- super(TimeField, self).set_client(record, value,
346
+ super().set_client(record, value,
347
347
  force_change=force_change)
348
348
 
349
349
  def time_format(self, record):
@@ -363,11 +363,11 @@ class TimeDeltaField(Field):
363
363
  def set_client(self, record, value, force_change=False):
364
364
  if isinstance(value, str):
365
365
  value = common.timedelta.parse(value, self.converter(record.group))
366
- super(TimeDeltaField, self).set_client(
366
+ super().set_client(
367
367
  record, value, force_change=force_change)
368
368
 
369
369
  def get_client(self, record):
370
- value = super(TimeDeltaField, self).get_client(record)
370
+ value = super().get_client(record)
371
371
  return common.timedelta.format(value, self.converter(record.group))
372
372
 
373
373
 
@@ -408,10 +408,16 @@ class FloatField(Field):
408
408
  self._digits[digits_id] = digits
409
409
  else:
410
410
  return
411
- if not digits or any(d is None for d in digits):
411
+ if not digits:
412
412
  return
413
413
  shift = int(round(math.log(abs(factor), 10)))
414
- return (digits[0] + shift, digits[1] - shift)
414
+ int_size = digits[0]
415
+ if int_size is not None:
416
+ int_size += shift
417
+ dec_size = digits[1]
418
+ if dec_size is not None:
419
+ dec_size -= shift
420
+ return (int_size, dec_size)
415
421
 
416
422
  def get_symbol(self, record, symbol):
417
423
  if record and symbol in record.group.fields:
@@ -462,7 +468,7 @@ class FloatField(Field):
462
468
 
463
469
  def set_client(self, record, value, force_change=False, factor=1):
464
470
  value = self.apply_factor(record, self.convert(value), factor)
465
- super(FloatField, self).set_client(record, value,
471
+ super().set_client(record, value,
466
472
  force_change=force_change)
467
473
 
468
474
  def get_client(self, record, factor=1, grouping=True):
@@ -472,7 +478,7 @@ class FloatField(Field):
472
478
  d = value * factor
473
479
  if not isinstance(d, Decimal):
474
480
  d = Decimal(repr(d))
475
- if digits:
481
+ if digits and digits[1] is not None:
476
482
  p = int(digits[1])
477
483
  elif d == d.to_integral_value():
478
484
  p = 0
@@ -495,11 +501,11 @@ class NumericField(FloatField):
495
501
  _convert = Decimal
496
502
 
497
503
  def set_client(self, record, value, force_change=False, factor=1):
498
- return super(NumericField, self).set_client(record, value,
504
+ return super().set_client(record, value,
499
505
  force_change=force_change, factor=Decimal(str(factor)))
500
506
 
501
507
  def get_client(self, record, factor=1, grouping=True):
502
- return super(NumericField, self).get_client(record,
508
+ return super().get_client(record,
503
509
  factor=Decimal(str(factor)), grouping=grouping)
504
510
 
505
511
 
@@ -508,11 +514,11 @@ class IntegerField(FloatField):
508
514
  _convert = int
509
515
 
510
516
  def set_client(self, record, value, force_change=False, factor=1):
511
- return super(IntegerField, self).set_client(record, value,
517
+ return super().set_client(record, value,
512
518
  force_change=force_change, factor=int(factor))
513
519
 
514
520
  def get_client(self, record, factor=1, grouping=True):
515
- return super(IntegerField, self).get_client(
521
+ return super().get_client(
516
522
  record, factor=int(factor), grouping=grouping)
517
523
 
518
524
 
@@ -522,7 +528,7 @@ class BooleanField(Field):
522
528
 
523
529
  def set_client(self, record, value, force_change=False):
524
530
  value = bool(value)
525
- super(BooleanField, self).set_client(record, value,
531
+ super().set_client(record, value,
526
532
  force_change=force_change)
527
533
 
528
534
  def get(self, record):
@@ -561,7 +567,7 @@ class M2OField(Field):
561
567
  if value and value < 0 and self.name != record.parent_name:
562
568
  value, rec_name = None, ''
563
569
  record.value.setdefault(self.name + '.', {})['rec_name'] = rec_name
564
- super(M2OField, self).set_client(record, value,
570
+ super().set_client(record, value,
565
571
  force_change=force_change)
566
572
 
567
573
  def set(self, record, value):
@@ -577,7 +583,7 @@ class M2OField(Field):
577
583
  record.value[self.name] = value
578
584
 
579
585
  def get_context(self, record, record_context=None, local=False):
580
- context = super(M2OField, self).get_context(
586
+ context = super().get_context(
581
587
  record, record_context=record_context, local=local)
582
588
  if self.attrs.get('datetime_field'):
583
589
  context['_datetime'] = record.get_eval(
@@ -597,7 +603,7 @@ class M2OField(Field):
597
603
  if record.parent_name == self.name and record.parent:
598
604
  return record.parent.get_on_change_value(
599
605
  skip={record.group.child_name})
600
- return super(M2OField, self).get_on_change_value(record)
606
+ return super().get_on_change_value(record)
601
607
 
602
608
 
603
609
  class O2OField(M2OField):
@@ -613,7 +619,7 @@ class O2MField(Field):
613
619
  _single_value = False
614
620
 
615
621
  def __init__(self, attrs):
616
- super(O2MField, self).__init__(attrs)
622
+ super().__init__(attrs)
617
623
 
618
624
  def _set_default_value(self, record, fields=None):
619
625
  if record.value.get(self.name) is not None:
@@ -882,7 +888,10 @@ class O2MField(Field):
882
888
  continue
883
889
  record2 = group.get(vals['id'])
884
890
  if record2 is not None:
885
- record2.set_on_change(vals)
891
+ to_set = {
892
+ k: v for k, v in vals.items
893
+ if k not in vals_to_set}
894
+ record2.set_on_change(to_set)
886
895
 
887
896
  def validation_domains(self, record, pre_validate=None):
888
897
  screen_domain, attr_domain = self.domains_get(record, pre_validate)
@@ -904,7 +913,7 @@ class O2MField(Field):
904
913
  if not record2.validate(softvalidation=softvalidation,
905
914
  pre_validate=ldomain):
906
915
  invalid = 'children'
907
- test = super(O2MField, self).validate(record, softvalidation,
916
+ test = super().validate(record, softvalidation,
908
917
  pre_validate)
909
918
  if test and invalid:
910
919
  self.get_state_attrs(record)['invalid'] = invalid
@@ -913,7 +922,7 @@ class O2MField(Field):
913
922
 
914
923
  def state_set(self, record, states=('readonly', 'required', 'invisible')):
915
924
  self._set_default_value(record)
916
- super(O2MField, self).state_set(record, states=states)
925
+ super().state_set(record, states=states)
917
926
 
918
927
  def get_removed_ids(self, record):
919
928
  return [x.id for x in record.value[self.name].record_removed]
@@ -936,7 +945,7 @@ class ReferenceField(Field):
936
945
  _default = None
937
946
 
938
947
  def _is_empty(self, record):
939
- result = super(ReferenceField, self)._is_empty(record)
948
+ result = super()._is_empty(record)
940
949
  if not result and (record.value[self.name] is None
941
950
  or record.value[self.name][1] < 0):
942
951
  result = True
@@ -978,7 +987,7 @@ class ReferenceField(Field):
978
987
  rec_name = ''
979
988
  record.value.setdefault(self.name + '.', {})['rec_name'] = rec_name
980
989
  value = (ref_model, ref_id)
981
- super(ReferenceField, self).set_client(record, value,
990
+ super().set_client(record, value,
982
991
  force_change=force_change)
983
992
 
984
993
  def set(self, record, value):
@@ -1013,7 +1022,7 @@ class ReferenceField(Field):
1013
1022
  record.value.setdefault(self.name + '.', {})['rec_name'] = rec_name
1014
1023
 
1015
1024
  def get_context(self, record, record_context=None, local=False):
1016
- context = super(ReferenceField, self).get_context(
1025
+ context = super().get_context(
1017
1026
  record, record_context, local=local)
1018
1027
  if self.attrs.get('datetime_field'):
1019
1028
  context['_datetime'] = record.get_eval(
@@ -1024,7 +1033,7 @@ class ReferenceField(Field):
1024
1033
  if record.parent_name == self.name and record.parent:
1025
1034
  return record.parent.model_name, record.parent.get_on_change_value(
1026
1035
  skip={record.group.child_name})
1027
- return super(ReferenceField, self).get_on_change_value(record)
1036
+ return super().get_on_change_value(record)
1028
1037
 
1029
1038
  def validation_domains(self, record, pre_validate=None):
1030
1039
  screen_domain, attr_domain = self.domains_get(record, pre_validate)
@@ -1172,7 +1181,7 @@ class DictField(Field):
1172
1181
  _single_value = False
1173
1182
 
1174
1183
  def __init__(self, attrs):
1175
- super(DictField, self).__init__(attrs)
1184
+ super().__init__(attrs)
1176
1185
  self.keys = {}
1177
1186
 
1178
1187
  def get(self, record):
@@ -1231,7 +1240,7 @@ class DictField(Field):
1231
1240
  return new_keys
1232
1241
 
1233
1242
  def validate(self, record, softvalidation=False, pre_validate=None):
1234
- valid = super(DictField, self).validate(
1243
+ valid = super().validate(
1235
1244
  record, softvalidation, pre_validate)
1236
1245
 
1237
1246
  if self.attrs.get('readonly'):
@@ -15,7 +15,7 @@ class Group(list):
15
15
  def __init__(self, model_name, fields, ids=None, parent=None,
16
16
  parent_name='', child_name='', context=None, domain=None,
17
17
  readonly=False, parent_datetime_field=None):
18
- super(Group, self).__init__()
18
+ super().__init__()
19
19
  if domain is None:
20
20
  domain = []
21
21
  self.__domain = domain
@@ -101,7 +101,7 @@ class Group(list):
101
101
  record.next[id(self)] = self.__getitem__(pos)
102
102
  else:
103
103
  record.next[id(self)] = None
104
- super(Group, self).insert(pos, record)
104
+ super().insert(pos, record)
105
105
  self.__id2record[record.id] = record
106
106
  if not self.lock_signal:
107
107
  self._group_list_changed('record-added', record, pos)
@@ -111,7 +111,7 @@ class Group(list):
111
111
  if self.__len__() >= 1:
112
112
  self.__getitem__(self.__len__() - 1).next[id(self)] = record
113
113
  record.next[id(self)] = None
114
- super(Group, self).append(record)
114
+ super().append(record)
115
115
  self.__id2record[record.id] = record
116
116
  if not self.lock_signal:
117
117
  self._group_list_changed(
@@ -126,7 +126,7 @@ class Group(list):
126
126
  else:
127
127
  self.__getitem__(idx - 1).next[id(self)] = None
128
128
  self._group_list_changed('record-removed', record, idx)
129
- super(Group, self).remove(record)
129
+ super().remove(record)
130
130
  del self.__id2record[record.id]
131
131
  record.destroy()
132
132
 
@@ -43,7 +43,7 @@ class Record:
43
43
  id = -1
44
44
 
45
45
  def __init__(self, model_name, obj_id, group=None):
46
- super(Record, self).__init__()
46
+ super().__init__()
47
47
  self.model_name = model_name
48
48
  if obj_id is None:
49
49
  self.id = Record.id
@@ -437,11 +437,12 @@ class Record:
437
437
  return ''
438
438
 
439
439
  def validate(self, fields=None, softvalidation=False, pre_validate=None):
440
- self._check_load(fields)
441
440
  res = True
442
441
  for field_name, field in list(self.group.fields.items()):
443
442
  if fields is not None and field_name not in fields:
444
443
  continue
444
+ if not self.get_loaded([field_name]):
445
+ continue
445
446
  if field.attrs.get('readonly'):
446
447
  continue
447
448
  if field_name == self.group.exclude_field:
@@ -554,7 +555,6 @@ class Record:
554
555
  else:
555
556
  for field in fields:
556
557
  self[field]
557
- self.validate(fields or [])
558
558
 
559
559
  def reset(self, value):
560
560
  self.cancel()
@@ -606,6 +606,7 @@ class Record:
606
606
  continue
607
607
  values.update(self._get_on_change_args(on_change))
608
608
 
609
+ modified = set(fieldnames)
609
610
  if values:
610
611
  values['id'] = self.id
611
612
  try:
@@ -625,9 +626,10 @@ class Record:
625
626
  else:
626
627
  for change in changes:
627
628
  self.set_on_change(change)
629
+ modified.update(change)
628
630
 
629
631
  notification_fields = common.MODELNOTIFICATION.get(self.model_name)
630
- if set(fieldnames) & set(notification_fields):
632
+ if modified & set(notification_fields):
631
633
  values = self._get_on_change_args(notification_fields)
632
634
  try:
633
635
  notifications = RPCExecute(
@@ -699,6 +701,17 @@ class Record:
699
701
  except RPCException:
700
702
  return
701
703
  self.set_on_change(changed)
704
+ notification_fields = common.MODELNOTIFICATION.get(self.model_name)
705
+ if set(field_names) & set(notification_fields):
706
+ values = self._get_on_change_args(notification_fields)
707
+ try:
708
+ notifications = RPCExecute(
709
+ 'model', self.model_name, 'on_change_notify', values,
710
+ context=self.get_context())
711
+ except RPCException:
712
+ pass
713
+ else:
714
+ self.group.record_notify(notifications)
702
715
 
703
716
  def autocomplete_with(self, field_name):
704
717
  for fieldname, fieldinfo in self.group.fields.items():
@@ -16,8 +16,8 @@ from gi.repository import GLib, Gtk
16
16
 
17
17
  from tryton.action import Action
18
18
  from tryton.common import (
19
- MODELACCESS, RPCContextReload, RPCException, RPCExecute, node_attributes,
20
- sur, warning)
19
+ MODELACCESS, RPCContextReload, RPCException, RPCExecute, get_monitor_size,
20
+ node_attributes, sur, warning)
21
21
  from tryton.common.domain_inversion import canonicalize
22
22
  from tryton.common.domain_parser import DomainParser
23
23
  from tryton.config import CONFIG
@@ -162,6 +162,12 @@ class Screen:
162
162
  def count_limit(self):
163
163
  return self.limit * 100 + self.offset
164
164
 
165
+ @property
166
+ def current_reference(self):
167
+ if self.current_record and self.current_record.id > 0:
168
+ return f"{self.model_name},{self.current_record.id}"
169
+ return None
170
+
165
171
  def search_active(self, active=True):
166
172
  if active and not self.parent:
167
173
  self.screen_container.show_filter()
@@ -178,6 +184,7 @@ class Screen:
178
184
  if view_id not in self.fields_view_tree:
179
185
  context = self.context
180
186
  context['view_tree_width'] = CONFIG['client.save_tree_width']
187
+ context['screen_size'] = get_monitor_size()
181
188
  try:
182
189
  self.fields_view_tree[view_id] = view_tree = RPCExecute(
183
190
  'model', self.model_name, 'fields_view_get', False, 'tree',
@@ -429,6 +436,7 @@ class Screen:
429
436
  for name, views in fields_views.items():
430
437
  self.__group.fields[name].views.update(views)
431
438
  self.__group.exclude_field = self.exclude_field
439
+ self.__group.readonly = self.__readonly
432
440
 
433
441
  group = property(__get_group, __set_group)
434
442
 
@@ -446,12 +454,17 @@ class Screen:
446
454
  for window in self.windows:
447
455
  if hasattr(window, 'record_modified'):
448
456
  window.record_modified()
457
+ if self.parent:
458
+ for screen in self.parent.group.screens:
459
+ screen.record_modified(display=display)
449
460
  if display:
450
461
  self.display()
451
462
 
452
463
  def record_notify(self, notifications):
464
+ notified = False
453
465
  for window in self.windows:
454
466
  if isinstance(window, InfoBar):
467
+ notified = True
455
468
  window.info_bar_refresh('notification')
456
469
  for type_, message in notifications:
457
470
  type_ = {
@@ -460,6 +473,8 @@ class Screen:
460
473
  'error': Gtk.MessageType.ERROR,
461
474
  }.get(type_, Gtk.MessageType.WARNING)
462
475
  window.info_bar_add(message, type_, 'notification')
476
+ if not notified and self.group.parent:
477
+ self.group.parent.group.record_notify(notifications)
463
478
 
464
479
  def record_message(self, position, size, max_size, record_id):
465
480
  for window in self.windows:
@@ -486,8 +501,6 @@ class Screen:
486
501
  return self.__current_record
487
502
 
488
503
  def __set_current_record(self, record):
489
- if self.__current_record == record and record:
490
- return
491
504
  self.__current_record = record
492
505
  if record:
493
506
  try:
@@ -552,7 +565,8 @@ class Screen:
552
565
  return next_view
553
566
 
554
567
  def switch_view(
555
- self, view_type=None, view_id=None, creatable=None, display=True):
568
+ self, view_type=None, view_id=None, creatable=None,
569
+ searchable=None, display=True):
556
570
  if view_id is not None:
557
571
  view_id = int(view_id)
558
572
  if self.current_view:
@@ -578,6 +592,9 @@ class Screen:
578
592
  result &= self.current_view.view_id == view_id
579
593
  if creatable is not None:
580
594
  result &= self.current_view.creatable == creatable
595
+ if searchable is not None:
596
+ result &= (self.current_view.view_type in {
597
+ 'tree', 'graph', 'calendar'}) == searchable
581
598
  return result
582
599
  for i in range(len(self.views) + len(self.view_to_load)):
583
600
  if len(self.view_to_load):
@@ -616,6 +633,7 @@ class Screen:
616
633
  else:
617
634
  context = self.context
618
635
  context['view_tree_width'] = CONFIG['client.save_tree_width']
636
+ context['screen_size'] = get_monitor_size()
619
637
  try:
620
638
  view = RPCExecute(
621
639
  'model', self.model_name, 'fields_view_get', view_id,
@@ -820,7 +838,7 @@ class Screen:
820
838
  # to set deleted record as current_record
821
839
  self.current_record = None
822
840
  # call only once
823
- record.set_modified()
841
+ self.group.record_modified()
824
842
 
825
843
  if delete:
826
844
  for record in records:
@@ -17,7 +17,7 @@ class Calendar_(goocalendar.Calendar):
17
17
  'Calendar'
18
18
 
19
19
  def __init__(self, attrs, view, fields, event_store=None):
20
- super(Calendar_, self).__init__(
20
+ super().__init__(
21
21
  event_store, attrs.get('mode', 'month'))
22
22
  self.props.selected_border_color = _colors[1]
23
23
  if hasattr(self.props, 'selected_text_color'):
@@ -16,7 +16,7 @@ _ = gettext.gettext
16
16
  class Toolbar(Gtk.Toolbar):
17
17
 
18
18
  def __init__(self, goocalendar):
19
- super(Toolbar, self).__init__()
19
+ super().__init__()
20
20
  self.goocalendar = goocalendar
21
21
  self.accel_group = Main().accel_group
22
22
 
@@ -17,7 +17,7 @@ from tryton.gui.window.code_scanner import CodeScanner
17
17
  from . import View, XMLViewParser
18
18
  from .form_gtk.binary import Binary
19
19
  from .form_gtk.calendar_ import Date, DateTime, Time
20
- from .form_gtk.char import Char, Password
20
+ from .form_gtk.char import Char, Color, Password
21
21
  from .form_gtk.checkbox import CheckBox
22
22
  from .form_gtk.dictionary import DictWidget
23
23
  from .form_gtk.document import Document
@@ -167,6 +167,7 @@ class FormXMLViewParser(XMLViewParser):
167
167
  'boolean': CheckBox,
168
168
  'callto': CallTo,
169
169
  'char': Char,
170
+ 'color': Color,
170
171
  'date': Date,
171
172
  'datetime': DateTime,
172
173
  'dict': DictWidget,