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.

Files changed (105) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +149 -73
  4. tryton/common/completion.py +2 -2
  5. tryton/common/datetime_.py +3 -1
  6. tryton/common/domain_inversion.py +2 -1
  7. tryton/common/domain_parser.py +22 -11
  8. tryton/common/popup_menu.py +1 -1
  9. tryton/common/selection.py +6 -3
  10. tryton/common/tempfile.py +34 -0
  11. tryton/config.py +4 -5
  12. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  13. tryton/data/locale/bg/LC_MESSAGES/tryton.po +69 -20
  14. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  15. tryton/data/locale/ca/LC_MESSAGES/tryton.po +70 -25
  16. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  17. tryton/data/locale/cs/LC_MESSAGES/tryton.po +68 -21
  18. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  19. tryton/data/locale/de/LC_MESSAGES/tryton.po +71 -26
  20. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  21. tryton/data/locale/es/LC_MESSAGES/tryton.po +68 -23
  22. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  23. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +72 -22
  24. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  25. tryton/data/locale/et/LC_MESSAGES/tryton.po +73 -23
  26. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  27. tryton/data/locale/fa/LC_MESSAGES/tryton.po +74 -25
  28. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  29. tryton/data/locale/fi/LC_MESSAGES/tryton.po +63 -20
  30. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  31. tryton/data/locale/fr/LC_MESSAGES/tryton.po +73 -28
  32. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/hu/LC_MESSAGES/tryton.po +75 -23
  34. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/id/LC_MESSAGES/tryton.po +72 -23
  36. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/it/LC_MESSAGES/tryton.po +73 -24
  38. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lo/LC_MESSAGES/tryton.po +74 -25
  41. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  42. tryton/data/locale/lt/LC_MESSAGES/tryton.po +73 -23
  43. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  44. tryton/data/locale/nl/LC_MESSAGES/tryton.po +72 -27
  45. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  46. tryton/data/locale/pl/LC_MESSAGES/tryton.po +113 -78
  47. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  48. tryton/data/locale/pt/LC_MESSAGES/tryton.po +73 -24
  49. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  50. tryton/data/locale/ro/LC_MESSAGES/tryton.po +87 -36
  51. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  52. tryton/data/locale/ru/LC_MESSAGES/tryton.po +72 -25
  53. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  54. tryton/data/locale/sl/LC_MESSAGES/tryton.po +77 -26
  55. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  56. tryton/data/locale/tr/LC_MESSAGES/tryton.po +64 -20
  57. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  58. tryton/data/locale/uk/LC_MESSAGES/tryton.po +75 -23
  59. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  60. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +76 -24
  61. tryton/device_cookie.py +1 -1
  62. tryton/gui/main.py +14 -12
  63. tryton/gui/window/about.py +1 -1
  64. tryton/gui/window/dblogin.py +2 -2
  65. tryton/gui/window/email_.py +2 -2
  66. tryton/gui/window/form.py +10 -5
  67. tryton/gui/window/log.py +24 -2
  68. tryton/gui/window/tabcontent.py +2 -2
  69. tryton/gui/window/view_form/model/field.py +84 -34
  70. tryton/gui/window/view_form/model/group.py +7 -2
  71. tryton/gui/window/view_form/model/record.py +70 -31
  72. tryton/gui/window/view_form/screen/screen.py +98 -47
  73. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
  74. tryton/gui/window/view_form/view/form.py +6 -12
  75. tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
  76. tryton/gui/window/view_form/view/form_gtk/dictionary.py +49 -29
  77. tryton/gui/window/view_form/view/form_gtk/document.py +15 -10
  78. tryton/gui/window/view_form/view/form_gtk/many2many.py +49 -7
  79. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  80. tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
  81. tryton/gui/window/view_form/view/form_gtk/one2many.py +42 -10
  82. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  83. tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
  84. tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
  85. tryton/gui/window/view_form/view/list.py +116 -48
  86. tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
  87. tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
  88. tryton/gui/window/view_form/view/screen_container.py +3 -5
  89. tryton/gui/window/win_csv.py +6 -12
  90. tryton/gui/window/win_export.py +49 -26
  91. tryton/gui/window/win_form.py +9 -7
  92. tryton/gui/window/win_import.py +45 -15
  93. tryton/gui/window/wizard.py +13 -10
  94. tryton/jsonrpc.py +75 -34
  95. tryton/plugins/__init__.py +5 -3
  96. tryton/pyson.py +57 -6
  97. tryton/rpc.py +18 -0
  98. tryton/tests/test_common_domain_parser.py +31 -2
  99. tryton/translate.py +5 -2
  100. {tryton-7.0.7.data → tryton-7.4.4.data}/scripts/tryton +8 -7
  101. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/METADATA +6 -6
  102. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/RECORD +105 -103
  103. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/WHEEL +1 -1
  104. {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
  105. {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
- self._connection = host, http.client.HTTPConnection(chost,
198
- timeout=CONNECT_TIMEOUT)
199
- self._connection[1].connect()
200
- sock = self._connection[1].sock
201
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
202
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
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
- self._connection = host, http.client.HTTPSConnection(chost,
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
- self._connection[1].connect()
209
- sock = self._connection[1].sock
210
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
211
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
212
- try:
213
- peercert = sock.getpeercert(True)
214
- except socket.error:
215
- peercert = None
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(hashlib.sha1(peercert).hexdigest())
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
- self._connection[1].timeout = DEFAULT_TIMEOUT
241
- self._connection[1].sock.settimeout(DEFAULT_TIMEOUT)
242
- return self._connection[1]
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
- response = self.__transport.request(
278
- self.__host,
279
- self.__handler,
280
- request,
281
- verbose=self.__verbose
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('Invalid response id (%s) excpected %s' %
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
- self.store = defaultdict(dict)
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
@@ -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
- importlib.machinery.SourceFileLoader,
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
- klass = self.__class__.__name__
85
- return '%s(%s)' % (klass, ', '.join(map(repr, self.__repr_params__)))
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
- return self._value, self._default
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
- return (self._obj, self._key, self._default)
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.utcnow()
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
- continue
200
- conv[field] = locale_dict[field]
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'] = prefix
18
- os.environ['XDG_DATA_DIRS'] = os.path.join(prefix, 'share')
19
- etc = os.path.join(prefix, 'etc')
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
- etc, 'gtk-3.0', 'gdk-pixbuf.loaders')
22
+ share, 'gtk-3.0', 'gdk-pixbuf.loaders')
22
23
  os.environ['GTK_IM_MODULE_FILE'] = os.path.join(
23
- etc, 'gtk-3.0', 'gtk.immodules')
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(etc, 'ssl', 'cert.pem'))
28
+ os.path.join(share, 'ssl', 'cert.pem'))
28
29
  os.environ.setdefault('SSL_CERT_DIR',
29
- os.path.join(etc, 'ssl', 'certs'))
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.0.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.0/
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 >=3.19
55
+ Requires-Dist: PyGObject>=3.19
56
56
  Provides-Extra: calendar
57
- Requires-Dist: GooCalendar >=0.7 ; extra == 'calendar'
57
+ Requires-Dist: GooCalendar>=0.7; extra == "calendar"
58
58
  Provides-Extra: sound
59
- Requires-Dist: playsound ; extra == 'sound'
59
+ Requires-Dist: playsound; extra == "sound"
60
60
 
61
61
  tryton
62
62
  ======