tryton 7.0.21__py3-none-any.whl → 7.2.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/cache.py +34 -0
- tryton/common/common.py +125 -69
- tryton/common/completion.py +2 -2
- tryton/common/domain_inversion.py +1 -2
- tryton/common/domain_parser.py +7 -17
- tryton/common/selection.py +6 -3
- tryton/common/tempfile.py +34 -0
- tryton/config.py +3 -2
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +28 -3
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +33 -5
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +28 -3
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +32 -4
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +32 -4
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +29 -4
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +32 -5
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +32 -6
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +28 -3
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +32 -4
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +32 -5
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +30 -3
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +31 -5
- 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 +31 -5
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +32 -5
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +32 -4
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +32 -5
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +31 -5
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +43 -16
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +29 -5
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +32 -4
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +28 -3
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +32 -5
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +32 -4
- 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 +4 -3
- tryton/gui/window/log.py +24 -2
- tryton/gui/window/view_form/model/field.py +56 -62
- tryton/gui/window/view_form/model/group.py +3 -1
- tryton/gui/window/view_form/model/record.py +55 -16
- tryton/gui/window/view_form/screen/screen.py +22 -22
- tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +7 -12
- tryton/gui/window/view_form/view/form.py +4 -14
- tryton/gui/window/view_form/view/form_gtk/binary.py +3 -3
- tryton/gui/window/view_form/view/form_gtk/dictionary.py +33 -27
- tryton/gui/window/view_form/view/form_gtk/document.py +10 -9
- 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/list.py +47 -56
- tryton/gui/window/view_form/view/list_gtk/widget.py +36 -23
- tryton/gui/window/view_form/view/screen_container.py +5 -3
- tryton/gui/window/win_export.py +1 -2
- tryton/gui/window/win_form.py +6 -8
- tryton/gui/window/wizard.py +11 -10
- tryton/jsonrpc.py +41 -27
- tryton/pyson.py +54 -4
- tryton/rpc.py +18 -0
- tryton/tests/test_common_domain_parser.py +0 -8
- {tryton-7.0.21.data → tryton-7.2.0.data}/scripts/tryton +1 -2
- {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/METADATA +6 -20
- {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/RECORD +94 -92
- {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/WHEEL +1 -1
- {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/LICENSE +0 -0
- {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/top_level.txt +0 -0
tryton/__init__.py
CHANGED
tryton/cache.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CacheDict(OrderedDict):
|
|
8
|
+
|
|
9
|
+
def __init__(self, *args, cache_len=10, default_factory=None, **kwargs):
|
|
10
|
+
assert cache_len > 0
|
|
11
|
+
self.cache_len = cache_len
|
|
12
|
+
self.default_factory = default_factory
|
|
13
|
+
|
|
14
|
+
super().__init__(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
def __setitem__(self, key, value):
|
|
17
|
+
super().__setitem__(key, value)
|
|
18
|
+
self.move_to_end(key)
|
|
19
|
+
|
|
20
|
+
while len(self) > self.cache_len:
|
|
21
|
+
oldkey = next(iter(self))
|
|
22
|
+
self.__delitem__(oldkey)
|
|
23
|
+
|
|
24
|
+
def __getitem__(self, key):
|
|
25
|
+
value = super().__getitem__(key)
|
|
26
|
+
self.move_to_end(key)
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
def __missing__(self, key):
|
|
30
|
+
if self.default_factory is None:
|
|
31
|
+
raise KeyError(key)
|
|
32
|
+
value = self.default_factory()
|
|
33
|
+
self[key] = value
|
|
34
|
+
return value
|
tryton/common/common.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
3
|
|
|
4
|
+
import base64
|
|
4
5
|
import colorsys
|
|
6
|
+
import concurrent.futures
|
|
5
7
|
import gettext
|
|
6
8
|
import locale
|
|
7
9
|
import logging
|
|
@@ -9,10 +11,9 @@ import os
|
|
|
9
11
|
import platform
|
|
10
12
|
import re
|
|
11
13
|
import subprocess
|
|
12
|
-
import tempfile
|
|
13
14
|
import unicodedata
|
|
14
15
|
import xml.etree.ElementTree as ET
|
|
15
|
-
from collections import defaultdict
|
|
16
|
+
from collections import OrderedDict, defaultdict
|
|
16
17
|
from decimal import Decimal
|
|
17
18
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
18
19
|
from pathlib import PurePath
|
|
@@ -31,11 +32,12 @@ import urllib.error
|
|
|
31
32
|
import urllib.parse
|
|
32
33
|
import urllib.request
|
|
33
34
|
import webbrowser
|
|
34
|
-
from functools import
|
|
35
|
+
from functools import wraps
|
|
35
36
|
from string import Template
|
|
36
37
|
from threading import Lock, Thread
|
|
37
38
|
|
|
38
39
|
import tryton.rpc as rpc
|
|
40
|
+
from tryton.cache import CacheDict
|
|
39
41
|
from tryton.config import CONFIG, PIXMAPS_DIR, SOUNDS_DIR, TRYTON_ICON
|
|
40
42
|
|
|
41
43
|
try:
|
|
@@ -50,6 +52,7 @@ from tryton import __version__
|
|
|
50
52
|
from tryton.exceptions import TrytonError, TrytonServerError
|
|
51
53
|
from tryton.pyson import PYSONEncoder
|
|
52
54
|
|
|
55
|
+
from . import tempfile
|
|
53
56
|
from .underline import set_underline
|
|
54
57
|
from .widget_style import widget_class
|
|
55
58
|
|
|
@@ -60,11 +63,15 @@ logger = logging.getLogger(__name__)
|
|
|
60
63
|
class IconFactory:
|
|
61
64
|
|
|
62
65
|
batchnum = 10
|
|
63
|
-
|
|
64
|
-
_name2id = {}
|
|
66
|
+
_name2id = OrderedDict()
|
|
65
67
|
_icons = {}
|
|
66
68
|
_local_icons = {}
|
|
67
69
|
_pixbufs = defaultdict(dict)
|
|
70
|
+
_url_pixbufs = CacheDict(cache_len=CONFIG['image.cache_size'])
|
|
71
|
+
_empty_pixbufs = {}
|
|
72
|
+
_empty_gif = base64.b64decode(
|
|
73
|
+
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7')
|
|
74
|
+
_executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
|
|
68
75
|
|
|
69
76
|
@classmethod
|
|
70
77
|
def load_local_icons(cls):
|
|
@@ -75,69 +82,61 @@ class IconFactory:
|
|
|
75
82
|
|
|
76
83
|
@classmethod
|
|
77
84
|
def load_icons(cls, refresh=False):
|
|
78
|
-
if not refresh:
|
|
79
|
-
cls._name2id.clear()
|
|
80
|
-
cls._icons.clear()
|
|
81
|
-
del cls._tryton_icons[:]
|
|
82
|
-
|
|
83
85
|
try:
|
|
84
86
|
icons = rpc.execute('model', 'ir.ui.icon', 'list_icons',
|
|
85
87
|
rpc.CONTEXT)
|
|
86
88
|
except TrytonServerError:
|
|
87
89
|
icons = []
|
|
88
|
-
for
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
cls._name2id[icon_name] = icon_id
|
|
90
|
+
cls._name2id = name2id = OrderedDict((n, i) for i, n in icons)
|
|
91
|
+
if not refresh:
|
|
92
|
+
cls._icons.clear()
|
|
93
|
+
return name2id
|
|
93
94
|
|
|
94
95
|
@classmethod
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
def _get_icon(cls, iconname):
|
|
97
|
+
data = cls._icons.get(iconname)
|
|
98
|
+
if data is not None:
|
|
99
|
+
return data
|
|
100
|
+
path = cls._local_icons.get(iconname)
|
|
101
|
+
if path is not None:
|
|
102
|
+
with open(path, 'rb') as fp:
|
|
103
|
+
return fp.read()
|
|
104
|
+
|
|
105
|
+
name2id = cls._name2id
|
|
106
|
+
if iconname not in name2id:
|
|
107
|
+
name2id = cls.load_icons(refresh=True)
|
|
108
|
+
if iconname not in name2id:
|
|
109
|
+
logger.error(f"Unknown icon {iconname}")
|
|
110
|
+
cls._icons[iconname] = None
|
|
111
|
+
return
|
|
112
|
+
names = [n for n in name2id if n not in cls._icons or n == iconname]
|
|
113
|
+
idx = names.index(iconname)
|
|
114
|
+
to_load = slice(
|
|
115
|
+
max(0, idx - cls.batchnum // 2),
|
|
111
116
|
idx + cls.batchnum // 2)
|
|
112
|
-
ids = [
|
|
117
|
+
ids = [name2id[n] for n in names[to_load]]
|
|
113
118
|
try:
|
|
114
119
|
icons = rpc.execute('model', 'ir.ui.icon', 'read', ids,
|
|
115
120
|
['name', 'icon'], rpc.CONTEXT)
|
|
116
121
|
except TrytonServerError:
|
|
117
122
|
icons = []
|
|
123
|
+
data = None
|
|
118
124
|
for icon in icons:
|
|
119
125
|
name = icon['name']
|
|
120
|
-
|
|
121
|
-
cls._icons[name] =
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
icondata = icon['icon'].encode('utf-8')
|
|
127
|
+
cls._icons[name] = icondata
|
|
128
|
+
if name == iconname:
|
|
129
|
+
data = icondata
|
|
130
|
+
return data
|
|
124
131
|
|
|
125
132
|
@classmethod
|
|
126
133
|
def get_pixbuf(cls, iconname, size=16, color=None, badge=None):
|
|
127
134
|
if not iconname:
|
|
128
135
|
return
|
|
129
136
|
colors = CONFIG['icon.colors'].split(',')
|
|
130
|
-
cls.register_icon(iconname)
|
|
131
137
|
if iconname not in cls._pixbufs[(size, badge)]:
|
|
132
|
-
data =
|
|
133
|
-
if iconname in cls._icons:
|
|
134
|
-
data = cls._icons[iconname]
|
|
135
|
-
elif iconname in cls._local_icons:
|
|
136
|
-
path = cls._local_icons[iconname]
|
|
137
|
-
with open(path, 'rb') as fp:
|
|
138
|
-
data = fp.read()
|
|
138
|
+
data = cls._get_icon(iconname)
|
|
139
139
|
if not data:
|
|
140
|
-
logger.error("Unknown icon %s" % iconname)
|
|
141
140
|
return
|
|
142
141
|
if not color:
|
|
143
142
|
color = colors[0]
|
|
@@ -199,16 +198,40 @@ class IconFactory:
|
|
|
199
198
|
return urllib.parse.urlunsplit(parts)
|
|
200
199
|
|
|
201
200
|
@classmethod
|
|
202
|
-
|
|
203
|
-
def get_pixbuf_url(cls, url, size=16, size_param=None):
|
|
201
|
+
def _get_pixbuf_url(cls, url, size=16):
|
|
204
202
|
if not url:
|
|
205
203
|
return
|
|
206
|
-
|
|
204
|
+
pixbuf = None
|
|
205
|
+
logger.info(f'GET {url}')
|
|
207
206
|
try:
|
|
208
207
|
with urllib.request.urlopen(url) as response:
|
|
209
|
-
|
|
208
|
+
pixbuf = data2pixbuf(response.read(), size, size)
|
|
210
209
|
except urllib.error.URLError:
|
|
211
210
|
logger.info("Can not fetch %s", url, exc_info=True)
|
|
211
|
+
cls._url_pixbufs[url] = pixbuf
|
|
212
|
+
return pixbuf
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def get_pixbuf_url(cls, url, size=16, size_param=None, callback=None):
|
|
216
|
+
if not url:
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
url = cls._convert_url(url, size, size_param=size_param)
|
|
220
|
+
pixbuf = cls._url_pixbufs.get(url)
|
|
221
|
+
if pixbuf is not None:
|
|
222
|
+
return pixbuf
|
|
223
|
+
|
|
224
|
+
if callback:
|
|
225
|
+
def fetch(url, size):
|
|
226
|
+
pixbuf = cls._get_pixbuf_url(url, size)
|
|
227
|
+
GLib.idle_add(lambda: callback(pixbuf))
|
|
228
|
+
cls._executor.submit(fetch, url, size)
|
|
229
|
+
if size not in cls._empty_pixbufs:
|
|
230
|
+
cls._empty_pixbufs[size] = _data2pixbuf(
|
|
231
|
+
cls._empty_gif, size, size)
|
|
232
|
+
return cls._empty_pixbufs[size]
|
|
233
|
+
else:
|
|
234
|
+
return cls._get_pixbuf_url(url, size, size_param)
|
|
212
235
|
|
|
213
236
|
|
|
214
237
|
IconFactory.load_local_icons()
|
|
@@ -806,7 +829,7 @@ class ConcurrencyDialog(UniqueDialog):
|
|
|
806
829
|
dialog = Gtk.MessageDialog(
|
|
807
830
|
transient_for=parent, modal=True, destroy_with_parent=True,
|
|
808
831
|
message_type=Gtk.MessageType.QUESTION,
|
|
809
|
-
buttons=Gtk.ButtonsType.NONE, text=_(
|
|
832
|
+
buttons=Gtk.ButtonsType.NONE, text=_("Concurrency Warning"))
|
|
810
833
|
dialog.format_secondary_text(
|
|
811
834
|
_('This record has been modified while you were editing it.'))
|
|
812
835
|
cancel_button = dialog.add_button(
|
|
@@ -851,7 +874,7 @@ class ErrorDialog(UniqueDialog):
|
|
|
851
874
|
dialog = Gtk.MessageDialog(
|
|
852
875
|
transient_for=parent, modal=True, destroy_with_parent=True,
|
|
853
876
|
message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.NONE)
|
|
854
|
-
dialog.set_default_size(600,
|
|
877
|
+
dialog.set_default_size(600, 200)
|
|
855
878
|
dialog.set_position(Gtk.WindowPosition.CENTER)
|
|
856
879
|
|
|
857
880
|
dialog.add_button(set_underline(_("Close")), Gtk.ResponseType.CANCEL)
|
|
@@ -866,6 +889,7 @@ class ErrorDialog(UniqueDialog):
|
|
|
866
889
|
scrolledwindow.set_policy(
|
|
867
890
|
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
868
891
|
scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE)
|
|
892
|
+
scrolledwindow.set_min_content_height(300)
|
|
869
893
|
|
|
870
894
|
viewport = Gtk.Viewport()
|
|
871
895
|
viewport.set_shadow_type(Gtk.ShadowType.NONE)
|
|
@@ -877,8 +901,12 @@ class ErrorDialog(UniqueDialog):
|
|
|
877
901
|
|
|
878
902
|
viewport.add(textview)
|
|
879
903
|
scrolledwindow.add(viewport)
|
|
904
|
+
expander = Gtk.Expander()
|
|
905
|
+
expander.set_label(_("Details"))
|
|
906
|
+
expander.add(scrolledwindow)
|
|
907
|
+
expander.set_resize_toplevel(True)
|
|
880
908
|
dialog.vbox.pack_start(
|
|
881
|
-
|
|
909
|
+
expander, expand=False, fill=True, padding=0)
|
|
882
910
|
|
|
883
911
|
button_roundup = Gtk.LinkButton.new_with_label(
|
|
884
912
|
CONFIG['bug.url'], _("Report Bug"))
|
|
@@ -980,10 +1008,13 @@ def process_exception(exception, *args, **kwargs):
|
|
|
980
1008
|
name, msg, description = exception.args
|
|
981
1009
|
res = userwarning(description, msg)
|
|
982
1010
|
if res in ('always', 'ok'):
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1011
|
+
try:
|
|
1012
|
+
RPCExecute(
|
|
1013
|
+
'model', 'res.user.warning', 'skip',
|
|
1014
|
+
name, (res == 'always'),
|
|
1015
|
+
process_exception=False)
|
|
1016
|
+
except RPCException:
|
|
1017
|
+
pass
|
|
987
1018
|
return rpc_execute(*args)
|
|
988
1019
|
elif exception.faultCode == 'UserError':
|
|
989
1020
|
msg, description, domain = exception.args
|
|
@@ -1021,12 +1052,11 @@ def process_exception(exception, *args, **kwargs):
|
|
|
1021
1052
|
PLOCK.release()
|
|
1022
1053
|
if args:
|
|
1023
1054
|
return rpc_execute(*args)
|
|
1024
|
-
elif exception.faultCode
|
|
1055
|
+
elif exception.faultCode in map(str, HTTPStatus):
|
|
1056
|
+
err_msg = '[%s] %s' % (exception.faultCode, exception.faultString)
|
|
1025
1057
|
message(
|
|
1026
|
-
_('
|
|
1058
|
+
_('Error: "%s". Try again later.') % err_msg,
|
|
1027
1059
|
msg_type=Gtk.MessageType.ERROR)
|
|
1028
|
-
elif exception.faultCode == str(int(HTTPStatus.NOT_FOUND)):
|
|
1029
|
-
message(_("Not found."), msg_type=Gtk.MessageType.ERROR)
|
|
1030
1060
|
else:
|
|
1031
1061
|
error(exception, exception.faultString)
|
|
1032
1062
|
else:
|
|
@@ -1111,7 +1141,7 @@ class Login(object):
|
|
|
1111
1141
|
if exception.faultCode != 'LoginException':
|
|
1112
1142
|
raise
|
|
1113
1143
|
name, msg, type = exception.args
|
|
1114
|
-
value = getattr(self, 'get_%s' % type)(msg)
|
|
1144
|
+
value = getattr(self, 'get_%s' % type)(msg, name)
|
|
1115
1145
|
if value is None:
|
|
1116
1146
|
raise TrytonError('QueryCanceled')
|
|
1117
1147
|
parameters[name] = value
|
|
@@ -1120,12 +1150,35 @@ class Login(object):
|
|
|
1120
1150
|
return
|
|
1121
1151
|
|
|
1122
1152
|
@classmethod
|
|
1123
|
-
def get_char(cls, message):
|
|
1153
|
+
def get_char(cls, message, name):
|
|
1124
1154
|
return ask(message)
|
|
1125
1155
|
|
|
1126
1156
|
@classmethod
|
|
1127
|
-
def get_password(cls,
|
|
1128
|
-
|
|
1157
|
+
def get_password(cls, message_, name):
|
|
1158
|
+
class AskPasswordDialog(AskDialog):
|
|
1159
|
+
def build_dialog(self, *args, **kwargs):
|
|
1160
|
+
tooltips = Tooltips()
|
|
1161
|
+
dialog = super().build_dialog(*args, **kwargs)
|
|
1162
|
+
box = dialog.get_message_area()
|
|
1163
|
+
button = Gtk.Button.new_with_label(
|
|
1164
|
+
_("Reset forgotten password"))
|
|
1165
|
+
button.set_alignment(0, 0.5)
|
|
1166
|
+
button.set_relief(Gtk.ReliefStyle.NONE)
|
|
1167
|
+
tooltips.set_tip(
|
|
1168
|
+
button, _("Send you an email to reset your password."))
|
|
1169
|
+
button.connect('clicked', self.reset_password)
|
|
1170
|
+
box.pack_start(button, False, False, 0)
|
|
1171
|
+
return dialog
|
|
1172
|
+
|
|
1173
|
+
def reset_password(self, button):
|
|
1174
|
+
rpc.reset_password()
|
|
1175
|
+
message(
|
|
1176
|
+
_("A request to reset your password has been sent.\n"
|
|
1177
|
+
"Please check your mailbox."))
|
|
1178
|
+
self.entry.grab_focus()
|
|
1179
|
+
if name == 'password':
|
|
1180
|
+
ask = AskPasswordDialog()
|
|
1181
|
+
return ask(message_, visibility=False)
|
|
1129
1182
|
|
|
1130
1183
|
|
|
1131
1184
|
class Logout:
|
|
@@ -1261,9 +1314,7 @@ class RPCProgress(object):
|
|
|
1261
1314
|
|
|
1262
1315
|
def return_():
|
|
1263
1316
|
if self.exception:
|
|
1264
|
-
|
|
1265
|
-
raise RPCException(self.exception)
|
|
1266
|
-
raise self.exception
|
|
1317
|
+
raise RPCException(self.exception)
|
|
1267
1318
|
else:
|
|
1268
1319
|
return self.res
|
|
1269
1320
|
|
|
@@ -1284,10 +1335,15 @@ def RPCExecute(*args, **kwargs):
|
|
|
1284
1335
|
|
|
1285
1336
|
|
|
1286
1337
|
def RPCContextReload(callback=None):
|
|
1338
|
+
def clean(context):
|
|
1339
|
+
return {
|
|
1340
|
+
k: v for k, v in context.items()
|
|
1341
|
+
if k != 'locale' and not k.endswith('.rec_name')}
|
|
1342
|
+
|
|
1287
1343
|
def update(context):
|
|
1288
1344
|
rpc.context_reset()
|
|
1289
1345
|
try:
|
|
1290
|
-
rpc.CONTEXT.update(context())
|
|
1346
|
+
rpc.CONTEXT.update(clean(context()))
|
|
1291
1347
|
except RPCException:
|
|
1292
1348
|
pass
|
|
1293
1349
|
if callback:
|
|
@@ -1297,7 +1353,7 @@ def RPCContextReload(callback=None):
|
|
|
1297
1353
|
callback=update if callback else None)
|
|
1298
1354
|
if not callback:
|
|
1299
1355
|
rpc.context_reset()
|
|
1300
|
-
rpc.CONTEXT.update(context)
|
|
1356
|
+
rpc.CONTEXT.update(clean(context))
|
|
1301
1357
|
|
|
1302
1358
|
|
|
1303
1359
|
class Tooltips(object):
|
tryton/common/completion.py
CHANGED
|
@@ -5,7 +5,7 @@ import logging
|
|
|
5
5
|
|
|
6
6
|
from gi.repository import GLib, Gtk
|
|
7
7
|
|
|
8
|
-
from tryton.common import RPCExecute
|
|
8
|
+
from tryton.common import RPCException, RPCExecute
|
|
9
9
|
from tryton.config import CONFIG
|
|
10
10
|
from tryton.exceptions import TrytonError, TrytonServerError
|
|
11
11
|
|
|
@@ -72,7 +72,7 @@ def update_completion(entry, record, field, model, domain=None):
|
|
|
72
72
|
'model', model, 'autocomplete', search_text, domain,
|
|
73
73
|
CONFIG['client.limit'], order, context=context,
|
|
74
74
|
process_exception=False, callback=callback)
|
|
75
|
-
except
|
|
75
|
+
except RPCException:
|
|
76
76
|
logger.warning(
|
|
77
77
|
"Unable to search for completion of %s", model,
|
|
78
78
|
exc_info=True)
|
|
@@ -433,8 +433,7 @@ def unique_value(domain, single_value=True):
|
|
|
433
433
|
and operator == 'in' and len(value) == 1))
|
|
434
434
|
and (not count
|
|
435
435
|
or (count == 1 and model and name.endswith('.id')))):
|
|
436
|
-
if operator == '
|
|
437
|
-
value = value[0]
|
|
436
|
+
value = value if operator == '=' and single_value else value[0]
|
|
438
437
|
if model and name.endswith('.id'):
|
|
439
438
|
model = model[0]
|
|
440
439
|
value = [model, value]
|
tryton/common/domain_parser.py
CHANGED
|
@@ -128,12 +128,10 @@ def unescape(value, escape='\\'):
|
|
|
128
128
|
return value.replace(escape + '%', '%').replace(escape + '_', '_')
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
def quote(value
|
|
131
|
+
def quote(value):
|
|
132
132
|
"Quote string if needed"
|
|
133
133
|
if not isinstance(value, str):
|
|
134
134
|
return value
|
|
135
|
-
if empty and value == '':
|
|
136
|
-
return '""'
|
|
137
135
|
if '\\' in value:
|
|
138
136
|
value = value.replace('\\', '\\\\')
|
|
139
137
|
if '"' in value:
|
|
@@ -309,7 +307,7 @@ def convert_value(field, value, context=None):
|
|
|
309
307
|
return converts.get(field['type'], lambda: value)()
|
|
310
308
|
|
|
311
309
|
|
|
312
|
-
def format_value(field, value, target=None, context=None
|
|
310
|
+
def format_value(field, value, target=None, context=None):
|
|
313
311
|
"Format value for field"
|
|
314
312
|
if context is None:
|
|
315
313
|
context = {}
|
|
@@ -412,11 +410,9 @@ def format_value(field, value, target=None, context=None, _quote_empty=False):
|
|
|
412
410
|
'many2one': format_many2one,
|
|
413
411
|
}
|
|
414
412
|
if isinstance(value, (list, tuple)):
|
|
415
|
-
return ';'.join(
|
|
416
|
-
format_value(field, x, context=context, _quote_empty=True)
|
|
417
|
-
for x in value)
|
|
413
|
+
return ';'.join(format_value(field, x, context=context) for x in value)
|
|
418
414
|
return quote(converts.get(field['type'],
|
|
419
|
-
lambda: value if value is not None else '')()
|
|
415
|
+
lambda: value if value is not None else '')())
|
|
420
416
|
|
|
421
417
|
|
|
422
418
|
def complete_value(field, value):
|
|
@@ -651,16 +647,10 @@ class DomainParser(object):
|
|
|
651
647
|
operator = operator.rstrip(def_operator
|
|
652
648
|
).replace('not', '!').strip()
|
|
653
649
|
if operator.endswith('in'):
|
|
654
|
-
if
|
|
655
|
-
|
|
656
|
-
operator = '!='
|
|
657
|
-
else:
|
|
658
|
-
operator = '='
|
|
650
|
+
if operator == 'not in':
|
|
651
|
+
operator = '!'
|
|
659
652
|
else:
|
|
660
|
-
|
|
661
|
-
operator = '!'
|
|
662
|
-
else:
|
|
663
|
-
operator = ''
|
|
653
|
+
operator = ''
|
|
664
654
|
formatted_value = format_value(field, value, target, self.context)
|
|
665
655
|
if (operator in OPERATORS
|
|
666
656
|
and field['type'] in ('char', 'text', 'selection')
|
tryton/common/selection.py
CHANGED
|
@@ -30,10 +30,13 @@ class SelectionMixin(object):
|
|
|
30
30
|
and key not in self._values2selection):
|
|
31
31
|
try:
|
|
32
32
|
if self.attrs.get('selection_change_with'):
|
|
33
|
-
selection = RPCExecute(
|
|
34
|
-
value
|
|
33
|
+
selection = RPCExecute(
|
|
34
|
+
'model', self.model_name, selection, value,
|
|
35
|
+
process_exception=False)
|
|
35
36
|
else:
|
|
36
|
-
selection = RPCExecute(
|
|
37
|
+
selection = RPCExecute(
|
|
38
|
+
'model', self.model_name, selection,
|
|
39
|
+
process_exception=False)
|
|
37
40
|
except RPCException:
|
|
38
41
|
selection = []
|
|
39
42
|
self._values2selection[key] = selection
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
import atexit
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
_files, _directories = [], []
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def mkstemp(*args, **kwargs):
|
|
12
|
+
fileno, fname = tempfile.mkstemp(*args, **kwargs)
|
|
13
|
+
_files.append(fname)
|
|
14
|
+
return fileno, fname
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def mkdtemp(*args, **kwargs):
|
|
18
|
+
dname = tempfile.mkdtemp(*args, **kwargs)
|
|
19
|
+
_directories.append(dname)
|
|
20
|
+
return dname
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@atexit.register
|
|
24
|
+
def clean():
|
|
25
|
+
for fname in _files:
|
|
26
|
+
try:
|
|
27
|
+
os.remove(fname)
|
|
28
|
+
except FileNotFoundError:
|
|
29
|
+
pass
|
|
30
|
+
_files.clear()
|
|
31
|
+
|
|
32
|
+
for dname in _directories:
|
|
33
|
+
shutil.rmtree(dname, ignore_errors=True)
|
|
34
|
+
_directories.clear()
|
tryton/config.py
CHANGED
|
@@ -8,7 +8,7 @@ import optparse
|
|
|
8
8
|
import os
|
|
9
9
|
import shutil
|
|
10
10
|
import sys
|
|
11
|
-
import
|
|
11
|
+
from tempfile import NamedTemporaryFile
|
|
12
12
|
|
|
13
13
|
from gi.repository import GdkPixbuf
|
|
14
14
|
|
|
@@ -81,6 +81,7 @@ class ConfigManager(object):
|
|
|
81
81
|
'login.db': demo_database,
|
|
82
82
|
'login.expanded': False,
|
|
83
83
|
'client.title': 'Tryton',
|
|
84
|
+
'rpc.cache_size': 1024,
|
|
84
85
|
'client.modepda': False,
|
|
85
86
|
'client.toolbar': 'default',
|
|
86
87
|
'client.save_tree_width': True,
|
|
@@ -185,7 +186,7 @@ class ConfigManager(object):
|
|
|
185
186
|
parser.read([self.rcfile])
|
|
186
187
|
except configparser.Error:
|
|
187
188
|
config_dir = os.path.dirname(self.rcfile)
|
|
188
|
-
with
|
|
189
|
+
with NamedTemporaryFile(
|
|
189
190
|
delete=False, prefix='tryton_', suffix='.conf',
|
|
190
191
|
dir=config_dir) as temp_file:
|
|
191
192
|
temp_name = temp_file.name
|
|
Binary file
|
|
@@ -30,6 +30,10 @@ msgstr "укажете адреса на сървъра"
|
|
|
30
30
|
msgid "disable thread usage"
|
|
31
31
|
msgstr ""
|
|
32
32
|
|
|
33
|
+
#, python-format
|
|
34
|
+
msgid "Invalid response id (%s) expected %s"
|
|
35
|
+
msgstr ""
|
|
36
|
+
|
|
33
37
|
#, python-format
|
|
34
38
|
msgid "Unable to set locale %s"
|
|
35
39
|
msgstr "Не може да зададе локални настройки %s"
|
|
@@ -135,15 +139,27 @@ msgstr ""
|
|
|
135
139
|
msgid "Could not get a session."
|
|
136
140
|
msgstr "Не може да се свърже със сървъра!"
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
#, python-format
|
|
143
|
+
msgid "Error \"%s\". Try again later."
|
|
139
144
|
msgstr ""
|
|
140
145
|
|
|
141
|
-
msgid "
|
|
146
|
+
msgid "Too many requests. Try again later."
|
|
142
147
|
msgstr ""
|
|
143
148
|
|
|
144
149
|
msgid "Not Found."
|
|
145
150
|
msgstr ""
|
|
146
151
|
|
|
152
|
+
msgid "Reset forgotten password"
|
|
153
|
+
msgstr ""
|
|
154
|
+
|
|
155
|
+
msgid "Send you an email to reset your password."
|
|
156
|
+
msgstr ""
|
|
157
|
+
|
|
158
|
+
msgid ""
|
|
159
|
+
"A request to reset your password has been sent.\n"
|
|
160
|
+
"Please check your mailbox."
|
|
161
|
+
msgstr ""
|
|
162
|
+
|
|
147
163
|
#, fuzzy
|
|
148
164
|
msgid "..."
|
|
149
165
|
msgstr "..."
|
|
@@ -1157,7 +1173,16 @@ msgstr "Размера на изображението е много голям!
|
|
|
1157
1173
|
msgid "Copy"
|
|
1158
1174
|
msgstr ""
|
|
1159
1175
|
|
|
1160
|
-
msgid "
|
|
1176
|
+
msgid "Copy Row"
|
|
1177
|
+
msgstr ""
|
|
1178
|
+
|
|
1179
|
+
msgid "Copy Rows"
|
|
1180
|
+
msgstr ""
|
|
1181
|
+
|
|
1182
|
+
msgid "Copy Column"
|
|
1183
|
+
msgstr ""
|
|
1184
|
+
|
|
1185
|
+
msgid "Paste Rows"
|
|
1161
1186
|
msgstr ""
|
|
1162
1187
|
|
|
1163
1188
|
msgid ".."
|
|
Binary file
|