tryton 7.0.21__py3-none-any.whl → 7.2.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 (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 +42 -4
  12. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  13. tryton/data/locale/ca/LC_MESSAGES/tryton.po +47 -8
  14. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  15. tryton/data/locale/cs/LC_MESSAGES/tryton.po +42 -4
  16. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  17. tryton/data/locale/de/LC_MESSAGES/tryton.po +45 -6
  18. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  19. tryton/data/locale/es/LC_MESSAGES/tryton.po +46 -7
  20. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  21. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +43 -5
  22. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  23. tryton/data/locale/et/LC_MESSAGES/tryton.po +46 -6
  24. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  25. tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -7
  26. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  27. tryton/data/locale/fi/LC_MESSAGES/tryton.po +41 -4
  28. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  29. tryton/data/locale/fr/LC_MESSAGES/tryton.po +46 -7
  30. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  31. tryton/data/locale/hu/LC_MESSAGES/tryton.po +46 -6
  32. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/id/LC_MESSAGES/tryton.po +43 -4
  34. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/it/LC_MESSAGES/tryton.po +45 -6
  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 +45 -6
  39. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lt/LC_MESSAGES/tryton.po +46 -6
  41. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  42. tryton/data/locale/nl/LC_MESSAGES/tryton.po +46 -7
  43. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  44. tryton/data/locale/pl/LC_MESSAGES/tryton.po +84 -60
  45. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  46. tryton/data/locale/pt/LC_MESSAGES/tryton.po +45 -6
  47. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  48. tryton/data/locale/ro/LC_MESSAGES/tryton.po +57 -17
  49. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  50. tryton/data/locale/ru/LC_MESSAGES/tryton.po +43 -6
  51. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  52. tryton/data/locale/sl/LC_MESSAGES/tryton.po +46 -5
  53. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  54. tryton/data/locale/tr/LC_MESSAGES/tryton.po +41 -4
  55. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  56. tryton/data/locale/uk/LC_MESSAGES/tryton.po +46 -6
  57. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  58. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +46 -5
  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.1.data}/scripts/tryton +1 -2
  90. {tryton-7.0.21.dist-info → tryton-7.2.1.dist-info}/METADATA +6 -20
  91. {tryton-7.0.21.dist-info → tryton-7.2.1.dist-info}/RECORD +94 -92
  92. {tryton-7.0.21.dist-info → tryton-7.2.1.dist-info}/WHEEL +1 -1
  93. {tryton-7.0.21.dist-info → tryton-7.2.1.dist-info}/LICENSE +0 -0
  94. {tryton-7.0.21.dist-info → tryton-7.2.1.dist-info}/top_level.txt +0 -0
@@ -75,7 +75,6 @@ class One2Many(Widget):
75
75
 
76
76
  hbox.pack_start(Gtk.VSeparator(), expand=False, fill=True, padding=0)
77
77
 
78
- self.focus_out = True
79
78
  self.wid_completion = None
80
79
  if attrs.get('add_remove'):
81
80
 
@@ -182,6 +181,8 @@ class One2Many(Widget):
182
181
  if self.attrs.get('add_remove'):
183
182
  self.wid_text.connect('key_press_event', self.on_keypress)
184
183
 
184
+ self._popup = False
185
+
185
186
  def get_access(self, type_):
186
187
  model = self.attrs['relation']
187
188
  if model:
@@ -370,12 +371,17 @@ class One2Many(Widget):
370
371
  self._new_single(defaults)
371
372
 
372
373
  def _new_single(self, defaults=None):
374
+ if self._popup:
375
+ return
376
+ else:
377
+ self._popup = True
373
378
  sequence = self._sequence()
374
379
 
375
380
  def update_sequence():
376
381
  if sequence:
377
382
  self.screen.group.set_sequence(
378
383
  field=sequence, position=self.screen.new_position)
384
+ self._popup = False
379
385
 
380
386
  if self.screen.current_view.creatable:
381
387
  self.screen.new()
@@ -392,6 +398,10 @@ class One2Many(Widget):
392
398
  fields = self.attrs['product'].split(',')
393
399
  product = {}
394
400
 
401
+ if self._popup:
402
+ return
403
+ else:
404
+ self._popup = True
395
405
  first = self.screen.new(default=False)
396
406
  default = first.default_get(defaults=defaults)
397
407
  first.set_default(default)
@@ -420,6 +430,7 @@ class One2Many(Widget):
420
430
  win_search.show()
421
431
 
422
432
  def make_product():
433
+ self._popup = False
423
434
  self.screen.group.remove(first, remove=True)
424
435
  if not product:
425
436
  return
@@ -448,7 +459,14 @@ class One2Many(Widget):
448
459
  return
449
460
  record = self.screen.current_record
450
461
  if record:
451
- WinForm(self.screen, lambda a: None)
462
+ if self._popup:
463
+ return
464
+ else:
465
+ self._popup = True
466
+
467
+ def callback(result):
468
+ self._popup = False
469
+ WinForm(self.screen, callback)
452
470
 
453
471
  def _sig_next(self, widget):
454
472
  if not self._validate():
@@ -475,8 +493,6 @@ class One2Many(Widget):
475
493
  self.screen.unremove()
476
494
 
477
495
  def _sig_add(self, *args):
478
- if not self.focus_out:
479
- return
480
496
  if not self.write_access or not self.read_access:
481
497
  return
482
498
  self.view.set_value()
@@ -487,12 +503,14 @@ class One2Many(Widget):
487
503
  domain = ['OR', domain, ('id', 'in', removed_ids)]
488
504
  text = self.wid_text.get_text()
489
505
 
490
- self.focus_out = False
506
+ if self._popup:
507
+ return
508
+ else:
509
+ self._popup = True
491
510
 
492
511
  sequence = self._sequence()
493
512
 
494
513
  def callback(result):
495
- self.focus_out = True
496
514
  if result:
497
515
  ids = [x[0] for x in result]
498
516
  self.screen.load(ids, modified=True)
@@ -501,6 +519,7 @@ class One2Many(Widget):
501
519
  field=sequence, position=self.screen.new_position)
502
520
  self.screen.set_cursor()
503
521
  self.wid_text.set_text('')
522
+ self._popup = False
504
523
 
505
524
  order = self.field.get_search_order(self.record)
506
525
  win = WinSearch(self.attrs['relation'], callback, sel_multi=True,
@@ -50,6 +50,8 @@ class Label(StateMixin, Gtk.Label):
50
50
  readonly = ((field and field.attrs.get('readonly'))
51
51
  or state_changes.get('readonly', not bool(field)))
52
52
  common.apply_label_attributes(self, readonly, required)
53
+ self.set_max_width_chars(80)
54
+ self.set_line_wrap(True)
53
55
 
54
56
 
55
57
  class VBox(StateMixin, Gtk.VBox):
@@ -187,14 +189,16 @@ class Link(StateMixin, Gtk.Button):
187
189
  ['AND', domain, tab_domain], 0, 100, context=context,
188
190
  callback=functools.partial(
189
191
  self._set_count, idx=i, current=self._current,
190
- counter=counter, label=label))
192
+ counter=counter, label=label),
193
+ process_exception=False)
191
194
  else:
192
195
  common.RPCExecute(
193
196
  'model', action['res_model'], 'search_count',
194
197
  domain, 0, 100, context=context,
195
198
  callback=functools.partial(
196
199
  self._set_count, current=self._current,
197
- counter=counter, label=label))
200
+ counter=counter, label=label),
201
+ process_exception=False)
198
202
 
199
203
  def _set_count(self, value, idx=0, current=None, counter=None, label=''):
200
204
  if current != self._current or not self.get_parent():
@@ -1,10 +1,12 @@
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 csv
3
4
  import gettext
