tryton 7.2.14__py3-none-any.whl → 7.4.0__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_inversion.py +1 -2
- 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/about.py +1 -1
- 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/field.py +2 -0
- tryton/gui/window/view_form/model/group.py +4 -1
- tryton/gui/window/view_form/model/record.py +11 -26
- tryton/gui/window/view_form/screen/screen.py +24 -17
- tryton/gui/window/view_form/view/form.py +3 -5
- tryton/gui/window/view_form/view/form_gtk/binary.py +3 -3
- 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 +52 -24
- tryton/gui/window/view_form/view/list_gtk/widget.py +1 -1
- tryton/gui/window/view_form/view/screen_container.py +5 -3
- 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 +28 -6
- tryton/pyson.py +2 -1
- tryton/tests/test_common_domain_parser.py +23 -2
- {tryton-7.2.14.dist-info → tryton-7.4.0.dist-info}/METADATA +3 -17
- {tryton-7.2.14.dist-info → tryton-7.4.0.dist-info}/RECORD +86 -86
- {tryton-7.2.14.dist-info → tryton-7.4.0.dist-info}/WHEEL +1 -1
- {tryton-7.2.14.data → tryton-7.4.0.data}/scripts/tryton +0 -0
- {tryton-7.2.14.dist-info → tryton-7.4.0.dist-info}/LICENSE +0 -0
- {tryton-7.2.14.dist-info → tryton-7.4.0.dist-info}/top_level.txt +0 -0
|
@@ -33,15 +33,23 @@ class MultiSelection(Widget, SelectionMixin):
|
|
|
33
33
|
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
|
|
34
34
|
selection.connect('changed', self.changed)
|
|
35
35
|
self.widget.add(self.tree)
|
|
36
|
-
|
|
36
|
+
column = Gtk.TreeViewColumn()
|
|
37
|
+
select_cell = Gtk.CellRendererToggle()
|
|
38
|
+
select_cell.set_sensitive(False)
|
|
39
|
+
column.pack_start(select_cell, expand=False)
|
|
40
|
+
column.set_cell_data_func(
|
|
41
|
+
select_cell, self._select_data_func, selection)
|
|
37
42
|
name_cell = Gtk.CellRendererText()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
self.tree.append_column(
|
|
43
|
+
column.pack_start(name_cell, expand=True)
|
|
44
|
+
column.add_attribute(name_cell, 'text', 1)
|
|
45
|
+
self.tree.append_column(column)
|
|
41
46
|
|
|
42
47
|
self.nullable_widget = False
|
|
43
48
|
self.init_selection()
|
|
44
49
|
|
|
50
|
+
def _select_data_func(self, column, cell, model, iter_, selection):
|
|
51
|
+
cell.set_property('active', selection.iter_is_selected(iter_))
|
|
52
|
+
|
|
45
53
|
def _readonly_set(self, readonly):
|
|
46
54
|
super(MultiSelection, self)._readonly_set(readonly)
|
|
47
55
|
selection = self.tree.get_selection()
|
|
@@ -70,6 +78,8 @@ class MultiSelection(Widget, SelectionMixin):
|
|
|
70
78
|
self.field.set_client(self.record, self.get_value())
|
|
71
79
|
|
|
72
80
|
def display(self):
|
|
81
|
+
def freeze(iter):
|
|
82
|
+
return list(map(tuple, iter))
|
|
73
83
|
selection = self.tree.get_selection()
|
|
74
84
|
selection.handler_block_by_func(self.changed)
|
|
75
85
|
try:
|
|
@@ -77,7 +87,7 @@ class MultiSelection(Widget, SelectionMixin):
|
|
|
77
87
|
# it will be set back in the super call
|
|
78
88
|
selection.set_select_function(lambda *a: True)
|
|
79
89
|
self.update_selection(self.record, self.field)
|
|
80
|
-
new_model = self.selection !=
|
|
90
|
+
new_model = freeze(self.selection) != freeze(self.model)
|
|
81
91
|
if new_model:
|
|
82
92
|
self.model.clear()
|
|
83
93
|
if not self.field:
|
|
@@ -141,7 +141,7 @@ class One2Many(Widget):
|
|
|
141
141
|
hbox.pack_start(self.but_del, expand=False, fill=False, padding=0)
|
|
142
142
|
|
|
143
143
|
self.but_undel = Gtk.Button(can_focus=False)
|
|
144
|
-
tooltips.set_tip(self.but_undel, _(
|
|
144
|
+
tooltips.set_tip(self.but_undel, _("Undelete selected record"))
|
|
145
145
|
self.but_undel.connect('clicked', self._sig_undelete)
|
|
146
146
|
self.but_undel.add(common.IconFactory.get_image(
|
|
147
147
|
'tryton-undo', Gtk.IconSize.SMALL_TOOLBAR))
|
|
@@ -295,7 +295,13 @@ class One2Many(Widget):
|
|
|
295
295
|
if isinstance(self._position, int):
|
|
296
296
|
first = self._position <= 1
|
|
297
297
|
last = self._position >= self._length
|
|
298
|
-
deletable =
|
|
298
|
+
deletable = (
|
|
299
|
+
self.screen.deletable
|
|
300
|
+
and any(
|
|
301
|
+
not r.deleted and not r.removed
|
|
302
|
+
for r in self.screen.selected_records))
|
|
303
|
+
undeletable = any(
|
|
304
|
+
r.deleted or r.removed for r in self.screen.selected_records)
|
|
299
305
|
view_type = self.screen.current_view.view_type
|
|
300
306
|
has_views = self.screen.number_of_views > 1
|
|
301
307
|
|
|
@@ -313,15 +319,16 @@ class One2Many(Widget):
|
|
|
313
319
|
self.but_undel.set_sensitive(bool(
|
|
314
320
|
not self._readonly
|
|
315
321
|
and not size_limit
|
|
322
|
+
and undeletable
|
|
316
323
|
and self._position))
|
|
317
324
|
self.but_open.set_sensitive(bool(
|
|
318
325
|
self._position
|
|
319
326
|
and self.read_access))
|
|
320
327
|
self.but_next.set_sensitive(bool(
|
|
321
|
-
self.
|
|
328
|
+
self._length
|
|
322
329
|
and not last))
|
|
323
330
|
self.but_pre.set_sensitive(bool(
|
|
324
|
-
self.
|
|
331
|
+
self._length
|
|
325
332
|
and not first))
|
|
326
333
|
if self.attrs.get('add_remove'):
|
|
327
334
|
self.but_add.set_sensitive(bool(
|
|
@@ -499,6 +506,9 @@ class One2Many(Widget):
|
|
|
499
506
|
domain = self.field.domain_get(self.record)
|
|
500
507
|
context = self.field.get_search_context(self.record)
|
|
501
508
|
domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'))]
|
|
509
|
+
existing_ids = self.field.get_eval(self.record)
|
|
510
|
+
if existing_ids:
|
|
511
|
+
domain = [domain, ('id', 'not in', existing_ids)]
|
|
502
512
|
removed_ids = self.field.get_removed_ids(self.record)
|
|
503
513
|
domain = ['OR', domain, ('id', 'in', removed_ids)]
|
|
504
514
|
text = self.wid_text.get_text()
|
|
@@ -605,6 +615,9 @@ class One2Many(Widget):
|
|
|
605
615
|
model = self.attrs['relation']
|
|
606
616
|
domain = self.field.domain_get(self.record)
|
|
607
617
|
domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'))]
|
|
618
|
+
existing_ids = self.field.get_eval(self.record)
|
|
619
|
+
if existing_ids:
|
|
620
|
+
domain = [domain, ('id', 'not in', existing_ids)]
|
|
608
621
|
removed_ids = self.field.get_removed_ids(self.record)
|
|
609
622
|
domain = ['OR', domain, ('id', 'in', removed_ids)]
|
|
610
623
|
update_completion(self.wid_text, self.record, self.field, model,
|
|
@@ -130,8 +130,10 @@ class HTML(Widget, TranslateMixin):
|
|
|
130
130
|
self.button, expand=False, fill=False, padding=0)
|
|
131
131
|
|
|
132
132
|
if attrs.get('translate'):
|
|
133
|
-
self.
|
|
134
|
-
|
|
133
|
+
self.button.set_image(common.IconFactory.get_image(
|
|
134
|
+
'tryton-translate', Gtk.IconSize.SMALL_TOOLBAR))
|
|
135
|
+
self.button.set_always_show_image(True)
|
|
136
|
+
self.button.connect('clicked', self.translate)
|
|
135
137
|
|
|
136
138
|
def uri(self, language=None):
|
|
137
139
|
if not self.record or self.record.id < 0 or CONNECTION.url is None:
|
|
@@ -149,7 +151,8 @@ class HTML(Widget, TranslateMixin):
|
|
|
149
151
|
|
|
150
152
|
def display(self):
|
|
151
153
|
super().display()
|
|
152
|
-
self.
|
|
154
|
+
if self.attrs.get('translate'):
|
|
155
|
+
self.button.set_uri(self.uri())
|
|
153
156
|
|
|
154
157
|
def _readonly_set(self, value):
|
|
155
158
|
super()._readonly_set(value)
|
|
@@ -158,6 +161,7 @@ class HTML(Widget, TranslateMixin):
|
|
|
158
161
|
|
|
159
162
|
def translate_dialog(self, languages):
|
|
160
163
|
languages = {l['name']: l['code'] for l in languages}
|
|
161
|
-
result = selection(
|
|
164
|
+
result = selection(
|
|
165
|
+
_('Choose a language'), languages, default=CONFIG['client.lang'])
|
|
162
166
|
if result:
|
|
163
167
|
webbrowser.open(self.uri(language=result[1]), new=2)
|
|
@@ -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
|
|
|
@@ -1067,12 +1094,9 @@ class ViewTree(View):
|
|
|
1067
1094
|
elif tree_sel.get_mode() == Gtk.SelectionMode.MULTIPLE:
|
|
1068
1095
|
model, paths = tree_sel.get_selected_rows()
|
|
1069
1096
|
if model and paths:
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
records.append(model.get_value(iter_, 0))
|
|
1074
|
-
if self.record not in records:
|
|
1075
|
-
self.record = records[0]
|
|
1097
|
+
iter_ = model.get_iter(paths[0])
|
|
1098
|
+
record = model.get_value(iter_, 0)
|
|
1099
|
+
self.record = record
|
|
1076
1100
|
else:
|
|
1077
1101
|
self.record = None
|
|
1078
1102
|
|
|
@@ -1112,10 +1136,6 @@ class ViewTree(View):
|
|
|
1112
1136
|
def display(self, force=False):
|
|
1113
1137
|
self.treeview.display_counter += 1
|
|
1114
1138
|
current_record = self.record
|
|
1115
|
-
if current_record and current_record not in current_record.group:
|
|
1116
|
-
# current record may have been removed by on_change calls without
|
|
1117
|
-
# changing the current record of screen before the display
|
|
1118
|
-
current_record = None
|
|
1119
1139
|
if (force
|
|
1120
1140
|
or not self.treeview.get_model()
|
|
1121
1141
|
or self.group != self.treeview.get_model().group):
|
|
@@ -1129,9 +1149,14 @@ class ViewTree(View):
|
|
|
1129
1149
|
selection.select_path(path)
|
|
1130
1150
|
# The search column must be set each time the model is changed
|
|
1131
1151
|
self.treeview.set_search_column(0)
|
|
1152
|
+
selection = self.treeview.get_selection()
|
|
1132
1153
|
if not current_record:
|
|
1133
|
-
selection = self.treeview.get_selection()
|
|
1134
1154
|
selection.unselect_all()
|
|
1155
|
+
else:
|
|
1156
|
+
model = self.treeview.get_model()
|
|
1157
|
+
path = Gtk.TreePath(current_record.get_index_path(model.group))
|
|
1158
|
+
if not selection.path_is_selected(path):
|
|
1159
|
+
selection.select_path(path)
|
|
1135
1160
|
self.treeview.queue_draw()
|
|
1136
1161
|
if self.editable:
|
|
1137
1162
|
self.set_state()
|
|
@@ -1155,7 +1180,8 @@ class ViewTree(View):
|
|
|
1155
1180
|
continue
|
|
1156
1181
|
widget = self.get_column_widget(column)
|
|
1157
1182
|
widget.set_editable()
|
|
1158
|
-
if
|
|
1183
|
+
if ('optional' in widget.attrs
|
|
1184
|
+
and column.name in tree_column_optional):
|
|
1159
1185
|
optional = tree_column_optional[column.name]
|
|
1160
1186
|
else:
|
|
1161
1187
|
optional = bool(int(widget.attrs.get('optional', '0')))
|
|
@@ -1174,7 +1200,7 @@ class ViewTree(View):
|
|
|
1174
1200
|
column.set_visible(not unique or bool(self.children_field))
|
|
1175
1201
|
if self.children_field:
|
|
1176
1202
|
for i, column in enumerate(self.treeview.get_columns()):
|
|
1177
|
-
if
|
|
1203
|
+
if column._type in {'selection', 'optional', 'drag'}:
|
|
1178
1204
|
continue
|
|
1179
1205
|
if column.get_visible():
|
|
1180
1206
|
self.treeview.set_expander_column(column)
|
|
@@ -1241,9 +1267,9 @@ class ViewTree(View):
|
|
|
1241
1267
|
label.set_text(text)
|
|
1242
1268
|
|
|
1243
1269
|
def set_cursor(self, new=False, reset_view=True):
|
|
1244
|
-
self.treeview.grab_focus()
|
|
1245
1270
|
model = self.treeview.get_model()
|
|
1246
1271
|
if self.record and model and self.treeview.get_realized():
|
|
1272
|
+
self.treeview.grab_focus()
|
|
1247
1273
|
path = self.record.get_index_path(model.group)
|
|
1248
1274
|
if model.get_flags() & Gtk.TreeModelFlags.LIST_ONLY:
|
|
1249
1275
|
path = (path[0],)
|
|
@@ -1267,6 +1293,8 @@ class ViewTree(View):
|
|
|
1267
1293
|
records = []
|
|
1268
1294
|
sel = self.treeview.get_selection()
|
|
1269
1295
|
sel.selected_foreach(_func_sel_get, records)
|
|
1296
|
+
if not records and self.record:
|
|
1297
|
+
records.append(self.record)
|
|
1270
1298
|
return records
|
|
1271
1299
|
|
|
1272
1300
|
def get_selected_paths(self):
|
|
@@ -742,7 +742,7 @@ class _BinarySelect(_BinaryIcon):
|
|
|
742
742
|
invisible = field.get_state_attrs(record).get('invisible', False)
|
|
743
743
|
readonly = self.attrs.get('readonly',
|
|
744
744
|
field.get_state_attrs(record).get('readonly', False))
|
|
745
|
-
if readonly
|
|
745
|
+
if readonly and size:
|
|
746
746
|
cell.set_property('visible', False)
|
|
747
747
|
else:
|
|
748
748
|
cell.set_property('visible', not invisible)
|
|
@@ -185,8 +185,7 @@ class Selection(Gtk.ScrolledWindow):
|
|
|
185
185
|
|
|
186
186
|
class ScreenContainer(object):
|
|
187
187
|
|
|
188
|
-
def __init__(self,
|
|
189
|
-
self.screen = screen
|
|
188
|
+
def __init__(self, tab_domain):
|
|
190
189
|
self.viewport = Gtk.Viewport()
|
|
191
190
|
self.viewport.set_shadow_type(Gtk.ShadowType.NONE)
|
|
192
191
|
self.vbox = Gtk.VBox(spacing=3)
|
|
@@ -352,9 +351,12 @@ class ScreenContainer(object):
|
|
|
352
351
|
def widget_get(self):
|
|
353
352
|
return self.vbox
|
|
354
353
|
|
|
355
|
-
def
|
|
354
|
+
def set_screen(self, screen):
|
|
355
|
+
self.screen = screen
|
|
356
356
|
self.but_bookmark.set_sensitive(bool(list(self.bookmarks())))
|
|
357
357
|
self.bookmark_match()
|
|
358
|
+
|
|
359
|
+
def show_filter(self):
|
|
358
360
|
if self.filter_vbox:
|
|
359
361
|
self.filter_vbox.show()
|
|
360
362
|
if self.notebook:
|
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
|