tryton 7.0.7__py3-none-any.whl → 7.4.4__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 (105) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +149 -73
  4. tryton/common/completion.py +2 -2
  5. tryton/common/datetime_.py +3 -1
  6. tryton/common/domain_inversion.py +2 -1
  7. tryton/common/domain_parser.py +22 -11
  8. tryton/common/popup_menu.py +1 -1
  9. tryton/common/selection.py +6 -3
  10. tryton/common/tempfile.py +34 -0
  11. tryton/config.py +4 -5
  12. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  13. tryton/data/locale/bg/LC_MESSAGES/tryton.po +69 -20
  14. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  15. tryton/data/locale/ca/LC_MESSAGES/tryton.po +70 -25
  16. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  17. tryton/data/locale/cs/LC_MESSAGES/tryton.po +68 -21
  18. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  19. tryton/data/locale/de/LC_MESSAGES/tryton.po +71 -26
  20. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  21. tryton/data/locale/es/LC_MESSAGES/tryton.po +68 -23
  22. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  23. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +72 -22
  24. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  25. tryton/data/locale/et/LC_MESSAGES/tryton.po +73 -23
  26. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  27. tryton/data/locale/fa/LC_MESSAGES/tryton.po +74 -25
  28. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  29. tryton/data/locale/fi/LC_MESSAGES/tryton.po +63 -20
  30. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  31. tryton/data/locale/fr/LC_MESSAGES/tryton.po +73 -28
  32. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/hu/LC_MESSAGES/tryton.po +75 -23
  34. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/id/LC_MESSAGES/tryton.po +72 -23
  36. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/it/LC_MESSAGES/tryton.po +73 -24
  38. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lo/LC_MESSAGES/tryton.po +74 -25
  41. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  42. tryton/data/locale/lt/LC_MESSAGES/tryton.po +73 -23
  43. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  44. tryton/data/locale/nl/LC_MESSAGES/tryton.po +72 -27
  45. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  46. tryton/data/locale/pl/LC_MESSAGES/tryton.po +113 -78
  47. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  48. tryton/data/locale/pt/LC_MESSAGES/tryton.po +73 -24
  49. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  50. tryton/data/locale/ro/LC_MESSAGES/tryton.po +87 -36
  51. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  52. tryton/data/locale/ru/LC_MESSAGES/tryton.po +72 -25
  53. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  54. tryton/data/locale/sl/LC_MESSAGES/tryton.po +77 -26
  55. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  56. tryton/data/locale/tr/LC_MESSAGES/tryton.po +64 -20
  57. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  58. tryton/data/locale/uk/LC_MESSAGES/tryton.po +75 -23
  59. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  60. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +76 -24
  61. tryton/device_cookie.py +1 -1
  62. tryton/gui/main.py +14 -12
  63. tryton/gui/window/about.py +1 -1
  64. tryton/gui/window/dblogin.py +2 -2
  65. tryton/gui/window/email_.py +2 -2
  66. tryton/gui/window/form.py +10 -5
  67. tryton/gui/window/log.py +24 -2
  68. tryton/gui/window/tabcontent.py +2 -2
  69. tryton/gui/window/view_form/model/field.py +84 -34
  70. tryton/gui/window/view_form/model/group.py +7 -2
  71. tryton/gui/window/view_form/model/record.py +70 -31
  72. tryton/gui/window/view_form/screen/screen.py +98 -47
  73. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
  74. tryton/gui/window/view_form/view/form.py +6 -12
  75. tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
  76. tryton/gui/window/view_form/view/form_gtk/dictionary.py +49 -29
  77. tryton/gui/window/view_form/view/form_gtk/document.py +15 -10
  78. tryton/gui/window/view_form/view/form_gtk/many2many.py +49 -7
  79. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  80. tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
  81. tryton/gui/window/view_form/view/form_gtk/one2many.py +42 -10
  82. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  83. tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
  84. tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
  85. tryton/gui/window/view_form/view/list.py +116 -48
  86. tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
  87. tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
  88. tryton/gui/window/view_form/view/screen_container.py +3 -5
  89. tryton/gui/window/win_csv.py +6 -12
  90. tryton/gui/window/win_export.py +49 -26
  91. tryton/gui/window/win_form.py +9 -7
  92. tryton/gui/window/win_import.py +45 -15
  93. tryton/gui/window/wizard.py +13 -10
  94. tryton/jsonrpc.py +75 -34
  95. tryton/plugins/__init__.py +5 -3
  96. tryton/pyson.py +57 -6
  97. tryton/rpc.py +18 -0
  98. tryton/tests/test_common_domain_parser.py +31 -2
  99. tryton/translate.py +5 -2
  100. {tryton-7.0.7.data → tryton-7.4.4.data}/scripts/tryton +8 -7
  101. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/METADATA +6 -6
  102. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/RECORD +105 -103
  103. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/WHEEL +1 -1
  104. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
  105. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,8 @@ from tryton.common.cellrenderertext import (
23
23
  CellRendererText, CellRendererTextCompletion)