4
5
  import json
5
6
  import locale
6
7
  import sys
7
8
  from functools import wraps
9
+ from io import StringIO
8
10
 
9
11
  from gi.repository import Gdk, GLib, GObject, Gtk
10
12
  from pygtkcompat.generictreemodel import GenericTreeModel
@@ -333,7 +335,7 @@ class TreeXMLViewParser(XMLViewParser):
333
335
 
334
336
  self.view.treeview.append_column(column)
335
337
 
336
- if 'optional' in attributes and name != self.exclude_field:
338
+ if 'optional' in attributes:
337
339
  self.view.optionals.append(column)
338
340
 
339
341
  def _parse_button(self, node, attributes):
@@ -720,48 +722,42 @@ class ViewTree(View):
720
722
  record.cancel()
721
723
  iter_ = model.iter_next(iter_)
722
724
 
723
- def on_copy(self):
725
+ def on_copy(self, columns=None):
726
+ if columns is None:
727
+ columns = self.treeview.get_columns()
728
+
729
+ def copy_foreach(treemodel, path, iter, add):
730
+ record = treemodel.get_value(iter, 0)
731
+ values = []
732
+ for col in columns:
733
+ if not col.get_visible() or not col.name:
734
+ continue
735
+ widget = self.get_column_widget(col)
736
+ values.append(widget.get_textual_value(record))
737
+ add(values)
738
+
724
739
  for clipboard_type in [
725
740
  Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
726
741
  clipboard = self.treeview.get_clipboard(clipboard_type)
727
742
  selection = self.treeview.get_selection()
728
- data = []
729
- selection.selected_foreach(self.copy_foreach, data)
730
- clipboard.set_text('\n'.join(data), -1)
731
-
732
- def copy_foreach(self, treemodel, path, iter, data):
733
- record = treemodel.get_value(iter, 0)
734
- values = []
735
- for col in self.treeview.get_columns():
736
- if not col.get_visible() or not col.name:
737
- continue
738
- widget = self.get_column_widget(col)
739
- values.append('"'
740
- + str(widget.get_textual_value(record)).replace('"', '""')
741
- + '"')
742
- data.append('\t'.join(values))
743
- return
743
+ data = StringIO()
744
+ writer = csv.writer(data, delimiter='\t', lineterminator='\n')
745
+ selection.selected_foreach(copy_foreach, writer.writerow)
746
+ clipboard.set_text(data.getvalue(), -1)
744
747
 
745
748
  def on_paste(self):
746
749
  if not self.editable:
747
750
  return
748
751
 
749
- def unquote(value):
750
- if value[:1] == '"' and value[-1:] == '"':
751
- return value[1:-1]
752
- return value
753
- data = []
752
+ reader = None
754
753
  for clipboard_type in [
755
754
  Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
756
755
  clipboard = self.treeview.get_clipboard(clipboard_type)
757
756
  text = clipboard.wait_for_text()
758
757
  if not text:
759
758
  continue
760
- data = [[unquote(v) for v in l.split('\t')]
761
- for l in text.splitlines()]
759
+ reader = csv.reader(StringIO(text), delimiter='\t')
762
760
  break
763
- else:
764
- return
765
761
  col = self.treeview.get_cursor()[1]
766
762
  columns = [c for c in self.treeview.get_columns()
767
763
  if c.get_visible() and c.name]
@@ -776,7 +772,7 @@ class ViewTree(View):
776
772
  group = self.group
777
773
  idx = len(group)
778
774
  default = None
779
- for line in data:
775
+ for line in reader:
780
776
  if idx >= len(group):
781
777
  record = group.new(default=False)
782
778
  if default is None:
@@ -787,25 +783,13 @@ class ViewTree(View):
787
783
  for col, value in zip(columns, line):
788
784
  widget = self.get_column_widget(col)
789
785
  if widget.get_textual_value(record) != value:
790
- if hasattr(widget, 'search_remote'):
791
- field = record.group.fields[widget.attrs['name']]
792
- win = widget.search_remote(record, field, value)
793
- if len(win.screen.group) == 1:
794
- target, = win.screen.group
795
- field.set_client(
796
- record, (target.id, target.rec_name()))
797
- else:
798
- field.set_client(record, (None, ''))
799
- win.response(win, Gtk.ResponseType.CANCEL)
800
- else:
801
- widget.value_from_text(record, value)
786
+ widget.value_from_text(record, value)
802
787
  if value and not widget.get_textual_value(record):
803
788
  # Stop setting value if a value is correctly set
804
789
  idx = len(group)
805
790
  break
806
791
  if not record.validate():
807
792
  break
808
- record.save()
809
793
  idx += 1
810
794
  self.record = record
811
795
  self.screen.display(set_cursor=True)
@@ -914,12 +898,27 @@ class ViewTree(View):
914
898
  except TypeError:
915
899
  # Outside row
916
900
  return False
901
+ selection = treeview.get_selection()
917
902
  menu = Gtk.Menu()
918
- copy_item = Gtk.MenuItem(label=_('Copy'))
919
- copy_item.connect('activate', lambda x: self.on_copy())
920
- menu.append(copy_item)
903
+ if selection.count_selected_rows():
904
+ if selection.count_selected_rows() == 1:
905
+ copy_item = Gtk.MenuItem(label=_("Copy"))
906
+ copy_item.connect(
907
+ 'activate', lambda x: self.on_copy([col]))
908
+ menu.append(copy_item)
909
+ copy_row_label = _("Copy Row")
910
+ else:
911
+ copy_row_label = _("Copy Rows")
912
+ if col:
913
+ copy_column_item = Gtk.MenuItem(label=_("Copy Column"))
914
+ copy_column_item.connect(
915
+ 'activate', lambda x: self.on_copy(columns=[col]))
916
+ menu.append(copy_column_item)
917
+ copy_row_item = Gtk.MenuItem(label=copy_row_label)
918
+ copy_row_item.connect('activate', lambda x: self.on_copy())
919
+ menu.append(copy_row_item)
921
920
  if self.editable:
922
- paste_item = Gtk.MenuItem(label=_('Paste'))
921
+ paste_item = Gtk.MenuItem(label=_("Paste Rows"))
923
922
  paste_item.connect('activate', lambda x: self.on_paste())
924
923
  menu.append(paste_item)
925
924
 
@@ -953,7 +952,6 @@ class ViewTree(View):
953
952
  menu, model, record_id, title=label, field=field,
954
953
  context=context)
955
954
 
956
- selection = treeview.get_selection()
957
955
  if selection.count_selected_rows() == 1:
958
956
  group = self.group
959
957
  if selection.get_mode() == Gtk.SelectionMode.SINGLE:
@@ -1055,12 +1053,9 @@ class ViewTree(View):
1055
1053
  elif tree_sel.get_mode() == Gtk.SelectionMode.MULTIPLE:
1056
1054
  model, paths = tree_sel.get_selected_rows()
1057
1055
  if model and paths:
1058
- records = []
1059
- for path in paths:
1060
- iter_ = model.get_iter(path)
1061
- records.append(model.get_value(iter_, 0))
1062
- if self.record not in records:
1063
- self.record = records[0]
1056
+ iter_ = model.get_iter(paths[0])
1057
+ record = model.get_value(iter_, 0)
1058
+ self.record = record
1064
1059
  else:
1065
1060
  self.record = None
1066
1061
 
@@ -1100,10 +1095,6 @@ class ViewTree(View):
1100
1095
  def display(self, force=False):
1101
1096
  self.treeview.display_counter += 1
1102
1097
  current_record = self.record
1103
- if current_record and current_record not in current_record.group:
1104
- # current record may have been removed by on_change calls without
1105
- # changing the current record of screen before the display
1106
- current_record = None
1107
1098
  if (force
1108
1099
  or not self.treeview.get_model()
1109
1100
  or self.group != self.treeview.get_model().group):
@@ -215,8 +215,12 @@ class Affix(Cell):
215
215
  else:
216
216
  value = self.icon
217
217
  if self.attrs.get('icon_type') == 'url':
218
+ def callback(pixbuf):
219
+ self.display_counters.pop(record.id, None)
220
+ self.view.treeview.queue_resize() # trigger a redraw
218
221
  pixbuf = common.IconFactory.get_pixbuf_url(
219
- value, size_param=self.attrs.get('url_size'))
222
+ value, size_param=self.attrs.get('url_size'),
223
+ callback=callback)
220
224
  else:
