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.
- tryton/__init__.py +1 -1
- tryton/cache.py +34 -0
- tryton/common/common.py +149 -73
- tryton/common/completion.py +2 -2
- tryton/common/datetime_.py +3 -1
- tryton/common/domain_inversion.py +2 -1
- tryton/common/domain_parser.py +22 -11
- tryton/common/popup_menu.py +1 -1
- tryton/common/selection.py +6 -3
- tryton/common/tempfile.py +34 -0
- tryton/config.py +4 -5
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +69 -20
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +70 -25
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +68 -21
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +71 -26
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +68 -23
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +72 -22
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +73 -23
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +74 -25
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +63 -20
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +73 -28
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +75 -23
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +72 -23
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +73 -24
- 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 +74 -25
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +73 -23
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +72 -27
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +113 -78
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +73 -24
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +87 -36
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +72 -25
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +77 -26
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +64 -20
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +75 -23
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +76 -24
- tryton/device_cookie.py +1 -1
- tryton/gui/main.py +14 -12
- tryton/gui/window/about.py +1 -1
- tryton/gui/window/dblogin.py +2 -2
- tryton/gui/window/email_.py +2 -2
- tryton/gui/window/form.py +10 -5
- tryton/gui/window/log.py +24 -2
- tryton/gui/window/tabcontent.py +2 -2
- tryton/gui/window/view_form/model/field.py +84 -34
- tryton/gui/window/view_form/model/group.py +7 -2
- tryton/gui/window/view_form/model/record.py +70 -31
- tryton/gui/window/view_form/screen/screen.py +98 -47
- tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
- tryton/gui/window/view_form/view/form.py +6 -12
- tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
- tryton/gui/window/view_form/view/form_gtk/dictionary.py +49 -29
- tryton/gui/window/view_form/view/form_gtk/document.py +15 -10
- tryton/gui/window/view_form/view/form_gtk/many2many.py +49 -7
- tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
- tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
- tryton/gui/window/view_form/view/form_gtk/one2many.py +42 -10
- tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
- tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
- tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
- tryton/gui/window/view_form/view/list.py +116 -48
- tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
- tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
- tryton/gui/window/view_form/view/screen_container.py +3 -5
- tryton/gui/window/win_csv.py +6 -12
- tryton/gui/window/win_export.py +49 -26
- tryton/gui/window/win_form.py +9 -7
- tryton/gui/window/win_import.py +45 -15
- tryton/gui/window/wizard.py +13 -10
- tryton/jsonrpc.py +75 -34
- tryton/plugins/__init__.py +5 -3
- tryton/pyson.py +57 -6
- tryton/rpc.py +18 -0
- tryton/tests/test_common_domain_parser.py +31 -2
- tryton/translate.py +5 -2
- {tryton-7.0.7.data → tryton-7.4.4.data}/scripts/tryton +8 -7
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/METADATA +6 -6
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/RECORD +105 -103
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/WHEEL +1 -1
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
- {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
|
|
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.
|
|
405
|
-
self.
|
|
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.
|
|
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.
|
|
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.
|
|
558
|
-
self.
|
|
581
|
+
self._cell_save = _BinarySave(self)
|
|
582
|
+
self._cell_select = _BinarySelect(self)
|
|
559
583
|
if self.attrs.get('filename'):
|
|
560
|
-
self.
|
|
584
|
+
self._cell_open = _BinaryOpen(self)
|
|
561
585
|
else:
|
|
562
|
-
self.
|
|
586
|
+
self._cell_open = None
|
|
563
587
|
|
|
564
588
|
@property
|
|
565
589
|
def prefixes(self):
|
|
566
|
-
return filter(None, [self.
|
|
590
|
+
return filter(None, [self._cell_open])
|
|
567
591
|
|
|
568
592
|
@property
|
|
569
593
|
def suffixes(self):
|
|
570
|
-
return [self.
|
|
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
|
|
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
|
|
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)
|
|
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.
|
|
1270
|
+
self._cell_selection = _ReferenceSelection(view, attrs)
|
|
1237
1271
|
|
|
1238
1272
|
@property
|
|
1239
1273
|
def prefixes(self):
|
|
1240
|
-
return [self.
|
|
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
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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
|
|
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:
|
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
|
@@ -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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
418
|
+
else:
|
|
419
|
+
self.destroy()
|
|
397
420
|
|
|
398
|
-
def export_csv(self, fname, data, paths,
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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'))
|
tryton/gui/window/win_form.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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.
|
|
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()
|
tryton/gui/window/win_import.py
CHANGED
|
@@ -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.
|
|
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):
|
|
@@ -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_
|
|
216
|
-
val =
|
|
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)
|
|
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
|
tryton/gui/window/wizard.py
CHANGED
|
@@ -150,16 +150,17 @@ class Wizard(InfoBar):
|
|
|
150
150
|
|
|
151
151
|
def end(self, callback=None):
|
|
152
152
|
def end_callback(action):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
callback
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|