24
24
  from tryton.common.cellrenderertoggle import CellRendererToggle
25
25
  from tryton.common.completion import get_completion, update_completion
26
- from tryton.common.datetime_ import CellRendererDate, CellRendererTime
26
+ from tryton.common.datetime_ import (
27
+ CellRendererDate, CellRendererTime, date_parse)
27
28
  from tryton.common.domain_parser import quote
28
29
  from tryton.common.selection import (
29
30
  PopdownMixin, SelectionMixin, selection_shortcuts)
@@ -214,8 +215,12 @@ class Affix(Cell):
214
215
  else:
215
216
  value = self.icon
216
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
217
221
  pixbuf = common.IconFactory.get_pixbuf_url(
218
- value, size_param=self.attrs.get('url_size'))
222
+ value, size_param=self.attrs.get('url_size'),
223
+ callback=callback)
219
224
  else:
220
225
  pixbuf = common.IconFactory.get_pixbuf(
221
226
  value, Gtk.IconSize.BUTTON)
@@ -376,6 +381,14 @@ class GenericText(Cell):
376
381
  editable.connect('remove-widget', remove)
377
382
  return False
378
383
 
384
+ def get_editable(self, renderer):
385
+ if self.renderer == renderer:
386
+ return self.editable
387
+ for cell in self.prefixes + self.suffixes:
388
+ editable = cell.get_editable(renderer)
389
+ if editable:
390
+ return editable
391
+
379
392
 
380
393
  class Char(GenericText):
381
394
 
@@ -401,19 +414,19 @@ class Int(GenericText):
401
414
  self.symbol = attrs.get('symbol')
402
415
  self.grouping = bool(int(attrs.get('grouping', 1)))
403
416
  if self.symbol:
404
- self.renderer_prefix = Symbol(view, attrs, 0)
405
- self.renderer_suffix = Symbol(view, attrs, 1)
417
+ self._cell_prefix = Symbol(view, attrs, 0)
418
+ self._cell_suffix = Symbol(view, attrs, 1)
406
419
 
407
420
  @property
408
421
  def prefixes(self):
409
422
  if self.symbol:
410
- return [self.renderer_prefix]
423
+ return [self._cell_prefix]
411
424
  return []
412
425
 
413
426
  @property
414
427
  def suffixes(self):
415
428
  if self.symbol:
416
- return [self.renderer_suffix]
429
+ return [self._cell_suffix]
417
430
  return []
418
431
 
419
432
  @catch_errors()
@@ -493,6 +506,17 @@ class Date(GenericText):
493
506
  else:
494
507
  return ''
495
508
 
509
+ def value_from_text(self, record, text, callback=None):
510
+ if isinstance(text, str):
511
+ field = record[self.attrs['name']]
512
+ try:
513
+ # Use a datetime instance and rely on field to convert to the
514
+ # proper type
515
+ text = date_parse(text, self.get_format(record, field))
516
+ except (ValueError, OverflowError):
517
+ text = None
518
+ return super().value_from_text(record, text, callback=callback)
519
+
496
520
 
497
521
  class Time(Date):
498
522
 
@@ -554,20 +578,20 @@ class Binary(GenericText):
554
578
  super(Binary, self).__init__(view, attrs, renderer=renderer)
555
579
  self.renderer.set_property('editable', False)
556
580
  self.renderer.set_property('xalign', self.align)
557
- self.renderer_save = _BinarySave(self)
558
- self.renderer_select = _BinarySelect(self)
581
+ self._cell_save = _BinarySave(self)
582
+ self._cell_select = _BinarySelect(self)
559
583
  if self.attrs.get('filename'):