221
225
  pixbuf = common.IconFactory.get_pixbuf(
222
226
  value, Gtk.IconSize.BUTTON)
@@ -410,19 +414,19 @@ class Int(GenericText):
410
414
  self.symbol = attrs.get('symbol')
411
415
  self.grouping = bool(int(attrs.get('grouping', 1)))
412
416
  if self.symbol:
413
- self.renderer_prefix = Symbol(view, attrs, 0)
414
- self.renderer_suffix = Symbol(view, attrs, 1)
417
+ self._cell_prefix = Symbol(view, attrs, 0)
418
+ self._cell_suffix = Symbol(view, attrs, 1)
415
419
 
416
420
  @property
417
421
  def prefixes(self):
418
422
  if self.symbol:
419
- return [self.renderer_prefix]
423
+ return [self._cell_prefix]
420
424
  return []
421
425
 
422
426
  @property
423
427
  def suffixes(self):
424
428
  if self.symbol:
425
- return [self.renderer_suffix]
429
+ return [self._cell_suffix]
426
430
  return []
427
431
 
428
432
  @catch_errors()
@@ -574,20 +578,20 @@ class Binary(GenericText):
574
578
  super(Binary, self).__init__(view, attrs, renderer=renderer)
575
579
  self.renderer.set_property('editable', False)
