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.

Files changed (100) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +137 -69
  4. tryton/common/completion.py +2 -2
  5. tryton/common/datetime_.py +3 -1
  6. tryton/common/domain_inversion.py +10 -7
  7. tryton/common/domain_parser.py +17 -7
  8. tryton/common/selection.py +6 -3
  9. tryton/common/tempfile.py +34 -0
  10. tryton/config.py +4 -5
  11. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  12. tryton/data/locale/bg/LC_MESSAGES/tryton.po +42 -4
  13. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  14. tryton/data/locale/ca/LC_MESSAGES/tryton.po +47 -8
  15. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  16. tryton/data/locale/cs/LC_MESSAGES/tryton.po +42 -4
  17. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  18. tryton/data/locale/de/LC_MESSAGES/tryton.po +45 -6
  19. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  20. tryton/data/locale/es/LC_MESSAGES/tryton.po +46 -7
  21. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  22. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +43 -5
  23. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  24. tryton/data/locale/et/LC_MESSAGES/tryton.po +46 -6
  25. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  26. tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -7
  27. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  28. tryton/data/locale/fi/LC_MESSAGES/tryton.po +41 -4
  29. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  30. tryton/data/locale/fr/LC_MESSAGES/tryton.po +46 -7
  31. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  32. tryton/data/locale/hu/LC_MESSAGES/tryton.po +46 -6
  33. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  34. tryton/data/locale/id/LC_MESSAGES/tryton.po +43 -4
  35. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  36. tryton/data/locale/it/LC_MESSAGES/tryton.po +45 -6
  37. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  38. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/lo/LC_MESSAGES/tryton.po +45 -6
  40. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/lt/LC_MESSAGES/tryton.po +46 -6
  42. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  43. tryton/data/locale/nl/LC_MESSAGES/tryton.po +46 -7
  44. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  45. tryton/data/locale/pl/LC_MESSAGES/tryton.po +84 -60
  46. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  47. tryton/data/locale/pt/LC_MESSAGES/tryton.po +45 -6
  48. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  49. tryton/data/locale/ro/LC_MESSAGES/tryton.po +57 -17
  50. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  51. tryton/data/locale/ru/LC_MESSAGES/tryton.po +43 -6
  52. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  53. tryton/data/locale/sl/LC_MESSAGES/tryton.po +46 -5
  54. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  55. tryton/data/locale/tr/LC_MESSAGES/tryton.po +41 -4
  56. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  57. tryton/data/locale/uk/LC_MESSAGES/tryton.po +46 -6
  58. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  59. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +46 -5
  60. tryton/device_cookie.py +1 -1
  61. tryton/gui/main.py +3 -2
  62. tryton/gui/window/about.py +1 -1
  63. tryton/gui/window/dblogin.py +2 -2
  64. tryton/gui/window/email_.py +1 -1
  65. tryton/gui/window/form.py +6 -4
  66. tryton/gui/window/log.py +24 -2
  67. tryton/gui/window/view_form/model/field.py +84 -34
  68. tryton/gui/window/view_form/model/group.py +3 -1
  69. tryton/gui/window/view_form/model/record.py +64 -15
  70. tryton/gui/window/view_form/screen/screen.py +83 -46
  71. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
  72. tryton/gui/window/view_form/view/form.py +6 -12
  73. tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
  74. tryton/gui/window/view_form/view/form_gtk/dictionary.py +37 -24
  75. tryton/gui/window/view_form/view/form_gtk/document.py +9 -10
  76. tryton/gui/window/view_form/view/form_gtk/many2many.py +17 -7
  77. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  78. tryton/gui/window/view_form/view/form_gtk/one2many.py +25 -6
  79. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  80. tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
  81. tryton/gui/window/view_form/view/list.py +68 -35
  82. tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
  83. tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
  84. tryton/gui/window/view_form/view/screen_container.py +3 -5
  85. tryton/gui/window/win_export.py +1 -2
  86. tryton/gui/window/win_form.py +9 -7
  87. tryton/gui/window/win_import.py +9 -4
  88. tryton/gui/window/wizard.py +13 -10
  89. tryton/jsonrpc.py +46 -28
  90. tryton/plugins/__init__.py +5 -3
  91. tryton/pyson.py +55 -5
  92. tryton/rpc.py +18 -0
  93. tryton/tests/test_common_domain_parser.py +8 -0
  94. tryton/translate.py +5 -2
  95. {tryton-7.0.6.data → tryton-7.2.13.data}/scripts/tryton +8 -7
  96. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/METADATA +6 -6
  97. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/RECORD +100 -98
  98. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/WHEEL +1 -1
  99. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/LICENSE +0 -0
  100. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/top_level.txt +0 -0