560
- self.renderer_open = _BinaryOpen(self)
584
+ self._cell_open = _BinaryOpen(self)
561
585
  else:
562
- self.renderer_open = None
586
+ self._cell_open = None
563
587
 
564
588
  @property
565
589
  def prefixes(self):
566
- return filter(None, [self.renderer_open])
590
+ return filter(None, [self._cell_open])
567
591
 
568
592
  @property
569
593
  def suffixes(self):
570
- return [self.renderer_save, self.renderer_select]
594
+ return [self._cell_save, self._cell_select]
571
595
 
572
596
  @catch_errors()
573
597
  def get_textual_value(self, record):
@@ -718,7 +742,7 @@ class _BinarySelect(_BinaryIcon):
718
742
  invisible = field.get_state_attrs(record).get('invisible', False)
719
743
  readonly = self.attrs.get('readonly',
720
744
  field.get_state_attrs(record).get('readonly', False))
721
- if readonly and size:
745
+ if readonly or size:
722
746
  cell.set_property('visible', False)
723
747
  else:
724
748
  cell.set_property('visible', not invisible)
@@ -802,6 +826,7 @@ class M2O(GenericText):
802
826
  if renderer is None and int(attrs.get('completion', 1)):
803
827
  renderer = partial(CellRendererTextCompletion, self.set_completion)
804
828
  super(M2O, self).__init__(view, attrs, renderer=renderer)
829
+ self._popup = False
805
830
 
806
831
  def get_model(self, record, field):
807
832
  return self.attrs['relation']
@@ -830,7 +855,8 @@ class M2O(GenericText):
830
855
  if model and common.get_toplevel_window().get_focus():
831
856
  field = record[self.attrs['name']]
832
857
  win = self.search_remote(record, field, text, callback=callback)
833
- win.show()
858
+ if win:
859
+ win.show()
834
860
 
835
861
  def editing_started(self, cell, editable, path):
836
862
  super(M2O, self).editing_started(cell, editable, path)
@@ -899,7 +925,9 @@ class M2O(GenericText):
899
925
  domain = field.domain_get(record)
900
926
  context = field.get_context(record)
901
927
  if not create and changed:
902
- 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()
903
931
  return
904
932
  target_id = self.id_from_value(field.get(record))
905
933
 
@@ -939,6 +967,11 @@ class M2O(GenericText):
939
967
  access = common.MODELACCESS[model]
940
968
  create_access = int(self.attrs.get('create', 1)) and access['create']
941
969
 
970
+ if self._popup:
971
+ return
972
+ else:
973
+ self._popup = True
974
+
942
975
  def search_callback(found):
943
976
  value = None
944
977
  if found:
@@ -946,6 +979,7 @@ class M2O(GenericText):
946
979
  field.set_client(record, value)
947
980
  if callback:
948
981
  callback()
