tryton 7.0.6__py3-none-any.whl → 7.2.13__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 +137 -69
- tryton/common/completion.py +2 -2
- tryton/common/datetime_.py +3 -1
- tryton/common/domain_inversion.py +10 -7
- tryton/common/domain_parser.py +17 -7
- 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 +42 -4
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +47 -8
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +42 -4
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +45 -6
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +43 -5
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +41 -4
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +43 -4
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +45 -6
- 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 +45 -6
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +84 -60
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +45 -6
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +57 -17
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +43 -6
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +46 -5
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +41 -4
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +46 -5
- tryton/device_cookie.py +1 -1
- tryton/gui/main.py +3 -2
- tryton/gui/window/about.py +1 -1
- tryton/gui/window/dblogin.py +2 -2
- tryton/gui/window/email_.py +1 -1
- tryton/gui/window/form.py +6 -4
- tryton/gui/window/log.py +24 -2
- tryton/gui/window/view_form/model/field.py +84 -34
- tryton/gui/window/view_form/model/group.py +3 -1
- tryton/gui/window/view_form/model/record.py +64 -15
- tryton/gui/window/view_form/screen/screen.py +83 -46
- 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 +37 -24
- tryton/gui/window/view_form/view/form_gtk/document.py +9 -10
- tryton/gui/window/view_form/view/form_gtk/many2many.py +17 -7
- tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
- tryton/gui/window/view_form/view/form_gtk/one2many.py +25 -6
- tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
- tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
- tryton/gui/window/view_form/view/list.py +68 -35
- 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_export.py +1 -2
- tryton/gui/window/win_form.py +9 -7
- tryton/gui/window/win_import.py +9 -4
- tryton/gui/window/wizard.py +13 -10
- tryton/jsonrpc.py +46 -28
- tryton/plugins/__init__.py +5 -3
- tryton/pyson.py +55 -5
- tryton/rpc.py +18 -0
- tryton/tests/test_common_domain_parser.py +8 -0
- tryton/translate.py +5 -2
- {tryton-7.0.6.data → tryton-7.2.13.data}/scripts/tryton +8 -7
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/METADATA +6 -6
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/RECORD +100 -98
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/WHEEL +1 -1
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/LICENSE +0 -0
- {tryton-7.0.6.dist-info → tryton-7.2.13.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_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,7 +14,7 @@ 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 RPCException, RPCExecute, tempfile
|
|
19
18
|
from tryton.config import CONFIG
|
|
20
19
|
from tryton.gui.window.win_csv import WinCSV
|
|
21
20
|
from tryton.jsonrpc import JSONEncoder
|
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
|
|
@@ -10,7 +11,6 @@ from gi.repository import Gtk
|
|
|
10
11
|
|
|
11
12
|
import tryton.common as common
|
|
12
13
|
from tryton.common import RPCException, RPCExecute
|
|
13
|
-
from tryton.common.datetime_ import date_parse
|
|
14
14
|
from tryton.gui.window.win_csv import WinCSV
|
|
15
15
|
|
|
16
16
|
_ = gettext.gettext
|
|
@@ -212,13 +212,18 @@ class WinImport(WinCSV):
|
|
|
212
212
|
val = locale.atof(val)
|
|
213
213
|
elif type_ == 'numeric':
|
|
214
214
|
val = Decimal(locale.delocalize(val))
|
|
215
|
-
elif type_
|
|
216
|
-
val =
|
|
215
|
+
elif type_ == 'date':
|
|
216
|
+
val = dt.datetime.strptime(
|
|
217
|
+
val, common.date_format()).date()
|
|
218
|
+
elif type_ == 'datetime':
|
|
219
|
+
val = dt.datetime.strptime(
|
|
220
|
+
val, common.date_format() + ' %X')
|
|
217
221
|
elif type_ == 'binary':
|
|
218
222
|
val = base64.b64decode(val)
|
|
219
223
|
row.append(val)
|
|
220
224
|
data.append(row)
|
|
221
|
-
except (IOError, UnicodeDecodeError, csv.Error)
|
|
225
|
+
except (IOError, UnicodeDecodeError, csv.Error, ValueError) \
|
|
226
|
+
as exception:
|
|
222
227
|
common.warning(str(exception), _("Import failed"))
|
|
223
228
|
return
|
|
224
229
|
try:
|
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
|
tryton/jsonrpc.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import base64
|
|
4
4
|
import datetime
|
|
5
5
|
import errno
|
|
6
|
+
import gettext
|
|
6
7
|
import hashlib
|
|
7
8
|
import http.client
|
|
8
9
|
import json
|
|
@@ -11,17 +12,20 @@ import socket
|
|
|
11
12
|
import ssl
|
|
12
13
|
import threading
|
|
13
14
|
import xmlrpc.client
|
|
14
|
-
from collections import defaultdict
|
|
15
15
|
from contextlib import contextmanager
|
|
16
16
|
from decimal import Decimal
|
|
17
17
|
from functools import partial, reduce
|
|
18
18
|
from urllib.parse import quote, urljoin
|
|
19
19
|
|
|
20
|
+
from .cache import CacheDict
|
|
21
|
+
from .config import CONFIG
|
|
22
|
+
|
|
20
23
|
__all__ = ["ResponseError", "Fault", "ProtocolError", "Transport",
|
|
21
24
|
"ServerProxy", "ServerPool"]
|
|
22
25
|
CONNECT_TIMEOUT = 5
|
|
23
26
|
DEFAULT_TIMEOUT = None
|
|
24
27
|
logger = logging.getLogger(__name__)
|
|
28
|
+
_ = gettext.gettext
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
def deepcopy(obj):
|
|
@@ -138,7 +142,6 @@ class JSONUnmarshaller(object):
|
|
|
138
142
|
class Transport(xmlrpc.client.SafeTransport):
|
|
139
143
|
|
|
140
144
|
accept_gzip_encoding = True
|
|
141
|
-
encode_threshold = 1400 # common MTU
|
|
142
145
|
|
|
143
146
|
def __init__(
|
|
144
147
|
self, fingerprints=None, ca_certs=None, session=None):
|
|
@@ -194,34 +197,38 @@ class Transport(xmlrpc.client.SafeTransport):
|
|
|
194
197
|
ssl_ctx = ssl.create_default_context(cafile=self.__ca_certs)
|
|
195
198
|
|
|
196
199
|
def http_connection():
|
|
197
|
-
|
|
198
|
-
timeout=CONNECT_TIMEOUT)
|
|
199
|
-
self._connection
|
|
200
|
-
|
|
201
|
-
sock
|
|
202
|
-
sock
|
|
200
|
+
connection = http.client.HTTPConnection(
|
|
201
|
+
chost, timeout=CONNECT_TIMEOUT)
|
|
202
|
+
self._connection = host, connection
|
|
203
|
+
connection.connect()
|
|
204
|
+
sock = connection.sock
|
|
205
|
+
if sock:
|
|
206
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
207
|
+
return connection
|
|
203
208
|
|
|
204
209
|
def https_connection(allow_http=False):
|
|
205
|
-
|
|
206
|
-
timeout=CONNECT_TIMEOUT, context=ssl_ctx)
|
|
210
|
+
connection = http.client.HTTPSConnection(
|
|
211
|
+
chost, timeout=CONNECT_TIMEOUT, context=ssl_ctx)
|
|
212
|
+
self._connection = host, connection
|
|
207
213
|
try:
|
|
208
|
-
|
|
209
|
-
sock =
|
|
210
|
-
sock
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
connection.connect()
|
|
215
|
+
sock = connection.sock
|
|
216
|
+
if sock:
|
|
217
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
218
|
+
try:
|
|
219
|
+
peercert = sock.getpeercert(True)
|
|
220
|
+
except socket.error:
|
|
221
|
+
peercert = None
|
|
216
222
|
|
|
217
223
|
def format_hash(value):
|
|
218
224
|
return reduce(lambda x, y: x + y[1].upper()
|
|
219
225
|
+ ((y[0] % 2 and y[0] + 1 < len(value)) and ':' or ''),
|
|
220
226
|
enumerate(value), '')
|
|
221
|
-
return format_hash(
|
|
227
|
+
return connection, format_hash(
|
|
228
|
+
hashlib.sha1(peercert).hexdigest())
|
|
222
229
|
except (socket.error, ssl.SSLError, ssl.CertificateError):
|
|
223
230
|
if allow_http:
|
|
224
|
-
http_connection()
|
|
231
|
+
return http_connection(), None
|
|
225
232
|
else:
|
|
226
233
|
raise
|
|
227
234
|
|
|
@@ -229,17 +236,24 @@ class Transport(xmlrpc.client.SafeTransport):
|
|
|
229
236
|
if (self.__fingerprints is not None
|
|
230
237
|
and self.__fingerprints.exists(chost)):
|
|
231
238
|
if self.__fingerprints.get(chost):
|
|
232
|
-
fingerprint = https_connection()
|
|
239
|
+
connection, fingerprint = https_connection()
|
|
233
240
|
else:
|
|
234
|
-
http_connection()
|
|
241
|
+
connection = http_connection()
|
|
235
242
|
else:
|
|
236
|
-
fingerprint = https_connection(allow_http=True)
|
|
243
|
+
connection, fingerprint = https_connection(allow_http=True)
|
|
237
244
|
|
|
238
245
|
if self.__fingerprints is not None:
|
|
239
246
|
self.__fingerprints.set(chost, fingerprint)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
connection.timeout = DEFAULT_TIMEOUT
|
|
248
|
+
sock = connection.sock
|
|
249
|
+
if sock:
|
|
250
|
+
sock.settimeout(DEFAULT_TIMEOUT)
|
|
251
|
+
return connection
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def encode_threshold(self):
|
|
255
|
+
if self.session:
|
|
256
|
+
return 1400 # common MTU
|
|
243
257
|
|
|
244
258
|
|
|
245
259
|
class ServerProxy(xmlrpc.client.ServerProxy):
|
|
@@ -298,7 +312,8 @@ class ServerProxy(xmlrpc.client.ServerProxy):
|
|
|
298
312
|
self.__transport.close()
|
|
299
313
|
raise
|
|
300
314
|
if response['id'] != id_:
|
|
301
|
-
raise ResponseError(
|
|
315
|
+
raise ResponseError(
|
|
316
|
+
_("Invalid response id (%s) expected %s") %
|
|
302
317
|
(response['id'], id_))
|
|
303
318
|
if response.get('error'):
|
|
304
319
|
raise Fault(*response['error'])
|
|
@@ -392,7 +407,10 @@ class ServerPool(object):
|
|
|
392
407
|
class _Cache:
|
|
393
408
|
|
|
394
409
|
def __init__(self):
|
|
395
|
-
|
|
410
|
+
cache_size = CONFIG['rpc.cache_size']
|
|
411
|
+
self.store = CacheDict(
|
|
412
|
+
cache_len=cache_size,
|
|
413
|
+
default_factory=lambda: CacheDict(cache_len=cache_size))
|
|
396
414
|
|
|
397
415
|
def cached(self, prefix):
|
|
398
416
|
return prefix in self.store
|
tryton/plugins/__init__.py
CHANGED
|
@@ -24,9 +24,11 @@ def register():
|
|
|
24
24
|
imported = set()
|
|
25
25
|
for path in paths:
|
|
26
26
|
finder = importlib.machinery.FileFinder(
|
|
27
|
-
path,
|
|
28
|
-
|
|
29
|
-
importlib.machinery.SOURCE_SUFFIXES)
|
|
27
|
+
path,
|
|
28
|
+
(importlib.machinery.SourceFileLoader,
|
|
29
|
+
importlib.machinery.SOURCE_SUFFIXES),
|
|
30
|
+
(importlib.machinery.SourcelessFileLoader,
|
|
31
|
+
importlib.machinery.BYTECODE_SUFFIXES))
|
|
30
32
|
for plugin in os.listdir(path):
|
|
31
33
|
module = os.path.splitext(plugin)[0]
|
|
32
34
|
if (module.startswith('_') or module in imported):
|
tryton/pyson.py
CHANGED
|
@@ -9,6 +9,8 @@ from dateutil.relativedelta import relativedelta
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class PYSON(object):
|
|
12
|
+
_operator = None
|
|
13
|
+
_binary_operator = None
|
|
12
14
|
|
|
13
15
|
def pyson(self):
|
|
14
16
|
raise NotImplementedError
|
|
@@ -81,8 +83,17 @@ class PYSON(object):
|
|
|
81
83
|
return In(k, self)
|
|
82
84
|
|
|
83
85
|
def __repr__(self):
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
params = self.__repr_params__
|
|
87
|
+
if self._operator and isinstance(params[0], PYSON):
|
|
88
|
+
return '%s.%s(%s)' % (
|
|
89
|
+
repr(params[0]), self._operator,
|
|
90
|
+
', '.join(map(repr, params[1:])))
|
|
91
|
+
elif self._binary_operator and isinstance(params[0], PYSON):
|
|
92
|
+
return '(%s %s %s)' % (
|
|
93
|
+
repr(params[0]), self._binary_operator, repr(params[1]))
|
|
94
|
+
else:
|
|
95
|
+
klass = self.__class__.__name__
|
|
96
|
+
return '%s(%s)' % (klass, ', '.join(map(repr, params)))
|
|
86
97
|
|
|
87
98
|
@property
|
|
88
99
|
def __repr_params__(self):
|
|
@@ -137,7 +148,10 @@ class Eval(PYSON):
|
|
|
137
148
|
|
|
138
149
|
@property
|
|
139
150
|
def __repr_params__(self):
|
|
140
|
-
|
|
151
|
+
params = (self._value,)
|
|
152
|
+
if isinstance(self._default, PYSON) or self._default != '':
|
|
153
|
+
params += (self._default,)
|
|
154
|
+
return params
|
|
141
155
|
|
|
142
156
|
def pyson(self):
|
|
143
157
|
return {
|
|
@@ -183,6 +197,17 @@ class Not(PYSON):
|
|
|
183
197
|
v = bool(v)
|
|
184
198
|
self._value = v
|
|
185
199
|
|
|
200
|
+
def __repr__(self):
|
|
201
|
+
if (isinstance(self._value, Equal)
|
|
202
|
+
and isinstance(self._value._statement1, PYSON)):
|
|
203
|
+
return '(%s != %s)' % (
|
|
204
|
+
repr(self._value._statement1),
|
|
205
|
+
repr(self._value._statement2))
|
|
206
|
+
elif isinstance(self._value, PYSON):
|
|
207
|
+
return '~%s' % repr(self._value)
|
|
208
|
+
else:
|
|
209
|
+
return super().__repr__()
|
|
210
|
+
|
|
186
211
|
@property
|
|
187
212
|
def __repr_params__(self):
|
|
188
213
|
return (self._value,)
|
|
@@ -198,7 +223,7 @@ class Not(PYSON):
|
|
|
198
223
|
|
|
199
224
|
@staticmethod
|
|
200
225
|
def eval(dct, context):
|
|
201
|
-
return not dct['v']
|
|
226
|
+
return not Bool(dct['v']).eval(dct, context)
|
|
202
227
|
|
|
203
228
|
|
|
204
229
|
class Bool(PYSON):
|
|
@@ -226,6 +251,8 @@ class Bool(PYSON):
|
|
|
226
251
|
|
|
227
252
|
|
|
228
253
|
class And(PYSON):
|
|
254
|
+
_pyson_class = 'And'
|
|
255
|
+
_binary_operator = '&'
|
|
229
256
|
|
|
230
257
|
def __init__(self, *statements, **kwargs):
|
|
231
258
|
super(And, self).__init__()
|
|
@@ -258,6 +285,7 @@ class And(PYSON):
|
|
|
258
285
|
|
|
259
286
|
|
|
260
287
|
class Or(And):
|
|
288
|
+
_binary_operator = '|'
|
|
261
289
|
|
|
262
290
|
def pyson(self):
|
|
263
291
|
res = super(Or, self).pyson()
|
|
@@ -270,6 +298,7 @@ class Or(And):
|
|
|
270
298
|
|
|
271
299
|
|
|
272
300
|
class Equal(PYSON):
|
|
301
|
+
_binary_operator = '=='
|
|
273
302
|
|
|
274
303
|
def __init__(self, s1, s2):
|
|
275
304
|
statement1, statement2 = s1, s2
|
|
@@ -332,6 +361,10 @@ class Greater(PYSON):
|
|
|
332
361
|
self._statement2 = statement2
|
|
333
362
|
self._equal = equal
|
|
334
363
|
|
|
364
|
+
@property
|
|
365
|
+
def _binary_operator(self):
|
|
366
|
+
return '>=' if self._equal else '>'
|
|
367
|
+
|
|
335
368
|
@property
|
|
336
369
|
def __repr_params__(self):
|
|
337
370
|
return (self._statement1, self._statement2, self._equal)
|
|
@@ -378,6 +411,10 @@ class Greater(PYSON):
|
|
|
378
411
|
|
|
379
412
|
class Less(Greater):
|
|
380
413
|
|
|
414
|
+
@property
|
|
415
|
+
def _binary_operator(self):
|
|
416
|
+
return '<=' if self._equal else '<'
|
|
417
|
+
|
|
381
418
|
def pyson(self):
|
|
382
419
|
res = super(Less, self).pyson()
|
|
383
420
|
res['__class__'] = 'Less'
|
|
@@ -440,6 +477,7 @@ class If(PYSON):
|
|
|
440
477
|
|
|
441
478
|
|
|
442
479
|
class Get(PYSON):
|
|
480
|
+
_operator = 'get'
|
|
443
481
|
|
|
444
482
|
def __init__(self, v, k, d=''):
|
|
445
483
|
obj, key, default = v, k, d
|
|
@@ -458,7 +496,10 @@ class Get(PYSON):
|
|
|
458
496
|
|
|
459
497
|
@property
|
|
460
498
|
def __repr_params__(self):
|
|
461
|
-
|
|
499
|
+
params = (self._obj, self._key)
|
|
500
|
+
if self._default != '':
|
|
501
|
+
params += (self._default,)
|
|
502
|
+
return params
|
|
462
503
|
|
|
463
504
|
def pyson(self):
|
|
464
505
|
return {
|
|
@@ -480,6 +521,7 @@ class Get(PYSON):
|
|
|
480
521
|
|
|
481
522
|
|
|
482
523
|
class In(PYSON):
|
|
524
|
+
_operator = 'in_'
|
|
483
525
|
|
|
484
526
|
def __init__(self, k, v):
|
|
485
527
|
key, obj = k, v
|
|
@@ -508,6 +550,14 @@ class In(PYSON):
|
|
|
508
550
|
self._key = key
|
|
509
551
|
self._obj = obj
|
|
510
552
|
|
|
553
|
+
def __repr__(self):
|
|
554
|
+
params = self.__repr_params__
|
|
555
|
+
if isinstance(params[1], PYSON):
|
|
556
|
+
return '%s.contains(%s)' % (
|
|
557
|
+
repr(params[1]), ', '.join(map(repr, params[:1] + params[2:])))
|
|
558
|
+
else:
|
|
559
|
+
return super().__repr__()
|
|
560
|
+
|
|
511
561
|
@property
|
|
512
562
|
def __repr_params__(self):
|
|
513
563
|
return (self._key, self._obj)
|