@@ -75,7 +75,8 @@ 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(attributes.get('tab_domain'))
78
+ self.screen_container = ScreenContainer(
79
+ self, attributes.get('tab_domain'))
79
80
  self.screen_container.alternate_view = attributes.get(
80
81
  'alternate_view', False)
81
82
  self.widget = self.screen_container.widget_get()
@@ -116,11 +117,10 @@ class Screen:
116
117
  return child
117
118
 
118
119
  # Remove first level Viewport and ScrolledWindow to fill the Vbox
119
- for widget in [
120
- self.context_screen.screen_container.viewport,
121
- self.context_screen.current_view.widget.get_children()[0],
122
- ]:
123
- remove_bin(widget)
120
+ remove_bin(self.context_screen.screen_container.viewport)
121
+ if self.context_screen.current_view:
122
+ remove_bin(
123
+ self.context_screen.current_view.widget.get_children()[0])
124
124
 
125
125
  self.screen_container.filter_vbox.pack_start(
126
126
  context_widget, expand=False, fill=True, padding=0)
@@ -164,7 +164,6 @@ class Screen:
164
164
 
165
165
  def search_active(self, active=True):
166
166
  if active and not self.parent:
167
- self.screen_container.set_screen(self)
168
167
  self.screen_container.show_filter()
169
168
  else:
170
169
  self.screen_container.hide_filter()
@@ -308,7 +307,8 @@ class Screen:
308
307
  try:
309
308
  self.search_count = RPCExecute(
310
309
  'model', self.model_name, 'search_count',
311
- domain, 0, self.count_limit, context=context)
310
+ domain, 0, self.count_limit, context=context,
311
+ process_exception=False)
312
312
  except RPCException:
313
313
  self.search_count = 0
314
314
  else:
@@ -520,7 +520,8 @@ class Screen:
520
520
  self.group.destroy()
521
521
 
522
522
  def default_row_activate(self):