982
+ self._popup = False
949
983
  win = WinSearch(model, search_callback, sel_multi=False,
950
984
  context=context, domain=domain,
951
985
  order=order, view_ids=self.attrs.get('view_ids', '').split(','),
@@ -1233,11 +1267,11 @@ class Reference(M2O):
1233
1267
 
1234
1268
  def __init__(self, view, attrs, renderer=None):
1235
1269
  super(Reference, self).__init__(view, attrs, renderer=renderer)
1236
- self.renderer_selection = _ReferenceSelection(view, attrs)
1270
+ self._cell_selection = _ReferenceSelection(view, attrs)
1237
1271
 
1238
1272
  @property
1239
1273
  def prefixes(self):
1240
- return [self.renderer_selection]
1274
+ return [self._cell_selection]
1241
1275
 
1242
1276
  def get_model(self, record, field):
1243
1277
  value = field.get_client(record)
@@ -1385,12 +1419,13 @@ class Button(Cell):
1385
1419
  cell.set_property('visible', not invisible)
1386
1420
  readonly = states.get('readonly', False)
1387
1421
  cell.set_property('sensitive', not readonly)
1388
- parent = record.parent if record else None
1389
- while parent:
1390
- if parent.modified:
1391
- cell.set_property('sensitive', False)
1392
- break
1393
- parent = parent.parent
1422
+ if self.attrs.get('type', 'class') == 'class':
1423
+ parent = record.parent if record else None
1424
+ while parent:
1425
+ if parent.modified:
1426
+ cell.set_property('sensitive', False)
1427
+ break
1428
+ parent = parent.parent
1394
1429
  # TODO icon
1395
1430
  self._set_visual(cell, record)
1396
1431
 
@@ -185,7 +185,8 @@ class Selection(Gtk.ScrolledWindow):
185
185
 
186
186
  class ScreenContainer(object):
187
187
 
188
- def __init__(self, tab_domain):
188
+ def __init__(self, screen, tab_domain):
189
+ self.screen = screen
189
190
  self.viewport = Gtk.Viewport()
190
191
  self.viewport.set_shadow_type(Gtk.ShadowType.NONE)
191
192
  self.vbox = Gtk.VBox(spacing=3)
@@ -351,12 +352,9 @@ class ScreenContainer(object):
351
352
  def widget_get(self):
352
353
  return self.vbox
353
354
 
354
- def set_screen(self, screen):
355
- self.screen = screen
355
+ def show_filter(self):
356
356
  self.but_bookmark.set_sensitive(bool(list(self.bookmarks())))
357
357
  self.bookmark_match()
358
-
359
- def show_filter(self):
360
358
  if self.filter_vbox:
361
359
  self.filter_vbox.show()
362
360
  if self.notebook:
@@ -7,8 +7,7 @@ import sys
7
7
 
8
8
  from gi.repository import Gdk, GObject, Gtk
9
9
 
10
- from tryton.common import IconFactory
11
- from tryton.common.underline import set_underline
10
+ from tryton.common import IconFactory, RPCExecute
12
11
  from tryton.config import TRYTON_ICON
13
12
  from tryton.gui import Main
14
13
  from tryton.gui.window.nomodal import NoModal
@@ -36,6 +35,11 @@ class WinCSV(NoModal):
36
35
  def __init__(self, *args, **kwargs):
37
36
  super(WinCSV, self).__init__(*args, **kwargs)
38
37
 
38
+ self.languages = RPCExecute(
39
+ 'model', 'ir.lang', 'search_read', [
40
+ ('translatable', '=', True),
41
+ ], 0, None, [('name', 'DESC')], ['code', 'name'])
42
+
39
43
  self.dialog = Gtk.Dialog(
40
44
  transient_for=self.parent, destroy_with_parent=True)
41
45
  Main().add_window(self.dialog)
@@ -172,16 +176,6 @@ class WinCSV(NoModal):
172
176
 
173
177
  self.add_csv_header_param(box)
174
178
 
175
- button_cancel = self.dialog.add_button(
176
- set_underline(_("Cancel")), Gtk.ResponseType.CANCEL)
177
- button_cancel.set_image(IconFactory.get_image(
178
- 'tryton-cancel', Gtk.IconSize.BUTTON))
179
-
180
- button_ok = self.dialog.add_button(
181
- set_underline(_("OK")), Gtk.ResponseType.OK)
182
- button_ok.set_image(IconFactory.get_image(
183
- 'tryton-ok', Gtk.IconSize.BUTTON))
184
-
185
179
  self.dialog.vbox.pack_start(
186
180
  dialog_vbox, expand=True, fill=True, padding=0)
187
181
 
@@ -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,16 +14,19 @@ 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 IconFactory, RPCException, RPCExecute, tempfile
18
+ from tryton.common.underline import set_underline
19
19
  from tryton.config import CONFIG
20
20
  from tryton.gui.window.win_csv import WinCSV
21
21
  from tryton.jsonrpc import JSONEncoder
22
22
  from tryton.rpc import CONNECTION, clear_cache
23
23
 
24
+ from .infobar import InfoBar
25
+
24
26
  _ = gettext.gettext
25
27
 
26
28
 
27
- class WinExport(WinCSV):
29
+ class WinExport(WinCSV, InfoBar):
28
30
  "Window export"
29
31
 
30
32
  def __init__(self, name, screen):
@@ -33,8 +35,23 @@ class WinExport(WinCSV):
33
35
  self.fields = {}
34
36
  super(WinExport, self).__init__()
35
37
  self.dialog.set_title(_('CSV Export: %s') % name)
36
- # Hide as selected record is the default
37
- self.ignore_search_limit.hide()
38
+ self.dialog.vbox.pack_start(
39
+ self.create_info_bar(), expand=False, fill=True, padding=0)
40
+ # Trigger changed event to update ignore search limit
41
+ active_index = self.selected_records.get_active()
42
+ self.selected_records.set_active(-1)
43
+ self.selected_records.set_active(active_index)
44
+
45
+ self.dialog.add_button(
46
+ set_underline(_("Close")), Gtk.ResponseType.CANCEL).set_image(
47
+ IconFactory.get_image('tryton-close', Gtk.IconSize.BUTTON))
48
+ self.dialog.add_button(
49
+ set_underline(_("Open")), Gtk.ResponseType.OK).set_image(
50
+ IconFactory.get_image('tryton-open', Gtk.IconSize.BUTTON))
51
+ self.dialog.add_button(
52
+ set_underline(_("Save As...")), Gtk.ResponseType.ACCEPT).set_image(
53
+ IconFactory.get_image('tryton-save', Gtk.IconSize.BUTTON))
54
+ self.dialog.set_default_response(Gtk.ResponseType.OK)
38
55
 
39
56
  @property
40
57
  def model(self):
@@ -51,6 +68,10 @@ class WinExport(WinCSV):
51
68
  and self.screen.current_view.view_type == 'tree'
52
69
  and self.screen.current_view.children_field)
