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,8 +75,7 @@ class Screen:
75
75
  self.__current_record = None
76
76
  self.new_group(context or {})
77
77
  self.current_record = None
78
- self.screen_container = ScreenContainer(
79
- self, attributes.get('tab_domain'))
78
+ self.screen_container = ScreenContainer(attributes.get('tab_domain'))
80
79
  self.screen_container.alternate_view = attributes.get(
81
80
  'alternate_view', False)
82
81
  self.widget = self.screen_container.widget_get()
@@ -164,6 +163,7 @@ class Screen:
164
163
 
165
164
  def search_active(self, active=True):
166
165
  if active and not self.parent:
166
+ self.screen_container.set_screen(self)
167
167
  self.screen_container.show_filter()
168
168
  else:
169
169
  self.screen_container.hide_filter()
@@ -307,7 +307,8 @@ class Screen:
307
307
  try:
308
308
  self.search_count = RPCExecute(
309
309
  'model', self.model_name, 'search_count',
310
- domain, 0, self.count_limit, context=context)
310
+ domain, 0, self.count_limit, context=context,
311
+ process_exception=False)
311
312
  except RPCException:
312
313
  self.search_count = 0
313
314
  else:
@@ -536,6 +537,10 @@ class Screen:
536
537
  def number_of_views(self):
537
538
  return len(self.views) + len(self.view_to_load)
538
539
 
540
+ @property
541
+ def view_index(self):
542
+ return self.__current_view
543
+
539
544
  def switch_view(
540
545
  self, view_type=None, view_id=None, creatable=None, display=True):
541
546
  if view_id is not None:
@@ -800,9 +805,6 @@ class Screen:
800
805
  record.group.remove(
801
806
  record, remove=remove, modified=False,
802
807
  force_remove=force_remove)
803
- # set current_record to None to prevent __select_changed
804
- # to set deleted record as current_record
805
- self.current_record = None
806
808
  # call only once
807
809
  record.set_modified()
808
810
 
@@ -852,12 +854,11 @@ class Screen:
852
854
  parent = self.parent.id if self.parent else None
853
855
  if parent is not None and parent < 0:
854
856
  return
855
- state = self.tree_states[parent][view.children_field]
856
- if state:
857
- expanded_nodes, selected_nodes = state
858
- else:
859
- expanded_nodes, selected_nodes = [], []
857
+ expanded_nodes, selected_nodes = [], []
860
858
  if view.view_type in {'tree', 'list-form'}:
859
+ state = self.tree_states[parent][view.children_field]
860
+ if state:
861
+ expanded_nodes, selected_nodes = state
861
862
  if (state is None
862
863
  and CONFIG['client.save_tree_state']
863
864
  and int(view.attributes.get('tree_state', False))):
@@ -905,8 +906,7 @@ class Screen:
905
906
  for widget in widgets:
906
907
  if hasattr(widget, 'screen'):
907
908
  widget.screen.save_tree_state(store)
908
- if view == self.current_view and view.view_type == 'form':
909
- if self.current_record:
909
+ if len(self.views) == 1 and self.current_record:
910
910
  path = self.current_record.id
911
911
  if path < 0:
912
912
  path = -self.current_record.group.index(
@@ -920,9 +920,8 @@ class Screen:
920
920
  else:
921
921
  paths = []
922
922
  selected_paths = view.get_selected_paths()
923
- if view == self.current_view:
924
- self.tree_states[parent][view.children_field] = (
925
- paths, selected_paths)
923
+ self.tree_states[parent][view.children_field] = (
924
+ paths, selected_paths)
926
925
  if (store
927
926
  and int(view.attributes.get('tree_state', False))
928
927
  and CONFIG['client.save_tree_state']):
@@ -936,7 +935,7 @@ class Screen:
936
935
  json_paths, json_selected_path,
937
936
  process_exception=False)
938
937
  clear_cache('model.ir.ui.view_tree_state.get')
939
- except Exception:
938
+ except RPCException:
940
939
  logger.warn(
941
940
  _('Unable to set view tree state'), exc_info=True)
942
941
 
@@ -1169,6 +1168,8 @@ class Screen:
1169
1168
  def selected_paths(self):
1170
1169
  if self.current_view and self.current_view.view_type == 'tree':
1171
1170
  return self.current_view.get_selected_paths()
1171
+ else:
1172
+ return []
1172
1173
 
1173
1174
  @property
1174
1175
  def listed_records(self):