523
- if (self.current_view.view_type == 'tree'
523
+ if (self.current_view
524
+ and self.current_view.view_type == 'tree'
524
525
  and int(self.current_view.attributes.get('keyword_open', 0))):
525
526
  return Action.exec_keyword('tree_open', {
526
527
  'model': self.model_name,
@@ -536,6 +537,10 @@ class Screen:
536
537
  def number_of_views(self):
537
538
  return len(self.views) + len(self.view_to_load)
538
539
 
540
+ @property
541
+ def view_index(self):
542
+ return self.__current_view
543
+
539
544
  def switch_view(
540
545
  self, view_type=None, view_id=None, creatable=None, display=True):
541
546
  if view_id is not None:
@@ -642,7 +647,7 @@ class Screen:
642
647
 
643
648
  def new(self, default=True, defaults=None):
644
649
  previous_view = self.current_view
645
- if self.current_view.view_type == 'calendar':
650
+ if self.current_view and self.current_view.view_type == 'calendar':
646
651
  selected_date = self.current_view.get_selected_date()
647
652
  if self.current_view and not self.current_view.creatable:
648
653
  self.switch_view(creatable=True)
@@ -695,16 +700,19 @@ class Screen:
695
700
 
696
701
  def save_current(self):
697
702
  if not self.current_record:
698
- if self.current_view.view_type == 'tree' and len(self.group):
703
+ if (self.current_view
704
+ and self.current_view.view_type == 'tree'
705
+ and len(self.group)):
699
706
  self.current_record = self.group[0]
700
707
  else:
701
708
  return True
702
- self.current_view.set_value()
703
709
  saved = False
704
710
  record_id = None
705
- fields = self.current_view.get_fields()
711
+ if self.current_view:
712
+ self.current_view.set_value()
713
+ fields = self.current_view.get_fields()
706
714
  path = self.current_record.get_path(self.group)
707
- if self.current_view.view_type == 'tree':
715
+ if self.current_view and self.current_view.view_type == 'tree':
708
716
  # False value must be not saved
709
717
  saved = all((
710
718
  x is not False and x >= 0
@@ -714,7 +722,7 @@ class Screen:
714
722
  record_id = self.current_record.save(force_reload=True)
715
723
  # False value must be not saved
716
724
  saved = record_id is not False and record_id >= 0
717
- else:
725
+ elif self.current_view:
718
726
  self.set_cursor()
719
727
  self.current_view.display()
720
728
  return False
@@ -742,17 +750,19 @@ class Screen:
742
750
  def get(self):
743
751
  if not self.current_record:
744
752
  return None
745
- self.current_view.set_value()
753
+ if self.current_view:
754
+ self.current_view.set_value()
746
755
  return self.current_record.get()
747
756
 
748
757
  def get_on_change_value(self):
749
758
  if not self.current_record:
750
759
  return None
751
- self.current_view.set_value()
760
+ if self.current_view:
761
+ self.current_view.set_value()
752
762
  return self.current_record.get_on_change_value()
753
763
 
754
764
  def modified(self):
755
- if self.current_view.view_type != 'tree':
765
+ if self.current_view and self.current_view.view_type != 'tree':
756
766
  if self.current_record:
757
767
  if self.current_record.modified or self.current_record.id < 0:
758
768
  return True
@@ -760,7 +770,7 @@ class Screen:
760
770
  for record in self.group:
761
771
  if record.modified or record.id < 0:
762
772
  return True
763
- if self.current_view.modified:
773
+ if self.current_view and self.current_view.modified:
764
774
  return True
765
775
  return False
766
776
 
@@ -795,6 +805,9 @@ class Screen:
795
805
  record.group.remove(
796
806
  record, remove=remove, modified=False,
797
807
  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
798
811
  # call only once
799
812
  record.set_modified()
800
813
 
@@ -821,11 +834,16 @@ class Screen:
821
834
  context=self.context)
822
835
  except RPCException:
823
836
  return False
824
- self.load(new_ids, position=self.new_position)
837
+ self.group.load(new_ids, position=self.new_position)
838
+ if new_ids:
839
+ self.current_record = self.group.get(new_ids[0])
840
+ self.display(set_cursor=True)
825
841
  return True
826
842
 
827
843
  def set_tree_state(self):
828
844
  view = self.current_view
845
+ if not view:
846
+ return
829
847
  if view.view_type not in {'tree', 'form', 'list-form'}:
830
848
  return
831
849
  if id(view) in self.tree_states_done:
@@ -920,7 +938,7 @@ class Screen:
920
938
  json_paths, json_selected_path,
921
939
  process_exception=False)
922
940
  clear_cache('model.ir.ui.view_tree_state.get')
923
- except Exception:
941
+ except RPCException:
924
942
  logger.warn(
925
943
  _('Unable to set view tree state'), exc_info=True)
926
944
 
@@ -935,12 +953,13 @@ class Screen:
935
953
 
936
954
  def load(self, ids, set_cursor=True, modified=False, position=-1):
937
955
  self.group.load(ids, modified=modified, position=position)
938
- self.current_view.reset()
956
+ if self.current_view:
957
+ self.current_view.reset()
939
958
  self.current_record = None
940
959
  self.display(set_cursor=set_cursor)
941
960
 
942
961
  def display(self, set_cursor=False):
943
- if self.views:
962
+ if self.views and self.current_view:
944
963
  self.search_active(self.current_view.view_type
945
964
  in ('tree', 'graph', 'calendar'))
946
965
  for view in self.views:
@@ -972,7 +991,8 @@ class Screen:
972
991
 
973
992
  def _get_next_record(self, test=False):
974
993
  view = self.current_view
975
- if (view.view_type in {'tree', 'form'}
994
+ if (view
995
+ and view.view_type in {'tree', 'form'}
976
996
  and self.current_record
977
997
  and self.current_record.group):
978
998
  group = self.current_record.group
@@ -1000,12 +1020,14 @@ class Screen:
1000
1020
  record = next
1001
1021
  break
1002
1022
  return record
1003
- elif (view.view_type == 'list-form' and len(self.group)
1023
+ elif (view
1024
+ and view.view_type == 'list-form'
1025
+ and len(self.group)
1004
1026
  and self.current_record in self.group):
1005
1027
  idx = self.group.index(self.current_record)
1006
1028
  if 0 <= idx < len(self.group) - 1:
1007
1029
  return self.group[idx + 1]
1008
- elif view.view_type == 'calendar':
1030
+ elif view and view.view_type == 'calendar':
1009
1031
  record = self.current_record
1010
1032
  goocalendar = view.widgets.get('goocalendar')
1011
1033
  if goocalendar:
@@ -1039,15 +1061,18 @@ class Screen:
1039
1061
 
1040
1062
  def display_next(self):
1041
1063
  view = self.current_view
1042
- view.set_value()
1064
+ if view:
1065
+ view.set_value()
1043
1066
  self.set_cursor(reset_view=False)
1044
1067
  self.current_record = self._get_next_record()
1045
1068
  self.set_cursor(reset_view=False)
1046
- view.display()
1069
+ if view:
1070
+ view.display()
1047
1071
 
1048
1072
  def _get_prev_record(self, test=False):
1049
1073
  view = self.current_view
1050
- if (view.view_type in {'tree', 'form'}
1074
+ if (view
1075
+ and view.view_type in {'tree', 'form'}
1051
1076
  and self.current_record
1052
1077
  and self.current_record.group):
1053
1078
  group = self.current_record.group
@@ -1065,7 +1090,7 @@ class Screen:
1065
1090
  if parent and record.model_name == parent.model_name:
1066
1091
  record = parent
1067
1092
  return record
1068
- elif view.view_type == 'calendar':
1093
+ elif view and view.view_type == 'calendar':
1069
1094
  record = self.current_record
1070
1095
  goocalendar = view.widgets.get('goocalendar')
1071
1096
  if goocalendar:
@@ -1090,7 +1115,9 @@ class Screen:
1090
1115
  if prev_id >= 0:
1091
1116
  return events[prev_id].record
1092
1117
  break
1093
- elif (view.view_type == 'list-form' and len(self.group)
1118
+ elif (view
1119
+ and view.view_type == 'list-form'
1120
+ and len(self.group)
1094
1121
  and self.current_record in self.group):
1095
1122
  idx = self.group.index(self.current_record)
1096
1123
  if 0 < idx <= len(self.group) - 1:
@@ -1104,11 +1131,13 @@ class Screen:
1104
1131
 
1105
1132
  def display_prev(self):
1106
1133
  view = self.current_view
1107
- view.set_value()
1134
+ if view:
1135
+ view.set_value()
1108
1136
  self.set_cursor(reset_view=False)
1109
1137
  self.current_record = self._get_prev_record()
1110
1138
  self.set_cursor(reset_view=False)
1111
- view.display()
1139
+ if view:
1140
+ view.display()
1112
1141
 
1113
1142
  def invalid_message(self, record=None):
1114
1143
  if record is None:
@@ -1142,6 +1171,8 @@ class Screen:
1142
1171
  def selected_paths(self):
1143
1172
  if self.current_view and self.current_view.view_type == 'tree':
1144
1173
  return self.current_view.get_selected_paths()
1174
+ else:
1175
+ return []
1145
1176
 
1146
1177
  @property
1147
1178
  def listed_records(self):
@@ -1181,7 +1212,7 @@ class Screen:
1181
1212
  if not self.selected_records:
1182
1213
  return []
1183
1214
 
1184
- buttons = self.current_view.get_buttons()
1215
+ buttons = self.current_view.get_buttons() if self.current_view else []
1185
1216
 
1186
1217
  for record in self.selected_records:
1187
1218
  buttons = [b for b in buttons if is_active(record, b)]
@@ -1191,8 +1222,9 @@ class Screen:
1191
1222
 
1192
1223
  def button(self, button):
1193
1224
  'Execute button on the selected records'
1194
- self.current_view.set_value()
1195
- fields = self.current_view.get_fields()
1225
+ if self.current_view:
1226
+ self.current_view.set_value()
1227
+ fields = self.current_view.get_fields()
1196
1228
  for record in self.selected_records:
1197
1229
  domain = record.expr_eval(
1198
1230
  button.get('states', {})).get('pre_validate', [])
@@ -1206,9 +1238,9 @@ class Screen:
1206
1238
  if button.get('confirm', False) and not sur(button['confirm']):
1207
1239
  return
1208
1240
  if button.get('type', 'class') == 'class':
1209
- if not self.current_record.save(force_reload=False):
1241
+ record_id = self.current_record.save(force_reload=False)
1242
+ if record_id is False or record_id < 0:
1210
1243
  return
1211
- if button.get('type', 'class') == 'class':
1212
1244
  self._button_class(button)
1213
1245
  else:
1214
1246
  self._button_instance(button)
@@ -1217,6 +1249,7 @@ class Screen:
1217
1249
  record = self.current_record
1218
1250
  args = record.expr_eval(button.get('change', []))
1219
1251
  values = record._get_on_change_args(args)
1252
+ values['id'] = record.id
1220
1253
  try:
1221
1254
  changes = RPCExecute('model', self.model_name, button['name'],
1222
1255
  values, context=self.context)
@@ -1237,6 +1270,7 @@ class Screen:
1237
1270
  except RPCException:
1238
1271
  action = None
1239
1272
  self.reload(ids, written=True)
1273
+ self.record_saved()
1240
1274
  if isinstance(action, str):
1241
1275
  self.client_action(action)
1242
1276
  elif action:
@@ -1273,7 +1307,9 @@ class Screen:
1273
1307
  elif action.startswith('switch'):
1274
1308
  self.switch_view(*action.split(None, 2)[1:])
1275
1309
  elif action == 'reload':
1276
- if (self.current_view.view_type in ['tree', 'graph', 'calendar']
1310
+ if (self.current_view
1311
+ and self.current_view.view_type in [
1312
+ 'tree', 'graph', 'calendar']
1277
1313
  and not self.parent):
1278
1314
  self.search_filter()
1279
1315
  elif action == 'reload menu':
@@ -1297,13 +1333,13 @@ class Screen:
1297
1333
  if name:
1298
1334
  query_string.append(
1299
1335
  ('name', json.dumps(name, separators=(',', ':'))))
1300
- if self.screen_container.tab_domain:
1301
- query_string.append(('tab_domain', json.dumps(
1302
- self.screen_container.tab_domain,
1303
- cls=JSONEncoder, separators=(',', ':'))))
1304
1336
  path = [CONFIG['login.db'], 'model', self.model_name]
1305
1337
  view_ids = [v.view_id for v in self.views] + self.view_ids
1306
- if self.current_view.view_type != 'form':
1338
+ if self.current_view and self.current_view.view_type != 'form':
1339
+ if self.screen_container.tab_domain:
1340
+ query_string.append(('tab_domain', json.dumps(
1341
+ self.screen_container.tab_domain,
1342
+ cls=JSONEncoder, separators=(',', ':'))))
1307
1343
  if self.search_value:
1308
1344
  search_value = self.search_value
1309
1345
  else:
@@ -1315,8 +1351,9 @@ class Screen:
1315
1351
  separators=(',', ':'))))
1316
1352
  elif self.current_record and self.current_record.id > -1:
1317
1353
  path.append(str(self.current_record.id))
1318
- i = view_ids.index(self.current_view.view_id)
1319
- view_ids = view_ids[i:] + view_ids[:i]
1354
+ if self.current_view:
1355
+ i = view_ids.index(self.current_view.view_id)
1356
+ view_ids = view_ids[i:] + view_ids[:i]
1320
1357
  if view_ids:
1321
1358
  query_string.append(('views', json.dumps(
1322
1359
  view_ids, separators=(',', ':'))))
@@ -63,17 +63,23 @@ class Calendar_(goocalendar.Calendar):
63
63
  return False
64
64
 
65
65
  def current_domain(self):
66
- first_datetime, last_datetime = \
67
- self.current_domain_period.get_dates(True)
66
+ start, end = self.current_domain_period.get_dates(True)
68
67
  dtstart = self.attrs['dtstart']
69
68
  dtend = self.attrs.get('dtend') or dtstart
70
- domain = ['OR',
71
- ['AND', (dtstart, '>=', first_datetime),
72
- (dtstart, '<', last_datetime)],
73
- ['AND', (dtend, '>=', first_datetime),
74
- (dtend, '<', last_datetime)],
75
- ['AND', (dtstart, '<', first_datetime),
76
- (dtend, '>', last_datetime)]]
69
+ fields = self.view_calendar.screen.group.fields
70
+ if fields[dtstart].attrs['type'] == 'date':
71
+ start = start.date()
72
+ if fields[dtend].attrs['type'] == 'date':
73
+ end = end.date()
74
+ domain = [
75
+ (dtstart, '!=', None),
76
+ (dtend, '!=', None),
77
+ ['OR',
78
+ ['AND', (dtstart, '>=', start), (dtstart, '<', end)],
79
+ ['AND', (dtend, '>=', start), (dtend, '<', end)],
80
+ ['AND', (dtstart, '<', start), (dtend, '>', end)],
81
+ ],
82
+ ]
77
83
  return domain
78
84
 
79
85
  def get_colors(self, record):
@@ -230,6 +230,9 @@ class FormXMLViewParser(XMLViewParser):
230
230
  self.container.add(None, attributes)
231
231
  return
232
232
 
233
+ if int(attributes.get('visible', 0)):
234
+ self.field_attrs[name]['visible'] = True
235
+
233
236
  widget = self.WIDGETS[attributes['widget']](self.view, attributes)
234
237
  self.view.widgets[name].append(widget)
235
238
 
@@ -340,18 +343,7 @@ class FormXMLViewParser(XMLViewParser):
340
343
  int(attributes.get('width', -1)),
341
344
  int(attributes.get('height', -1)))
342
345
 
343
- # Force to display the first time it switches on a page
344
- # This avoids glitch in position of widgets
345
- def switch(notebook, page, page_num):
346
- if not self.view.widget:
347
- # Not yet finish to parse
348
- return
349
- notebook.grab_focus()
350
- self.view.display()
351
- notebook.disconnect(handler_id)
352
- handler_id = notebook.connect('switch-page', switch)
353
346
  self.view.state_widgets.append(notebook)
354
-
355
347
  self.view.notebooks.append(notebook)
356
348
  self.container.add(notebook, attributes)
357
349
  self.parse_child(node, notebook)
@@ -390,7 +382,6 @@ class FormXMLViewParser(XMLViewParser):
390
382
  group = Container.constructor(
391
383
  int(attributes.get('col', 4)),
392
384
  attributes.get('homogeneous', False))
393
- self.parse_child(node, group)
394
385
 
395
386
  if 'name' in attributes and attributes['name'] == self.exclude_field:
396
387
  self.container.add(None, attributes)
@@ -414,6 +405,9 @@ class FormXMLViewParser(XMLViewParser):
414
405
  bool(attributes.get('yexpand'))))
415
406
  self.view.state_widgets.append(widget)
416
407
  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)
417
411
 
418
412
  def _parse_hpaned(self, node, attributes):
419
413
  self._parse_paned(node, attributes, Gtk.HPaned)
@@ -171,10 +171,9 @@ class Password(Char):
171
171
  self.widget.pack_start(
172
172
  self.visibility_checkbox, expand=False, fill=True, padding=0)
173
173
 
174
- def _readonly_set(self, value):
175
- super(Char, self)._readonly_set(value)
176
- self.entry.set_editable(not value)
177
- self.visibility_checkbox.props.visible = not value
178
-
179
174
  def toggle_visibility(self, button):
180
- self.entry.props.visibility = not self.entry.props.visibility
175
+ if self.autocomplete:
176
+ entry = self.entry.get_child()
177
+ else:
178
+ entry = self.entry
179
+ entry.props.visibility = not self.entry.props.visibility
@@ -1,6 +1,7 @@
1
1
  # This file is part of Tryton. The COPYRIGHT file at the top level of this
2
2
  # repository contains the full copyright notices and license terms.
3
3
 
4
+ import datetime as dt
4
5
  import decimal
5
6
  import gettext
6
7
  import locale
@@ -64,7 +65,7 @@ class DictEntry(object):
64
65
  return self.widget.get_text()
65
66
 
66
67
  def set_value(self, value):
67
- self.widget.set_text(value or '')
68
+ self.widget.set_text(str(value or ''))
68
69
  reset_position(self.widget)
69
70
 
70
71
  def set_readonly(self, readonly):
@@ -241,9 +242,10 @@ class DictMultiSelectionEntry(DictEntry):
241
242
  selection.handler_block_by_func(self._changed)
242
243
  try:
243
244
  selection.unselect_all()
244
- for v in value:
245
- if v in value2path:
246
- selection.select_path(value2path[v])
245
+ if value:
246
+ for v in value:
247
+ if v in value2path:
248
+ selection.select_path(value2path[v])
247
249
  finally:
248
250
  selection.handler_unblock_by_func(self._changed)
249
251
 
@@ -278,7 +280,7 @@ class DictIntegerEntry(DictEntry):
278
280
  return None
279
281
 
280
282
  def set_value(self, value):
281
- if value is not None:
283
+ if isinstance(value, (int, float, Decimal)):
282
284
  txt_val = locale.format_string('%d', value, True)
283
285
  else:
284
286
  txt_val = ''
@@ -315,7 +317,7 @@ class DictFloatEntry(DictIntegerEntry):
315
317
  else:
316
318
  self.widget.digits = None
317
319
  self.widget.set_width_chars(self.width)
318
- if value is not None:
320
+ if isinstance(value, (int, float, Decimal)):
319
321
  txt_val = locale.localize(
320
322
  '{0:.{1}f}'.format(value, digits[1]), True)
321
323
  else:
@@ -361,7 +363,8 @@ class DictDateTimeEntry(DictEntry):
361
363
  return untimezoned_date(self.widget.props.value)
362
364
 
363
365
  def set_value(self, value):
364
- self.widget.props.value = timezoned_date(value)
366
+ self.widget.props.value = (
367
+ timezoned_date(value) if isinstance(value, dt.datetime) else None)
365
368
 
366
369
  def set_readonly(self, readonly):
367
370
  for child in self.widget.get_children():
@@ -396,7 +399,7 @@ class DictDateEntry(DictEntry):
396
399
  return self.widget.props.value
397
400
 
398
401
  def set_value(self, value):
399
- self.widget.props.value = value
402
+ self.widget.props.value = value if isinstance(value, dt.date) else None
400
403
 
401
404
  def set_readonly(self, readonly):
402
405
  super().set_readonly(readonly)
@@ -470,6 +473,7 @@ class DictWidget(Widget):
470
473
 
471
474
  self._readonly = False
472
475
  self._record_id = None
476
+ self._popup = False
473
477
 
474
478
  @property
475
479
  def _invalid_widget(self):
@@ -491,10 +495,16 @@ class DictWidget(Widget):
491
495
  value = self.wid_text.get_text()
492
496
  domain = self.field.domain_get(self.record)
493
497
 
498
+ if self._popup:
499
+ return
500
+ else:
501
+ self._popup = True
502
+
494
503
  def callback(result):
495
504
  if result:
496
505
  self.add_new_keys([r[0] for r in result])
497
506
  self.wid_text.set_text('')
507
+ self._popup = False
498
508
 
499
509
  win = WinSearch(self.schema_model, callback, sel_multi=True,
500
510
  context=context, domain=domain, new=False)
@@ -504,15 +514,14 @@ class DictWidget(Widget):
504
514
  def add_new_keys(self, ids):
505
515
  new_keys = self.field.add_new_keys(ids, self.record)
506
516
  self.send_modified()
507
- focus = False
508
- for key_name in new_keys:
509
- if key_name not in self.fields:
510
- self.add_line(key_name)
511
- if not focus:
512
- # Use idle add because it can be called from the callback
513
- # of WinSearch while the popup is still there
514
- GLib.idle_add(self.fields[key_name].widget.grab_focus)
515
- focus = True
517
+ value = self.field.get_client(self.record)
518
+ value.update({k: None for k in new_keys})
519
+ self.field.set_client(self.record, value)
520
+ self.display()
521
+
522
+ # Use idle add because it can be called from the callback
523
+ # of WinSearch while the popup is still there
524
+ GLib.idle_add(self.fields[new_keys[0]].widget.grab_focus)
516
525
 
517
526
  def _sig_remove(self, button, key, modified=True):
518
527
  self.fields[key].disconnect_signals()
@@ -558,7 +567,7 @@ class DictWidget(Widget):
558
567
  not self._readonly
559
568
  and self.attrs.get('delete', True)))