576
580
  self.renderer.set_property('xalign', self.align)
577
- self.renderer_save = _BinarySave(self)
578
- self.renderer_select = _BinarySelect(self)
581
+ self._cell_save = _BinarySave(self)
582
+ self._cell_select = _BinarySelect(self)
579
583
  if self.attrs.get('filename'):
580
- self.renderer_open = _BinaryOpen(self)
584
+ self._cell_open = _BinaryOpen(self)
581
585
  else:
582
- self.renderer_open = None
586
+ self._cell_open = None
583
587
 
584
588
  @property
585
589
  def prefixes(self):
586
- return filter(None, [self.renderer_open])
590
+ return filter(None, [self._cell_open])
587
591
 
588
592
  @property
589
593
  def suffixes(self):
590
- return [self.renderer_save, self.renderer_select]
594
+ return [self._cell_save, self._cell_select]
591
595
 
592
596
  @catch_errors()
593
597
  def get_textual_value(self, record):
@@ -738,7 +742,7 @@ class _BinarySelect(_BinaryIcon):
738
742
  invisible = field.get_state_attrs(record).get('invisible', False)
739
743
  readonly = self.attrs.get('readonly',
740
744
  field.get_state_attrs(record).get('readonly', False))
741
- if readonly or size:
745
+ if readonly and size:
742
746
  cell.set_property('visible', False)
743
747
  else:
744
748
  cell.set_property('visible', not invisible)
@@ -822,6 +826,7 @@ class M2O(GenericText):
822
826
  if renderer is None and int(attrs.get('completion', 1)):
823
827
  renderer = partial(CellRendererTextCompletion, self.set_completion)
824
828
  super(M2O, self).__init__(view, attrs, renderer=renderer)
829
+ self._popup = False
825
830
 
826
831
  def get_model(self, record, field):
827
832
  return self.attrs['relation']
@@ -850,7 +855,8 @@ class M2O(GenericText):
850
855
  if model and common.get_toplevel_window().get_focus():
