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
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
|
|
@@ -10,18 +11,27 @@ import logging
|
|
|
10
11
|
import socket
|
|
11
12
|
import ssl
|
|
12
13
|
import threading
|
|
14
|
+
import time
|
|
13
15
|
import xmlrpc.client
|
|
14
|
-
from collections import defaultdict
|
|
15
16
|
from contextlib import contextmanager
|
|
16
17
|
from decimal import Decimal
|
|
17
18
|
from functools import partial, reduce
|
|
18
19
|
from urllib.parse import quote, urljoin
|
|
19
20
|
|
|
21
|
+
try:
|
|
22
|
+
from http import HTTPStatus
|
|
23
|
+
except ImportError:
|
|
24
|
+
from http import client as HTTPStatus
|
|
25
|
+
|
|
26
|
+
from .cache import CacheDict
|
|
27
|
+
from .config import CONFIG
|
|
28
|
+
|
|
20
29
|
__all__ = ["ResponseError", "Fault", "ProtocolError", "Transport",
|
|
21
30
|
"ServerProxy", "ServerPool"]
|
|
22
31
|
CONNECT_TIMEOUT = 5
|
|
23
32
|
DEFAULT_TIMEOUT = None
|
|
24
33
|
logger = logging.getLogger(__name__)
|
|
34
|
+
_ = gettext.gettext
|
|
25
35
|
|
|
26
36
|
|
|
27
37
|
def deepcopy(obj):
|
|
@@ -138,7 +148,6 @@ class JSONUnmarshaller(object):
|
|
|
138
148
|
class Transport(xmlrpc.client.SafeTransport):
|
|
139
149
|
|
|
140
150
|
accept_gzip_encoding = True
|
|
141
|
-
encode_threshold = 1400 # common MTU
|
|
142
151
|
|
|
143
152
|
def __init__(
|
|
144
153
|
self, fingerprints=None, ca_certs=None, session=None):
|
|
@@ -194,34 +203,38 @@ class Transport(xmlrpc.client.SafeTransport):
|
|
|
194
203
|
ssl_ctx = ssl.create_default_context(cafile=self.__ca_certs)
|
|
195
204
|
|
|
196
205
|
def http_connection():
|
|
197
|
-
|
|
198
|
-
timeout=CONNECT_TIMEOUT)
|
|
199
|
-
self._connection
|
|
200
|
-
|
|
201
|
-
sock
|
|
202
|
-
sock
|
|
206
|
+
connection = http.client.HTTPConnection(
|
|
207
|
+
chost, timeout=CONNECT_TIMEOUT)
|
|
208
|
+
self._connection = host, connection
|
|
209
|
+
connection.connect()
|
|
210
|
+
sock = connection.sock
|
|
211
|
+
if sock:
|
|
212
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
213
|
+
return connection
|
|
203
214
|
|
|
204
215
|
def https_connection(allow_http=False):
|
|
205
|
-
|
|
206
|
-
timeout=CONNECT_TIMEOUT, context=ssl_ctx)
|
|
216
|
+
connection = http.client.HTTPSConnection(
|
|
217
|
+
chost, timeout=CONNECT_TIMEOUT, context=ssl_ctx)
|
|
218
|
+
self._connection = host, connection
|
|
207
219
|
try:
|
|
208
|
-
|
|
209
|
-
sock =
|
|
210
|
-
sock
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
connection.connect()
|
|
221
|
+
sock = connection.sock
|
|
222
|
+
if sock:
|
|
223
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
224
|
+
try:
|
|
225
|
+
peercert = sock.getpeercert(True)
|
|
226
|
+
except socket.error:
|
|
227
|
+
peercert = None
|
|
216
228
|
|
|
217
229
|
def format_hash(value):
|
|
218
230
|
return reduce(lambda x, y: x + y[1].upper()
|
|
219
231
|
+ ((y[0] % 2 and y[0] + 1 < len(value)) and ':' or ''),
|
|
220
232
|
enumerate(value), '')
|
|
221
|
-
return format_hash(
|
|
233
|
+
return connection, format_hash(
|
|
234
|
+
hashlib.sha1(peercert).hexdigest())
|
|
222
235
|
except (socket.error, ssl.SSLError, ssl.CertificateError):
|
|
223
236
|
if allow_http:
|
|
224
|
-
http_connection()
|
|
237
|
+
return http_connection(), None
|
|
225
238
|
else:
|
|
226
239
|
raise
|
|
227
240
|
|
|
@@ -229,17 +242,24 @@ class Transport(xmlrpc.client.SafeTransport):
|
|
|
229
242
|
if (self.__fingerprints is not None
|
|
230
243
|
and self.__fingerprints.exists(chost)):
|
|
231
244
|
if self.__fingerprints.get(chost):
|
|
232
|
-
fingerprint = https_connection()
|
|
245
|
+
connection, fingerprint = https_connection()
|
|
233
246
|
else:
|
|
234
|
-
http_connection()
|
|
247
|
+
connection = http_connection()
|
|
235
248
|
else:
|
|
236
|
-
fingerprint = https_connection(allow_http=True)
|
|
249
|
+
connection, fingerprint = https_connection(allow_http=True)
|
|
237
250
|
|
|
238
251
|
if self.__fingerprints is not None:
|
|
239
252
|
self.__fingerprints.set(chost, fingerprint)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
253
|
+
connection.timeout = DEFAULT_TIMEOUT
|
|
254
|
+
sock = connection.sock
|
|
255
|
+
if sock:
|
|
256
|
+
sock.settimeout(DEFAULT_TIMEOUT)
|
|
257
|
+
return connection
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def encode_threshold(self):
|
|
261
|
+
if self.session:
|
|
262
|
+
return 1400 # common MTU
|
|
243
263
|
|
|
244
264
|
|
|
245
265
|
class ServerProxy(xmlrpc.client.ServerProxy):
|
|
@@ -274,12 +294,29 @@ class ServerProxy(xmlrpc.client.ServerProxy):
|
|
|
274
294
|
|
|
275
295
|
try:
|
|
276
296
|
try:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
297
|
+
for i in range(5):
|
|
298
|
+
try:
|
|
299
|
+
response = self.__transport.request(
|
|
300
|
+
self.__host,
|
|
301
|
+
self.__handler,
|
|
302
|
+
request,
|
|
303
|
+
verbose=self.__verbose
|
|
304
|
+
)
|
|
305
|
+
break
|
|
306
|
+
except xmlrpc.client.ProtocolError as e:
|
|
307
|
+
if e.errcode == HTTPStatus.SERVICE_UNAVAILABLE:
|
|
308
|
+
try:
|
|
309
|
+
delay = int(e.headers.get('Retry-After', i))
|
|
310
|
+
except ValueError:
|
|
311
|
+
if isinstance(
|
|
312
|
+
e.errcode, HTTPStatus.GATEWAY_TIMEOUT):
|
|
313
|
+
# Do not retry on timeout without delay
|
|
314
|
+
raise
|
|
315
|
+
delay = i
|
|
316
|
+
delay = min(delay, 10)
|
|
317
|
+
time.sleep(delay)
|
|
318
|
+
else:
|
|
319
|
+
raise
|
|
283
320
|
except (socket.error, http.client.HTTPException) as v:
|
|
284
321
|
if (isinstance(v, socket.error)
|
|
285
322
|
and v.args[0] == errno.EPIPE):
|
|
@@ -298,7 +335,8 @@ class ServerProxy(xmlrpc.client.ServerProxy):
|
|
|
298
335
|
self.__transport.close()
|
|
299
336
|
raise
|
|
300
337
|
if response['id'] != id_:
|
|
301
|
-
raise ResponseError(
|
|
338
|
+
raise ResponseError(
|
|
339
|
+
_("Invalid response id (%s) expected %s") %
|
|
302
340
|
(response['id'], id_))
|
|
303
341
|
if response.get('error'):
|
|
304
342
|
raise Fault(*response['error'])
|
|
@@ -392,7 +430,10 @@ class ServerPool(object):
|
|
|
392
430
|
class _Cache:
|
|
393
431
|
|
|
394
432
|
def __init__(self):
|
|
395
|
-
|
|
433
|
+
cache_size = CONFIG['rpc.cache_size']
|
|
434
|
+
self.store = CacheDict(
|
|
435
|
+
cache_len=cache_size,
|
|
436
|
+
default_factory=lambda: CacheDict(cache_len=cache_size))
|
|
396
437
|
|
|
397
438
|
def cached(self, prefix):
|
|
398
439
|
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)
|
|
@@ -662,7 +712,8 @@ class DateTime(Date):
|
|
|
662
712
|
and not isinstance(now, datetime.datetime)):
|
|
663
713
|
now = datetime.datetime.combine(now, datetime.time())
|
|
664
714
|
if not isinstance(now, datetime.datetime):
|
|
665
|
-
now = datetime.datetime.
|
|
715
|
+
now = datetime.datetime.now(
|
|
716
|
+
datetime.timezone.utc).replace(tzinfo=None)
|
|
666
717
|
return now + relativedelta(
|
|
667
718
|
year=dct['y'],
|
|
668
719
|
month=dct['M'],
|
tryton/rpc.py
CHANGED
|
@@ -145,6 +145,24 @@ def logout():
|
|
|
145
145
|
_USER = None
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
def reset_password():
|
|
149
|
+
from tryton import common
|
|
150
|
+
host = CONFIG['login.host']
|
|
151
|
+
hostname = common.get_hostname(host)
|
|
152
|
+
port = common.get_port(host)
|
|
153
|
+
database = CONFIG['login.db']
|
|
154
|
+
username = CONFIG['login.login']
|
|
155
|
+
language = CONFIG['client.lang']
|
|
156
|
+
if not all([host, database, username]):
|
|
157
|
+
return
|
|
158
|
+
try:
|
|
159
|
+
connection = ServerProxy(hostname, port, database)
|
|
160
|
+
logger.info('common.db.reset_password(%s, %s)', (username, language))
|
|
161
|
+
connection.common.db.reset_password(username, language)
|
|
162
|
+
except Fault as exception:
|
|
163
|
+
logger.debug(exception.faultCode)
|
|
164
|
+
|
|
165
|
+
|
|
148
166
|
def execute(*args):
|
|
149
167
|
global CONNECTION, _USER
|
|
150
168
|
if CONNECTION is None:
|
|
@@ -592,6 +592,8 @@ class DomainParserTestCase(TestCase):
|
|
|
592
592
|
})
|
|
593
593
|
valid = ('name', '=', 'Doe')
|
|
594
594
|
invalid = ('surname', '=', 'John')
|
|
595
|
+
self.assertTrue(dom.stringable([]))
|
|
596
|
+
self.assertTrue(dom.stringable([[]]))
|
|
595
597
|
self.assertTrue(dom.stringable([valid]))
|
|
596
598
|
self.assertFalse(dom.stringable([invalid]))
|
|
597
599
|
self.assertTrue(dom.stringable(['AND', valid]))
|
|
@@ -679,9 +681,15 @@ class DomainParserTestCase(TestCase):
|
|
|
679
681
|
dom.string([('name', 'not ilike', '%Doe%')]), 'Name: !Doe')
|
|
680
682
|
self.assertEqual(
|
|
681
683
|
dom.string([('name', 'in', ['John', 'Jane'])]), 'Name: John;Jane')
|
|
684
|
+
self.assertEqual(
|
|
685
|
+
dom.string([('name', 'in', ['John', ''])]), 'Name: John;""')
|
|
686
|
+
self.assertEqual(
|
|
687
|
+
dom.string([('name', 'in', ['John'])]), 'Name: =John')
|
|
682
688
|
self.assertEqual(
|
|
683
689
|
dom.string([('name', 'not in', ['John', 'Jane'])]),
|
|
684
690
|
'Name: !John;Jane')
|
|
691
|
+
self.assertEqual(
|
|
692
|
+
dom.string([('name', 'not in', ['John'])]), 'Name: !=John')
|
|
685
693
|
self.assertEqual(
|
|
686
694
|
dom.string([
|
|
687
695
|
('name', 'ilike', '%Doe%'),
|
|
@@ -954,14 +962,26 @@ class DomainParserTestCase(TestCase):
|
|
|
954
962
|
},
|
|
955
963
|
'relation': {
|
|
956
964
|
'string': "Relation",
|
|
957
|
-
'relation': 'relation',
|
|
958
965
|
'name': 'relation',
|
|
966
|
+
'type': 'many2one',
|
|
959
967
|
'relation_fields': {
|
|
960
968
|
'name': {
|
|
961
969
|
'string': "Name",
|
|
962
970
|
'name': 'name',
|
|
963
971
|
'type': 'char',
|
|
964
972
|
},
|
|
973
|
+
'm2o': {
|
|
974
|
+
'string': "M2O",
|
|
975
|
+
'name': 'm2o',
|
|
976
|
+
'type': 'many2one',
|
|
977
|
+
'relation_fields': {
|
|
978
|
+
'foo': {
|
|
979
|
+
'string': "Foo",
|
|
980
|
+
'name': 'foo',
|
|
981
|
+
'type': 'integer',
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
},
|
|
965
985
|
},
|
|
966
986
|
},
|
|
967
987
|
})
|
|
@@ -1086,7 +1106,7 @@ class DomainParserTestCase(TestCase):
|
|
|
1086
1106
|
])
|
|
1087
1107
|
self.assertEqual(
|
|
1088
1108
|
rlist(dom.parse_clause([('Many2One', None, 'John')])), [
|
|
1089
|
-
('many2one', 'ilike', '%John%'),
|
|
1109
|
+
('many2one.rec_name', 'ilike', '%John%'),
|
|
1090
1110
|
])
|
|
1091
1111
|
self.assertEqual(
|
|
1092
1112
|
rlist(dom.parse_clause([('Many2One', None, ['John', 'Jane'])])), [
|
|
@@ -1095,9 +1115,18 @@ class DomainParserTestCase(TestCase):
|
|
|
1095
1115
|
self.assertEqual(
|
|
1096
1116
|
rlist(dom.parse_clause(iter([iter([['John']])]))), [
|
|
1097
1117
|
[('rec_name', 'ilike', '%John%')]])
|
|
1118
|
+
self.assertEqual(
|
|
1119
|
+
rlist(dom.parse_clause(iter([['Relation', None, "Test"]]))),
|
|
1120
|
+
[('relation.rec_name', 'ilike', "%Test%")])
|
|
1098
1121
|
self.assertEqual(
|
|
1099
1122
|
rlist(dom.parse_clause(iter([['Relation.Name', None, "Test"]]))),
|
|
1100
1123
|
[('relation.name', 'ilike', "%Test%")])
|
|
1124
|
+
self.assertEqual(
|
|
1125
|
+
rlist(dom.parse_clause(iter([['Relation.M2O', None, "Foo"]]))),
|
|
1126
|
+
[('relation.m2o.rec_name', 'ilike', "%Foo%")])
|
|
1127
|
+
self.assertEqual(
|
|
1128
|
+
rlist(dom.parse_clause(iter([['Relation.M2O.Foo', None, '42']]))),
|
|
1129
|
+
[('relation.m2o.foo', '=', 42)])
|
|
1101
1130
|
self.assertEqual(
|
|
1102
1131
|
rlist(dom.parse_clause(iter([['OR']]))),
|
|
1103
1132
|
[('rec_name', 'ilike', "%OR%")])
|
tryton/translate.py
CHANGED
|
@@ -12,6 +12,7 @@ from gi.repository import Gtk
|
|
|
12
12
|
from tryton.config import CURRENT_DIR
|
|
13
13
|
|
|
14
14
|
_ = gettext.gettext
|
|
15
|
+
DATE = None
|
|
15
16
|
|
|
16
17
|
_LOCALE2WIN32 = {
|
|
17
18
|
'af_ZA': 'Afrikaans_South Africa',
|
|
@@ -196,8 +197,10 @@ def setlang(lang=None, locale_dict=None):
|
|
|
196
197
|
conv = locale.localeconv()
|
|
197
198
|
for field in list(locale_dict.keys()):
|
|
198
199
|
if field == 'date':
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
global DATE
|
|
201
|
+
DATE = locale_dict[field]
|
|
202
|
+
else:
|
|
203
|
+
conv[field] = locale_dict[field]
|
|
201
204
|
locale.localeconv = lambda: conv
|
|
202
205
|
|
|
203
206
|
|
|
@@ -12,21 +12,22 @@ except NameError:
|
|
|
12
12
|
|
|
13
13
|
if hasattr(sys, 'frozen'):
|
|
14
14
|
prefix = os.path.dirname(sys.executable)
|
|
15
|
+
share = os.path.join(prefix, 'share')
|
|
15
16
|
os.environ['GTK_EXE_PREFIX'] = prefix
|
|
16
17
|
os.environ['GTK_DATA_PREFIX'] = prefix
|
|
17
|
-
os.environ['EV_BACKENDS_DIR'] =
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
os.environ['EV_BACKENDS_DIR'] = os.path.join(
|
|
19
|
+
prefix, 'lib', 'evince', '4', 'backends')
|
|
20
|
+
os.environ['XDG_DATA_DIRS'] = share
|
|
20
21
|
os.environ['GDK_PIXBUF_MODULE_FILE'] = os.path.join(
|
|
21
|
-
|
|
22
|
+
share, 'gtk-3.0', 'gdk-pixbuf.loaders')
|
|
22
23
|
os.environ['GTK_IM_MODULE_FILE'] = os.path.join(
|
|
23
|
-
|
|
24
|
+
share, 'gtk-3.0', 'gtk.immodules')
|
|
24
25
|
os.environ['GI_TYPELIB_PATH'] = os.path.join(
|
|
25
26
|
prefix, 'lib', 'girepository-1.0')
|
|
26
27
|
os.environ.setdefault('SSL_CERT_FILE',
|
|
27
|
-
os.path.join(
|
|
28
|
+
os.path.join(share, 'ssl', 'cert.pem'))
|
|
28
29
|
os.environ.setdefault('SSL_CERT_DIR',
|
|
29
|
-
os.path.join(
|
|
30
|
+
os.path.join(share, 'ssl', 'certs'))
|
|
30
31
|
|
|
31
32
|
if sys.platform == 'win32':
|
|
32
33
|
# cx_freeze >= 5 put python modules under lib directory
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tryton
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.4.4
|
|
4
4
|
Summary: Tryton desktop client
|
|
5
5
|
Home-page: http://www.tryton.org/
|
|
6
|
-
Download-URL: http://downloads.tryton.org/7.
|
|
6
|
+
Download-URL: http://downloads.tryton.org/7.4/
|
|
7
7
|
Author: Tryton
|
|
8
8
|
Author-email: foundation@tryton.org
|
|
9
9
|
License: GPL-3
|
|
10
10
|
Project-URL: Bug Tracker, https://bugs.tryton.org/
|
|
11
|
-
Project-URL: Documentation, https://docs.tryton.org/
|
|
11
|
+
Project-URL: Documentation, https://docs.tryton.org/latest/client-desktop/
|
|
12
12
|
Project-URL: Forum, https://www.tryton.org/forum
|
|
13
13
|
Project-URL: Source Code, https://code.tryton.org/tryton
|
|
14
14
|
Keywords: business application ERP
|
|
@@ -52,11 +52,11 @@ Requires-Python: >=3.8
|
|
|
52
52
|
License-File: LICENSE
|
|
53
53
|
Requires-Dist: pycairo
|
|
54
54
|
Requires-Dist: python-dateutil
|
|
55
|
-
Requires-Dist: PyGObject
|
|
55
|
+
Requires-Dist: PyGObject>=3.19
|
|
56
56
|
Provides-Extra: calendar
|
|
57
|
-
Requires-Dist: GooCalendar
|
|
57
|
+
Requires-Dist: GooCalendar>=0.7; extra == "calendar"
|
|
58
58
|
Provides-Extra: sound
|
|
59
|
-
Requires-Dist: playsound
|
|
59
|
+
Requires-Dist: playsound; extra == "sound"
|
|
60
60
|
|
|
61
61
|
tryton
|
|
62
62
|
======
|