53
70
 
71
+ @property
72
+ def screen_has_selected(self):
73
+ return bool(self.screen.selected_records)
74
+
54
75
  def add_buttons(self, box):
55
76
  button_save_export = Gtk.Button(
56
77
  label=_('_Save Export'), stock=None, use_underline=True)
@@ -114,19 +135,13 @@ class WinExport(WinCSV):
114
135
  def add_chooser(self, box):
115
136
  hbox_csv_export = Gtk.HBox()
116
137
  box.pack_start(hbox_csv_export, expand=False, fill=True, padding=0)
117
- self.saveas = Gtk.ComboBoxText()
118
- hbox_csv_export.pack_start(
119
- self.saveas, expand=True, fill=True, padding=3)
120
- self.saveas.append_text(_("Open"))
121
- self.saveas.append_text(_("Save"))
122
- self.saveas.set_active(0)
123
138
 
124
139
  self.selected_records = Gtk.ComboBoxText()
125
140
  hbox_csv_export.pack_start(
126
141
  self.selected_records, expand=True, fill=True, padding=3)
127
142
  self.selected_records.append_text(_("Listed Records"))
128
143
  self.selected_records.append_text(_("Selected Records"))
129
- if not self.screen_is_tree:
144
+ if not self.screen_is_tree and self.screen_has_selected:
130
145
  self.selected_records.set_active(1)
131
146
  else:
132
147
  self.selected_records.set_active(0)
@@ -177,6 +192,13 @@ class WinExport(WinCSV):
177
192
  if '.' not in name:
178
193
  if field.get('relation'):
179
194
  self.model1.insert(node, 0, [None, ''])
195
+ elif field.get('translate', False):
196
+ for language in self.languages:
197
+ l_path = f"{path}:lang={language['code']}"
198
+ l_string = f"{string_} ({language['name']})"
199
+ self.model1.insert(
200
+ node, 0, [language['name'], l_path])
201
+ self.fields[l_path] = (l_string, None)
180
202
 
181
203
  def _get_fields(self, model):
182
204
  try:
@@ -335,7 +357,8 @@ class WinExport(WinCSV):
335
357
  self.model2.append((string_, name))
336
358
 
337
359
  def response(self, dialog, response):
338
- if response == Gtk.ResponseType.OK:
360
+ self.info_bar_clear()
361
+ if response in {Gtk.ResponseType.OK, Gtk.ResponseType.ACCEPT}:
339
362
  fields = []
340
363
  iter = self.model2.get_iter_first()
341
364
  while iter:
@@ -379,7 +402,7 @@ class WinExport(WinCSV):
379
402
  except RPCException:
380
403
  data = []
381
404
 
382
- if self.saveas.get_active():
405
+ if response == Gtk.ResponseType.ACCEPT:
383
406
  fname = common.file_selection(_('Save As...'),
384
407
  action=Gtk.FileChooserAction.SAVE)
385
408
  if fname:
@@ -387,15 +410,15 @@ class WinExport(WinCSV):
387
410
  else:
388
411
  fileno, fname = tempfile.mkstemp(
389
412
  '.csv', common.slugify(self.name) + '_')
390
- if self.export_csv(
391
- fname, data, paths, popup=False, header=header):
413
+ if self.export_csv(fname, data, paths, header=header):
392
414
  os.close(fileno)