851
856
  field = record[self.attrs['name']]
852
857
  win = self.search_remote(record, field, text, callback=callback)
853
- win.show()
858
+ if win:
859
+ win.show()
854
860
 
855
861
  def editing_started(self, cell, editable, path):
856
862
  super(M2O, self).editing_started(cell, editable, path)
@@ -919,7 +925,9 @@ class M2O(GenericText):
919
925
  domain = field.domain_get(record)
920
926
  context = field.get_context(record)
921
927
  if not create and changed:
922
- self.search_remote(record, field, text, callback=callback).show()
928
+ win = self.search_remote(record, field, text, callback=callback)
929
+ if win:
930
+ win.show()
923
931
  return
924
932
  target_id = self.id_from_value(field.get(record))
925
933
 
@@ -959,6 +967,11 @@ class M2O(GenericText):
959
967
  access = common.MODELACCESS[model]
960
968
  create_access = int(self.attrs.get('create', 1)) and access['create']
961
969
 
970
+ if self._popup:
971
+ return
972
+ else:
973
+ self._popup = True
974
+
962
975
  def search_callback(found):
963
976
  value = None
964
977
  if found:
@@ -966,6 +979,7 @@ class M2O(GenericText):
966
979
  field.set_client(record, value)
967
980
  if callback:
968
981
  callback()
