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.
- tryton/__init__.py +1 -1
- tryton/common/common.py +12 -4
- tryton/common/domain_parser.py +5 -4
- tryton/common/popup_menu.py +1 -1
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +27 -16
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +23 -17
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +26 -17
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +27 -21
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +22 -16
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +31 -19
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +27 -17
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +28 -18
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +22 -16
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +28 -22
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +29 -17
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +31 -21
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +30 -20
- tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.po +29 -19
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +27 -17
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +26 -20
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +29 -18
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +28 -18
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +37 -26
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +29 -19
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +45 -35
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +23 -16
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +31 -19
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +32 -21
- tryton/gui/main.py +11 -10
- tryton/gui/window/email_.py +1 -1
- tryton/gui/window/form.py +4 -1
- tryton/gui/window/tabcontent.py +2 -2
- tryton/gui/window/view_form/model/group.py +4 -1
- tryton/gui/window/view_form/model/record.py +9 -19
- tryton/gui/window/view_form/screen/screen.py +15 -1
- tryton/gui/window/view_form/view/form_gtk/dictionary.py +12 -5
- tryton/gui/window/view_form/view/form_gtk/document.py +6 -0
- tryton/gui/window/view_form/view/form_gtk/many2many.py +32 -0
- tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
- tryton/gui/window/view_form/view/form_gtk/one2many.py +17 -4
- tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
- tryton/gui/window/view_form/view/list.py +49 -14
- tryton/gui/window/win_csv.py +6 -12
- tryton/gui/window/win_export.py +49 -25
- tryton/gui/window/win_import.py +36 -11
- tryton/jsonrpc.py +29 -6
- tryton/pyson.py +2 -1
- tryton/tests/test_common_domain_parser.py +23 -2
- {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/METADATA +2 -2
- {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/RECORD +79 -79
- {tryton-7.2.13.data → tryton-7.4.4.data}/scripts/tryton +0 -0
- {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
- {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/WHEEL +0 -0
- {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,
|
|
523
|
-
|
|
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
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
menuitem.
|
|
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 = {
|
|
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
|
-
|
|
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
|
|
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
|
|
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):
|
tryton/gui/window/win_csv.py
CHANGED
|
@@ -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
|
|
tryton/gui/window/win_export.py
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
418
|
+
else:
|
|
419
|
+
self.destroy()
|
|
396
420
|
|
|
397
|
-
def export_csv(self, fname, data, paths,
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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'))
|
tryton/gui/window/win_import.py
CHANGED
|
@@ -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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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 +
|
|
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
|
|
79
|
-
relation =
|
|
90
|
+
if field['type'] == 'one2many':
|
|
91
|
+
relation = field.get('relation')
|
|
80
92
|
else:
|
|
81
93
|
relation = None
|
|
82
|
-
self.fields[prefix_field +
|
|
83
|
-
self.fields_invert[name] = prefix_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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|