tryton 6.6.8__py3-none-any.whl → 6.8.1__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/action/main.py +32 -43
- tryton/bus.py +2 -0
- tryton/client.py +3 -0
- tryton/common/button.py +3 -1
- tryton/common/common.py +55 -43
- tryton/common/datetime_.py +14 -2
- tryton/common/domain_inversion.py +10 -10
- tryton/common/domain_parser.py +5 -2
- tryton/common/popup_menu.py +7 -0
- tryton/common/selection.py +3 -1
- tryton/config.py +22 -5
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +45 -39
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +41 -35
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +46 -39
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +41 -35
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +41 -35
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +171 -167
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +47 -39
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -38
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +38 -32
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +42 -36
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +44 -34
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +40 -34
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +44 -34
- 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 +46 -38
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +47 -37
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +41 -35
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +45 -35
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +46 -38
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +50 -48
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +45 -39
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +47 -38
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +39 -33
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +43 -35
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +45 -35
- tryton/data/pixmaps/tryton/tryton-icon.svg +1 -0
- tryton/gui/main.py +54 -61
- tryton/gui/window/dblogin.py +27 -10
- tryton/gui/window/form.py +21 -53
- tryton/gui/window/infobar.py +9 -4
- tryton/gui/window/log.py +95 -0
- tryton/gui/window/view_board/action.py +0 -4
- tryton/gui/window/view_form/model/field.py +36 -14
- tryton/gui/window/view_form/model/record.py +22 -9
- tryton/gui/window/view_form/screen/screen.py +45 -76
- tryton/gui/window/view_form/view/calendar_.py +24 -11
- tryton/gui/window/view_form/view/calendar_gtk/toolbar.py +6 -5
- tryton/gui/window/view_form/view/form.py +14 -5
- tryton/gui/window/view_form/view/form_gtk/many2many.py +10 -1
- tryton/gui/window/view_form/view/form_gtk/many2one.py +1 -0
- tryton/gui/window/view_form/view/form_gtk/one2many.py +7 -7
- tryton/gui/window/view_form/view/form_gtk/textbox.py +0 -2
- tryton/gui/window/view_form/view/form_gtk/widget.py +8 -10
- tryton/gui/window/view_form/view/list_form.py +61 -5
- tryton/gui/window/view_form/view/list_gtk/editabletree.py +13 -3
- tryton/gui/window/view_form/view/list_gtk/widget.py +97 -27
- tryton/gui/window/win_form.py +6 -5
- tryton/rpc.py +13 -15
- tryton/tests/test_common.py +46 -0
- tryton/tests/test_common_domain_parser.py +24 -24
- {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/METADATA +6 -6
- {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/RECORD +92 -89
- {tryton-6.6.8.data → tryton-6.8.1.data}/scripts/tryton +0 -0
- {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/LICENSE +0 -0
- {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/WHEEL +0 -0
- {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/top_level.txt +0 -0
tryton/__init__.py
CHANGED
tryton/action/main.py
CHANGED
|
@@ -6,7 +6,6 @@ import webbrowser
|
|
|
6
6
|
import tryton.rpc as rpc
|
|
7
7
|
from tryton.common import (
|
|
8
8
|
RPCException, RPCExecute, file_open, file_write, message, selection)
|
|
9
|
-
from tryton.config import CONFIG
|
|
10
9
|
from tryton.pyson import PYSONDecoder
|
|
11
10
|
|
|
12
11
|
_ = gettext.gettext
|
|
@@ -91,14 +90,15 @@ class Action(object):
|
|
|
91
90
|
return name
|
|
92
91
|
|
|
93
92
|
data['action_id'] = action['id']
|
|
93
|
+
params = {
|
|
94
|
+
'icon': action.get('icon.rec_name') or '',
|
|
95
|
+
}
|
|
94
96
|
if action['type'] == 'ir.action.act_window':
|
|
95
|
-
view_ids = []
|
|
96
|
-
view_mode = None
|
|
97
97
|
if action.get('views', []):
|
|
98
|
-
view_ids = [x[0] for x in action['views']]
|
|
99
|
-
view_mode = [x[1] for x in action['views']]
|
|
98
|
+
params['view_ids'] = [x[0] for x in action['views']]
|
|
99
|
+
params['view_mode'] = [x[1] for x in action['views']]
|
|
100
100
|
elif action.get('view_id', False):
|
|
101
|
-
view_ids = [action['view_id'][0]]
|
|
101
|
+
params['view_ids'] = [action['view_id'][0]]
|
|
102
102
|
|
|
103
103
|
action.setdefault('pyson_domain', '[]')
|
|
104
104
|
ctx = {
|
|
@@ -109,58 +109,47 @@ class Action(object):
|
|
|
109
109
|
ctx.update(rpc.CONTEXT)
|
|
110
110
|
ctx['_user'] = rpc._USER
|
|
111
111
|
decoder = PYSONDecoder(ctx)
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
params['context'] = context.copy()
|
|
113
|
+
params['context'].update(
|
|
114
114
|
decoder.decode(action.get('pyson_context') or '{}'))
|
|
115
|
-
ctx.update(
|
|
115
|
+
ctx.update(params['context'])
|
|
116
116
|
|
|
117
117
|
ctx['context'] = ctx
|
|
118
118
|
decoder = PYSONDecoder(ctx)
|
|
119
|
-
domain = decoder.decode(action['pyson_domain'])
|
|
120
|
-
order = decoder.decode(action['pyson_order'])
|
|
121
|
-
search_value = decoder.decode(
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
params['domain'] = decoder.decode(action['pyson_domain'])
|
|
120
|
+
params['order'] = decoder.decode(action['pyson_order'])
|
|
121
|
+
params['search_value'] = decoder.decode(
|
|
122
|
+
action['pyson_search_value'] or '[]')
|
|
123
|
+
params['tab_domain'] = [
|
|
124
|
+
(n, decoder.decode(d), c) for n, d, c in action['domains']]
|
|
124
125
|
|
|
125
126
|
name = action.get('name', '')
|
|
126
127
|
if action.get('keyword', ''):
|
|
127
|
-
name = add_name_suffix(name,
|
|
128
|
+
name = add_name_suffix(name, params['context'])
|
|
129
|
+
params['name'] = name
|
|
128
130
|
|
|
129
131
|
res_model = action.get('res_model', data.get('res_model'))
|
|
130
|
-
res_id = action.get('res_id', data.get('res_id'))
|
|
132
|
+
params['res_id'] = action.get('res_id', data.get('res_id'))
|
|
133
|
+
params['context_model'] = action.get('context_model')
|
|
134
|
+
params['context_domain'] = action.get('context_domain')
|
|
131
135
|
limit = action.get('limit')
|
|
132
|
-
if limit is None:
|
|
133
|
-
limit =
|
|
134
|
-
|
|
135
|
-
Window.create(res_model,
|
|
136
|
-
view_ids=view_ids,
|
|
137
|
-
res_id=res_id,
|
|
138
|
-
domain=domain,
|
|
139
|
-
context=action_ctx,
|
|
140
|
-
order=order,
|
|
141
|
-
mode=view_mode,
|
|
142
|
-
name=name,
|
|
143
|
-
limit=limit,
|
|
144
|
-
search_value=search_value,
|
|
145
|
-
icon=(action.get('icon.rec_name') or ''),
|
|
146
|
-
tab_domain=tab_domain,
|
|
147
|
-
context_model=action['context_model'],
|
|
148
|
-
context_domain=action['context_domain'])
|
|
136
|
+
if limit is not None:
|
|
137
|
+
params['limit'] = limit
|
|
138
|
+
|
|
139
|
+
Window.create(res_model, **params)
|
|
149
140
|
elif action['type'] == 'ir.action.wizard':
|
|
141
|
+
params['context'] = context
|
|
142
|
+
params['window'] = action.get('window')
|
|
150
143
|
name = action.get('name', '')
|
|
151
144
|
if action.get('keyword', 'form_action') == 'form_action':
|
|
152
145
|
name = add_name_suffix(name, context)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
name=name,
|
|
156
|
-
context=context, icon=(action.get('icon.rec_name') or ''),
|
|
157
|
-
window=action.get('window', False))
|
|
158
|
-
|
|
146
|
+
params['name'] = name
|
|
147
|
+
Window.create_wizard(action['wiz_name'], data, **params)
|
|
159
148
|
elif action['type'] == 'ir.action.report':
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
149
|
+
params['direct_print'] = action.get('direct_print', False)
|
|
150
|
+
params['context'] = context
|
|
151
|
+
del params['icon']
|
|
152
|
+
Action.exec_report(action['report_name'], data, **params)
|
|
164
153
|
|
|
165
154
|
elif action['type'] == 'ir.action.url':
|
|
166
155
|
if action['url']:
|
tryton/bus.py
CHANGED
tryton/client.py
CHANGED
tryton/common/button.py
CHANGED
|
@@ -37,7 +37,9 @@ class Button(Gtk.Button):
|
|
|
37
37
|
self.hide()
|
|
38
38
|
else:
|
|
39
39
|
self.show()
|
|
40
|
-
self.set_sensitive(
|
|
40
|
+
self.set_sensitive(
|
|
41
|
+
not (record.readonly if record else False)
|
|
42
|
+
and not states.get('readonly', False))
|
|
41
43
|
self._set_icon(states.get('icon', self.attrs.get('icon')))
|
|
42
44
|
|
|
43
45
|
if self.attrs.get('rule'):
|
tryton/common/common.py
CHANGED
|
@@ -23,7 +23,6 @@ try:
|
|
|
23
23
|
except ImportError:
|
|
24
24
|
from http import client as HTTPStatus
|
|
25
25
|
|
|
26
|
-
import _thread
|
|
27
26
|
import shlex
|
|
28
27
|
import socket
|
|
29
28
|
import sys
|
|
@@ -34,6 +33,7 @@ import urllib.request
|
|
|
34
33
|
import webbrowser
|
|
35
34
|
from functools import lru_cache, wraps
|
|
36
35
|
from string import Template
|
|
36
|
+
from threading import Lock, Thread
|
|
37
37
|
|
|
38
38
|
import tryton.rpc as rpc
|
|
39
39
|
from tryton.config import CONFIG, PIXMAPS_DIR, TRYTON_ICON
|
|
@@ -43,9 +43,8 @@ try:
|
|
|
43
43
|
except ImportError:
|
|
44
44
|
ssl = None
|
|
45
45
|
import zipfile
|
|
46
|
-
from threading import Lock
|
|
47
46
|
|
|
48
|
-
from gi.repository import Gdk, GdkPixbuf, GLib, GObject, Gtk
|
|
47
|
+
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk
|
|
49
48
|
|
|
50
49
|
from tryton import __version__
|
|
51
50
|
from tryton.exceptions import TrytonError, TrytonServerError
|
|
@@ -104,6 +103,8 @@ class IconFactory:
|
|
|
104
103
|
try:
|
|
105
104
|
icon_ref = (cls._name2id[iconname], iconname)
|
|
106
105
|
except KeyError:
|
|
106
|
+
logger.error(f"Unknown icon {iconname}")
|
|
107
|
+
cls._icons[iconname] = None
|
|
107
108
|
return
|
|
108
109
|
idx = cls._tryton_icons.index(icon_ref)
|
|
109
110
|
to_load = slice(max(0, idx - cls.batchnum // 2),
|
|
@@ -128,13 +129,14 @@ class IconFactory:
|
|
|
128
129
|
colors = CONFIG['icon.colors'].split(',')
|
|
129
130
|
cls.register_icon(iconname)
|
|
130
131
|
if iconname not in cls._pixbufs[(size, badge)]:
|
|
132
|
+
data = None
|
|
131
133
|
if iconname in cls._icons:
|
|
132
134
|
data = cls._icons[iconname]
|
|
133
135
|
elif iconname in cls._local_icons:
|
|
134
136
|
path = cls._local_icons[iconname]
|
|
135
137
|
with open(path, 'rb') as fp:
|
|
136
138
|
data = fp.read()
|
|
137
|
-
|
|
139
|
+
if not data:
|
|
138
140
|
logger.error("Unknown icon %s" % iconname)
|
|
139
141
|
return
|
|
140
142
|
if not color:
|
|
@@ -439,16 +441,8 @@ def file_selection(title, filename='',
|
|
|
439
441
|
action=Gtk.FileChooserAction.OPEN, preview=True, multi=False,
|
|
440
442
|
filters=None):
|
|
441
443
|
parent = get_toplevel_window()
|
|
442
|
-
|
|
443
|
-
buttons = (set_underline(_("Cancel")), Gtk.ResponseType.CANCEL,
|
|
444
|
-
set_underline(_("Select")), Gtk.ResponseType.OK)
|
|
445
|
-
else:
|
|
446
|
-
buttons = (set_underline(_("Cancel")), Gtk.ResponseType.CANCEL,
|
|
447
|
-
set_underline(_("Save")), Gtk.ResponseType.OK)
|
|
448
|
-
win = Gtk.FileChooserDialog(
|
|
444
|
+
win = Gtk.FileChooserNative(
|
|
449
445
|
title=title, transient_for=parent, action=action)
|
|
450
|
-
win.add_buttons(*buttons)
|
|
451
|
-
win.set_icon(TRYTON_ICON)
|
|
452
446
|
if filename:
|
|
453
447
|
if action in (Gtk.FileChooserAction.SAVE,
|
|
454
448
|
Gtk.FileChooserAction.CREATE_FOLDER):
|
|
@@ -459,7 +453,6 @@ def file_selection(title, filename='',
|
|
|
459
453
|
if hasattr(win, 'set_do_overwrite_confirmation'):
|
|
460
454
|
win.set_do_overwrite_confirmation(True)
|
|
461
455
|
win.set_select_multiple(multi)
|
|
462
|
-
win.set_default_response(Gtk.ResponseType.OK)
|
|
463
456
|
if filters is not None:
|
|
464
457
|
for filt in filters:
|
|
465
458
|
win.add_filter(filt)
|
|
@@ -484,7 +477,7 @@ def file_selection(title, filename='',
|
|
|
484
477
|
win.connect('update-preview', update_preview_cb, img_preview)
|
|
485
478
|
|
|
486
479
|
button = win.run()
|
|
487
|
-
if button != Gtk.ResponseType.
|
|
480
|
+
if button != Gtk.ResponseType.ACCEPT:
|
|
488
481
|
result = None
|
|
489
482
|
elif not multi:
|
|
490
483
|
result = PurePath(win.get_filename())
|
|
@@ -546,10 +539,8 @@ def file_open(filename, type=None, print_p=False):
|
|
|
546
539
|
file_open(zfilename, type=ztype, print_p=True)
|
|
547
540
|
return
|
|
548
541
|
|
|
549
|
-
if os
|
|
550
|
-
operation = 'open'
|
|
551
|
-
if print_p:
|
|
552
|
-
operation = 'print'
|
|
542
|
+
if hasattr(os, 'startfile'):
|
|
543
|
+
operation = 'print' if print_p else 'open'
|
|
553
544
|
try:
|
|
554
545
|
os.startfile(os.path.normpath(filename), operation)
|
|
555
546
|
except WindowsError:
|
|
@@ -574,12 +565,20 @@ def file_open(filename, type=None, print_p=False):
|
|
|
574
565
|
except OSError:
|
|
575
566
|
save()
|
|
576
567
|
else:
|
|
568
|
+
uri = GLib.filename_to_uri(filename)
|
|
577
569
|
try:
|
|
578
|
-
|
|
579
|
-
except
|
|
570
|
+
Gio.AppInfo.launch_default_for_uri(uri)
|
|
571
|
+
except GLib.Error:
|
|
580
572
|
save()
|
|
581
573
|
|
|
582
574
|
|
|
575
|
+
def webbrowser_open(url):
|
|
576
|
+
try:
|
|
577
|
+
Gio.AppInfo.launch_default_for_uri(url)
|
|
578
|
+
except GLib.Error:
|
|
579
|
+
webbrowser.open(url)
|
|
580
|
+
|
|
581
|
+
|
|
583
582
|
def url_open(uri):
|
|
584
583
|
try:
|
|
585
584
|
return urllib.request.urlopen(uri)
|
|
@@ -640,7 +639,7 @@ def mailto(to=None, cc=None, subject=None, body=None, attachment=None):
|
|
|
640
639
|
url += "&body=" + urllib.parse.quote(body, "")
|
|
641
640
|
if attachment:
|
|
642
641
|
url += "&attachment=" + urllib.parse.quote(attachment, "")
|
|
643
|
-
|
|
642
|
+
webbrowser_open(url, new=1)
|
|
644
643
|
|
|
645
644
|
|
|
646
645
|
class UniqueDialog(object):
|
|
@@ -882,7 +881,7 @@ class ErrorDialog(UniqueDialog):
|
|
|
882
881
|
CONFIG['bug.url'], _("Report Bug"))
|
|
883
882
|
button_roundup.get_child().set_halign(Gtk.Align.START)
|
|
884
883
|
button_roundup.connect('activate-link',
|
|
885
|
-
lambda widget:
|
|
884
|
+
lambda widget: webbrowser_open(CONFIG['bug.url'], new=2))
|
|
886
885
|
dialog.vbox.pack_start(
|
|
887
886
|
button_roundup, expand=False, fill=False, padding=0)
|
|
888
887
|
|
|
@@ -892,8 +891,7 @@ class ErrorDialog(UniqueDialog):
|
|
|
892
891
|
if isinstance(title, Exception):
|
|
893
892
|
title = "%s: %s" % (title.__class__.__name__, title)
|
|
894
893
|
details += '\n' + title
|
|
895
|
-
|
|
896
|
-
log.error(details)
|
|
894
|
+
logger.error(details)
|
|
897
895
|
return super(ErrorDialog, self).__call__(title, details)
|
|
898
896
|
|
|
899
897
|
|
|
@@ -903,7 +901,7 @@ error = ErrorDialog()
|
|
|
903
901
|
def check_version(box, version=__version__):
|
|
904
902
|
def info_bar_response(info_bar, response, box, url):
|
|
905
903
|
if response == Gtk.ResponseType.ACCEPT:
|
|
906
|
-
|
|
904
|
+
webbrowser_open(url)
|
|
907
905
|
box.remove(info_bar)
|
|
908
906
|
|
|
909
907
|
class HeadRequest(urllib.request.Request):
|
|
@@ -955,7 +953,7 @@ def open_documentation():
|
|
|
955
953
|
version = 'latest'
|
|
956
954
|
else:
|
|
957
955
|
version = '.'.join(version)
|
|
958
|
-
|
|
956
|
+
webbrowser_open(CONFIG['doc.url'] % {
|
|
959
957
|
'lang': CONFIG['client.lang'],
|
|
960
958
|
'version': version,
|
|
961
959
|
})
|
|
@@ -1049,7 +1047,7 @@ def get_credentials(user_id=None):
|
|
|
1049
1047
|
query['renew'] = user_id
|
|
1050
1048
|
url_parts[4] = urlencode(query)
|
|
1051
1049
|
url = urlunparse(url_parts)
|
|
1052
|
-
|
|
1050
|
+
webbrowser_open(url)
|
|
1053
1051
|
|
|
1054
1052
|
class RequestHandler(BaseHTTPRequestHandler):
|
|
1055
1053
|
def do_GET(self):
|
|
@@ -1213,7 +1211,7 @@ class RPCProgress(object):
|
|
|
1213
1211
|
else:
|
|
1214
1212
|
if not self.res:
|
|
1215
1213
|
self.error = True
|
|
1216
|
-
if self.callback:
|
|
1214
|
+
if self.callback and CONFIG['thread']:
|
|
1217
1215
|
# Post to GTK queue to be run by the main thread
|
|
1218
1216
|
GLib.idle_add(self.process)
|
|
1219
1217
|
return True
|
|
@@ -1222,12 +1220,12 @@ class RPCProgress(object):
|
|
|
1222
1220
|
self.process_exception_p = process_exception_p
|
|
1223
1221
|
self.callback = callback
|
|
1224
1222
|
|
|
1225
|
-
if callback:
|
|
1223
|
+
if callback and CONFIG['thread']:
|
|
1226
1224
|
# Parent is only useful if it is asynchronous
|
|
1227
1225
|
# otherwise the cursor is not updated.
|
|
1228
1226
|
self.parent = get_toplevel_window()
|
|
1229
1227
|
self._cursor_timeout = GLib.timeout_add(3000, self._set_cursor)
|
|
1230
|
-
|
|
1228
|
+
Thread(target=self.start).start()
|
|
1231
1229
|
return
|
|
1232
1230
|
else:
|
|
1233
1231
|
self.start()
|
|
@@ -1365,14 +1363,29 @@ def untimezoned_date(date):
|
|
|
1365
1363
|
|
|
1366
1364
|
|
|
1367
1365
|
def humanize(size, suffix=''):
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
if
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1366
|
+
if 0 < abs(size) < 1:
|
|
1367
|
+
for u in ['', 'm', 'µ', 'n', 'p', 'f', 'a', 'z', 'y', 'r', 'q']:
|
|
1368
|
+
if abs(size) >= 0.01:
|
|
1369
|
+
break
|
|
1370
|
+
size *= 1000.0
|
|
1371
|
+
else:
|
|
1372
|
+
size /= 1000.0
|
|
1373
|
+
else:
|
|
1374
|
+
for u in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q']:
|
|
1375
|
+
if abs(size) <= 1000:
|
|
1376
|
+
break
|
|
1377
|
+
size /= 1000.0
|
|
1378
|
+
else:
|
|
1379
|
+
size *= 1000.0
|
|
1380
|
+
if isinstance(size, int) or size.is_integer():
|
|
1381
|
+
size = locale.localize(str(int(size)))
|
|
1382
|
+
elif abs(size) < 0.01:
|
|
1383
|
+
size = locale.localize(
|
|
1384
|
+
'{0:f}'.format(size).rstrip('0').rstrip('.'))
|
|
1385
|
+
else:
|
|
1386
|
+
size = locale.localize(
|
|
1387
|
+
'{0:.{1}f}'.format(size, 2).rstrip('0').rstrip('.'))
|
|
1388
|
+
return ''.join([size, u, suffix])
|
|
1376
1389
|
|
|
1377
1390
|
|
|
1378
1391
|
def get_hostname(netloc):
|
|
@@ -1445,13 +1458,12 @@ def ellipsize(string, length):
|
|
|
1445
1458
|
def get_align(float_, expand=True):
|
|
1446
1459
|
"Convert float align into Gtk.Align"
|
|
1447
1460
|
value = float(float_)
|
|
1461
|
+
if expand:
|
|
1462
|
+
return Gtk.Align.FILL
|
|
1448
1463
|
if value < 0.5:
|
|
1449
1464
|
return Gtk.Align.START
|
|
1450
1465
|
elif value == 0.5:
|
|
1451
|
-
|
|
1452
|
-
return Gtk.Align.FILL
|
|
1453
|
-
else:
|
|
1454
|
-
return Gtk.Align.CENTER
|
|
1466
|
+
return Gtk.Align.CENTER
|
|
1455
1467
|
else:
|
|
1456
1468
|
return Gtk.Align.END
|
|
1457
1469
|
|
tryton/common/datetime_.py
CHANGED
|
@@ -129,8 +129,9 @@ class Date(Gtk.Entry):
|
|
|
129
129
|
self.__date.month - 1, self.__date.year)
|
|
130
130
|
self.__calendar.select_day(self.__date.day)
|
|
131
131
|
self.__cal_popup.set_transient_for(self.get_toplevel())
|
|
132
|
-
|
|
132
|
+
# Show popup before because position needs the popup allocation
|
|
133
133
|
popup_show(self.__cal_popup)
|
|
134
|
+
popup_position(self, self.__cal_popup)
|
|
134
135
|
|
|
135
136
|
def cal_popup_changed(self, calendar):
|
|
136
137
|
year, month, day = self.__calendar.get_date()
|
|
@@ -532,8 +533,19 @@ GObject.type_register(DateTime)
|
|
|
532
533
|
|
|
533
534
|
def popup_position(widget, popup):
|
|
534
535
|
allocation = widget.get_allocation()
|
|
536
|
+
popup_allocation = popup.get_allocation()
|
|
535
537
|
x, y = widget.get_window().get_root_coords(allocation.x, allocation.y)
|
|
536
|
-
|
|
538
|
+
display = widget.get_display()
|
|
539
|
+
monitor = display.get_monitor_at_window(widget.get_window())
|
|
540
|
+
monitor_geometry = monitor.get_geometry()
|
|
541
|
+
if (monitor_geometry.height
|
|
542
|
+
< y + allocation.height + popup_allocation.height):
|
|
543
|
+
y -= popup_allocation.height
|
|
544
|
+
else:
|
|
545
|
+
y += allocation.height
|
|
546
|
+
if monitor_geometry.width < x + popup_allocation.width:
|
|
547
|
+
x -= popup_allocation.width
|
|
548
|
+
popup.move(x, y)
|
|
537
549
|
|
|
538
550
|
|
|
539
551
|
def popup_show(popup):
|
|
@@ -381,16 +381,16 @@ def unique_value(domain):
|
|
|
381
381
|
"Return if unique, the field and the value"
|
|
382
382
|
if (isinstance(domain, list)
|
|
383
383
|
and len(domain) == 1):
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
384
|
+
name, operator, value, *model = domain[0]
|
|
385
|
+
if operator == '=' or (operator == 'in' and len(value) == 1):
|
|
386
|
+
value = value if operator == '=' else value[0]
|
|
387
|
+
count = 0
|
|
388
|
+
if model and name.endswith('.id'):
|
|
389
|
+
count = 1
|
|
390
|
+
model = model[0]
|
|
391
|
+
value = [model, value]
|
|
392
|
+
if name.count('.') == count:
|
|
393
|
+
return True, name, value
|
|
394
394
|
return False, None, None
|
|
395
395
|
|
|
396
396
|
|
tryton/common/domain_parser.py
CHANGED
|
@@ -484,7 +484,10 @@ def parenthesize(tokens):
|
|
|
484
484
|
|
|
485
485
|
def operatorize(tokens, operator='or'):
|
|
486
486
|
"Convert operators"
|
|
487
|
-
test =
|
|
487
|
+
test = {
|
|
488
|
+
'or': ('|', ('|',)),
|
|
489
|
+
'and': ('&', ('&',)),
|
|
490
|
+
}[operator]
|
|
488
491
|
try:
|
|
489
492
|
cur = next(tokens)
|
|
490
493
|
while cur in test:
|
|
@@ -656,7 +659,7 @@ class DomainParser(object):
|
|
|
656
659
|
if not domain:
|
|
657
660
|
return ''
|
|
658
661
|
if domain[0] in ('AND', 'OR'):
|
|
659
|
-
nary = ' ' if domain[0] == 'AND' else '
|
|
662
|
+
nary = ' ' if domain[0] == 'AND' else ' | '
|
|
660
663
|
domain = domain[1:]
|
|
661
664
|
else:
|
|
662
665
|
nary = ' '
|
tryton/common/popup_menu.py
CHANGED
|
@@ -10,6 +10,7 @@ from tryton.common.common import selection
|
|
|
10
10
|
from tryton.gui.window import Window
|
|
11
11
|
from tryton.gui.window.attachment import Attachment
|
|
12
12
|
from tryton.gui.window.email_ import Email
|
|
13
|
+
from tryton.gui.window.log import Log
|
|
13
14
|
from tryton.gui.window.note import Note
|
|
14
15
|
from tryton.gui.window.view_form.screen import Screen
|
|
15
16
|
|
|
@@ -56,6 +57,9 @@ def populate(menu, model, record, title='', field=None, context=None):
|
|
|
56
57
|
with Window(hide_current=True, allow_similar=allow_similar):
|
|
57
58
|
Action.execute(action, data, context=rec.get_context())
|
|
58
59
|
|
|
60
|
+
def log(menuitem):
|
|
61
|
+
Log(load(record))
|
|
62
|
+
|
|
59
63
|
def attachment(menuitem):
|
|
60
64
|
Attachment(load(record), None)
|
|
61
65
|
|
|
@@ -103,6 +107,9 @@ def populate(menu, model, record, title='', field=None, context=None):
|
|
|
103
107
|
edit_item.connect('activate', edit)
|
|
104
108
|
action_menu.append(edit_item)
|
|
105
109
|
action_menu.append(Gtk.SeparatorMenuItem())
|
|
110
|
+
log_item = Gtk.MenuItem(label=_("View Logs..."))
|
|
111
|
+
action_menu.append(log_item)
|
|
112
|
+
log_item.connect('activate', log)
|
|
106
113
|
attachment_item = Gtk.MenuItem(label=_('Attachments...'))
|
|
107
114
|
action_menu.append(attachment_item)
|
|
108
115
|
attachment_item.connect('activate', attachment)
|
tryton/common/selection.py
CHANGED
|
@@ -48,6 +48,8 @@ class SelectionMixin(object):
|
|
|
48
48
|
def update_selection(self, record, field):
|
|
49
49
|
if not field:
|
|
50
50
|
return
|
|
51
|
+
if not self.selection:
|
|
52
|
+
self.init_selection()
|
|
51
53
|
|
|
52
54
|
domain = field.domain_get(record)
|
|
53
55
|
if 'relation' not in self.attrs:
|
|
@@ -71,7 +73,7 @@ class SelectionMixin(object):
|
|
|
71
73
|
try:
|
|
72
74
|
result = RPCExecute('model', self.attrs['relation'],
|
|
73
75
|
'search_read', domain, 0, None, None, fields,
|
|
74
|
-
context=context)
|
|
76
|
+
context=context, process_exception=False)
|
|
75
77
|
except RPCException:
|
|
76
78
|
result = False
|
|
77
79
|
if isinstance(result, list):
|
tryton/config.py
CHANGED
|
@@ -6,12 +6,15 @@ import locale
|
|
|
6
6
|
import logging
|
|
7
7
|
import optparse
|
|
8
8
|
import os
|
|
9
|
+
import shutil
|
|
9
10
|
import sys
|
|
11
|
+
import tempfile
|
|
10
12
|
|
|
11
13
|
from gi.repository import GdkPixbuf
|
|
12
14
|
|
|
13
15
|
from tryton import __version__
|
|
14
16
|
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
15
18
|
_ = gettext.gettext
|
|
16
19
|
|
|
17
20
|
|
|
@@ -68,7 +71,7 @@ class ConfigManager(object):
|
|
|
68
71
|
'bug.url': 'https://bugs.tryton.org/',
|
|
69
72
|
'download.url': 'https://downloads.tryton.org/',
|
|
70
73
|
'download.frequency': 60 * 60 * 8,
|
|
71
|
-
'menu.pane':
|
|
74
|
+
'menu.pane': 320,
|
|
72
75
|
}
|
|
73
76
|
self.config = {}
|
|
74
77
|
self.options = {}
|
|
@@ -94,6 +97,9 @@ class ConfigManager(object):
|
|
|
94
97
|
help=_("specify the login user"))
|
|
95
98
|
parser.add_option("-s", "--server", dest="host",
|
|
96
99
|
help=_("specify the server hostname:port"))
|
|
100
|
+
parser.add_option(
|
|
101
|
+
'--no-thread', default=True, action='store_false', dest='thread',
|
|
102
|
+
help=_("disable thread usage"))
|
|
97
103
|
opt, self.arguments = parser.parse_args()
|
|
98
104
|
self.rcfile = opt.config or os.path.join(
|
|
99
105
|
get_config_dir(), 'tryton.conf')
|
|
@@ -122,6 +128,7 @@ class ConfigManager(object):
|
|
|
122
128
|
for arg in ['login', 'host']:
|
|
123
129
|
if getattr(opt, arg):
|
|
124
130
|
self.options['login.' + arg] = getattr(opt, arg)
|
|
131
|
+
self.options['thread'] = opt.thread
|
|
125
132
|
|
|
126
133
|
def save(self):
|
|
127
134
|
try:
|
|
@@ -136,15 +143,25 @@ class ConfigManager(object):
|
|
|
136
143
|
with open(self.rcfile, 'w') as fp:
|
|
137
144
|
parser.write(fp)
|
|
138
145
|
except IOError:
|
|
139
|
-
|
|
140
|
-
_('Unable to write config file %s.')
|
|
141
|
-
% (self.rcfile,))
|
|
146
|
+
logger.warn("Unable to write config file %s", self.rcfile)
|
|
142
147
|
return False
|
|
143
148
|
return True
|
|
144
149
|
|
|
145
150
|
def load(self):
|
|
146
151
|
parser = configparser.ConfigParser()
|
|
147
|
-
|
|
152
|
+
try:
|
|
153
|
+
parser.read([self.rcfile])
|
|
154
|
+
except configparser.Error:
|
|
155
|
+
config_dir = os.path.dirname(self.rcfile)
|
|
156
|
+
with tempfile.NamedTemporaryFile(
|
|
157
|
+
delete=False, prefix='tryton_', suffix='.conf',
|
|
158
|
+
dir=config_dir) as temp_file:
|
|
159
|
+
temp_name = temp_file.name
|
|
160
|
+
shutil.copy(self.rcfile, temp_name)
|
|
161
|
+
logger.error(
|
|
162
|
+
f"Failed to parse {self.rcfile}. "
|
|
163
|
+
f"A backup can be found at {temp_name}", exc_info=True)
|
|
164
|
+
return
|
|
148
165
|
for section in parser.sections():
|
|
149
166
|
for (name, value) in parser.items(section):
|
|
150
167
|
if value.lower() == 'true':
|
|
Binary file
|