tryton 7.2.13__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 (79) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/common/common.py +12 -4
  3. tryton/common/domain_parser.py +5 -4
  4. tryton/common/popup_menu.py +1 -1
  5. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  6. tryton/data/locale/bg/LC_MESSAGES/tryton.po +27 -16
  7. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  8. tryton/data/locale/ca/LC_MESSAGES/tryton.po +23 -17
  9. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  10. tryton/data/locale/cs/LC_MESSAGES/tryton.po +26 -17
  11. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  12. tryton/data/locale/de/LC_MESSAGES/tryton.po +27 -21
  13. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  14. tryton/data/locale/es/LC_MESSAGES/tryton.po +22 -16
  15. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  16. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +31 -19
  17. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  18. tryton/data/locale/et/LC_MESSAGES/tryton.po +27 -17
  19. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  20. tryton/data/locale/fa/LC_MESSAGES/tryton.po +28 -18
  21. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  22. tryton/data/locale/fi/LC_MESSAGES/tryton.po +22 -16
  23. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  24. tryton/data/locale/fr/LC_MESSAGES/tryton.po +28 -22
  25. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  26. tryton/data/locale/hu/LC_MESSAGES/tryton.po +29 -17
  27. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  28. tryton/data/locale/id/LC_MESSAGES/tryton.po +31 -21
  29. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  30. tryton/data/locale/it/LC_MESSAGES/tryton.po +30 -20
  31. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  32. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/lo/LC_MESSAGES/tryton.po +29 -19
  34. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/lt/LC_MESSAGES/tryton.po +27 -17
  36. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/nl/LC_MESSAGES/tryton.po +26 -20
  38. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/pl/LC_MESSAGES/tryton.po +29 -18
  40. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/pt/LC_MESSAGES/tryton.po +28 -18
  42. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  43. tryton/data/locale/ro/LC_MESSAGES/tryton.po +37 -26
  44. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  45. tryton/data/locale/ru/LC_MESSAGES/tryton.po +29 -19
  46. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  47. tryton/data/locale/sl/LC_MESSAGES/tryton.po +45 -35
  48. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  49. tryton/data/locale/tr/LC_MESSAGES/tryton.po +23 -16
  50. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  51. tryton/data/locale/uk/LC_MESSAGES/tryton.po +31 -19
  52. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  53. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +32 -21
  54. tryton/gui/main.py +11 -10
  55. tryton/gui/window/email_.py +1 -1
  56. tryton/gui/window/form.py +4 -1
  57. tryton/gui/window/tabcontent.py +2 -2
  58. tryton/gui/window/view_form/model/group.py +4 -1
  59. tryton/gui/window/view_form/model/record.py +9 -19
  60. tryton/gui/window/view_form/screen/screen.py +15 -1
  61. tryton/gui/window/view_form/view/form_gtk/dictionary.py +12 -5
  62. tryton/gui/window/view_form/view/form_gtk/document.py +6 -0
  63. tryton/gui/window/view_form/view/form_gtk/many2many.py +32 -0
  64. tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
  65. tryton/gui/window/view_form/view/form_gtk/one2many.py +17 -4
  66. tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
  67. tryton/gui/window/view_form/view/list.py +49 -14
  68. tryton/gui/window/win_csv.py +6 -12
  69. tryton/gui/window/win_export.py +49 -25
  70. tryton/gui/window/win_import.py +36 -11
  71. tryton/jsonrpc.py +29 -6
  72. tryton/pyson.py +2 -1
  73. tryton/tests/test_common_domain_parser.py +23 -2
  74. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/METADATA +2 -2
  75. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/RECORD +79 -79
  76. {tryton-7.2.13.data → tryton-7.4.4.data}/scripts/tryton +0 -0
  77. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
  78. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/WHEEL +0 -0
  79. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ import gettext
5
5
  import json
6
6
  import locale
7
7
  import sys
8
+ from collections import defaultdict
8
9
  from functools import wraps
9
10
  from io import StringIO
10
11
 
@@ -336,7 +337,7 @@ class TreeXMLViewParser(XMLViewParser):
336
337
  self.view.treeview.append_column(column)
337
338
 
338
339
  if 'optional' in attributes and name != self.exclude_field:
339
- self.view.optionals.append(column)
340
+ self.view.optionals[column.name].append(column)
340
341
 
341
342
  def _parse_button(self, node, attributes):
342
343
  button = Button(self.view, attributes)
@@ -437,7 +438,7 @@ class ViewTree(View):
437
438
 