560
569
 
561
- def add_line(self, key):
570
+ def add_line(self, key, position):
562
571
  key_schema = self.field.keys[key]
563
572
  self.fields[key] = DICT_ENTRIES[key_schema['type']](key, self)
564
573
  field = self.fields[key]
@@ -566,8 +575,8 @@ class DictWidget(Widget):
566
575
  label = Gtk.Label(
567
576
  label=set_underline(text),
568
577
  use_underline=True, halign=Gtk.Align.END)
569
- self.grid.attach_next_to(
570
- label, None, Gtk.PositionType.BOTTOM, 1, 1)
578
+ self.grid.insert_row(position)
579
+ self.grid.attach(label, 0, position, 1, 1)
571
580
  label.set_mnemonic_widget(field.widget)
572
581
  label.show()
573
582
  hbox = Gtk.HBox(hexpand=True)
@@ -604,6 +613,11 @@ class DictWidget(Widget):
604
613
  self.field.add_keys(list(new_key_names), self.record)
605
614
  decoder = PYSONDecoder()
606
615
 
616
+ # We remove first the old keys in order to keep the order when
617
+ # inserting the new ones
618
+ for key in set(self.fields.keys()) - set(value.keys()):
619
+ self._sig_remove(None, key, modified=False)
620
+
607
621
  def filter_func(item):
