tryton 7.0.6__py3-none-any.whl → 7.2.13__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 +137 -69
- tryton/common/completion.py +2 -2
- tryton/common/datetime_.py +3 -1
- tryton/common/domain_inversion.py +10 -7
- tryton/common/domain_parser.py +17 -7
- 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 +42 -4
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +47 -8
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +42 -4
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +45 -6
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +43 -5
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +41 -4
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +43 -4
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +45 -6
- 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 +45 -6
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +46 -7
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +84 -60
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +45 -6
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +57 -17
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +43 -6
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +46 -5
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +41 -4
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +46 -6
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +46 -5
- tryton/device_cookie.py +1 -1
- tryton/gui/main.py +3 -2
- tryton/gui/window/about.py +1 -1
- tryton/gui/window/dblogin.py +2 -2
- tryton/gui/window/email_.py +1 -1
- tryton/gui/window/form.py +6 -4
- tryton/gui/window/log.py +24 -2
- tryton/gui/window/view_form/model/field.py +84 -34
- tryton/gui/window/view_form/model/group.py +3 -1
- tryton/gui/window/view_form/model/record.py +64 -15
- tryton/gui/window/view_form/screen/screen.py +83 -46
- 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 +37 -24
- tryton/gui/window/view_form/view/form_gtk/document.py +9 -10
- tryton/gui/window/view_form/view/form_gtk/many2many.py +17 -7
- tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
- tryton/gui/window/view_form/view/form_gtk/one2many.py +25 -6
- tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
- tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
- tryton/gui/window/view_form/view/list.py +68 -35
- 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_export.py +1 -2
- tryton/gui/window/win_form.py +9 -7
- tryton/gui/window/win_import.py +9 -4
- tryton/gui/window/wizard.py +13 -10
- tryton/jsonrpc.py +46 -28
- tryton/plugins/__init__.py +5 -3
- tryton/pyson.py +55 -5
- tryton/rpc.py +18 -0
- tryton/tests/test_common_domain_parser.py +8 -0
- tryton/translate.py +5 -2
- {tryton-7.0.6.data → tryton-7.2.13.data}/scripts/tryton +8 -7
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/METADATA +6 -6
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/RECORD +100 -98
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/WHEEL +1 -1
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/LICENSE +0 -0
- {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/top_level.txt +0 -0
|
@@ -48,7 +48,6 @@ class Many2Many(Widget):
|
|
|
48
48
|
self.wid_text.set_placeholder_text(_('Search'))
|
|
49
49
|
self.wid_text.set_property('width_chars', 13)
|
|
50
50
|
self.wid_text.connect('focus-out-event', self._focus_out)
|
|
51
|
-
self.focus_out = True
|
|
52
51
|
hbox.pack_start(self.wid_text, expand=True, fill=True, padding=0)
|
|
53
52
|
|
|
54
53
|
if int(self.attrs.get('completion', 1)):
|
|
@@ -113,6 +112,8 @@ class Many2Many(Widget):
|
|
|
113
112
|
self.screen.widget.connect('key_press_event', self.on_keypress)
|
|
114
113
|
self.wid_text.connect('key_press_event', self.on_keypress)
|
|
115
114
|
|
|
115
|
+
self._popup = False
|
|
116
|
+
|
|
116
117
|
def on_keypress(self, widget, event):
|
|
117
118
|
editable = self.wid_text.get_editable()
|
|
118
119
|
activate_keys = [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]
|
|
@@ -161,8 +162,6 @@ class Many2Many(Widget):
|
|
|
161
162
|
return int(self.attrs.get('create', 1)) and self.get_access('create')
|
|
162
163
|
|
|
163
164
|
def _sig_add(self, *args):
|
|
164
|
-
if not self.focus_out:
|
|
165
|
-
return
|
|
166
165
|
domain = self.field.domain_get(self.record)
|
|
167
166
|
add_remove = self.record.expr_eval(self.attrs.get('add_remove'))
|
|
168
167
|
if add_remove:
|
|
@@ -171,15 +170,18 @@ class Many2Many(Widget):
|
|
|
171
170
|
order = self.field.get_search_order(self.record)
|
|
172
171
|
value = self.wid_text.get_text()
|
|
173
172
|
|
|
174
|
-
self.
|
|
173
|
+
if self._popup:
|
|
174
|
+
return
|
|
175
|
+
else:
|
|
176
|
+
self._popup = True
|
|
175
177
|
|
|
176
178
|
def callback(result):
|
|
177
|
-
self.focus_out = True
|
|
178
179
|
if result:
|
|
179
180
|
ids = [x[0] for x in result]
|
|
180
181
|
self.screen.load(ids, modified=True)
|
|
181
182
|
self.screen.set_cursor()
|
|
182
183
|
self.wid_text.set_text('')
|
|
184
|
+
self._popup = False
|
|
183
185
|
win = WinSearch(self.attrs['relation'], callback, sel_multi=True,
|
|
184
186
|
context=context, domain=domain, order=order,
|
|
185
187
|
view_ids=self.attrs.get('view_ids', '').split(','),
|
|
@@ -215,6 +217,10 @@ class Many2Many(Widget):
|
|
|
215
217
|
def _sig_edit(self):
|
|
216
218
|
if not self.screen.current_record:
|
|
217
219
|
return
|
|
220
|
+
if self._popup:
|
|
221
|
+
return
|
|
222
|
+
else:
|
|
223
|
+
self._popup = True
|
|
218
224
|
# Create a new screen that is not linked to the parent otherwise on the
|
|
219
225
|
# save of the record will trigger the save of the parent
|
|
220
226
|
screen = self._get_screen_form()
|
|
@@ -231,22 +237,26 @@ class Many2Many(Widget):
|
|
|
231
237
|
self.screen.current_record.modified_fields.setdefault('id')
|
|
232
238
|
# Force a display to clear the CellCache
|
|
233
239
|
self.screen.display()
|
|
240
|
+
self._popup = False
|
|
234
241
|
WinForm(screen, callback)
|
|
235
242
|
|
|
236
243
|
def _sig_new(self, defaults=None):
|
|
244
|
+
if self._popup:
|
|
245
|
+
return
|
|
246
|
+
else:
|
|
247
|
+
self._popup = True
|
|
237
248
|
screen = self._get_screen_form()
|
|
238
249
|
defaults = defaults.copy() if defaults is not None else {}
|
|
239
250
|
defaults['rec_name'] = self.wid_text.get_text()
|
|
240
251
|
|
|
241
252
|
def callback(result):
|
|
242
|
-
self.focus_out = True
|
|
243
253
|
if result:
|
|
244
254
|
record = screen.current_record
|
|
245
255
|
self.screen.load([record.id], modified=True)
|
|
246
256
|
self.wid_text.set_text('')
|
|
247
257
|
self.wid_text.grab_focus()
|
|
258
|
+
self._popup = False
|
|
248
259
|
|
|
249
|
-
self.focus_out = False
|
|
250
260
|
WinForm(
|
|
251
261
|
screen, callback, new=True, save_current=True, defaults=defaults)
|
|
252
262
|
|
|
@@ -37,7 +37,7 @@ class Many2One(Widget):
|
|
|
37
37
|
lambda x, y: self._focus_out())
|
|
38
38
|
self.wid_text.connect('changed', self.sig_changed)
|
|
39
39
|
self.changed = True
|
|
40
|
-
self.
|
|
40
|
+
self._popup = False
|
|
41
41
|
|
|
42
42
|
if int(self.attrs.get('completion', 1)):
|
|
43
43
|
self.wid_text.connect('changed', self._update_completion)
|
|
@@ -103,13 +103,16 @@ class Many2One(Widget):
|
|
|
103
103
|
model = self.get_model()
|
|
104
104
|
if not model or not common.MODELACCESS[model]['read']:
|
|
105
105
|
return
|
|
106
|
-
if not self.
|
|
106
|
+
if not self.field:
|
|
107
107
|
return
|
|
108
108
|
self.changed = False
|
|
109
109
|
value = self.field.get(self.record)
|
|
110
110
|
model = self.get_model()
|
|
111
111
|
|
|
112
|
-
self.
|
|
112
|
+
if self._popup:
|
|
113
|
+
return
|
|
114
|
+
else:
|
|
115
|
+
self._popup = True
|
|
113
116
|
if model and not self.has_target(value):
|
|
114
117
|
if (not self._readonly
|
|
115
118
|
and (self.wid_text.get_text()
|
|
@@ -126,7 +129,7 @@ class Many2One(Widget):
|
|
|
126
129
|
self.value_from_id(*result[0]), force_change=True)
|
|
127
130
|
else:
|
|
128
131
|
self.wid_text.set_text('')
|
|
129
|
-
self.
|
|
132
|
+
self._popup = False
|
|
130
133
|
self.changed = True
|
|
131
134
|
|
|
132
135
|
win = WinSearch(model, callback, sel_multi=False,
|
|
@@ -139,7 +142,7 @@ class Many2One(Widget):
|
|
|
139
142
|
win.screen.search_filter(quote(text))
|
|
140
143
|
win.show()
|
|
141
144
|
return
|
|
142
|
-
self.
|
|
145
|
+
self._popup = False
|
|
143
146
|
self.changed = True
|
|
144
147
|
return
|
|
145
148
|
|
|
@@ -164,7 +167,10 @@ class Many2One(Widget):
|
|
|
164
167
|
def sig_new(self, defaults=None):
|
|
165
168
|
if not self.create_access:
|
|
166
169
|
return
|
|
167
|
-
self.
|
|
170
|
+
if self._popup:
|
|
171
|
+
return
|
|
172
|
+
else:
|
|
173
|
+
self._popup = True
|
|
168
174
|
screen = self.get_screen(search=True)
|
|
169
175
|
defaults = defaults.copy() if defaults is not None else {}
|
|
170
176
|
defaults['rec_name'] = self.wid_text.get_text()
|
|
@@ -174,7 +180,7 @@ class Many2One(Widget):
|
|
|
174
180
|
self.field.set_client(self.record,
|
|
175
181
|
self.value_from_id(screen.current_record.id,
|
|
176
182
|
screen.current_record.rec_name()))
|
|
177
|
-
self.
|
|
183
|
+
self._popup = False
|
|
178
184
|
WinForm(
|
|
179
185
|
screen, callback, new=True, save_current=True, defaults=defaults)
|
|
180
186
|
|
|
@@ -184,10 +190,9 @@ class Many2One(Widget):
|
|
|
184
190
|
model = self.get_model()
|
|
185
191
|
if not model or not common.MODELACCESS[model]['read']:
|
|
186
192
|
return
|
|
187
|
-
if not self.
|
|
193
|
+
if not self.field:
|
|
188
194
|
return
|
|
189
195
|
self.changed = False
|
|
190
|
-
self.focus_out = False
|
|
191
196
|
value = self.field.get(self.record)
|
|
192
197
|
|
|
193
198
|
if (icon_pos == Gtk.EntryIconPosition.SECONDARY
|
|
@@ -196,9 +201,12 @@ class Many2One(Widget):
|
|
|
196
201
|
self.field.set_client(self.record, self.value_from_id(None, ''))
|
|
197
202
|
self.wid_text.set_text('')
|
|
198
203
|
self.changed = True
|
|
199
|
-
self.focus_out = True
|
|
200
204
|
return
|
|
201
205
|
|
|
206
|
+
if self._popup:
|
|
207
|
+
return
|
|
208
|
+
else:
|
|
209
|
+
self._popup = True
|
|
202
210
|
if self.has_target(value):
|
|
203
211
|
m2o_id = self.id_from_value(self.field.get(self.record))
|
|
204
212
|
screen = self.get_screen()
|
|
@@ -211,7 +219,7 @@ class Many2One(Widget):
|
|
|
211
219
|
self.value_from_id(screen.current_record.id,
|
|
212
220
|
screen.current_record.rec_name()),
|
|
213
221
|
force_change=True)
|
|
214
|
-
self.
|
|
222
|
+
self._popup = False
|
|
215
223
|
self.changed = True
|
|
216
224
|
WinForm(screen, callback, save_current=True)
|
|
217
225
|
return
|
|
@@ -225,7 +233,7 @@ class Many2One(Widget):
|
|
|
225
233
|
if result:
|
|
226
234
|
self.field.set_client(self.record,
|
|
227
235
|
self.value_from_id(*result[0]), force_change=True)
|
|
228
|
-
self.
|
|
236
|
+
self._popup = False
|
|
229
237
|
self.changed = True
|
|
230
238
|
win = WinSearch(model, callback, sel_multi=False,
|
|
231
239
|
context=context, domain=domain, order=order,
|
|
@@ -236,7 +244,7 @@ class Many2One(Widget):
|
|
|
236
244
|
win.screen.search_filter(quote(text))
|
|
237
245
|
win.show()
|
|
238
246
|
return
|
|
239
|
-
self.
|
|
247
|
+
self._popup = False
|
|
240
248
|
self.changed = True
|
|
241
249
|
|
|
242
250
|
def sig_key_press(self, widget, event, *args):
|
|
@@ -75,7 +75,6 @@ class One2Many(Widget):
|
|
|
75
75
|
|
|
76
76
|
hbox.pack_start(Gtk.VSeparator(), expand=False, fill=True, padding=0)
|
|
77
77
|
|
|
78
|
-
self.focus_out = True
|
|
79
78
|
self.wid_completion = None
|
|
80
79
|
if attrs.get('add_remove'):
|
|
81
80
|
|
|
@@ -182,6 +181,8 @@ class One2Many(Widget):
|
|
|
182
181
|
if self.attrs.get('add_remove'):
|
|
183
182
|
self.wid_text.connect('key_press_event', self.on_keypress)
|
|
184
183
|
|
|
184
|
+
self._popup = False
|
|
185
|
+
|
|
185
186
|
def get_access(self, type_):
|
|
186
187
|
model = self.attrs['relation']
|
|
187
188
|
if model:
|
|
@@ -370,12 +371,17 @@ class One2Many(Widget):
|
|
|
370
371
|
self._new_single(defaults)
|
|
371
372
|
|
|
372
373
|
def _new_single(self, defaults=None):
|
|
374
|
+
if self._popup:
|
|
375
|
+
return
|
|
376
|
+
else:
|
|
377
|
+
self._popup = True
|
|
373
378
|
sequence = self._sequence()
|
|
374
379
|
|
|
375
380
|
def update_sequence():
|
|
376
381
|
if sequence:
|
|
377
382
|
self.screen.group.set_sequence(
|
|
378
383
|
field=sequence, position=self.screen.new_position)
|
|
384
|
+
self._popup = False
|
|
379
385
|
|
|
380
386
|
if self.screen.current_view.creatable:
|
|
381
387
|
self.screen.new()
|
|
@@ -392,6 +398,10 @@ class One2Many(Widget):
|
|
|
392
398
|
fields = self.attrs['product'].split(',')
|
|
393
399
|
product = {}
|
|
394
400
|
|
|
401
|
+
if self._popup:
|
|
402
|
+
return
|
|
403
|
+
else:
|
|
404
|
+
self._popup = True
|
|
395
405
|
first = self.screen.new(default=False)
|
|
396
406
|
default = first.default_get(defaults=defaults)
|
|
397
407
|
first.set_default(default)
|
|
@@ -420,6 +430,7 @@ class One2Many(Widget):
|
|
|
420
430
|
win_search.show()
|
|
421
431
|
|
|
422
432
|
def make_product():
|
|
433
|
+
self._popup = False
|
|
423
434
|
self.screen.group.remove(first, remove=True)
|
|
424
435
|
if not product:
|
|
425
436
|
return
|
|
@@ -448,7 +459,14 @@ class One2Many(Widget):
|
|
|
448
459
|
return
|
|
449
460
|
record = self.screen.current_record
|
|
450
461
|
if record:
|
|
451
|
-
|
|
462
|
+
if self._popup:
|
|
463
|
+
return
|
|
464
|
+
else:
|
|
465
|
+
self._popup = True
|
|
466
|
+
|
|
467
|
+
def callback(result):
|
|
468
|
+
self._popup = False
|
|
469
|
+
WinForm(self.screen, callback)
|
|
452
470
|
|
|
453
471
|
def _sig_next(self, widget):
|
|
454
472
|
if not self._validate():
|
|
@@ -475,8 +493,6 @@ class One2Many(Widget):
|
|
|
475
493
|
self.screen.unremove()
|
|
476
494
|
|
|
477
495
|
def _sig_add(self, *args):
|
|
478
|
-
if not self.focus_out:
|
|
479
|
-
return
|
|
480
496
|
if not self.write_access or not self.read_access:
|
|
481
497
|
return
|
|
482
498
|
self.view.set_value()
|
|
@@ -487,12 +503,14 @@ class One2Many(Widget):
|
|
|
487
503
|
domain = ['OR', domain, ('id', 'in', removed_ids)]
|
|
488
504
|
text = self.wid_text.get_text()
|
|
489
505
|
|
|
490
|
-
self.
|
|
506
|
+
if self._popup:
|
|
507
|
+
return
|
|
508
|
+
else:
|
|
509
|
+
self._popup = True
|
|
491
510
|
|
|
492
511
|
sequence = self._sequence()
|
|
493
512
|
|
|
494
513
|
def callback(result):
|
|
495
|
-
self.focus_out = True
|
|
496
514
|
if result:
|
|
497
515
|
ids = [x[0] for x in result]
|
|
498
516
|
self.screen.load(ids, modified=True)
|
|
@@ -501,6 +519,7 @@ class One2Many(Widget):
|
|
|
501
519
|
field=sequence, position=self.screen.new_position)
|
|
502
520
|
self.screen.set_cursor()
|
|
503
521
|
self.wid_text.set_text('')
|
|
522
|
+
self._popup = False
|
|
504
523
|
|
|
505
524
|
order = self.field.get_search_order(self.record)
|
|
506
525
|
win = WinSearch(self.attrs['relation'], callback, sel_multi=True,
|
|
@@ -50,6 +50,8 @@ class Label(StateMixin, Gtk.Label):
|
|
|
50
50
|
readonly = ((field and field.attrs.get('readonly'))
|
|
51
51
|
or state_changes.get('readonly', not bool(field)))
|
|
52
52
|
common.apply_label_attributes(self, readonly, required)
|
|
53
|
+
self.set_max_width_chars(80)
|
|
54
|
+
self.set_line_wrap(True)
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class VBox(StateMixin, Gtk.VBox):
|
|
@@ -187,14 +189,16 @@ class Link(StateMixin, Gtk.Button):
|
|
|
187
189
|
['AND', domain, tab_domain], 0, 100, context=context,
|
|
188
190
|
callback=functools.partial(
|
|
189
191
|
self._set_count, idx=i, current=self._current,
|
|
190
|
-
counter=counter, label=label)
|
|
192
|
+
counter=counter, label=label),
|
|
193
|
+
process_exception=False)
|
|
191
194
|
else:
|
|
192
195
|
common.RPCExecute(
|
|
193
196
|
'model', action['res_model'], 'search_count',
|
|
194
197
|
domain, 0, 100, context=context,
|
|
195
198
|
callback=functools.partial(
|
|
196
199
|
self._set_count, current=self._current,
|
|
197
|
-
counter=counter, label=label)
|
|
200
|
+
counter=counter, label=label),
|
|
201
|
+
process_exception=False)
|
|
198
202
|
|
|
199
203
|
def _set_count(self, value, idx=0, current=None, counter=None, label=''):
|
|
200
204
|
if current != self._current or not self.get_parent():
|
|
@@ -12,6 +12,7 @@ from dateutil.relativedelta import relativedelta
|
|
|
12
12
|
from gi.repository import Gdk, Gtk
|
|
13
13
|
|
|
14
14
|
import tryton.rpc as rpc
|
|
15
|
+
from tryton import common
|
|
15
16
|
from tryton.action import Action
|
|
16
17
|
from tryton.common import COLOR_SCHEMES, generateColorscheme, hex2rgb
|
|
17
18
|
from tryton.config import CONFIG
|
|
@@ -386,7 +387,8 @@ class Graph(Gtk.DrawingArea):
|
|
|
386
387
|
if isinstance(value, datetime.timedelta):
|
|
387
388
|
value = value.total_seconds()
|
|
388
389
|
self.datas[x][key] += float(value or 0)
|
|
389
|
-
date_format =
|
|
390
|
+
date_format = common.date_format(
|
|
391
|
+
self.view.screen.context.get('date_format'))
|
|
390
392
|
datetime_format = date_format + ' %X'
|
|
391
393
|
if isinstance(minx, datetime.datetime):
|
|
392
394
|
date = minx
|
|
@@ -1,10 +1,12 @@
|
|
|
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 csv
|
|
3
4
|
import gettext
|
|
4
5
|
import json
|
|
5
6
|
import locale
|
|
6
7
|
import sys
|
|
7
8
|
from functools import wraps
|
|
9
|
+
from io import StringIO
|
|
8
10
|
|
|
9
11
|
from gi.repository import Gdk, GLib, GObject, Gtk
|
|
10
12
|
from pygtkcompat.generictreemodel import GenericTreeModel
|
|
@@ -333,7 +335,7 @@ class TreeXMLViewParser(XMLViewParser):
|
|
|
333
335
|
|
|
334
336
|
self.view.treeview.append_column(column)
|
|
335
337
|
|
|
336
|
-
if 'optional' in attributes:
|
|
338
|
+
if 'optional' in attributes and name != self.exclude_field:
|
|
337
339
|
self.view.optionals.append(column)
|
|
338
340
|
|
|
339
341
|
def _parse_button(self, node, attributes):
|
|
@@ -720,46 +722,44 @@ class ViewTree(View):
|
|
|
720
722
|
record.cancel()
|
|
721
723
|
iter_ = model.iter_next(iter_)
|
|
722
724
|
|
|
723
|
-
def on_copy(self):
|
|
725
|
+
def on_copy(self, columns=None):
|
|
726
|
+
if columns is None:
|
|
727
|
+
columns = self.treeview.get_columns()
|
|
728
|
+
|
|
729
|
+
def copy_foreach(treemodel, path, iter, add):
|
|
730
|
+
record = treemodel.get_value(iter, 0)
|
|
731
|
+
values = []
|
|
732
|
+
for col in columns:
|
|
733
|
+
if not col.get_visible() or not col.name:
|
|
734
|
+
continue
|
|
735
|
+
widget = self.get_column_widget(col)
|
|
736
|
+
values.append(widget.get_textual_value(record))
|
|
737
|
+
add(values)
|
|
738
|
+
|
|
724
739
|
for clipboard_type in [
|
|
725
740
|
Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
|
|
726
741
|
clipboard = self.treeview.get_clipboard(clipboard_type)
|
|
727
742
|
selection = self.treeview.get_selection()
|
|
728
|
-
data =
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
def copy_foreach(self, treemodel, path, iter, data):
|
|
733
|
-
record = treemodel.get_value(iter, 0)
|
|
734
|
-
values = []
|
|
735
|
-
for col in self.treeview.get_columns():
|
|
736
|
-
if not col.get_visible() or not col.name:
|
|
737
|
-
continue
|
|
738
|
-
widget = self.get_column_widget(col)
|
|
739
|
-
values.append('"'
|
|
740
|
-
+ str(widget.get_textual_value(record)).replace('"', '""')
|
|
741
|
-
+ '"')
|
|
742
|
-
data.append('\t'.join(values))
|
|
743
|
-
return
|
|
743
|
+
data = StringIO()
|
|
744
|
+
writer = csv.writer(data, delimiter='\t', lineterminator='\n')
|
|
745
|
+
selection.selected_foreach(copy_foreach, writer.writerow)
|
|
746
|
+
clipboard.set_text(data.getvalue(), -1)
|
|
744
747
|
|
|
745
748
|
def on_paste(self):
|
|
746
749
|
if not self.editable:
|
|
747
750
|
return
|
|
748
751
|
|
|
749
|
-
|
|
750
|
-
if value[:1] == '"' and value[-1:] == '"':
|
|
751
|
-
return value[1:-1]
|
|
752
|
-
return value
|
|
753
|
-
data = []
|
|
752
|
+
reader = None
|
|
754
753
|
for clipboard_type in [
|
|
755
754
|
Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
|
|
756
755
|
clipboard = self.treeview.get_clipboard(clipboard_type)
|
|
757
756
|
text = clipboard.wait_for_text()
|
|
758
757
|
if not text:
|
|
759
758
|
continue
|
|
760
|
-
|
|
761
|
-
for l in text.splitlines()]
|
|
759
|
+
reader = csv.reader(StringIO(text), delimiter='\t')
|
|
762
760
|
break
|
|
761
|
+
else:
|
|
762
|
+
return
|
|
763
763
|
col = self.treeview.get_cursor()[1]
|
|
764
764
|
columns = [c for c in self.treeview.get_columns()
|
|
765
765
|
if c.get_visible() and c.name]
|
|
@@ -774,7 +774,7 @@ class ViewTree(View):
|
|
|
774
774
|
group = self.group
|
|
775
775
|
idx = len(group)
|
|
776
776
|
default = None
|
|
777
|
-
for line in
|
|
777
|
+
for line in reader:
|
|
778
778
|
if idx >= len(group):
|
|
779
779
|
record = group.new(default=False)
|
|
780
780
|
if default is None:
|
|
@@ -785,13 +785,25 @@ class ViewTree(View):
|
|
|
785
785
|
for col, value in zip(columns, line):
|
|
786
786
|
widget = self.get_column_widget(col)
|
|
787
787
|
if widget.get_textual_value(record) != value:
|
|
788
|
-
widget
|
|
788
|
+
if hasattr(widget, 'search_remote'):
|
|
789
|
+
field = record.group.fields[widget.attrs['name']]
|
|
790
|
+
win = widget.search_remote(record, field, value)
|
|
791
|
+
if len(win.screen.group) == 1:
|
|
792
|
+
target, = win.screen.group
|
|
793
|
+
field.set_client(
|
|
794
|
+
record, (target.id, target.rec_name()))
|
|
795
|
+
else:
|
|
796
|
+
field.set_client(record, (None, ''))
|
|
797
|
+
win.response(win, Gtk.ResponseType.CANCEL)
|
|
798
|
+
else:
|
|
799
|
+
widget.value_from_text(record, value)
|
|
789
800
|
if value and not widget.get_textual_value(record):
|
|
790
801
|
# Stop setting value if a value is correctly set
|
|
791
802
|
idx = len(group)
|
|
792
803
|
break
|
|
793
804
|
if not record.validate():
|
|
794
805
|
break
|
|
806
|
+
record.save()
|
|
795
807
|
idx += 1
|
|
796
808
|
self.record = record
|
|
797
809
|
self.screen.display(set_cursor=True)
|
|
@@ -900,12 +912,27 @@ class ViewTree(View):
|
|
|
900
912
|
except TypeError:
|
|
901
913
|
# Outside row
|
|
902
914
|
return False
|
|
915
|
+
selection = treeview.get_selection()
|
|
903
916
|
menu = Gtk.Menu()
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
917
|
+
if selection.count_selected_rows():
|
|
918
|
+
if selection.count_selected_rows() == 1:
|
|
919
|
+
copy_item = Gtk.MenuItem(label=_("Copy"))
|
|
920
|
+
copy_item.connect(
|
|
921
|
+
'activate', lambda x: self.on_copy([col]))
|
|
922
|
+
menu.append(copy_item)
|
|
923
|
+
copy_row_label = _("Copy Row")
|
|
924
|
+
else:
|
|
925
|
+
copy_row_label = _("Copy Rows")
|
|
926
|
+
if col:
|
|
927
|
+
copy_column_item = Gtk.MenuItem(label=_("Copy Column"))
|
|
928
|
+
copy_column_item.connect(
|
|
929
|
+
'activate', lambda x: self.on_copy(columns=[col]))
|
|
930
|
+
menu.append(copy_column_item)
|
|
931
|
+
copy_row_item = Gtk.MenuItem(label=copy_row_label)
|
|
932
|
+
copy_row_item.connect('activate', lambda x: self.on_copy())
|
|
933
|
+
menu.append(copy_row_item)
|
|
907
934
|
if self.editable:
|
|
908
|
-
paste_item = Gtk.MenuItem(label=_(
|
|
935
|
+
paste_item = Gtk.MenuItem(label=_("Paste Rows"))
|
|
909
936
|
paste_item.connect('activate', lambda x: self.on_paste())
|
|
910
937
|
menu.append(paste_item)
|
|
911
938
|
|
|
@@ -939,7 +966,6 @@ class ViewTree(View):
|
|
|
939
966
|
menu, model, record_id, title=label, field=field,
|
|
940
967
|
context=context)
|
|
941
968
|
|
|
942
|
-
selection = treeview.get_selection()
|
|
943
969
|
if selection.count_selected_rows() == 1:
|
|
944
970
|
group = self.group
|
|
945
971
|
if selection.get_mode() == Gtk.SelectionMode.SINGLE:
|
|
@@ -1041,9 +1067,12 @@ class ViewTree(View):
|
|
|
1041
1067
|
elif tree_sel.get_mode() == Gtk.SelectionMode.MULTIPLE:
|
|
1042
1068
|
model, paths = tree_sel.get_selected_rows()
|
|
1043
1069
|
if model and paths:
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1070
|
+
records = []
|
|
1071
|
+
for path in paths:
|
|
1072
|
+
iter_ = model.get_iter(path)
|
|
1073
|
+
records.append(model.get_value(iter_, 0))
|
|
1074
|
+
if self.record not in records:
|
|
1075
|
+
self.record = records[0]
|
|
1047
1076
|
else:
|
|
1048
1077
|
self.record = None
|
|
1049
1078
|
|
|
@@ -1083,6 +1112,10 @@ class ViewTree(View):
|
|
|
1083
1112
|
def display(self, force=False):
|
|
1084
1113
|
self.treeview.display_counter += 1
|
|
1085
1114
|
current_record = self.record
|
|
1115
|
+
if current_record and current_record not in current_record.group:
|
|
1116
|
+
# current record may have been removed by on_change calls without
|
|
1117
|
+
# changing the current record of screen before the display
|
|
1118
|
+
current_record = None
|
|
1086
1119
|
if (force
|
|
1087
1120
|
or not self.treeview.get_model()
|
|
1088
1121
|
or self.group != self.treeview.get_model().group):
|
|
@@ -163,7 +163,8 @@ class EditableTreeView(TreeView):
|
|
|
163
163
|
for renderer in column.get_cells():
|
|
164
164
|
if renderer.props.editing:
|
|
165
165
|
widget = self.view.get_column_widget(column)
|
|
166
|
-
|
|
166
|
+
editable = widget.get_editable(renderer)
|
|
167
|
+
self.on_editing_done(editable, renderer)
|
|
167
168
|
return True
|
|
168
169
|
|
|
169
170
|
def on_keypressed(self, entry, event, renderer):
|