982
+ self._popup = False
969
983
  win = WinSearch(model, search_callback, sel_multi=False,
970
984
  context=context, domain=domain,
971
985
  order=order, view_ids=self.attrs.get('view_ids', '').split(','),
@@ -1253,11 +1267,11 @@ class Reference(M2O):
1253
1267
 
1254
1268
  def __init__(self, view, attrs, renderer=None):
1255
1269
  super(Reference, self).__init__(view, attrs, renderer=renderer)
1256
- self.renderer_selection = _ReferenceSelection(view, attrs)
1270
+ self._cell_selection = _ReferenceSelection(view, attrs)
1257
1271
 
1258
1272
  @property
1259
1273
  def prefixes(self):
1260
- return [self.renderer_selection]
1274
+ return [self._cell_selection]
1261
1275
 
1262
1276
  def get_model(self, record, field):
1263
1277
  value = field.get_client(record)
@@ -1405,13 +1419,12 @@ class Button(Cell):
1405
1419
  cell.set_property('visible', not invisible)
1406
1420
  readonly = states.get('readonly', False)
1407
1421
  cell.set_property('sensitive', not readonly)
1408
- if self.attrs.get('type', 'class') == 'class':
1409
- parent = record.parent if record else None
1410
- while parent:
1411
- if parent.modified:
1412
- cell.set_property('sensitive', False)
1413
- break
1414
- parent = parent.parent
1422
+ parent = record.parent if record else None
1423
+ while parent:
1424
+ if parent.modified:
1425
+ cell.set_property('sensitive', False)
1426
+ break
1427
+ parent = parent.parent
1415
1428
  # TODO icon
1416
1429
  self._set_visual(cell, record)
1417
1430
 
@@ -185,8 +185,7 @@ class Selection(Gtk.ScrolledWindow):
185
185
 
186
186
  class ScreenContainer(object):
187
187
 
188
- def __init__(self, screen, tab_domain):
189
- self.screen = screen
188
+ def __init__(self, tab_domain):
190
189
  self.viewport = Gtk.Viewport()
191
190
  self.viewport.set_shadow_type(Gtk.ShadowType.NONE)
192
191
  self.vbox = Gtk.VBox(spacing=3)
@@ -352,9 +351,12 @@ class ScreenContainer(object):
352
351
  def widget_get(self):
353
352
  return self.vbox
354
353
 
355
- def show_filter(self):
354
+ def set_screen(self, screen):
355
+ self.screen = screen
356
356
  self.but_bookmark.set_sensitive(bool(list(self.bookmarks())))
357
357
  self.bookmark_match()
358
+
359
+ def show_filter(self):
358
360
  if self.filter_vbox:
359
361
  self.filter_vbox.show()
360
362
  if self.notebook:
@@ -7,7 +7,6 @@ import gettext
7
7
  import json
8
8
  import locale
9
9
  import os
10
- import tempfile
11
10
  import urllib.parse
12
11
  from itertools import zip_longest
13
12
  from numbers import Number
@@ -15,7 +14,7 @@ from numbers import Number
15
14
  from gi.repository import Gdk, GObject, Gtk
16
15
 
17
16
  import tryton.common as common
18
- from tryton.common import RPCException, RPCExecute
17
+ from tryton.common import RPCException, RPCExecute, tempfile
19
18
  from tryton.config import CONFIG
20
19
  from tryton.gui.window.win_csv import WinCSV
21
20
  from tryton.jsonrpc import JSONEncoder
@@ -69,7 +69,7 @@ class WinForm(NoModal, InfoBar):
69
69
  self.accel_group = Gtk.AccelGroup()
70
70
  self.win.add_accel_group(self.accel_group)
71
71
 
72
- readonly = self.screen.group.readonly
72
+ readonly = self.screen.readonly or self.screen.group.readonly
73
73
 
74
74
  self.but_ok = None
75
75
  self.but_new = None
@@ -360,7 +360,7 @@ class WinForm(NoModal, InfoBar):
360
360
  deletable = True
361
361
  if self.screen.current_record:
362
362
  deletable = self.screen.current_record.deletable
363
- readonly = self.screen.group.readonly
363
+ readonly = self.screen.readonly or self.screen.group.readonly
364
364
  if position >= 1:
365
365
  name = str(position)
366
366
  if self.domain is not None:
@@ -373,11 +373,9 @@ class WinForm(NoModal, InfoBar):
373
373
  self.but_pre.set_sensitive(True)
374
374
  else:
375
375
  self.but_pre.set_sensitive(False)
376
- self.but_del.set_sensitive(bool(
377
- not readonly
378
- and access['delete']
379
- and deletable))
380
- self.but_undel.set_sensitive(bool(not readonly))
376
+ if access['delete'] and not readonly and deletable:
377
+ self.but_del.set_sensitive(True)
378
+ self.but_undel.set_sensitive(True)
381
379
  else:
382
380
  self.but_del.set_sensitive(False)
383
381
  self.but_undel.set_sensitive(False)
@@ -401,7 +399,7 @@ class WinForm(NoModal, InfoBar):
401
399
  cancel_responses = [
402
400
  Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]
403
401
  self.screen.current_view.set_value()
404
- readonly = self.screen.group.readonly
402
+ readonly = self.screen.readonly or self.screen.group.readonly
405
403
  if (response_id not in cancel_responses
406
404
  and not readonly
407
405
  and self.screen.current_record is not None):
@@ -150,16 +150,17 @@ class Wizard(InfoBar):
150
150
 
151
151
  def end(self, callback=None):
152
152
  def end_callback(action):
153
- self.destroy(action=action())
154
- if callback:
155
- callback()
156
- try:
157
- RPCExecute('wizard', self.action, 'delete', self.session_id,
158
- process_exception=False, callback=end_callback)
159
- except Exception:
160
- logger.warn(
161
- "Unable to delete session %s of wizard %s",
162
- self.session_id, self.action, exc_info=True)
153
+ try:
154
+ self.destroy(action=action())
155
+ if callback:
156
+ callback()
157
+ except RPCException:
158
+ logger.warn(
159
+ "Unable to delete session %s of wizard %s",
160
+ self.session_id, self.action, exc_info=True)
161
+ RPCExecute(
162
+ 'wizard', self.action, 'delete', self.session_id,
163
+ process_exception=False, callback=end_callback)
163
164
 
164
165
  def clean(self):
165
166
  for widget in self.widget.get_children():