393
415
  common.file_open(fname, 'csv')
394
416
  else:
395
417
  os.close(fileno)
396
- self.destroy()
418
+ else:
419
+ self.destroy()
397
420
 
398
- def export_csv(self, fname, data, paths, popup=True, header=False):
421
+ def export_csv(self, fname, data, paths, header=False):
399
422
  encoding = self.csv_enc.get_active_text() or 'utf_8_sig'
400
423
  locale_format = self.csv_locale.get_active()
401
424
 
@@ -409,14 +432,14 @@ class WinExport(WinCSV):
409
432
  if row:
410
433
  writer.writerow(self.format_row(
411
434
  row, indent=indent, locale_format=locale_format))
412
- if popup:
413
- size = len(data)
414
- if header:
415
- size -= 1
416
- if size <= 1:
417
- common.message(_('%d record saved.') % size)
418
- else:
419
- common.message(_('%d records saved.') % size)
435
+ size = len(data)
436
+ if header:
437
+ size -= 1
438
+ if size <= 1:
439
+ message = _('%d record saved.') % size
440
+ else:
441
+ message = _('%d records saved.') % size
442
+ self.info_bar_add(message, Gtk.MessageType.INFO)
420
443
  return True
421
444
  except (IOError, UnicodeEncodeError, csv.Error) as exception:
422
445
  common.warning(str(exception), _('Export failed'))
@@ -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.readonly or self.screen.group.readonly
72
+ readonly = 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.readonly or self.screen.group.readonly
363
+ readonly = self.screen.group.readonly
364
364
  if position >= 1:
365
365
  name = str(position)
366
366
  if self.domain is not None:
@@ -373,9 +373,11 @@ class WinForm(NoModal, InfoBar):
373
373
  self.but_pre.set_sensitive(True)
374
374
  else:
375
375
  self.but_pre.set_sensitive(False)
376
- if access['delete'] and not readonly and deletable:
377
- self.but_del.set_sensitive(True)
378
- self.but_undel.set_sensitive(True)
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))
379
381
  else:
380
382
  self.but_del.set_sensitive(False)
381
383
  self.but_undel.set_sensitive(False)
@@ -399,7 +401,7 @@ class WinForm(NoModal, InfoBar):
399
401
  cancel_responses = [
400
402
  Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]
401
403
  self.screen.current_view.set_value()
402
- readonly = self.screen.readonly or self.screen.group.readonly
404
+ readonly = self.screen.group.readonly
403
405
  if (response_id not in cancel_responses
404
406
  and not readonly
405
407
  and self.screen.current_record is not None):
@@ -445,7 +447,7 @@ class WinForm(NoModal, InfoBar):
445
447
  record.modified_fields.setdefault('id')
446
448
  result = False
447
449
  else:
448
- result = response_id not in cancel_responses
450
+ result = (response_id not in cancel_responses) and not readonly
449
451
  if self.callback:
450
452
  self.callback(result)
451
453
  self.destroy()
@@ -2,6 +2,7 @@
2
2
  # this repository contains the full copyright notices and license terms.
3
3
  import base64
4
4
  import csv
5
+ import datetime as dt
5
6
  import gettext
6
7
  import locale
7
8
  from decimal import Decimal
@@ -9,8 +10,8 @@ from decimal import Decimal
9
10
  from gi.repository import Gtk
10
11
 
11
12
  import tryton.common as common
12
- from tryton.common import RPCException, RPCExecute
13
- from tryton.common.datetime_ import date_parse
13
+ from tryton.common import IconFactory, RPCException, RPCExecute
14
+ from tryton.common.underline import set_underline
14
15
  from tryton.gui.window.win_csv import WinCSV
15
16
 
16
17
  _ = gettext.gettext
@@ -29,6 +30,16 @@ class WinImport(WinCSV):
29
30
  super(WinImport, self).__init__()
30
31
  self.dialog.set_title(_('CSV Import: %s') % name)
31
32
 
33
+ button_cancel = self.dialog.add_button(
34
+ set_underline(_("Cancel")), Gtk.ResponseType.CANCEL)
35
+ button_cancel.set_image(IconFactory.get_image(
36
+ 'tryton-cancel', Gtk.IconSize.BUTTON))
37
+
38
+ button_ok = self.dialog.add_button(
39
+ set_underline(_("Import")), Gtk.ResponseType.OK)
40
+ button_ok.set_image(IconFactory.get_image(
41
+ 'tryton-import', Gtk.IconSize.BUTTON))
42
+
32
43
  def add_buttons(self, box):