@@ -1237,7 +1238,6 @@ class Screen:
1237
1238
  record_id = self.current_record.save(force_reload=False)
1238
1239
  if record_id is False or record_id < 0:
1239
1240
  return
1240
- if button.get('type', 'class') == 'class':
1241
1241
  self._button_class(button)
1242
1242
  else:
1243
1243
  self._button_instance(button)
@@ -1329,13 +1329,13 @@ class Screen:
1329
1329
  if name:
1330
1330
  query_string.append(
1331
1331
  ('name', json.dumps(name, separators=(',', ':'))))
1332
- if self.screen_container.tab_domain:
1333
- query_string.append(('tab_domain', json.dumps(
1334
- self.screen_container.tab_domain,
1335
- cls=JSONEncoder, separators=(',', ':'))))
1336
1332
  path = [CONFIG['login.db'], 'model', self.model_name]
1337
1333
  view_ids = [v.view_id for v in self.views] + self.view_ids
1338
1334
  if self.current_view and self.current_view.view_type != 'form':
1335
+ if self.screen_container.tab_domain:
1336
+ query_string.append(('tab_domain', json.dumps(
1337
+ self.screen_container.tab_domain,
1338
+ cls=JSONEncoder, separators=(',', ':'))))
1339
1339
  if self.search_value:
1340
1340
  search_value = self.search_value
1341
1341
  else:
@@ -67,18 +67,13 @@ class Calendar_(goocalendar.Calendar):
67
67
  self.current_domain_period.get_dates(True)
68
68
  dtstart = self.attrs['dtstart']
69
69
  dtend = self.attrs.get('dtend') or dtstart
70
- domain = [
71
- (dtstart, '!=', None),
72
- (dtend, '!=', None),
73
- ['OR',
74
- ['AND', (dtstart, '>=', first_datetime),
75
- (dtstart, '<', last_datetime)],
76
- ['AND', (dtend, '>=', first_datetime),
77
- (dtend, '<', last_datetime)],
78
- ['AND', (dtstart, '<', first_datetime),
79
- (dtend, '>', last_datetime)],
80
- ],
81
- ]
70
+ domain = ['OR',
71
+ ['AND', (dtstart, '>=', first_datetime),
72
+ (dtstart, '<', last_datetime)],
73
+ ['AND', (dtend, '>=', first_datetime),
74
+ (dtend, '<', last_datetime)],
75
+ ['AND', (dtstart, '<', first_datetime),
76
+ (dtend, '>', last_datetime)]]
82
77
  return domain
83
78
 
84
79
  def get_colors(self, record):
@@ -230,6 +230,9 @@ class FormXMLViewParser(XMLViewParser):
230
230
  self.container.add(None, attributes)
231
231
  return
232
232
 
233
+ if attributes.get('loading') == 'eager':
234
+ self.field_attrs[name]['loading'] = 'eager'
235
+
233
236
  widget = self.WIDGETS[attributes['widget']](self.view, attributes)
234
237
  self.view.widgets[name].append(widget)
235
238
 
@@ -340,18 +343,7 @@ class FormXMLViewParser(XMLViewParser):
340
343
  int(attributes.get('width', -1)),
341
344
  int(attributes.get('height', -1)))
342
345
 
343
- # Force to display the first time it switches on a page
344
- # This avoids glitch in position of widgets
345
- def switch(notebook, page, page_num):
346
- if not self.view.widget:
347
- # Not yet finish to parse
348
- return
349
- notebook.grab_focus()
350
- self.view.display()
351
- notebook.disconnect(handler_id)
352
- handler_id = notebook.connect('switch-page', switch)
353
346
  self.view.state_widgets.append(notebook)
354
-
355
347
  self.view.notebooks.append(notebook)
356
348
  self.container.add(notebook, attributes)
357
349
  self.parse_child(node, notebook)
@@ -390,6 +382,7 @@ class FormXMLViewParser(XMLViewParser):
390
382
  group = Container.constructor(
391
383
  int(attributes.get('col', 4)),
392
384
  attributes.get('homogeneous', False))
385
+ self.parse_child(node, group)
393
386
 
394
387
  if 'name' in attributes and attributes['name'] == self.exclude_field:
395
388
  self.container.add(None, attributes)
@@ -413,9 +406,6 @@ class FormXMLViewParser(XMLViewParser):
413
406
  bool(attributes.get('yexpand'))))
414
407
  self.view.state_widgets.append(widget)
415
408
  self.container.add(widget, attributes)