608
622
  key, value = item
609
623
  return key in self.field.keys
@@ -612,9 +626,10 @@ class DictWidget(Widget):
612
626
  key, value = item
613
627
  return self.field.keys[key]['sequence'] or 0
614
628
 
615
- for key, val in sorted(filter(filter_func, value.items()), key=key):
629
+ for position, (key, val) in enumerate(
630
+ sorted(filter(filter_func, value.items()), key=key)):
616
631
  if key not in self.fields:
617
- self.add_line(key)
632
+ self.add_line(key, position)
618
633
  widget = self.fields[key]
619
634
  widget.set_value(val)
620
635
  widget.set_readonly(self._readonly)
@@ -622,8 +637,6 @@ class DictWidget(Widget):
622
637
  self.field.keys[key].get('domain') or '[]')
623
638
  widget_class(
624
639
  widget.widget, 'invalid', not eval_domain(key_domain, value))
625
- for key in set(self.fields.keys()) - set(value.keys()):
626
- self._sig_remove(None, key, modified=False)
627
640
 
628
641
  self._set_button_sensitive()
629
642
 
@@ -1,7 +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
3
  from pathlib import Path
4
- from tempfile import NamedTemporaryFile
5
4
 
6
5
  from gi.repository import Gdk, GLib, Gtk
7
6
 
@@ -76,17 +75,17 @@ class Document(BinaryMixin, Widget):
76
75
  self.image.hide()
77
76
  if self.evince_view:
78
77
  self.evince_scroll.show()
78
+ suffix = None
79
79
  if self.filename_field:
80
- suffix = self.filename_field.get(self.record)
81
- else:
82
- suffix = None
80
+ filename = self.filename_field.get(self.record)
81
+ if filename:
82
+ suffix = Path(filename).suffix
83
+ filename = Path(self.field.get_filename(self.record, suffix))
83
84
  try:
84
- with NamedTemporaryFile(suffix=suffix) as fp:
85
- fp.write(data)
86
- path = Path(fp.name)
87
- document = (
88
- EvinceDocument.Document.factory_get_document(
89
- path.as_uri()))
85
+ document = (
86
+ EvinceDocument.Document.factory_get_document_full(
87
+ filename.as_uri(),
88
+ EvinceDocument.DocumentLoadFlags.NONE))
90
89
  model = EvinceView.DocumentModel()
91
90
  model.set_document(document)
92
91
  self.evince_view.set_model(model)