33
44
  button_autodetect = Gtk.Button(
34
45
  label=_('_Auto-Detect'), stock=None, use_underline=True)
@@ -67,22 +78,34 @@ class WinImport(WinCSV):
67
78
  fields_order = list(fields.keys())
68
79
  fields_order.sort(
69
80
  key=lambda x: fields[x].get('string', ''), reverse=True)
70
- for field in fields_order:
71
- if not fields[field].get('readonly', False) or field == 'id':
72
- self.fields_data[prefix_field + field] = fields[field]
73
- name = fields[field]['string'] or field
81
+ for field_name in fields_order:
82
+ field = fields[field_name]
83
+ if not field.get('readonly', False) or field_name == 'id':
84
+ self.fields_data[prefix_field + field_name] = field
85
+ name = field['string'] or field_name
74
86
  node = self.model1.insert(
75
- parent_node, 0, [name, prefix_field + field])
87
+ parent_node, 0, [name, prefix_field + field_name])
76
88
  name = prefix_name + name
77
89
  # Only One2Many can be nested for import
78
- if fields[field]['type'] == 'one2many':
79
- relation = fields[field].get('relation')
90
+ if field['type'] == 'one2many':
91
+ relation = field.get('relation')
80
92
  else:
81
93
  relation = None
82
- self.fields[prefix_field + field] = (name, relation)
83
- self.fields_invert[name] = prefix_field + field
94
+ self.fields[prefix_field + field_name] = (name, relation)
95
+ self.fields_invert[name] = prefix_field + field_name
84
96
  if relation:
85
97
  self.model1.insert(node, 0, [None, ''])
98
+ elif field.get('translate', False):
99
+ for language in self.languages:
100
+ l_field_name = f"{field_name}:lang={language['code']}"
101
+ self.model1.insert(
102
+ node, 0, [
103
+ language['name'], prefix_field + l_field_name])
104
+ l_name = prefix_name + name + f" ({language['name']})"
105
+ self.fields[prefix_field + l_field_name] = (
106
+ l_name, relation)
107
+ self.fields_invert[l_name] = (
108
+ prefix_field + l_field_name)
86
109
 
87
110
  def _get_fields(self, model):
88
111
  try:
@@ -185,7 +208,8 @@ class WinImport(WinCSV):
185
208
 
186
209
  fname = self.import_csv_file.get_filename()
187
210
  if fname:
188
- self.import_csv(fname, fields)
211
+ if not self.import_csv(fname, fields):
212
+ return
189
213
  self.destroy()
190
214
 
191
215
  def import_csv(self, fname, fields):
@@ -212,13 +236,18 @@ class WinImport(WinCSV):
212
236
  val = locale.atof(val)
213
237
  elif type_ == 'numeric':
214
238
  val = Decimal(locale.delocalize(val))
215
- elif type_ in ['date', 'datetime']:
216
- val = date_parse(val, common.date_format())
239
+ elif type_ == 'date':
240
+ val = dt.datetime.strptime(
241
+ val, common.date_format()).date()
242
+ elif type_ == 'datetime':
243
+ val = dt.datetime.strptime(
244
+ val, common.date_format() + ' %X')
217
245
  elif type_ == 'binary':
218
246
  val = base64.b64decode(val)
219
247
  row.append(val)
220
248
  data.append(row)
221
- except (IOError, UnicodeDecodeError, csv.Error) as exception:
249
+ except (IOError, UnicodeDecodeError, csv.Error, ValueError) \
250
+ as exception:
222
251
  common.warning(str(exception), _("Import failed"))
223
252
  return
224
253
  try:
@@ -231,3 +260,4 @@ class WinImport(WinCSV):
231
260
  common.message(_('%d record imported.') % count)
232
261
  else:
233
262
  common.message(_('%d records imported.') % count)
263
+ return count
@@ -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():
@@ -402,6 +403,8 @@ class WizardDialog(Wizard, NoModal):
402
403
  return True
403
404
 
404
405
  def show(self):
406
+ if not self.screen:
407
+ return
405
408
  view = self.screen.current_view
406
409
  if view.view_type == 'form':
407
410
  expand = False