416
- # Parse the children at the end to preserve the order of the state
417
- # widgets
418
- self.parse_child(node, group)
419
409
 
420
410
  def _parse_hpaned(self, node, attributes):
421
411
  self._parse_paned(node, attributes, Gtk.HPaned)
@@ -182,13 +182,13 @@ class Binary(BinaryMixin, Widget):
182
182
  def sig_key_press(self, widget, event, *args):
183
183
  editable = self.wid_text and self.wid_text.get_editable()
184
184
  if event.keyval == Gdk.KEY_F3 and editable:
185
- self.select(widget)
185
+ self.sig_new(widget)
186
186
  return True
187
187
  elif event.keyval == Gdk.KEY_F2:
188
188
  if self.filename:
189
- self.open_()
189
+ self.sig_open(widget)
190
190
  else:
191
- self.save_as()
191
+ self.sig_save_as(widget)
192
192
  return True
193
193
  return False
194
194
 
@@ -1,7 +1,6 @@
1
1
  # This file is part of Tryton. The COPYRIGHT file at the top level of this
2
2
  # repository contains the full copyright notices and license terms.
3
3
 
4
- import datetime as dt
5
4
  import decimal
6
5
  import gettext
7
6
  import locale
@@ -65,7 +64,7 @@ class DictEntry(object):
65
64
  return self.widget.get_text()
66
65
 
67
66
  def set_value(self, value):
68
- self.widget.set_text(str(value or ''))
67
+ self.widget.set_text(value or '')
69
68
  reset_position(self.widget)
70
69
 
71
70
  def set_readonly(self, readonly):
@@ -242,10 +241,9 @@ class DictMultiSelectionEntry(DictEntry):
242
241
  selection.handler_block_by_func(self._changed)
243
242
  try:
244
243
  selection.unselect_all()
245
- if value:
246
- for v in value:
247
- if v in value2path:
248
- selection.select_path(value2path[v])
244
+ for v in value:
245
+ if v in value2path:
246
+ selection.select_path(value2path[v])
249
247
  finally:
250
248
  selection.handler_unblock_by_func(self._changed)
251
249
 
@@ -280,7 +278,7 @@ class DictIntegerEntry(DictEntry):
280
278
  return None
281
279
 
282
280
  def set_value(self, value):
283
- if isinstance(value, (int, float, Decimal)):
281
+ if value is not None:
284
282
  txt_val = locale.format_string('%d', value, True)
285
283
  else:
286
284
  txt_val = ''
@@ -317,7 +315,7 @@ class DictFloatEntry(DictIntegerEntry):
317
315
  else:
318
316
  self.widget.digits = None
319
317
  self.widget.set_width_chars(self.width)
320
- if isinstance(value, (int, float, Decimal)):
318
+ if value is not None:
321
319
  txt_val = locale.localize(
322
320
  '{0:.{1}f}'.format(value, digits[1]), True)
323
321
  else:
@@ -363,8 +361,7 @@ class DictDateTimeEntry(DictEntry):
363
361
  return untimezoned_date(self.widget.props.value)
364
362
 
365
363
  def set_value(self, value):
366
- self.widget.props.value = (
367
- timezoned_date(value) if isinstance(value, dt.datetime) else None)
364
+ self.widget.props.value = timezoned_date(value)
368
365
 
369
366
  def set_readonly(self, readonly):
370
367
  for child in self.widget.get_children():
@@ -399,7 +396,7 @@ class DictDateEntry(DictEntry):
399
396
  return self.widget.props.value
400
397
 
401
398
  def set_value(self, value):
402
- self.widget.props.value = value if isinstance(value, dt.date) else None
399
+ self.widget.props.value = value
403
400
 
404
401
  def set_readonly(self, readonly):
405
402
  super().set_readonly(readonly)
@@ -473,6 +470,7 @@ class DictWidget(Widget):
473
470
 
474
471
  self._readonly = False
475
472
  self._record_id = None
473
+ self._popup = False
476
474
 
477
475
  @property
478
476
  def _invalid_widget(self):
@@ -494,10 +492,16 @@ class DictWidget(Widget):
494
492
  value = self.wid_text.get_text()
495
493
  domain = self.field.domain_get(self.record)
496
494
 
495
+ if self._popup:
496
+ return
497
+ else:
498
+ self._popup = True
499
+
497
500
  def callback(result):
498
501
  if result:
499
502
  self.add_new_keys([r[0] for r in result])
500
503
  self.wid_text.set_text('')
504
+ self._popup = False
501
505
 
