tryton 7.2.13__py3-none-any.whl → 7.4.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/common/common.py +12 -4
- tryton/common/domain_inversion.py +1 -2
- tryton/common/domain_parser.py +5 -4
- tryton/common/popup_menu.py +1 -1
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +27 -16
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +23 -17
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +26 -17
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +27 -21
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +22 -16
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +31 -19
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +27 -17
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +28 -18
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +22 -16
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +28 -22
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +29 -17
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +31 -21
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +30 -20
- 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 +29 -19
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +27 -17
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +26 -20
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +29 -18
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +28 -18
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +37 -26
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +29 -19
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +45 -35
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +23 -16
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +31 -19
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +32 -21
- tryton/gui/main.py +11 -10
- tryton/gui/window/about.py +1 -1
- tryton/gui/window/email_.py +1 -1
- tryton/gui/window/form.py +4 -1
- tryton/gui/window/tabcontent.py +2 -2
- tryton/gui/window/view_form/model/field.py +2 -0
- tryton/gui/window/view_form/model/group.py +4 -1
- tryton/gui/window/view_form/model/record.py +11 -26
- tryton/gui/window/view_form/screen/screen.py +17 -7
- tryton/gui/window/view_form/view/form.py +3 -5
- tryton/gui/window/view_form/view/form_gtk/dictionary.py +12 -5
- tryton/gui/window/view_form/view/form_gtk/document.py +6 -0
- tryton/gui/window/view_form/view/form_gtk/many2many.py +32 -0
- tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
- tryton/gui/window/view_form/view/form_gtk/one2many.py +17 -4
- tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
- tryton/gui/window/view_form/view/list.py +52 -24
- tryton/gui/window/view_form/view/list_gtk/widget.py +1 -1
- tryton/gui/window/view_form/view/screen_container.py +5 -3
- tryton/gui/window/win_csv.py +6 -12
- tryton/gui/window/win_export.py +49 -25
- tryton/gui/window/win_import.py +36 -11
- tryton/jsonrpc.py +28 -6
- tryton/pyson.py +2 -1
- tryton/tests/test_common_domain_parser.py +23 -2
- {tryton-7.2.13.dist-info → tryton-7.4.0.dist-info}/METADATA +2 -2
- {tryton-7.2.13.dist-info → tryton-7.4.0.dist-info}/RECORD +85 -85
- {tryton-7.2.13.dist-info → tryton-7.4.0.dist-info}/WHEEL +1 -1
- {tryton-7.2.13.data → tryton-7.4.0.data}/scripts/tryton +0 -0
- {tryton-7.2.13.dist-info → tryton-7.4.0.dist-info}/LICENSE +0 -0
- {tryton-7.2.13.dist-info → tryton-7.4.0.dist-info}/top_level.txt +0 -0
|
@@ -140,9 +140,9 @@ msgstr "并行操作异常"
|
|
|
140
140
|
msgid "Could not get a session."
|
|
141
141
|
msgstr "无法获取会话。"
|
|
142
142
|
|
|
143
|
-
#, python-format
|
|
143
|
+
#, fuzzy, python-format
|
|
144
144
|
msgid "Error: \"%s\". Try again later."
|
|
145
|
-
msgstr ""
|
|
145
|
+
msgstr "请求太多,请稍后重试."
|
|
146
146
|
|
|
147
147
|
msgid "Too many requests. Try again later."
|
|
148
148
|
msgstr "请求太多,请稍后重试."
|
|
@@ -240,8 +240,8 @@ msgstr "报告..."
|
|
|
240
240
|
msgid "Print..."
|
|
241
241
|
msgstr "打印..."
|
|
242
242
|
|
|
243
|
-
msgid "
|
|
244
|
-
msgstr "
|
|
243
|
+
msgid "Email..."
|
|
244
|
+
msgstr "电子邮件(_E)..."
|
|
245
245
|
|
|
246
246
|
msgid "Bold"
|
|
247
247
|
msgstr "粗体"
|
|
@@ -351,12 +351,6 @@ msgstr "帮助"
|
|
|
351
351
|
msgid "No result found."
|
|
352
352
|
msgstr "无符合条件的数据."
|
|
353
353
|
|
|
354
|
-
msgid "Favorites"
|
|
355
|
-
msgstr "收藏(_V)"
|
|
356
|
-
|
|
357
|
-
msgid "Manage..."
|
|
358
|
-
msgstr "管理..."
|
|
359
|
-
|
|
360
354
|
msgid "Action"
|
|
361
355
|
msgstr "操作"
|
|
362
356
|
|
|
@@ -587,6 +581,9 @@ msgstr "连接 Tryton 服务器"
|
|
|
587
581
|
msgid "Profile:"
|
|
588
582
|
msgstr "配置:"
|
|
589
583
|
|
|
584
|
+
msgid "Manage..."
|
|
585
|
+
msgstr "管理..."
|
|
586
|
+
|
|
590
587
|
msgid "Host / Database information"
|
|
591
588
|
msgstr "主机/数据库信息"
|
|
592
589
|
|
|
@@ -596,8 +593,8 @@ msgstr "用户名:"
|
|
|
596
593
|
msgid "Unable to complete email entry"
|
|
597
594
|
msgstr "无法完成电子邮件输入"
|
|
598
595
|
|
|
599
|
-
#, python-format
|
|
600
|
-
msgid "
|
|
596
|
+
#, fuzzy, python-format
|
|
597
|
+
msgid "Email %s"
|
|
601
598
|
msgstr "电子邮件 %s"
|
|
602
599
|
|
|
603
600
|
msgid "To:"
|
|
@@ -845,10 +842,12 @@ msgstr "报告(_R)..."
|
|
|
845
842
|
msgid "_Print..."
|
|
846
843
|
msgstr "打印(_P)..."
|
|
847
844
|
|
|
848
|
-
|
|
849
|
-
|
|
845
|
+
#, fuzzy
|
|
846
|
+
msgid "_Email..."
|
|
847
|
+
msgstr "电子邮件(_E)..."
|
|
850
848
|
|
|
851
|
-
|
|
849
|
+
#, fuzzy
|
|
850
|
+
msgid "Send an email using the record"
|
|
852
851
|
msgstr "使用当前记录发送电子邮件"
|
|
853
852
|
|
|
854
853
|
msgid "_Export Data..."
|
|
@@ -900,6 +899,9 @@ msgstr "字段名"
|
|
|
900
899
|
msgid "CSV Export: %s"
|
|
901
900
|
msgstr "CSV导出:%s"
|
|
902
901
|
|
|
902
|
+
msgid "Open"
|
|
903
|
+
msgstr "打开"
|
|
904
|
+
|
|
903
905
|
msgid "_Save Export"
|
|
904
906
|
msgstr "保存导出设置(_S)"
|
|
905
907
|
|
|
@@ -915,12 +917,6 @@ msgstr "<b>预定义导出设置</b>"
|
|
|
915
917
|
msgid "Name"
|
|
916
918
|
msgstr "名称"
|
|
917
919
|
|
|
918
|
-
msgid "Open"
|
|
919
|
-
msgstr "打开"
|
|
920
|
-
|
|
921
|
-
msgid "Save"
|
|
922
|
-
msgstr "保存"
|
|
923
|
-
|
|
924
920
|
msgid "Listed Records"
|
|
925
921
|
msgstr "已列出记录"
|
|
926
922
|
|
|
@@ -978,6 +974,9 @@ msgstr "保存和新建"
|
|
|
978
974
|
msgid "Add and New"
|
|
979
975
|
msgstr "添加和新建"
|
|
980
976
|
|
|
977
|
+
msgid "Save"
|
|
978
|
+
msgstr "保存"
|
|
979
|
+
|
|
981
980
|
msgid "Add"
|
|
982
981
|
msgstr "添加"
|
|
983
982
|
|
|
@@ -1003,6 +1002,10 @@ msgstr "已选记录取消删除 <Ins>"
|
|
|
1003
1002
|
msgid "CSV Import: %s"
|
|
1004
1003
|
msgstr "CSV导入: %s"
|
|
1005
1004
|
|
|
1005
|
+
#, fuzzy
|
|
1006
|
+
msgid "Import"
|
|
1007
|
+
msgstr "报告(_R)"
|
|
1008
|
+
|
|
1006
1009
|
msgid "_Auto-Detect"
|
|
1007
1010
|
msgstr "自动设置(_A)"
|
|
1008
1011
|
|
|
@@ -1206,6 +1209,10 @@ msgstr "添加已存在记录"
|
|
|
1206
1209
|
msgid "Remove selected record"
|
|
1207
1210
|
msgstr "删除所选记录"
|
|
1208
1211
|
|
|
1212
|
+
#, fuzzy
|
|
1213
|
+
msgid "Restore selected record"
|
|
1214
|
+
msgstr "删除所选记录"
|
|
1215
|
+
|
|
1209
1216
|
msgid "Open the record <F2>"
|
|
1210
1217
|
msgstr "打开记录 <F2>"
|
|
1211
1218
|
|
|
@@ -1221,6 +1228,10 @@ msgstr "编辑所选记录<F2>"
|
|
|
1221
1228
|
msgid "Delete selected record"
|
|
1222
1229
|
msgstr "删除所选记录"
|
|
1223
1230
|
|
|
1231
|
+
#, fuzzy
|
|
1232
|
+
msgid "Undelete selected record"
|
|
1233
|
+
msgstr "删除所选记录"
|
|
1234
|
+
|
|
1224
1235
|
#, python-format
|
|
1225
1236
|
msgid "%s%%"
|
|
1226
1237
|
msgstr "%s%%"
|
tryton/gui/main.py
CHANGED
|
@@ -463,10 +463,6 @@ class Main(Gtk.Application):
|
|
|
463
463
|
'id': id_,
|
|
464
464
|
})
|
|
465
465
|
|
|
466
|
-
def _manage_favorites(widget):
|
|
467
|
-
Window.create(self.menu_screen.model_name + '.favorite',
|
|
468
|
-
mode=['tree', 'form'],
|
|
469
|
-
name=_("Favorites"))
|
|
470
466
|
try:
|
|
471
467
|
favorites = RPCExecute('model',
|
|
472
468
|
self.menu_screen.model_name + '.favorite', 'get',
|
|
@@ -477,11 +473,6 @@ class Main(Gtk.Application):
|
|
|
477
473
|
menuitem = Gtk.MenuItem(label=name)
|
|
478
474
|
menuitem.connect('activate', _action_favorite, id_)
|
|
479
475
|
self.menu_favorite.add(menuitem)
|
|
480
|
-
self.menu_favorite.add(Gtk.SeparatorMenuItem())
|
|
481
|
-
manage_favorites = Gtk.MenuItem(label=_("Manage..."),
|
|
482
|
-
use_underline=True)
|
|
483
|
-
manage_favorites.connect('activate', _manage_favorites)
|
|
484
|
-
self.menu_favorite.add(manage_favorites)
|
|
485
476
|
self.menu_favorite.show_all()
|
|
486
477
|
|
|
487
478
|
def favorite_unset(self):
|
|
@@ -772,6 +763,9 @@ class Main(Gtk.Application):
|
|
|
772
763
|
expand=True, fill=True, padding=0)
|
|
773
764
|
treeview = screen.current_view.treeview
|
|
774
765
|
treeview.set_headers_visible(False)
|
|
766
|
+
for col in treeview.get_columns():
|
|
767
|
+
if col._type in {'selection', 'optional', 'drag'}:
|
|
768
|
+
col.set_visible(False)
|
|
775
769
|
|
|
776
770
|
# Favorite column
|
|
777
771
|
column = Gtk.TreeViewColumn()
|
|
@@ -864,7 +858,14 @@ class Main(Gtk.Application):
|
|
|
864
858
|
halign=Gtk.Align.START)
|
|
865
859
|
self.tooltips.set_tip(label, page.name)
|
|
866
860
|
self.tooltips.enable()
|
|
867
|
-
|
|
861
|
+
|
|
862
|
+
def remove_book_middle_click(widget, event):
|
|
863
|
+
if event.button == 2:
|
|
864
|
+
return self._sig_remove_book(widget, page.widget)
|
|
865
|
+
eventbox = Gtk.EventBox()
|
|
866
|
+
eventbox.connect('button-press-event', remove_book_middle_click)
|
|
867
|
+
eventbox.add(label)
|
|
868
|
+
hbox.pack_start(eventbox, expand=True, fill=True, padding=0)
|
|
868
869
|
|
|
869
870
|
button = Gtk.Button()
|
|
870
871
|
button.set_relief(Gtk.ReliefStyle.NONE)
|
tryton/gui/window/about.py
CHANGED
tryton/gui/window/email_.py
CHANGED
|
@@ -106,7 +106,7 @@ class Email(NoModal):
|
|
|
106
106
|
self.dialog.set_default_size(*self.default_size())
|
|
107
107
|
self.dialog.connect('response', self.response)
|
|
108
108
|
|
|
109
|
-
self.dialog.set_title(_('
|
|
109
|
+
self.dialog.set_title(_('Email %s') % name)
|
|
110
110
|
|
|
111
111
|
grid = Gtk.Grid(
|
|
112
112
|
column_spacing=3, row_spacing=3,
|
tryton/gui/window/form.py
CHANGED
|
@@ -579,6 +579,7 @@ class Form(TabContent):
|
|
|
579
579
|
name = str(position) if position else '_'
|
|
580
580
|
selected = len(self.screen.selected_records)
|
|
581
581
|
view_type = self.screen.current_view.view_type
|
|
582
|
+
next_view_type = self.screen.next_view_type
|
|
582
583
|
has_views = self.screen.number_of_views > 1
|
|
583
584
|
if selected > 1:
|
|
584
585
|
name += '#%i' % selected
|
|
@@ -594,7 +595,9 @@ class Form(TabContent):
|
|
|
594
595
|
for b in self.screen.get_buttons())
|
|
595
596
|
set_sensitive(button_id, bool(position) and can_be_sensitive)
|
|
596
597
|
set_sensitive(
|
|
597
|
-
'switch',
|
|
598
|
+
'switch',
|
|
599
|
+
(position or view_type == 'form' or next_view_type != 'form')
|
|
600
|
+
and has_views)
|
|
598
601
|
set_sensitive('remove', self.screen.deletable)
|
|
599
602
|
set_sensitive('previous', self.screen.has_prev())
|
|
600
603
|
set_sensitive('next', self.screen.has_next())
|
tryton/gui/window/tabcontent.py
CHANGED
|
@@ -141,8 +141,8 @@ class TabContent(InfoBar):
|
|
|
141
141
|
accel_path='<tryton>/Form/Print'),
|
|
142
142
|
ToolbarItem(
|
|
143
143
|
id='email',
|
|
144
|
-
label=_("
|
|
145
|
-
tooltip=_("Send an
|
|
144
|
+
label=_("_Email..."),
|
|
145
|
+
tooltip=_("Send an email using the record"),
|
|
146
146
|
icon_name='tryton-email',
|
|
147
147
|
accel_path='<tryton>/Form/Email'),
|
|
148
148
|
None,
|
|
@@ -621,10 +621,12 @@ class O2MField(Field):
|
|
|
621
621
|
from .group import Group
|
|
622
622
|
parent_name = self.attrs.get('relation_field', '')
|
|
623
623
|
fields = fields or {}
|
|
624
|
+
context = record.expr_eval(self.attrs.get('context', {}))
|
|
624
625
|
group = Group(self.attrs['relation'], fields,
|
|
625
626
|
parent=record,
|
|
626
627
|
parent_name=parent_name,
|
|
627
628
|
child_name=self.name,
|
|
629
|
+
context=context,
|
|
628
630
|
parent_datetime_field=self.attrs.get('datetime_field'))
|
|
629
631
|
if not fields and record.model_name == self.attrs['relation']:
|
|
630
632
|
group.fields = record.group.fields
|
|
@@ -128,6 +128,7 @@ class Group(list):
|
|
|
128
128
|
self._group_list_changed('record-removed', record, idx)
|
|
129
129
|
super(Group, self).remove(record)
|
|
130
130
|
del self.__id2record[record.id]
|
|
131
|
+
record.destroy()
|
|
131
132
|
|
|
132
133
|
def clear(self):
|
|
133
134
|
# Use reversed order to minimize the cursor reposition as the cursor
|
|
@@ -408,7 +409,9 @@ class Group(list):
|
|
|
408
409
|
if record not in self.record_deleted:
|
|
409
410
|
self.record_deleted.append(record)
|
|
410
411
|
record.modified_fields.setdefault('id')
|
|
411
|
-
if record.id < 0
|
|
412
|
+
if (record.id < 0
|
|
413
|
+
or (self.parent and self.parent.id < 0)
|
|
414
|
+
or force_remove):
|
|
412
415
|
self._remove(record)
|
|
413
416
|
|
|
414
417
|
if len(self):
|
|
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_x2m_sub_fields(f_attrs, prefix):
|
|
18
|
-
if f_attrs
|
|
18
|
+
if f_attrs['loading'] == 'eager' and f_attrs.get('views'):
|
|
19
19
|
sub_fields = functools.reduce(
|
|
20
20
|
operator.or_,
|
|
21
21
|
(v.get('fields', {}) for v in f_attrs['views'].values()),
|
|
@@ -177,10 +177,10 @@ class Record:
|
|
|
177
177
|
for record in id2record.values():
|
|
178
178
|
record.exception = True
|
|
179
179
|
if process_exception:
|
|
180
|
-
values = [
|
|
180
|
+
values = []
|
|
181
181
|
default_values = {f: None for f in fnames if f != 'id'}
|
|
182
|
-
for
|
|
183
|
-
|
|
182
|
+
for id in id2record:
|
|
183
|
+
values.append({'id': id, **default_values})
|
|
184
184
|
else:
|
|
185
185
|
raise
|
|
186
186
|
id2value = dict((value['id'], value) for value in values)
|
|
@@ -299,20 +299,13 @@ class Record:
|
|
|
299
299
|
return self.group.fields
|
|
300
300
|
|
|
301
301
|
def _check_load(self, fields=None):
|
|
302
|
-
if
|
|
303
|
-
|
|
304
|
-
self.reload(fields)
|
|
305
|
-
return True
|
|
306
|
-
return False
|
|
307
|
-
if not self.loaded:
|
|
308
|
-
self.reload()
|
|
309
|
-
return True
|
|
310
|
-
return False
|
|
302
|
+
if not self.get_loaded(fields):
|
|
303
|
+
self.reload(fields)
|
|
311
304
|
|
|
312
305
|
def get_loaded(self, fields=None):
|
|
313
|
-
if fields:
|
|
314
|
-
|
|
315
|
-
return set(
|
|
306
|
+
if fields is None:
|
|
307
|
+
fields = self.group.fields.keys()
|
|
308
|
+
return set(fields) <= (self._loaded | set(self.modified_fields))
|
|
316
309
|
|
|
317
310
|
loaded = property(get_loaded)
|
|
318
311
|
|
|
@@ -413,7 +406,6 @@ class Record:
|
|
|
413
406
|
return self.id
|
|
414
407
|
|
|
415
408
|
def default_get(self, defaults=None):
|
|
416
|
-
vals = {}
|
|
417
409
|
if len(self.group.fields):
|
|
418
410
|
context = self.get_context()
|
|
419
411
|
if defaults is not None:
|
|
@@ -423,7 +415,7 @@ class Record:
|
|
|
423
415
|
vals = RPCExecute('model', self.model_name, 'default_get',
|
|
424
416
|
list(self.group.fields.keys()), context=context)
|
|
425
417
|
except RPCException:
|
|
426
|
-
return
|
|
418
|
+
return
|
|
427
419
|
if (self.parent
|
|
428
420
|
and self.parent_name in self.group.fields):
|
|
429
421
|
parent_field = self.group.fields[self.parent_name]
|
|
@@ -444,10 +436,7 @@ class Record:
|
|
|
444
436
|
return ''
|
|
445
437
|
|
|
446
438
|
def validate(self, fields=None, softvalidation=False, pre_validate=None):
|
|
447
|
-
|
|
448
|
-
self._check_load(fields)
|
|
449
|
-
elif fields is None:
|
|
450
|
-
self._check_load()
|
|
439
|
+
self._check_load(fields)
|
|
451
440
|
res = True
|
|
452
441
|
for field_name, field in list(self.group.fields.items()):
|
|
453
442
|
if fields is not None and field_name not in fields:
|
|
@@ -617,7 +606,6 @@ class Record:
|
|
|
617
606
|
values.update(self._get_on_change_args(on_change))
|
|
618
607
|
|
|
619
608
|
if values:
|
|
620
|
-
values['id'] = self.id
|
|
621
609
|
try:
|
|
622
610
|
if len(fieldnames) == 1 or 'id' not in values:
|
|
623
611
|
changes = []
|
|
@@ -679,7 +667,6 @@ class Record:
|
|
|
679
667
|
'on_change_with_' + fieldname,
|
|
680
668
|
values, context=self.get_context()))
|
|
681
669
|
else:
|
|
682
|
-
values['id'] = self.id
|
|
683
670
|
changed = RPCExecute(
|
|
684
671
|
'model', self.model_name, 'on_change_with',
|
|
685
672
|
values, list(fieldnames), context=self.get_context())
|
|
@@ -702,7 +689,6 @@ class Record:
|
|
|
702
689
|
'on_change_with_' + fieldname,
|
|
703
690
|
values, context=self.get_context()))
|
|
704
691
|
else:
|
|
705
|
-
values['id'] = self.id
|
|
706
692
|
changed = RPCExecute(
|
|
707
693
|
'model', self.model_name, 'on_change_with',
|
|
708
694
|
values, list(later), context=self.get_context())
|
|
@@ -734,7 +720,6 @@ class Record:
|
|
|
734
720
|
def on_scan_code(self, code, depends):
|
|
735
721
|
depends = self.expr_eval(depends)
|
|
736
722
|
values = self._get_on_change_args(depends)
|
|
737
|
-
values['id'] = self.id
|
|
738
723
|
try:
|
|
739
724
|
changes = RPCExecute(
|
|
740
725
|
'model', self.model_name, 'on_scan_code', values, code,
|
|
@@ -75,8 +75,7 @@ class Screen:
|
|
|
75
75
|
self.__current_record = None
|
|
76
76
|
self.new_group(context or {})
|
|
77
77
|
self.current_record = None
|
|
78
|
-
self.screen_container = ScreenContainer(
|
|
79
|
-
self, attributes.get('tab_domain'))
|
|
78
|
+
self.screen_container = ScreenContainer(attributes.get('tab_domain'))
|
|
80
79
|
self.screen_container.alternate_view = attributes.get(
|
|
81
80
|
'alternate_view', False)
|
|
82
81
|
self.widget = self.screen_container.widget_get()
|
|
@@ -164,6 +163,7 @@ class Screen:
|
|
|
164
163
|
|
|
165
164
|
def search_active(self, active=True):
|
|
166
165
|
if active and not self.parent:
|
|
166
|
+
self.screen_container.set_screen(self)
|
|
167
167
|
self.screen_container.show_filter()
|
|
168
168
|
else:
|
|
169
169
|
self.screen_container.hide_filter()
|
|
@@ -323,6 +323,7 @@ class Screen:
|
|
|
323
323
|
if only_ids:
|
|
324
324
|
return ids
|
|
325
325
|
self.clear()
|
|
326
|
+
GLib.idle_add(self.screen_container.search_entry.grab_focus)
|
|
326
327
|
self.load(ids)
|
|
327
328
|
self.count_tab_domain()
|
|
328
329
|
return bool(ids)
|
|
@@ -541,6 +542,15 @@ class Screen:
|
|
|
541
542
|
def view_index(self):
|
|
542
543
|
return self.__current_view
|
|
543
544
|
|
|
545
|
+
@property
|
|
546
|
+
def next_view_type(self):
|
|
547
|
+
views = self.views + self.view_to_load
|
|
548
|
+
next_view_index = (self.view_index + 1) % len(views)
|
|
549
|
+
next_view = views[next_view_index]
|
|
550
|
+
if not isinstance(next_view, str):
|
|
551
|
+
next_view = next_view.view_type
|
|
552
|
+
return next_view
|
|
553
|
+
|
|
544
554
|
def switch_view(
|
|
545
555
|
self, view_type=None, view_id=None, creatable=None, display=True):
|
|
546
556
|
if view_id is not None:
|
|
@@ -792,6 +802,7 @@ class Screen:
|
|
|
792
802
|
records = records or self.selected_records
|
|
793
803
|
if not records:
|
|
794
804
|
return
|
|
805
|
+
current_record = self.current_record
|
|
795
806
|
if delete:
|
|
796
807
|
# Must delete children records before parent
|
|
797
808
|
records.sort(key=lambda r: r.depth, reverse=True)
|
|
@@ -805,9 +816,6 @@ class Screen:
|
|
|
805
816
|
record.group.remove(
|
|
806
817
|
record, remove=remove, modified=False,
|
|
807
818
|
force_remove=force_remove)
|
|
808
|
-
# set current_record to None to prevent __select_changed
|
|
809
|
-
# to set deleted record as current_record
|
|
810
|
-
self.current_record = None
|
|
811
819
|
# call only once
|
|
812
820
|
record.set_modified()
|
|
813
821
|
|
|
@@ -822,7 +830,10 @@ class Screen:
|
|
|
822
830
|
record.parent.save(force_reload=False)
|
|
823
831
|
record.destroy()
|
|
824
832
|
|
|
825
|
-
|
|
833
|
+
if current_record and not current_record.destroyed:
|
|
834
|
+
self.current_record = current_record
|
|
835
|
+
else:
|
|
836
|
+
self.current_record = None
|
|
826
837
|
self.set_cursor()
|
|
827
838
|
self.display()
|
|
828
839
|
return True
|
|
@@ -1249,7 +1260,6 @@ class Screen:
|
|
|
1249
1260
|
record = self.current_record
|
|
1250
1261
|
args = record.expr_eval(button.get('change', []))
|
|
1251
1262
|
values = record._get_on_change_args(args)
|
|
1252
|
-
values['id'] = record.id
|
|
1253
1263
|
try:
|
|
1254
1264
|
changes = RPCExecute('model', self.model_name, button['name'],
|
|
1255
1265
|
values, context=self.context)
|
|
@@ -230,8 +230,8 @@ class FormXMLViewParser(XMLViewParser):
|
|
|
230
230
|
self.container.add(None, attributes)
|
|
231
231
|
return
|
|
232
232
|
|
|
233
|
-
if
|
|
234
|
-
self.field_attrs[name]['
|
|
233
|
+
if attributes.get('loading') == 'eager':
|
|
234
|
+
self.field_attrs[name]['loading'] = 'eager'
|
|
235
235
|
|
|
236
236
|
widget = self.WIDGETS[attributes['widget']](self.view, attributes)
|
|
237
237
|
self.view.widgets[name].append(widget)
|
|
@@ -382,6 +382,7 @@ class FormXMLViewParser(XMLViewParser):
|
|
|
382
382
|
group = Container.constructor(
|
|
383
383
|
int(attributes.get('col', 4)),
|
|
384
384
|
attributes.get('homogeneous', False))
|
|
385
|
+
self.parse_child(node, group)
|
|
385
386
|
|
|
386
387
|
if 'name' in attributes and attributes['name'] == self.exclude_field:
|
|
387
388
|
self.container.add(None, attributes)
|
|
@@ -405,9 +406,6 @@ class FormXMLViewParser(XMLViewParser):
|
|
|
405
406
|
bool(attributes.get('yexpand'))))
|
|
406
407
|
self.view.state_widgets.append(widget)
|
|
407
408
|
self.container.add(widget, attributes)
|
|
408
|
-
# Parse the children at the end to preserve the order of the state
|
|
409
|
-
# widgets
|
|
410
|
-
self.parse_child(node, group)
|
|
411
409
|
|
|
412
410
|
def _parse_hpaned(self, node, attributes):
|
|
413
411
|
self._parse_paned(node, attributes, Gtk.HPaned)
|
|
@@ -224,17 +224,24 @@ class DictMultiSelectionEntry(DictEntry):
|
|
|
224
224
|
name = str(name)
|
|
225
225
|
model.append((value, name))
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
column = Gtk.TreeViewColumn()
|
|
228
|
+
select_cell = Gtk.CellRendererToggle()
|
|
229
|
+
select_cell.set_sensitive(False)
|
|
230
|
+
column.pack_start(select_cell, expand=False)
|
|
231
|
+
column.set_cell_data_func(select_cell, self._select_data_func)
|
|
228
232
|
name_cell = Gtk.CellRendererText()
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
self.tree.append_column(
|
|
233
|
+
column.pack_start(name_cell, expand=True)
|
|
234
|
+
column.add_attribute(name_cell, 'text', 1)
|
|
235
|
+
self.tree.append_column(column)
|
|
232
236
|
|
|
233
237
|
return widget
|
|
234
238
|
|
|
239
|
+
def _select_data_func(self, column, cell, model, iter_, selection):
|
|
240
|
+
cell.set_property('active', selection.iter_is_selected(iter_))
|
|
241
|
+
|
|
235
242
|
def get_value(self):
|
|
236
243
|
model, paths = self.tree.get_selection().get_selected_rows()
|
|
237
|
-
return [model[path][0] for path in paths]
|
|
244
|
+
return [model[path][0] for path in paths] or None
|
|
238
245
|
|
|
239
246
|
def set_value(self, value):
|
|
240
247
|
value2path = {v: idx for idx, (v, _) in enumerate(self.selection)}
|
|
@@ -1,5 +1,6 @@
|
|
|
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
|
+
import logging
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
from gi.repository import Gdk, GLib, Gtk
|
|
@@ -15,6 +16,8 @@ from tryton.common import data2pixbuf, resize_pixbuf
|
|
|
15
16
|
from .binary import BinaryMixin
|
|
16
17
|
from .widget import Widget
|
|
17
18
|
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
class Document(BinaryMixin, Widget):
|
|
20
23
|
expand = True
|
|
@@ -90,5 +93,8 @@ class Document(BinaryMixin, Widget):
|
|
|
90
93
|
model.set_document(document)
|
|
91
94
|
self.evince_view.set_model(model)
|
|
92
95
|
except GLib.GError:
|
|
96
|
+
logger.warning(
|
|
97
|
+
f"Could not open document {filename}",
|
|
98
|
+
exc_info=True)
|
|
93
99
|
self.evince_view.set_model(EvinceView.DocumentModel())
|
|
94
100
|
self.evince_scroll.hide()
|
|
@@ -82,6 +82,14 @@ class Many2Many(Widget):
|
|
|
82
82
|
self.but_remove.set_relief(Gtk.ReliefStyle.NONE)
|
|
83
83
|
hbox.pack_start(self.but_remove, expand=False, fill=False, padding=0)
|
|
84
84
|
|
|
85
|
+
self.but_unremove = Gtk.Button(can_focus=False)
|
|
86
|
+
tooltips.set_tip(self.but_unremove, _("Restore selected record"))
|
|
87
|
+
self.but_unremove.connect('clicked', self._sig_unremove)
|
|
88
|
+
self.but_unremove.add(common.IconFactory.get_image(
|
|
89
|
+
'tryton-undo', Gtk.IconSize.SMALL_TOOLBAR))
|
|
90
|
+
self.but_unremove.set_relief(Gtk.ReliefStyle.NONE)
|
|
91
|
+
hbox.pack_start(self.but_unremove, expand=False, fill=False, padding=0)
|
|
92
|
+
|
|
85
93
|
tooltips.enable()
|
|
86
94
|
|
|
87
95
|
frame = Gtk.Frame()
|
|
@@ -130,6 +138,9 @@ class Many2Many(Widget):
|
|
|
130
138
|
elif event.keyval in remove_keys and editable:
|
|
131
139
|
self._sig_remove()
|
|
132
140
|
return True
|
|
141
|
+
elif event.keyval == Gdk.KEY_Insert:
|
|
142
|
+
self._sig_unremove()
|
|
143
|
+
return True
|
|
133
144
|
elif widget == self.wid_text:
|
|
134
145
|
if event.keyval == Gdk.KEY_F3:
|
|
135
146
|
self._sig_new()
|
|
@@ -166,6 +177,9 @@ class Many2Many(Widget):
|
|
|
166
177
|
add_remove = self.record.expr_eval(self.attrs.get('add_remove'))
|
|
167
178
|
if add_remove:
|
|
168
179
|
domain = [domain, add_remove]
|
|
180
|
+
existing_ids = self.field.get_eval(self.record)
|
|
181
|
+
if existing_ids:
|
|
182
|
+
domain = [domain, ('id', 'not in', existing_ids)]
|
|
169
183
|
context = self.field.get_search_context(self.record)
|
|
170
184
|
order = self.field.get_search_order(self.record)
|
|
171
185
|
value = self.wid_text.get_text()
|
|
@@ -194,6 +208,9 @@ class Many2Many(Widget):
|
|
|
194
208
|
def _sig_remove(self, *args):
|
|
195
209
|
self.screen.remove(remove=True)
|
|
196
210
|
|
|
211
|
+
def _sig_unremove(self, *args):
|
|
212
|
+
self.screen.unremove()
|
|
213
|
+
|
|
197
214
|
def _on_activate(self):
|
|
198
215
|
self._sig_edit()
|
|
199
216
|
|
|
@@ -284,11 +301,23 @@ class Many2Many(Widget):
|
|
|
284
301
|
else:
|
|
285
302
|
size_limit = False
|
|
286
303
|
|
|
304
|
+
removable = any(
|
|
305
|
+
not r.deleted and not r.removed
|
|
306
|
+
for r in self.screen.selected_records)
|
|
307
|
+
unremovable = any(
|
|
308
|
+
r.deleted or r.removed for r in self.screen.selected_records)
|
|
309
|
+
|
|
287
310
|
self.but_add.set_sensitive(bool(
|
|
288
311
|
not self._readonly
|
|
289
312
|
and not size_limit))
|
|
290
313
|
self.but_remove.set_sensitive(bool(
|
|
291
314
|
not self._readonly
|
|
315
|
+
and removable
|
|
316
|
+
and self._position))
|
|
317
|
+
self.but_unremove.set_sensitive(bool(
|
|
318
|
+
not self._readonly
|
|
319
|
+
and not size_limit
|
|
320
|
+
and unremovable
|
|
292
321
|
and self._position))
|
|
293
322
|
|
|
294
323
|
def record_message(self, position, size, *args):
|
|
@@ -343,6 +372,9 @@ class Many2Many(Widget):
|
|
|
343
372
|
add_remove = self.record.expr_eval(self.attrs.get('add_remove'))
|
|
344
373
|
if add_remove:
|
|
345
374
|
domain = [domain, add_remove]
|
|
375
|
+
existing_ids = self.field.get_eval(self.record)
|
|
376
|
+
if existing_ids:
|
|
377
|
+
domain = [domain, ('id', 'not in', existing_ids)]
|
|
346
378
|
update_completion(
|
|
347
379
|
self.wid_text, self.record, self.field, model, domain)
|
|
348
380
|
|
|
@@ -33,15 +33,23 @@ class MultiSelection(Widget, SelectionMixin):
|
|
|
33
33
|
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
|
|
34
34
|
selection.connect('changed', self.changed)
|
|
35
35
|
self.widget.add(self.tree)
|
|
36
|
-
|
|
36
|
+
column = Gtk.TreeViewColumn()
|
|
37
|
+
select_cell = Gtk.CellRendererToggle()
|
|
38
|
+
select_cell.set_sensitive(False)
|
|
39
|
+
column.pack_start(select_cell, expand=False)
|
|
40
|
+
column.set_cell_data_func(
|
|
41
|
+
select_cell, self._select_data_func, selection)
|
|
37
42
|
name_cell = Gtk.CellRendererText()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
self.tree.append_column(
|
|
43
|
+
column.pack_start(name_cell, expand=True)
|
|
44
|
+
column.add_attribute(name_cell, 'text', 1)
|
|
45
|
+
self.tree.append_column(column)
|
|
41
46
|
|
|
42
47
|
self.nullable_widget = False
|
|
43
48
|
self.init_selection()
|
|
44
49
|
|
|
50
|
+
def _select_data_func(self, column, cell, model, iter_, selection):
|
|
51
|
+
cell.set_property('active', selection.iter_is_selected(iter_))
|
|
52
|
+
|
|
45
53
|
def _readonly_set(self, readonly):
|
|
46
54
|
super(MultiSelection, self)._readonly_set(readonly)
|
|
47
55
|
selection = self.tree.get_selection()
|
|
@@ -70,6 +78,8 @@ class MultiSelection(Widget, SelectionMixin):
|
|
|
70
78
|
self.field.set_client(self.record, self.get_value())
|
|
71
79
|
|
|
72
80
|
def display(self):
|
|
81
|
+
def freeze(iter):
|
|
82
|
+
return list(map(tuple, iter))
|
|
73
83
|
selection = self.tree.get_selection()
|
|
74
84
|
selection.handler_block_by_func(self.changed)
|
|
75
85
|
try:
|
|
@@ -77,7 +87,7 @@ class MultiSelection(Widget, SelectionMixin):
|
|
|
77
87
|
# it will be set back in the super call
|
|
78
88
|
selection.set_select_function(lambda *a: True)
|
|
79
89
|
self.update_selection(self.record, self.field)
|
|
80
|
-
new_model = self.selection !=
|
|
90
|
+
new_model = freeze(self.selection) != freeze(self.model)
|
|
81
91
|
if new_model:
|
|
82
92
|
self.model.clear()
|
|
83
93
|
if not self.field:
|