438
439
  def __init__(self, view_id, screen, xml, children_field):
439
440
  self.children_field = children_field
440
- self.optionals = []
441
+ self.optionals = defaultdict(list)
441
442
  self.sum_widgets = []
442
443
  self.sum_box = Gtk.HBox()
443
444
  self.treeview = None
@@ -451,6 +452,8 @@ class ViewTree(View):
451
452
  grid_lines = Gtk.TreeViewGridLines.VERTICAL
452
453
 
453
454
  super().__init__(view_id, screen, xml)
455
+
456
+ self.set_selection_column()
454
457
  self.set_drag_and_drop()
455
458
 
456
459
  self.mnemonic_widget = self.treeview
@@ -519,21 +522,27 @@ class ViewTree(View):
519
522
  self.treeview.append_column(column)
520
523
 
521
524
  def optional_menu(self, column):
522
- def toggle(menuitem, column):
523
- column.set_visible(menuitem.get_active())
525
+ def toggle(menuitem, columns):
526
+ visible = menuitem.get_active()
527
+ for column in columns:
528
+ column.set_visible(visible)
524
529
  self.save_optional()
525
530
 
526
531
  widget = column.get_widget()
527
532
  menu = Gtk.Menu()
528
- for optional in self.optionals:
529
- menuitem = Gtk.CheckMenuItem(label=optional.get_title())
530
- menuitem.set_active(optional.get_visible())
531
- menuitem.connect('toggled', toggle, optional)
533
+ for name, columns in self.optionals.items():
534
+ visible = any(c.get_visible() for c in columns)
535
+ title = ' / '.join({c.get_title() for c in columns})
536
+ menuitem = Gtk.CheckMenuItem(label=title)
537
+ menuitem.set_active(visible)
538
+ menuitem.connect('toggled', toggle, columns)
532
539
  menu.add(menuitem)
533
540
  popup(menu, widget)
534
541
 
535
542
  def save_optional(self):
536
- fields = {c.name: not c.get_visible() for c in self.optionals}
543
+ fields = {}
544
+ for name, columns in self.optionals.items():
545
+ fields[name] = all(not c.get_visible() for c in columns)
537
546
  try:
538
547
  RPCExecute(
539
548
  'model', 'ir.ui.view_tree_optional', 'set_optional',
@@ -616,6 +625,21 @@ class ViewTree(View):
616
625
  else:
617
626
  arrow.clear()
618
627
 
628
+ def set_selection_column(self):
629
+ def select_data_func(column, cell, model, iter_, selection):
630
+ cell.set_property('active', selection.iter_is_selected(iter_))
631
+
632
+ selection = self.treeview.get_selection()
633
+ column = Gtk.TreeViewColumn()
634
+ column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
635
+ column.name = None
636
+ column._type = 'selection'
637
+ cell = Gtk.CellRendererToggle()
638
+ cell.set_sensitive(False)
639
+ column.pack_start(cell, expand=False)
640
+ column.set_cell_data_func(cell, select_data_func, selection)
641
+ self.treeview.insert_column(column, 0)
642
+
619
643
  def set_drag_and_drop(self):
620
644
  dnd = False
621
645
  if self.children_field:
@@ -741,7 +765,10 @@ class ViewTree(View):
741
765
  clipboard = self.treeview.get_clipboard(clipboard_type)
742
766
  selection = self.treeview.get_selection()
743
767
  data = StringIO()
744
- writer = csv.writer(data, delimiter='\t', lineterminator='\n')
768
+ lineterminator = (
769
+ '\n' if selection.count_selected_rows() > 1 else '')
770
+ writer = csv.writer(
771
+ data, delimiter='\t', lineterminator=lineterminator)
745
772
  selection.selected_foreach(copy_foreach, writer.writerow)
746
773
  clipboard.set_text(data.getvalue(), -1)
747
774
 
@@ -1129,9 +1156,14 @@ class ViewTree(View):
1129
1156
  selection.select_path(path)
1130
1157
  # The search column must be set each time the model is changed
1131
1158
  self.treeview.set_search_column(0)
1159
+ selection = self.treeview.get_selection()
1132
1160
  if not current_record:
1133
- selection = self.treeview.get_selection()
1134
1161
  selection.unselect_all()
1162
+ else:
1163
+ model = self.treeview.get_model()
1164
+ path = Gtk.TreePath(current_record.get_index_path(model.group))
1165
+ if not selection.path_is_selected(path):
1166
+ selection.select_path(path)
1135
1167
  self.treeview.queue_draw()
1136
1168
  if self.editable:
1137
1169
  self.set_state()
@@ -1155,7 +1187,8 @@ class ViewTree(View):
1155
1187
  continue
1156
1188
  widget = self.get_column_widget(column)
1157
1189
  widget.set_editable()
1158
- if column.name in tree_column_optional:
1190
+ if ('optional' in widget.attrs
1191
+ and column.name in tree_column_optional):
1159
1192
  optional = tree_column_optional[column.name]
1160
1193
  else:
1161
1194
  optional = bool(int(widget.attrs.get('optional', '0')))
@@ -1174,7 +1207,7 @@ class ViewTree(View):
1174
1207
  column.set_visible(not unique or bool(self.children_field))
1175
1208
  if self.children_field:
1176
1209
  for i, column in enumerate(self.treeview.get_columns()):
1177
- if (self.draggable or self.optionals) and not i:
1210
+ if column._type in {'selection', 'optional', 'drag'}:
1178
1211
  continue
1179
1212
  if column.get_visible():
1180
1213
  self.treeview.set_expander_column(column)
@@ -1241,9 +1274,9 @@ class ViewTree(View):
1241
1274
  label.set_text(text)
1242
1275
 
1243
1276
  def set_cursor(self, new=False, reset_view=True):
1244
- self.treeview.grab_focus()
1245
1277
  model = self.treeview.get_model()
1246
1278
  if self.record and model and self.treeview.get_realized():
1279
+ self.treeview.grab_focus()
1247
1280
  path = self.record.get_index_path(model.group)
1248
1281
  if model.get_flags() & Gtk.TreeModelFlags.LIST_ONLY:
1249
1282
  path = (path[0],)
@@ -1267,6 +1300,8 @@ class ViewTree(View):
1267
1300
  records = []
1268
1301
  sel = self.treeview.get_selection()
1269
1302
  sel.selected_foreach(_func_sel_get, records)
1303
+ if not records and self.record:
1304
+ records.append(self.record)
1270
1305
  return records
1271
1306
 
1272
1307
  def get_selected_paths(self):
@@ -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
 
@@ -14,16 +14,19 @@ from numbers import Number
14
14
  from gi.repository import Gdk, GObject, Gtk
15
15
 
16
16
  import tryton.common as common
17
- from tryton.common import RPCException, RPCExecute, tempfile
17
+ from tryton.common import IconFactory, RPCException, RPCExecute, tempfile
18
+ from tryton.common.underline import set_underline
18
19
  from tryton.config import CONFIG
19
20
  from tryton.gui.window.win_csv import WinCSV
20
21
  from tryton.jsonrpc import JSONEncoder
21
22
  from tryton.rpc import CONNECTION, clear_cache
22
23
 
24
+ from .infobar import InfoBar
25
+
23
26
  _ = gettext.gettext
24
27
 
25
28
 
26
- class WinExport(WinCSV):
29
+ class WinExport(WinCSV, InfoBar):
27
30
  "Window export"
28
31
 
29
32
  def __init__(self, name, screen):
@@ -32,8 +35,23 @@ class WinExport(WinCSV):
32
35
  self.fields = {}
33
36
  super(WinExport, self).__init__()
34
37
  self.dialog.set_title(_('CSV Export: %s') % name)
35
- # Hide as selected record is the default
36
- 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)
37
55
 
38
56
  @property
39
57
  def model(self):
@@ -50,6 +68,10 @@ class WinExport(WinCSV):
50
68
  and self.screen.current_view.view_type == 'tree'
51
69
  and self.screen.current_view.children_field)
52
70
 
71
+ @property
72
+ def screen_has_selected(self):
73
+ return bool(self.screen.selected_records)
74
+
53
75
  def add_buttons(self, box):
54
76
  button_save_export = Gtk.Button(
55
77
  label=_('_Save Export'), stock=None, use_underline=True)
@@ -113,19 +135,13 @@ class WinExport(WinCSV):
113
135
  def add_chooser(self, box):
114
136
  hbox_csv_export = Gtk.HBox()
115
137
  box.pack_start(hbox_csv_export, expand=False, fill=True, padding=0)
116
- self.saveas = Gtk.ComboBoxText()
117
- hbox_csv_export.pack_start(
118
- self.saveas, expand=True, fill=True, padding=3)
119
- self.saveas.append_text(_("Open"))
120
- self.saveas.append_text(_("Save"))
121
- self.saveas.set_active(0)
122
138
 
123
139
  self.selected_records = Gtk.ComboBoxText()