502
506
  win = WinSearch(self.schema_model, callback, sel_multi=True,
503
507
  context=context, domain=domain, new=False)
@@ -507,15 +511,13 @@ class DictWidget(Widget):
507
511
  def add_new_keys(self, ids):
508
512
  new_keys = self.field.add_new_keys(ids, self.record)
509
513
  self.send_modified()
510
- focus = False
511
- for key_name in new_keys:
512
- if key_name not in self.fields:
513
- self.add_line(key_name)
514
- if not focus:
515
- # Use idle add because it can be called from the callback
516
- # of WinSearch while the popup is still there
517
- GLib.idle_add(self.fields[key_name].widget.grab_focus)
518
- focus = True
514
+ value = self.field.get_client(self.record)
515
+ value.update({k: None for k in new_keys})
516
+ self.display()
517
+
518
+ # Use idle add because it can be called from the callback
519
+ # of WinSearch while the popup is still there
520
+ GLib.idle_add(self.fields[new_keys[0]].widget.grab_focus)
519
521
 
520
522
  def _sig_remove(self, button, key, modified=True):
521
523
  self.fields[key].disconnect_signals()
@@ -561,7 +563,7 @@ class DictWidget(Widget):
561
563
  not self._readonly
562
564
  and self.attrs.get('delete', True)))
563
565
 
564
- def add_line(self, key):
566
+ def add_line(self, key, position):
565
567
  key_schema = self.field.keys[key]
566
568
  self.fields[key] = DICT_ENTRIES[key_schema['type']](key, self)
567
569
  field = self.fields[key]
@@ -569,8 +571,8 @@ class DictWidget(Widget):
569
571
  label = Gtk.Label(
570
572
  label=set_underline(text),
571
573
  use_underline=True, halign=Gtk.Align.END)
572
- self.grid.attach_next_to(
573
- label, None, Gtk.PositionType.BOTTOM, 1, 1)
574
+ self.grid.insert_row(position)
575
+ self.grid.attach(label, 0, position, 1, 1)
574
576
  label.set_mnemonic_widget(field.widget)
575
577
  label.show()
576
578
  hbox = Gtk.HBox(hexpand=True)
@@ -607,6 +609,11 @@ class DictWidget(Widget):
607
609
  self.field.add_keys(list(new_key_names), self.record)
608
610
  decoder = PYSONDecoder()
609
611
 
612
+ # We remove first the old keys in order to keep the order when
613
+ # inserting the new ones
614
+ for key in set(self.fields.keys()) - set(value.keys()):
615
+ self._sig_remove(None, key, modified=False)
616
+
610
617
  def filter_func(item):
611
618
  key, value = item
612
619
  return key in self.field.keys
@@ -615,9 +622,10 @@ class DictWidget(Widget):
615
622
  key, value = item
616
623
  return self.field.keys[key]['sequence'] or 0
617
624
 
618
- for key, val in sorted(filter(filter_func, value.items()), key=key):
625
+ for position, (key, val) in enumerate(
626
+ sorted(filter(filter_func, value.items()), key=key)):
619
627
  if key not in self.fields:
620
- self.add_line(key)
628
+ self.add_line(key, position)
621
629
  widget = self.fields[key]
622
630
  widget.set_value(val)
623
631
  widget.set_readonly(self._readonly)
@@ -625,8 +633,6 @@ class DictWidget(Widget):
625
633
  self.field.keys[key].get('domain') or '[]')
626
634
  widget_class(
627
635
  widget.widget, 'invalid', not eval_domain(key_domain, value))
628
- for key in set(self.fields.keys()) - set(value.keys()):
629
- self._sig_remove(None, key, modified=False)
630
636
 
631
637
  self._set_button_sensitive()
632
638
 
@@ -1,6 +1,7 @@
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
3
  from pathlib import Path
4
+ from tempfile import NamedTemporaryFile
4
5
 
5
6
  from gi.repository import Gdk, GLib, Gtk
6
7
 
@@ -75,17 +76,17 @@ class Document(BinaryMixin, Widget):
75
76
  self.image.hide()
76
77
  if self.evince_view:
77
78
  self.evince_scroll.show()
78
- suffix = None
79
79
  if self.filename_field:
80
- filename = self.filename_field.get(self.record)
81
- if filename:
82
- suffix = Path(filename).suffix
83
- filename = Path(self.field.get_filename(self.record, suffix))
80
+ suffix = self.filename_field.get(self.record)
81
+ else:
82
+ suffix = None
84
83
  try:
85
- document = (
86
- EvinceDocument.Document.factory_get_document_full(
87
- filename.as_uri(),
88
- EvinceDocument.DocumentLoadFlags.NONE))
84
+ with NamedTemporaryFile(suffix=suffix) as fp:
85
+ fp.write(data)
86
+ path = Path(fp.name)
87
+ document = (
88
+ EvinceDocument.Document.factory_get_document(
89
+ path.as_uri()))
89
90
  model = EvinceView.DocumentModel()
90
91
  model.set_document(document)
91
92
  self.evince_view.set_model(model)
@@ -48,7 +48,6 @@ class Many2Many(Widget):
48
48
  self.wid_text.set_placeholder_text(_('Search'))
49
49
  self.wid_text.set_property('width_chars', 13)
50
50
  self.wid_text.connect('focus-out-event', self._focus_out)
51
- self.focus_out = True
52
51
  hbox.pack_start(self.wid_text, expand=True, fill=True, padding=0)
53
52
 
54
53
  if int(self.attrs.get('completion', 1)):
@@ -113,6 +112,8 @@ class Many2Many(Widget):
113
112
  self.screen.widget.connect('key_press_event', self.on_keypress)
114
113
  self.wid_text.connect('key_press_event', self.on_keypress)
115
114
 
115
+ self._popup = False
116
+
116
117
  def on_keypress(self, widget, event):
117
118
  editable = self.wid_text.get_editable()
118
119
  activate_keys = [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]
@@ -161,8 +162,6 @@ class Many2Many(Widget):
161
162
  return int(self.attrs.get('create', 1)) and self.get_access('create')
162
163
 
163
164
  def _sig_add(self, *args):
164
- if not self.focus_out:
165
- return
166
165
  domain = self.field.domain_get(self.record)
167
166
  add_remove = self.record.expr_eval(self.attrs.get('add_remove'))
168
167
  if add_remove:
@@ -171,15 +170,18 @@ class Many2Many(Widget):
171
170
  order = self.field.get_search_order(self.record)
172
171
  value = self.wid_text.get_text()
173
172
 
174
- self.focus_out = False
173
+ if self._popup:
174
+ return
175
+ else:
176
+ self._popup = True
175
177
 
176
178
  def callback(result):
177
- self.focus_out = True
178
179
  if result:
179
180
  ids = [x[0] for x in result]
180
181
  self.screen.load(ids, modified=True)
181
182
  self.screen.set_cursor()
182
183
  self.wid_text.set_text('')
184
+ self._popup = False
183
185
  win = WinSearch(self.attrs['relation'], callback, sel_multi=True,
184
186
  context=context, domain=domain, order=order,
185
187
  view_ids=self.attrs.get('view_ids', '').split(','),
@@ -215,6 +217,10 @@ class Many2Many(Widget):
215
217
  def _sig_edit(self):
216
218
  if not self.screen.current_record:
217
219
  return
220
+ if self._popup:
221
+ return
222
+ else:
223
+ self._popup = True
218
224
  # Create a new screen that is not linked to the parent otherwise on the
219
225
  # save of the record will trigger the save of the parent
220
226
  screen = self._get_screen_form()
@@ -231,22 +237,26 @@ class Many2Many(Widget):
231
237
  self.screen.current_record.modified_fields.setdefault('id')
232
238
  # Force a display to clear the CellCache
233
239
  self.screen.display()
240
+ self._popup = False
234
241
  WinForm(screen, callback)
235
242
 
236
243
  def _sig_new(self, defaults=None):
244
+ if self._popup:
245
+ return
246
+ else:
247
+ self._popup = True
237
248
  screen = self._get_screen_form()
238
249
  defaults = defaults.copy() if defaults is not None else {}
239
250
  defaults['rec_name'] = self.wid_text.get_text()
240
251
 
241
252
  def callback(result):
242
- self.focus_out = True
243
253
  if result:
244
254
  record = screen.current_record
245
255
  self.screen.load([record.id], modified=True)
246
256
  self.wid_text.set_text('')
247
257
  self.wid_text.grab_focus()
258
+ self._popup = False
248
259
 
249
- self.focus_out = False
250
260
  WinForm(
251
261
  screen, callback, new=True, save_current=True, defaults=defaults)
252
262
 
@@ -37,7 +37,7 @@ class Many2One(Widget):
37
37
  lambda x, y: self._focus_out())
38
38
  self.wid_text.connect('changed', self.sig_changed)
39
39
  self.changed = True
40
- self.focus_out = True
40
+ self._popup = False
41
41
 
42
42
  if int(self.attrs.get('completion', 1)):
43
43
  self.wid_text.connect('changed', self._update_completion)
@@ -103,13 +103,16 @@ class Many2One(Widget):
103
103
  model = self.get_model()
104
104
  if not model or not common.MODELACCESS[model]['read']:
105
105
  return
106
- if not self.focus_out or not self.field:
106
+ if not self.field:
107
107
  return
108
108
  self.changed = False
109
109
  value = self.field.get(self.record)
110
110
  model = self.get_model()
111
111
 
112
- self.focus_out = False
112
+ if self._popup:
113
+ return
114
+ else:
115
+ self._popup = True
113
116
  if model and not self.has_target(value):
114
117
  if (not self._readonly
115
118
  and (self.wid_text.get_text()
@@ -126,7 +129,7 @@ class Many2One(Widget):
126
129
  self.value_from_id(*result[0]), force_change=True)
127
130
  else:
128
131
  self.wid_text.set_text('')
129
- self.focus_out = True
132
+ self._popup = False
130
133
  self.changed = True
131
134
 
132
135
  win = WinSearch(model, callback, sel_multi=False,
@@ -139,7 +142,7 @@ class Many2One(Widget):
139
142
  win.screen.search_filter(quote(text))
140
143
  win.show()
141
144
  return
142
- self.focus_out = True
145
+ self._popup = False
143
146
  self.changed = True
144
147
  return
145
148
 
@@ -164,7 +167,10 @@ class Many2One(Widget):
164
167
  def sig_new(self, defaults=None):
165
168
  if not self.create_access:
166
169
  return
167
- self.focus_out = False
170
+ if self._popup:
171
+ return
172
+ else:
173
+ self._popup = True
168
174
  screen = self.get_screen(search=True)
169
175
  defaults = defaults.copy() if defaults is not None else {}
170
176
  defaults['rec_name'] = self.wid_text.get_text()
@@ -174,7 +180,7 @@ class Many2One(Widget):
174
180
  self.field.set_client(self.record,
175
181
  self.value_from_id(screen.current_record.id,
176
182
  screen.current_record.rec_name()))
177
- self.focus_out = True
183
+ self._popup = False
178
184
  WinForm(
179
185
  screen, callback, new=True, save_current=True, defaults=defaults)
180
186
 
@@ -184,10 +190,9 @@ class Many2One(Widget):
184
190
  model = self.get_model()
185
191
  if not model or not common.MODELACCESS[model]['read']:
186
192
  return
187
- if not self.focus_out or not self.field:
193
+ if not self.field:
188
194
  return
189
195
  self.changed = False
190
- self.focus_out = False
191
196
  value = self.field.get(self.record)
192
197
 
193
198
  if (icon_pos == Gtk.EntryIconPosition.SECONDARY
@@ -196,9 +201,12 @@ class Many2One(Widget):
196
201
  self.field.set_client(self.record, self.value_from_id(None, ''))
197
202
  self.wid_text.set_text('')
198
203
  self.changed = True
199
- self.focus_out = True
200
204
  return
201
205
 
206
+ if self._popup:
207
+ return
208
+ else:
209
+ self._popup = True
202
210
  if self.has_target(value):
203
211
  m2o_id = self.id_from_value(self.field.get(self.record))
204
212
  screen = self.get_screen()
@@ -211,7 +219,7 @@ class Many2One(Widget):
211
219
  self.value_from_id(screen.current_record.id,
212
220
  screen.current_record.rec_name()),
213
221
  force_change=True)
214
- self.focus_out = True
222
+ self._popup = False
215
223
  self.changed = True
216
224
  WinForm(screen, callback, save_current=True)
217
225
  return
@@ -225,7 +233,7 @@ class Many2One(Widget):
225
233
  if result:
226
234
  self.field.set_client(self.record,
227
235
  self.value_from_id(*result[0]), force_change=True)
228
- self.focus_out = True
236
+ self._popup = False
229
237
  self.changed = True
230
238
  win = WinSearch(model, callback, sel_multi=False,
231
239
  context=context, domain=domain, order=order,
@@ -236,7 +244,7 @@ class Many2One(Widget):
236
244
  win.screen.search_filter(quote(text))
237
245
  win.show()
238
246
  return
239
- self.focus_out = True
247
+ self._popup = False
240
248
  self.changed = True
241
249
 
242
250
  def sig_key_press(self, widget, event, *args):