124
140
  hbox_csv_export.pack_start(
125
141
  self.selected_records, expand=True, fill=True, padding=3)
126
142
  self.selected_records.append_text(_("Listed Records"))
127
143
  self.selected_records.append_text(_("Selected Records"))
128
- if not self.screen_is_tree:
144
+ if not self.screen_is_tree and self.screen_has_selected:
129
145
  self.selected_records.set_active(1)
130
146
  else:
131
147
  self.selected_records.set_active(0)
@@ -176,6 +192,13 @@ class WinExport(WinCSV):
176
192
  if '.' not in name:
177
193
  if field.get('relation'):
178
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)
179
202
 
180
203
  def _get_fields(self, model):
181
204
  try:
@@ -334,7 +357,8 @@ class WinExport(WinCSV):
334
357
  self.model2.append((string_, name))
335
358
 
336
359
  def response(self, dialog, response):
337
- if response == Gtk.ResponseType.OK:
360
+ self.info_bar_clear()
361
+ if response in {Gtk.ResponseType.OK, Gtk.ResponseType.ACCEPT}:
338
362
  fields = []
339
363
  iter = self.model2.get_iter_first()
340
364
  while iter:
@@ -378,7 +402,7 @@ class WinExport(WinCSV):
378
402
  except RPCException:
379
403
  data = []
380
404
 
381
- if self.saveas.get_active():
405
+ if response == Gtk.ResponseType.ACCEPT:
382
406
  fname = common.file_selection(_('Save As...'),
383
407
  action=Gtk.FileChooserAction.SAVE)
384
408
  if fname:
@@ -386,15 +410,15 @@ class WinExport(WinCSV):
386
410
  else:
387
411
  fileno, fname = tempfile.mkstemp(
388
412
  '.csv', common.slugify(self.name) + '_')
389
- if self.export_csv(
390
- fname, data, paths, popup=False, header=header):
413
+ if self.export_csv(fname, data, paths, header=header):
391
414
  os.close(fileno)
392
415
  common.file_open(fname, 'csv')
393
416
  else:
394
417
  os.close(fileno)
395
- self.destroy()
418
+ else:
419
+ self.destroy()
396
420
 
397
- def export_csv(self, fname, data, paths, popup=True, header=False):
421
+ def export_csv(self, fname, data, paths, header=False):
398
422
  encoding = self.csv_enc.get_active_text() or 'utf_8_sig'
399
423
  locale_format = self.csv_locale.get_active()
400
424
 
@@ -408,14 +432,14 @@ class WinExport(WinCSV):
408
432
  if row:
409
433
  writer.writerow(self.format_row(
410
434
  row, indent=indent, locale_format=locale_format))
411
- if popup:
412
- size = len(data)
413
- if header:
414
- size -= 1
415
- if size <= 1:
416
- common.message(_('%d record saved.') % size)
417
- else:
418
- 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)
419
443
  return True
420
444
  except (IOError, UnicodeEncodeError, csv.Error) as exception:
421
445
  common.warning(str(exception), _('Export failed'))
@@ -10,7 +10,8 @@ from decimal import Decimal
10
10
  from gi.repository import Gtk
11
11
 
12
12
  import tryton.common as common
13
- from tryton.common import RPCException, RPCExecute
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):
@@ -236,3 +260,4 @@ class WinImport(WinCSV):
236
260
  common.message(_('%d record imported.') % count)
237
261
  else:
238
262
  common.message(_('%d records imported.') % count)
263
+ return count
tryton/jsonrpc.py CHANGED
@@ -11,12 +11,18 @@ import logging
11
11
  import socket
12
12
  import ssl
13
13
  import threading
14
+ import time
14
15
  import xmlrpc.client
15
16
  from contextlib import contextmanager
16
17
  from decimal import Decimal
17
18
  from functools import partial, reduce
18
19
  from urllib.parse import quote, urljoin
19
20
 
21
+ try:
22
+ from http import HTTPStatus
23
+ except ImportError:
24
+ from http import client as HTTPStatus
25
+
20
26
  from .cache import CacheDict
21
27
  from .config import CONFIG
22
28
 
@@ -288,12 +294,29 @@ class ServerProxy(xmlrpc.client.ServerProxy):
288
294
 
289
295
  try:
290
296
  try:
291
- response = self.__transport.request(
292
- self.__host,
293
- self.__handler,
294
- request,
295
- verbose=self.__verbose
296
- )
297
+ for i in range(5):
298
+ try:
299
+ response = self.__transport.request(
300
+ self.__host,
301
+ self.__handler,
302
+ request,
303
+ verbose=self.__verbose
304
+ )
305
+ break
306
+ except xmlrpc.client.ProtocolError as e:
307
+ if e.errcode == HTTPStatus.SERVICE_UNAVAILABLE:
308
+ try:
309
+ delay = int(e.headers.get('Retry-After', i))
310
+ except ValueError:
311
+ if isinstance(
312
+ e.errcode, HTTPStatus.GATEWAY_TIMEOUT):
313
+ # Do not retry on timeout without delay
314
+ raise
315
+ delay = i
316
+ delay = min(delay, 10)
317
+ time.sleep(delay)
318
+ else:
319
+ raise
297
320
  except (socket.error, http.client.HTTPException) as v:
298
321
  if (isinstance(v, socket.error)
299
322
  and v.args[0] == errno.EPIPE):
tryton/pyson.py CHANGED
@@ -712,7 +712,8 @@ class DateTime(Date):
712
712
  and not isinstance(now, datetime.datetime)):
713
713
  now = datetime.datetime.combine(now, datetime.time())
714
714
  if not isinstance(now, datetime.datetime):
715
- now = datetime.datetime.utcnow()
715
+ now = datetime.datetime.now(
716
+ datetime.timezone.utc).replace(tzinfo=None)
716
717
  return now + relativedelta(
717
718
  year=dct['y'],
718
719
  month=dct['M'],
@@ -962,14 +962,26 @@ class DomainParserTestCase(TestCase):
962
962
  },
963
963
  'relation': {
964
964
  'string': "Relation",
965
- 'relation': 'relation',
966
965
  'name': 'relation',
966
+ 'type': 'many2one',
967
967
  'relation_fields': {
968
968
  'name': {
969
969
  'string': "Name",
970
970
  'name': 'name',
971
971
  'type': 'char',
972
972
  },
973
+ 'm2o': {
974
+ 'string': "M2O",
975
+ 'name': 'm2o',
976
+ 'type': 'many2one',
977
+ 'relation_fields': {
978
+ 'foo': {
979
+ 'string': "Foo",
980
+ 'name': 'foo',
981
+ 'type': 'integer',
982
+ },
983
+ },
984
+ },
973
985
  },
974
986
  },
975
987
  })
@@ -1094,7 +1106,7 @@ class DomainParserTestCase(TestCase):
1094
1106
  ])
1095
1107
  self.assertEqual(
1096
1108
  rlist(dom.parse_clause([('Many2One', None, 'John')])), [
1097
- ('many2one', 'ilike', '%John%'),
1109
+ ('many2one.rec_name', 'ilike', '%John%'),
1098
1110
  ])
1099
1111
  self.assertEqual(
1100
1112
  rlist(dom.parse_clause([('Many2One', None, ['John', 'Jane'])])), [
@@ -1103,9 +1115,18 @@ class DomainParserTestCase(TestCase):
1103
1115
  self.assertEqual(
1104
1116
  rlist(dom.parse_clause(iter([iter([['John']])]))), [
1105
1117
  [('rec_name', 'ilike', '%John%')]])
1118
+ self.assertEqual(
1119
+ rlist(dom.parse_clause(iter([['Relation', None, "Test"]]))),
1120
+ [('relation.rec_name', 'ilike', "%Test%")])
1106
1121
  self.assertEqual(
1107
1122
  rlist(dom.parse_clause(iter([['Relation.Name', None, "Test"]]))),
1108
1123
  [('relation.name', 'ilike', "%Test%")])
1124
+ self.assertEqual(
1125
+ rlist(dom.parse_clause(iter([['Relation.M2O', None, "Foo"]]))),
1126
+ [('relation.m2o.rec_name', 'ilike', "%Foo%")])
1127
+ self.assertEqual(
1128
+ rlist(dom.parse_clause(iter([['Relation.M2O.Foo', None, '42']]))),
1129
+ [('relation.m2o.foo', '=', 42)])
1109
1130
  self.assertEqual(
1110
1131
  rlist(dom.parse_clause(iter([['OR']]))),
1111
1132
  [('rec_name', 'ilike', "%OR%")])
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tryton
3
- Version: 7.2.13
3
+ Version: 7.4.4
4
4
  Summary: Tryton desktop client
5
5
  Home-page: http://www.tryton.org/
6
- Download-URL: http://downloads.tryton.org/7.2/
6
+ Download-URL: http://downloads.tryton.org/7.4/
7
7
  Author: Tryton
8
8
  Author-email: foundation@tryton.org
9
9
  License: GPL-3