sapiopycommons 2025.7.17a612__py3-none-any.whl → 2025.7.18a614__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 sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/ai/__init__.py +0 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py +43 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +31 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +123 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +598 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2.py +45 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +42 -0
- sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/proto/step_pb2.py +43 -0
- sapiopycommons/ai/api/plan/proto/step_pb2.pyi +43 -0
- sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2.py +55 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +115 -0
- sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +153 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +57 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +96 -0
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +69 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +232 -0
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +154 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +39 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +32 -0
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py +24 -0
- sapiopycommons/ai/protobuf_utils.py +508 -0
- sapiopycommons/ai/test_client.py +251 -0
- sapiopycommons/ai/tool_service_base.py +826 -0
- sapiopycommons/callbacks/callback_util.py +332 -665
- sapiopycommons/callbacks/field_builder.py +0 -2
- sapiopycommons/chem/IndigoMolecules.py +1 -29
- sapiopycommons/chem/Molecules.py +3 -3
- sapiopycommons/customreport/auto_pagers.py +1 -26
- sapiopycommons/customreport/term_builder.py +1 -1
- sapiopycommons/datatype/pseudo_data_types.py +326 -349
- sapiopycommons/eln/experiment_handler.py +767 -408
- sapiopycommons/eln/experiment_report_util.py +6 -11
- sapiopycommons/eln/plate_designer.py +2 -7
- sapiopycommons/files/file_util.py +5 -7
- sapiopycommons/general/accession_service.py +2 -2
- sapiopycommons/general/aliases.py +1 -3
- sapiopycommons/general/audit_log.py +0 -7
- sapiopycommons/general/custom_report_util.py +0 -12
- sapiopycommons/processtracking/custom_workflow_handler.py +1 -11
- sapiopycommons/processtracking/endpoints.py +0 -27
- sapiopycommons/recordmodel/record_handler.py +391 -785
- sapiopycommons/rules/eln_rule_handler.py +1 -8
- sapiopycommons/rules/on_save_rule_handler.py +1 -8
- sapiopycommons/webhook/webhook_handlers.py +4 -9
- sapiopycommons/webhook/webservice_handlers.py +2 -2
- {sapiopycommons-2025.7.17a612.dist-info → sapiopycommons-2025.7.18a614.dist-info}/METADATA +2 -2
- sapiopycommons-2025.7.18a614.dist-info/RECORD +92 -0
- sapiopycommons/eln/experiment_cache.py +0 -188
- sapiopycommons/eln/experiment_step_factory.py +0 -476
- sapiopycommons/eln/step_creation.py +0 -236
- sapiopycommons/general/data_structure_util.py +0 -115
- sapiopycommons-2025.7.17a612.dist-info/RECORD +0 -68
- {sapiopycommons-2025.7.17a612.dist-info → sapiopycommons-2025.7.18a614.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.7.17a612.dist-info → sapiopycommons-2025.7.18a614.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,8 +4,7 @@ import io
|
|
|
4
4
|
import re
|
|
5
5
|
import warnings
|
|
6
6
|
from copy import copy
|
|
7
|
-
from
|
|
8
|
-
from typing import Iterable, TypeAlias, Any, Callable, Container, Collection
|
|
7
|
+
from typing import Iterable, TypeAlias, Any, Callable
|
|
9
8
|
from weakref import WeakValueDictionary
|
|
10
9
|
|
|
11
10
|
from requests import ReadTimeout
|
|
@@ -39,35 +38,11 @@ from sapiopycommons.general.aliases import FieldMap, SapioRecord, AliasUtil, Rec
|
|
|
39
38
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
40
39
|
from sapiopycommons.general.exceptions import SapioUserCancelledException, SapioException, SapioUserErrorException, \
|
|
41
40
|
SapioDialogTimeoutException
|
|
42
|
-
from sapiopycommons.general.time_util import TimeUtil
|
|
43
41
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
44
42
|
|
|
45
43
|
DataTypeLayoutIdentifier: TypeAlias = DataTypeLayout | str | None
|
|
46
44
|
|
|
47
45
|
|
|
48
|
-
# FR-47690: Added enum to customize blank handling result behavior, instead of using the require_selection/input
|
|
49
|
-
# boolean parameter.
|
|
50
|
-
class BlankResultHandling(Enum):
|
|
51
|
-
"""
|
|
52
|
-
An enum that controls how blank results are handled in dialogs.
|
|
53
|
-
"""
|
|
54
|
-
DEFAULT = 0
|
|
55
|
-
"""Used only by dialog functions. If a dialog function parameter is set to this value, then the blank result
|
|
56
|
-
handling of the CallbackUtil is used."""
|
|
57
|
-
REPEAT = 1
|
|
58
|
-
"""If the user provides a blank result, repeat the dialog."""
|
|
59
|
-
CANCEL = 2
|
|
60
|
-
"""If the user provides a blank result, throw a cancel exception."""
|
|
61
|
-
RETURN = 3
|
|
62
|
-
"""If the user provides a blank result, return it to the caller."""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# CR-47521: Updated various parameter type hints from list or Iterable to more specific type hints.
|
|
66
|
-
# If we need to iterate over the parameter, then it is Iterable.
|
|
67
|
-
# If we need to see if the parameter contains a value, then it is Container.
|
|
68
|
-
# If the length/size of the parameter is needed, then it is Collection.
|
|
69
|
-
# If we need to access the parameter by an index, then it is Sequence. (This excludes sets and dictionaries, so it's
|
|
70
|
-
# probably better to accept a Collection then cast the parameter to a list if you need to get an element from it.)
|
|
71
46
|
class CallbackUtil:
|
|
72
47
|
user: SapioUser
|
|
73
48
|
callback: ClientCallback
|
|
@@ -78,7 +53,6 @@ class CallbackUtil:
|
|
|
78
53
|
timeout_seconds: int
|
|
79
54
|
width_pixels: int | None
|
|
80
55
|
width_percent: float | None
|
|
81
|
-
_default_blank_result_handling: BlankResultHandling
|
|
82
56
|
|
|
83
57
|
__instances: WeakValueDictionary[SapioUser, CallbackUtil] = WeakValueDictionary()
|
|
84
58
|
__initialized: bool
|
|
@@ -116,7 +90,6 @@ class CallbackUtil:
|
|
|
116
90
|
self.timeout_seconds = self.user.timeout_seconds
|
|
117
91
|
self.width_pixels = None
|
|
118
92
|
self.width_percent = None
|
|
119
|
-
self._default_blank_result_handling = BlankResultHandling.CANCEL
|
|
120
93
|
self.__layouts = {}
|
|
121
94
|
|
|
122
95
|
def set_dialog_width(self, width_pixels: int | None = None, width_percent: float | None = None):
|
|
@@ -143,19 +116,6 @@ class CallbackUtil:
|
|
|
143
116
|
"""
|
|
144
117
|
self.timeout_seconds = timeout
|
|
145
118
|
|
|
146
|
-
def set_default_blank_result_handling(self, handling: BlankResultHandling):
|
|
147
|
-
"""
|
|
148
|
-
Set the default handling of blank results provided by the user in certain dialogs. This will only be used
|
|
149
|
-
if the dialog functions have their own blank_result_handling parameter set to DEFAULT.
|
|
150
|
-
|
|
151
|
-
:param handling: The handling to use for blank results in dialogs.
|
|
152
|
-
"""
|
|
153
|
-
if not isinstance(handling, BlankResultHandling):
|
|
154
|
-
raise SapioException("Invalid blank result handling provided.")
|
|
155
|
-
if handling == BlankResultHandling.DEFAULT:
|
|
156
|
-
raise SapioException("Blank result handling cannot be set to DEFAULT.")
|
|
157
|
-
self._default_blank_result_handling = handling
|
|
158
|
-
|
|
159
119
|
def toaster_popup(self, message: str, title: str = "", popup_type: PopupType = PopupType.Info) -> None:
|
|
160
120
|
"""
|
|
161
121
|
Display a toaster popup in the bottom right corner of the user's screen.
|
|
@@ -217,7 +177,7 @@ class CallbackUtil:
|
|
|
217
177
|
# Send the request to the user.
|
|
218
178
|
request = OptionDialogRequest(title, msg, options, default_option, user_can_cancel,
|
|
219
179
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
220
|
-
response: int = self.
|
|
180
|
+
response: int = self.__handle_dialog_request(request, self.callback.show_option_dialog)
|
|
221
181
|
return options[response]
|
|
222
182
|
|
|
223
183
|
def ok_dialog(self, title: str, msg: str) -> None:
|
|
@@ -233,7 +193,7 @@ class CallbackUtil:
|
|
|
233
193
|
def ok_cancel_dialog(self, title: str, msg: str, default_ok: bool = True) -> bool:
|
|
234
194
|
"""
|
|
235
195
|
Create an option dialog where the only options are "OK" and "Cancel". Doesn't allow the user to cancel the
|
|
236
|
-
dialog using the X
|
|
196
|
+
dialog using the X at the top right corner.
|
|
237
197
|
|
|
238
198
|
:param title: The title of the dialog.
|
|
239
199
|
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
@@ -245,7 +205,7 @@ class CallbackUtil:
|
|
|
245
205
|
def yes_no_dialog(self, title: str, msg: str, default_yes: bool = True) -> bool:
|
|
246
206
|
"""
|
|
247
207
|
Create an option dialog where the only options are "Yes" and "No". Doesn't allow the user to cancel the
|
|
248
|
-
dialog using the X
|
|
208
|
+
dialog using the X at the top right corner.
|
|
249
209
|
|
|
250
210
|
:param title: The title of the dialog.
|
|
251
211
|
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
@@ -254,46 +214,16 @@ class CallbackUtil:
|
|
|
254
214
|
"""
|
|
255
215
|
return self.option_dialog(title, msg, ["Yes", "No"], 0 if default_yes else 1, False) == "Yes"
|
|
256
216
|
|
|
257
|
-
# FR-47690: Added function.
|
|
258
|
-
def accept_decline_dialog(self, title: str, msg: str, default_accept: bool = True) -> bool:
|
|
259
|
-
"""
|
|
260
|
-
Create an option dialog where the only options are "Accept" and "Decline". Doesn't allow the user to cancel the
|
|
261
|
-
dialog using the X in the top right corner.
|
|
262
|
-
|
|
263
|
-
:param title: The title of the dialog.
|
|
264
|
-
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
265
|
-
:param default_accept: If true, "Accept" is the default choice. Otherwise, the default choice is "Decline".
|
|
266
|
-
:return: True if the user selected Accept. False if the user selected Decline.
|
|
267
|
-
"""
|
|
268
|
-
return self.option_dialog(title, msg, ["Accept", "Decline"], 0 if default_accept else 1, False) == "Accept"
|
|
269
|
-
|
|
270
|
-
# FR-47690: Added function.
|
|
271
|
-
def confirm_deny_dialog(self, title: str, msg: str, default_confirm: bool = True) -> bool:
|
|
272
|
-
"""
|
|
273
|
-
Create an option dialog where the only options are "Confirm" and "Deny". Doesn't allow the user to cancel the
|
|
274
|
-
dialog using the X in the top right corner.
|
|
275
|
-
|
|
276
|
-
:param title: The title of the dialog.
|
|
277
|
-
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
278
|
-
:param default_confirm: If true, "Confirm" is the default choice. Otherwise, the default choice is "Deny".
|
|
279
|
-
:return: True if the user selected Confirm. False if the user selected Deny.
|
|
280
|
-
"""
|
|
281
|
-
return self.option_dialog(title, msg, ["Confirm", "Deny"], 0 if default_confirm else 1, False) == "Confirm"
|
|
282
|
-
|
|
283
217
|
# CR-47310: Add a parameter to the list, input, selection, and e-sign dialog functions to control reprompting the
|
|
284
218
|
# user if no input/selection/valid credentials are provided.
|
|
285
|
-
# FR-47690: Added shortcut_single_option parameter. Updated with blank result handling behavior.
|
|
286
219
|
def list_dialog(self,
|
|
287
220
|
title: str,
|
|
288
221
|
options: Iterable[str],
|
|
289
222
|
multi_select: bool = False,
|
|
290
223
|
preselected_values: Iterable[str] | None = None,
|
|
291
224
|
*,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
blank_result_handling: BlankResultHandling = BlankResultHandling.DEFAULT,
|
|
295
|
-
repeat_message: str | None = "Please provide a selection to continue.",
|
|
296
|
-
cancel_message: str | None = "No selection was provided. Cancelling dialog.") -> list[str]:
|
|
225
|
+
require_selection: bool = False,
|
|
226
|
+
repeat_message: str | None = "Please provide a selection to continue.") -> list[str]:
|
|
297
227
|
"""
|
|
298
228
|
Create a list dialog with the given options for the user to choose from.
|
|
299
229
|
|
|
@@ -302,237 +232,33 @@ class CallbackUtil:
|
|
|
302
232
|
:param multi_select: Whether the user is able to select multiple options from the list.
|
|
303
233
|
:param preselected_values: A list of values that will already be selected when the list dialog is created. The
|
|
304
234
|
user can unselect these values if they want to.
|
|
305
|
-
:param
|
|
306
|
-
|
|
307
|
-
:param
|
|
308
|
-
|
|
309
|
-
:param blank_result_handling: Determine how to handle the result of a callback when the user provides a blank
|
|
310
|
-
result.
|
|
311
|
-
:param repeat_message: If blank_result_handling is REPEAT and a repeat_message is provided, then that message
|
|
312
|
-
appears as toaster text when the user provides a blank result.
|
|
313
|
-
:param cancel_message: If blank_result_handling is CANCEL and a cancel_message is provided, then that message
|
|
314
|
-
appears as toaster text when the user provides a blank result.
|
|
235
|
+
:param require_selection: If true, the request will be re-sent if the user submits the dialog without making
|
|
236
|
+
a selection.
|
|
237
|
+
:param repeat_message: If require_selection is true and a repeat_message is provided, then that message appears
|
|
238
|
+
as toaster text if the dialog is repeated.
|
|
315
239
|
:return: The list of options that the user selected.
|
|
316
240
|
"""
|
|
317
241
|
if not options:
|
|
318
242
|
raise SapioException("No options provided.")
|
|
319
|
-
options = list(options)
|
|
320
|
-
if len(options) == 1 and shortcut_single_option:
|
|
321
|
-
return [options[0]]
|
|
322
243
|
|
|
323
244
|
# Send the request to the user.
|
|
324
|
-
request = ListDialogRequest(title, multi_select, options,
|
|
245
|
+
request = ListDialogRequest(title, multi_select, list(options),
|
|
325
246
|
list(preselected_values) if preselected_values else None,
|
|
326
247
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
327
248
|
|
|
328
|
-
#
|
|
329
|
-
# set blank_result_handling to REPEAT.
|
|
330
|
-
if require_selection is True and blank_result_handling == BlankResultHandling.DEFAULT:
|
|
331
|
-
blank_result_handling = BlankResultHandling.REPEAT
|
|
332
|
-
def not_blank_func(r: list[str]) -> bool:
|
|
333
|
-
return bool(r)
|
|
334
|
-
return self.__send_dialog_blank_results(request, self.callback.show_list_dialog, not_blank_func,
|
|
335
|
-
blank_result_handling, repeat_message, cancel_message)
|
|
336
|
-
|
|
337
|
-
# FR-47690: Updated with blank result handling behavior.
|
|
338
|
-
def input_dialog(self,
|
|
339
|
-
title: str,
|
|
340
|
-
msg: str,
|
|
341
|
-
field: AbstractVeloxFieldDefinition,
|
|
342
|
-
*,
|
|
343
|
-
require_input = None,
|
|
344
|
-
blank_result_handling: BlankResultHandling = BlankResultHandling.DEFAULT,
|
|
345
|
-
repeat_message: str | None = "Please provide a value to continue.",
|
|
346
|
-
cancel_message: str | None = "No input was provided. Cancelling dialog.") -> FieldValue:
|
|
347
|
-
"""
|
|
348
|
-
Create an input dialog where the user must input data for a singular field.
|
|
349
|
-
|
|
350
|
-
:param title: The title of the dialog.
|
|
351
|
-
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
352
|
-
:param field: The definition for a field that the user must provide input to.
|
|
353
|
-
:param require_input: DEPRECATED. Use blank_result_handling with a value of BlankResultHandling.REPEAT
|
|
354
|
-
instead.
|
|
355
|
-
:param blank_result_handling: Determine how to handle the result of a callback when the user provides a blank
|
|
356
|
-
result.
|
|
357
|
-
:param repeat_message: If blank_result_handling is REPEAT and a repeat_message is provided, then that message
|
|
358
|
-
appears as toaster text when the user provides a blank result.
|
|
359
|
-
:param cancel_message: If blank_result_handling is CANCEL and a cancel_message is provided, then that message
|
|
360
|
-
appears as toaster text when the user provides a blank result.
|
|
361
|
-
:return: The response value from the user for the given field.
|
|
362
|
-
"""
|
|
363
|
-
# Send the request to the user.
|
|
364
|
-
request = InputDialogCriteria(title, msg, field,
|
|
365
|
-
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
366
|
-
|
|
367
|
-
# Reverse compatibility: If require_selection is true and blank_result_handling is not set, then
|
|
368
|
-
# set blank_result_handling to REPEAT.
|
|
369
|
-
handling = blank_result_handling
|
|
370
|
-
if require_input is True and handling == BlankResultHandling.DEFAULT:
|
|
371
|
-
handling = BlankResultHandling.REPEAT
|
|
372
|
-
if handling == BlankResultHandling.DEFAULT or handling is None:
|
|
373
|
-
handling = self._default_blank_result_handling
|
|
374
|
-
|
|
249
|
+
# If require_selection is true, repeat the request if the user didn't make a selection.
|
|
375
250
|
while True:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
raw_response = self.user.post('/clientcallback/showInputDialog', payload=request.to_json())
|
|
383
|
-
# A response status code of 204 is what represents a cancelled dialog.
|
|
384
|
-
if raw_response.status_code == 204:
|
|
385
|
-
raise SapioUserCancelledException()
|
|
386
|
-
self.user.raise_for_status(raw_response)
|
|
387
|
-
json_dct: dict | None = self.user.get_json_data_or_none(raw_response)
|
|
388
|
-
response: FieldValue = json_dct['result'] if json_dct else None
|
|
389
|
-
except ReadTimeout:
|
|
390
|
-
raise SapioDialogTimeoutException()
|
|
391
|
-
finally:
|
|
392
|
-
self.user.timeout_seconds = self._original_timeout
|
|
393
|
-
|
|
394
|
-
# String fields that the user didn't provide will return as an empty string instead of a None response.
|
|
395
|
-
is_str: bool = isinstance(response, str)
|
|
396
|
-
if (is_str and response) or (not is_str and response is not None):
|
|
397
|
-
return response
|
|
398
|
-
|
|
399
|
-
match handling:
|
|
400
|
-
case BlankResultHandling.CANCEL:
|
|
401
|
-
# If the user provided no selection, throw an exception.
|
|
402
|
-
if cancel_message:
|
|
403
|
-
self.toaster_popup(cancel_message, popup_type=PopupType.Warning)
|
|
404
|
-
raise SapioUserCancelledException()
|
|
405
|
-
case BlankResultHandling.REPEAT:
|
|
406
|
-
# If the user provided no selection, repeat the dialog.
|
|
407
|
-
# If a repeatMessage is provided, display it as a toaster popup.
|
|
408
|
-
if repeat_message:
|
|
409
|
-
self.toaster_popup(repeat_message, popup_type=PopupType.Warning)
|
|
410
|
-
case BlankResultHandling.RETURN:
|
|
411
|
-
# If the user provided no selection, return the blank result.
|
|
412
|
-
return response
|
|
413
|
-
|
|
414
|
-
def string_input_dialog(self,
|
|
415
|
-
title: str,
|
|
416
|
-
msg: str,
|
|
417
|
-
field_name: str,
|
|
418
|
-
default_value: str | None = None,
|
|
419
|
-
max_length: int | None = None,
|
|
420
|
-
editable: bool = True,
|
|
421
|
-
*,
|
|
422
|
-
require_input: bool = False,
|
|
423
|
-
repeat_message: str | None = "Please provide a value to continue.",
|
|
424
|
-
**kwargs) -> str:
|
|
425
|
-
"""
|
|
426
|
-
Create an input dialog where the user must input data for a singular text field.
|
|
427
|
-
|
|
428
|
-
:param title: The title of the dialog.
|
|
429
|
-
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
430
|
-
:param field_name: The name and display name of the string field.
|
|
431
|
-
:param default_value: The default value to place into the string field, if any.
|
|
432
|
-
:param max_length: The max length of the string value. If not provided, uses the length of the default value.
|
|
433
|
-
If neither this nor a default value are provided, defaults to 100 characters.
|
|
434
|
-
:param editable: Whether the field is editable by the user.
|
|
435
|
-
:param require_input: If true, the request will be re-sent if the user submits the dialog without making
|
|
436
|
-
a selection.
|
|
437
|
-
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
438
|
-
as toaster text if the dialog is repeated.
|
|
439
|
-
:param kwargs: Any additional keyword arguments to pass to the field definition.
|
|
440
|
-
:return: The string that the user input into the dialog.
|
|
441
|
-
"""
|
|
442
|
-
# FR-47690: Deprecated in favor of suggesting the use of the FieldBuilder to customize an input_dialog's field.
|
|
443
|
-
warnings.warn("Deprecated. Use the base input_dialog function and the FieldBuilder class to construct the "
|
|
444
|
-
"input field.", DeprecationWarning)
|
|
445
|
-
if max_length is None:
|
|
446
|
-
max_length = len(default_value) if default_value else 100
|
|
447
|
-
field = VeloxStringFieldDefinition("Input", field_name, field_name, default_value=default_value,
|
|
448
|
-
max_length=max_length, editable=editable, **kwargs)
|
|
449
|
-
return self.input_dialog(title, msg, field,
|
|
450
|
-
require_input=require_input, repeat_message=repeat_message)
|
|
451
|
-
|
|
452
|
-
def integer_input_dialog(self,
|
|
453
|
-
title: str,
|
|
454
|
-
msg: str,
|
|
455
|
-
field_name: str,
|
|
456
|
-
default_value: int = None,
|
|
457
|
-
min_value: int = -10000,
|
|
458
|
-
max_value: int = 10000,
|
|
459
|
-
editable: bool = True,
|
|
460
|
-
*,
|
|
461
|
-
require_input: bool = False,
|
|
462
|
-
repeat_message: str | None = "Please provide a value to continue.",
|
|
463
|
-
**kwargs) -> int:
|
|
464
|
-
"""
|
|
465
|
-
Create an input dialog where the user must input data for a singular integer field.
|
|
466
|
-
|
|
467
|
-
:param title: The title of the dialog.
|
|
468
|
-
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
469
|
-
:param field_name: The name and display name of the integer field.
|
|
470
|
-
:param default_value: The default value to place into the integer field. If not provided, defaults to the 0 or
|
|
471
|
-
the minimum value, whichever is higher.
|
|
472
|
-
:param min_value: The minimum allowed value of the input.
|
|
473
|
-
:param max_value: The maximum allowed value of the input.
|
|
474
|
-
:param editable: Whether the field is editable by the user.
|
|
475
|
-
:param require_input: If true, the request will be re-sent if the user submits the dialog without making
|
|
476
|
-
a selection.
|
|
477
|
-
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
478
|
-
as toaster text if the dialog is repeated.
|
|
479
|
-
:param kwargs: Any additional keyword arguments to pass to the field definition.
|
|
480
|
-
:return: The integer that the user input into the dialog.
|
|
481
|
-
"""
|
|
482
|
-
# FR-47690: Deprecated in favor of suggesting the use of the FieldBuilder to customize an input_dialog's field.
|
|
483
|
-
warnings.warn("Deprecated. Use the base input_dialog function and the FieldBuilder class to construct the "
|
|
484
|
-
"input field.", DeprecationWarning)
|
|
485
|
-
if default_value is None:
|
|
486
|
-
default_value = max(0, min_value)
|
|
487
|
-
field = VeloxIntegerFieldDefinition("Input", field_name, field_name, default_value=default_value,
|
|
488
|
-
min_value=min_value, max_value=max_value, editable=editable, **kwargs)
|
|
489
|
-
return self.input_dialog(title, msg, field,
|
|
490
|
-
require_input=require_input, repeat_message=repeat_message)
|
|
491
|
-
|
|
492
|
-
def double_input_dialog(self,
|
|
493
|
-
title: str,
|
|
494
|
-
msg: str,
|
|
495
|
-
field_name: str,
|
|
496
|
-
default_value: float = None,
|
|
497
|
-
min_value: float = -10000000,
|
|
498
|
-
max_value: float = 100000000,
|
|
499
|
-
editable: bool = True,
|
|
500
|
-
*,
|
|
501
|
-
require_input: bool = False,
|
|
502
|
-
repeat_message: str | None = "Please provide a value to continue.",
|
|
503
|
-
**kwargs) -> float:
|
|
504
|
-
"""
|
|
505
|
-
Create an input dialog where the user must input data for a singular double field.
|
|
506
|
-
|
|
507
|
-
:param title: The title of the dialog.
|
|
508
|
-
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
509
|
-
:param field_name: The name and display name of the double field.
|
|
510
|
-
:param default_value: The default value to place into the double field. If not provided, defaults to the 0 or
|
|
511
|
-
the minimum value, whichever is higher.
|
|
512
|
-
:param min_value: The minimum allowed value of the input.
|
|
513
|
-
:param max_value: The maximum allowed value of the input.
|
|
514
|
-
:param editable: Whether the field is editable by the user.
|
|
515
|
-
:param require_input: If true, the request will be re-sent if the user submits the dialog without making
|
|
516
|
-
a selection.
|
|
517
|
-
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
518
|
-
as toaster text if the dialog is repeated.
|
|
519
|
-
:param kwargs: Any additional keyword arguments to pass to the field definition.
|
|
520
|
-
:return: The float that the user input into the dialog.
|
|
521
|
-
"""
|
|
522
|
-
# FR-47690: Deprecated in favor of suggesting the use of the FieldBuilder to customize an input_dialog's field.
|
|
523
|
-
warnings.warn("Deprecated. Use the base input_dialog function and the FieldBuilder class to construct the "
|
|
524
|
-
"input field.", DeprecationWarning)
|
|
525
|
-
if default_value is None:
|
|
526
|
-
default_value = max(0., min_value)
|
|
527
|
-
field = VeloxDoubleFieldDefinition("Input", field_name, field_name, default_value=default_value,
|
|
528
|
-
min_value=min_value, max_value=max_value, editable=editable, **kwargs)
|
|
529
|
-
return self.input_dialog(title, msg, field,
|
|
530
|
-
require_input=require_input, repeat_message=repeat_message)
|
|
251
|
+
response: list[str] = self.__handle_dialog_request(request, self.callback.show_list_dialog)
|
|
252
|
+
if not require_selection or response:
|
|
253
|
+
break
|
|
254
|
+
if repeat_message:
|
|
255
|
+
self.toaster_popup(repeat_message, popup_type=PopupType.Warning)
|
|
256
|
+
return response
|
|
531
257
|
|
|
532
258
|
def form_dialog(self,
|
|
533
259
|
title: str,
|
|
534
260
|
msg: str,
|
|
535
|
-
fields:
|
|
261
|
+
fields: list[AbstractVeloxFieldDefinition],
|
|
536
262
|
values: FieldMap = None,
|
|
537
263
|
column_positions: dict[str, tuple[int, int]] = None,
|
|
538
264
|
*,
|
|
@@ -547,8 +273,7 @@ class CallbackUtil:
|
|
|
547
273
|
:param msg: The message to display at the top of the form. This can be formatted using HTML elements.
|
|
548
274
|
:param fields: The definitions of the fields to display in the form. Fields will be displayed in the order they
|
|
549
275
|
are provided in this list.
|
|
550
|
-
:param values: Sets the default values of the fields.
|
|
551
|
-
provided in this dictionary, it will be initialized with its default value.
|
|
276
|
+
:param values: Sets the default values of the fields.
|
|
552
277
|
:param column_positions: If a tuple is provided for a field name, alters that field's column position and column
|
|
553
278
|
span. (Field order is still determined by the fields list.)
|
|
554
279
|
:param data_type: The data type name for the temporary data type that will be created for this form.
|
|
@@ -562,23 +287,16 @@ class CallbackUtil:
|
|
|
562
287
|
# Build a temporary data type for the request.
|
|
563
288
|
temp_dt = self.__temp_dt_from_field_defs(data_type, display_name, plural_display_name, fields, column_positions)
|
|
564
289
|
|
|
565
|
-
# FR-47690: Set default values for fields that aren't present.
|
|
566
|
-
if values is None:
|
|
567
|
-
values = {}
|
|
568
|
-
for field in fields:
|
|
569
|
-
if field.data_field_name not in values:
|
|
570
|
-
values[field.data_field_name] = field.default_value
|
|
571
|
-
|
|
572
290
|
# Send the request to the user.
|
|
573
291
|
request = FormEntryDialogRequest(title, msg, temp_dt, values,
|
|
574
292
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
575
|
-
response: FieldMap = self.
|
|
293
|
+
response: FieldMap = self.__handle_dialog_request(request, self.callback.show_form_entry_dialog)
|
|
576
294
|
return response
|
|
577
295
|
|
|
578
296
|
def record_form_dialog(self,
|
|
579
297
|
title: str,
|
|
580
298
|
msg: str,
|
|
581
|
-
fields:
|
|
299
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
582
300
|
record: SapioRecord,
|
|
583
301
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
584
302
|
editable=None,
|
|
@@ -644,14 +362,14 @@ class CallbackUtil:
|
|
|
644
362
|
# Send the request to the user.
|
|
645
363
|
request = FormEntryDialogRequest(title, msg, temp_dt, values,
|
|
646
364
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
647
|
-
response: FieldMap = self.
|
|
365
|
+
response: FieldMap = self.__handle_dialog_request(request, self.callback.show_form_entry_dialog)
|
|
648
366
|
return response
|
|
649
367
|
|
|
650
368
|
# FR-47314: Create record form and table dialogs for updating or creating records.
|
|
651
369
|
def set_record_form_dialog(self,
|
|
652
370
|
title: str,
|
|
653
371
|
msg: str,
|
|
654
|
-
fields:
|
|
372
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
655
373
|
record: SapioRecord,
|
|
656
374
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
657
375
|
*,
|
|
@@ -694,7 +412,7 @@ class CallbackUtil:
|
|
|
694
412
|
def create_record_form_dialog(self,
|
|
695
413
|
title: str,
|
|
696
414
|
msg: str,
|
|
697
|
-
fields:
|
|
415
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
698
416
|
wrapper_type: type[WrappedType] | str,
|
|
699
417
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
700
418
|
*,
|
|
@@ -738,17 +456,176 @@ class CallbackUtil:
|
|
|
738
456
|
default_modifier=default_modifier, field_modifiers=field_modifiers)
|
|
739
457
|
return record
|
|
740
458
|
|
|
459
|
+
def input_dialog(self,
|
|
460
|
+
title: str,
|
|
461
|
+
msg: str,
|
|
462
|
+
field: AbstractVeloxFieldDefinition,
|
|
463
|
+
*,
|
|
464
|
+
require_input: bool = False,
|
|
465
|
+
repeat_message: str | None = "Please provide a value to continue.") -> FieldValue:
|
|
466
|
+
"""
|
|
467
|
+
Create an input dialog where the user must input data for a singular field.
|
|
468
|
+
|
|
469
|
+
:param title: The title of the dialog.
|
|
470
|
+
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
471
|
+
:param field: The definition for a field that the user must provide input to.
|
|
472
|
+
:param require_input: If true, the request will be re-sent if the user submits the dialog without providing an
|
|
473
|
+
input field value.
|
|
474
|
+
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
475
|
+
as toaster text if the dialog is repeated.
|
|
476
|
+
:return: The response value from the user for the given field.
|
|
477
|
+
"""
|
|
478
|
+
# Send the request to the user.
|
|
479
|
+
request = InputDialogCriteria(title, msg, field,
|
|
480
|
+
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
481
|
+
|
|
482
|
+
# If require_input is true, repeat the request if the user didn't provide a field value.
|
|
483
|
+
while True:
|
|
484
|
+
try:
|
|
485
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
486
|
+
# It's not possible to distinguish between the user cancelling this dialog and submitting the dialog
|
|
487
|
+
# with no input if the ClientCallback show_input_dialog function is used, as both cases just return
|
|
488
|
+
# None. Therefore, in order to be able to make that distinction, we need to call the endpoint without
|
|
489
|
+
# ClientCallback and get the raw response object.
|
|
490
|
+
raw_response = self.user.post('/clientcallback/showInputDialog', payload=request.to_json())
|
|
491
|
+
# A response status code of 204 is what represents a cancelled dialog.
|
|
492
|
+
if raw_response.status_code == 204:
|
|
493
|
+
raise SapioUserCancelledException()
|
|
494
|
+
self.user.raise_for_status(raw_response)
|
|
495
|
+
json_dct: dict | None = self.user.get_json_data_or_none(raw_response)
|
|
496
|
+
response: FieldValue = json_dct['result'] if json_dct else None
|
|
497
|
+
except ReadTimeout:
|
|
498
|
+
raise SapioDialogTimeoutException()
|
|
499
|
+
finally:
|
|
500
|
+
self.user.timeout_seconds = self._original_timeout
|
|
501
|
+
# String fields that the user didn't provide will return as an empty string instead of a None response.
|
|
502
|
+
is_str: bool = isinstance(response, str)
|
|
503
|
+
if not require_input or (is_str and response) or (not is_str and response is not None):
|
|
504
|
+
break
|
|
505
|
+
if repeat_message:
|
|
506
|
+
self.toaster_popup(repeat_message, popup_type=PopupType.Warning)
|
|
507
|
+
return response
|
|
508
|
+
|
|
509
|
+
def string_input_dialog(self,
|
|
510
|
+
title: str,
|
|
511
|
+
msg: str,
|
|
512
|
+
field_name: str,
|
|
513
|
+
default_value: str | None = None,
|
|
514
|
+
max_length: int | None = None,
|
|
515
|
+
editable: bool = True,
|
|
516
|
+
*,
|
|
517
|
+
require_input: bool = False,
|
|
518
|
+
repeat_message: str | None = "Please provide a value to continue.",
|
|
519
|
+
**kwargs) -> str:
|
|
520
|
+
"""
|
|
521
|
+
Create an input dialog where the user must input data for a singular text field.
|
|
522
|
+
|
|
523
|
+
:param title: The title of the dialog.
|
|
524
|
+
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
525
|
+
:param field_name: The name and display name of the string field.
|
|
526
|
+
:param default_value: The default value to place into the string field, if any.
|
|
527
|
+
:param max_length: The max length of the string value. If not provided, uses the length of the default value.
|
|
528
|
+
If neither this nor a default value are provided, defaults to 100 characters.
|
|
529
|
+
:param editable: Whether the field is editable by the user.
|
|
530
|
+
:param require_input: If true, the request will be re-sent if the user submits the dialog without making
|
|
531
|
+
a selection.
|
|
532
|
+
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
533
|
+
as toaster text if the dialog is repeated.
|
|
534
|
+
:param kwargs: Any additional keyword arguments to pass to the field definition.
|
|
535
|
+
:return: The string that the user input into the dialog.
|
|
536
|
+
"""
|
|
537
|
+
if max_length is None:
|
|
538
|
+
max_length = len(default_value) if default_value else 100
|
|
539
|
+
field = VeloxStringFieldDefinition("Input", field_name, field_name, default_value=default_value,
|
|
540
|
+
max_length=max_length, editable=editable, **kwargs)
|
|
541
|
+
return self.input_dialog(title, msg, field,
|
|
542
|
+
require_input=require_input, repeat_message=repeat_message)
|
|
543
|
+
|
|
544
|
+
def integer_input_dialog(self,
|
|
545
|
+
title: str,
|
|
546
|
+
msg: str,
|
|
547
|
+
field_name: str,
|
|
548
|
+
default_value: int = None,
|
|
549
|
+
min_value: int = -10000,
|
|
550
|
+
max_value: int = 10000,
|
|
551
|
+
editable: bool = True,
|
|
552
|
+
*,
|
|
553
|
+
require_input: bool = False,
|
|
554
|
+
repeat_message: str | None = "Please provide a value to continue.",
|
|
555
|
+
**kwargs) -> int:
|
|
556
|
+
"""
|
|
557
|
+
Create an input dialog where the user must input data for a singular integer field.
|
|
558
|
+
|
|
559
|
+
:param title: The title of the dialog.
|
|
560
|
+
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
561
|
+
:param field_name: The name and display name of the integer field.
|
|
562
|
+
:param default_value: The default value to place into the integer field. If not provided, defaults to the 0 or
|
|
563
|
+
the minimum value, whichever is higher.
|
|
564
|
+
:param min_value: The minimum allowed value of the input.
|
|
565
|
+
:param max_value: The maximum allowed value of the input.
|
|
566
|
+
:param editable: Whether the field is editable by the user.
|
|
567
|
+
:param require_input: If true, the request will be re-sent if the user submits the dialog without making
|
|
568
|
+
a selection.
|
|
569
|
+
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
570
|
+
as toaster text if the dialog is repeated.
|
|
571
|
+
:param kwargs: Any additional keyword arguments to pass to the field definition.
|
|
572
|
+
:return: The integer that the user input into the dialog.
|
|
573
|
+
"""
|
|
574
|
+
if default_value is None:
|
|
575
|
+
default_value = max(0, min_value)
|
|
576
|
+
field = VeloxIntegerFieldDefinition("Input", field_name, field_name, default_value=default_value,
|
|
577
|
+
min_value=min_value, max_value=max_value, editable=editable, **kwargs)
|
|
578
|
+
return self.input_dialog(title, msg, field,
|
|
579
|
+
require_input=require_input, repeat_message=repeat_message)
|
|
580
|
+
|
|
581
|
+
def double_input_dialog(self,
|
|
582
|
+
title: str,
|
|
583
|
+
msg: str,
|
|
584
|
+
field_name: str,
|
|
585
|
+
default_value: float = None,
|
|
586
|
+
min_value: float = -10000000,
|
|
587
|
+
max_value: float = 100000000,
|
|
588
|
+
editable: bool = True,
|
|
589
|
+
*,
|
|
590
|
+
require_input: bool = False,
|
|
591
|
+
repeat_message: str | None = "Please provide a value to continue.",
|
|
592
|
+
**kwargs) -> float:
|
|
593
|
+
"""
|
|
594
|
+
Create an input dialog where the user must input data for a singular double field.
|
|
595
|
+
|
|
596
|
+
:param title: The title of the dialog.
|
|
597
|
+
:param msg: The message to display in the dialog. This can be formatted using HTML elements.
|
|
598
|
+
:param field_name: The name and display name of the double field.
|
|
599
|
+
:param default_value: The default value to place into the double field. If not provided, defaults to the 0 or
|
|
600
|
+
the minimum value, whichever is higher.
|
|
601
|
+
:param min_value: The minimum allowed value of the input.
|
|
602
|
+
:param max_value: The maximum allowed value of the input.
|
|
603
|
+
:param editable: Whether the field is editable by the user.
|
|
604
|
+
:param require_input: If true, the request will be re-sent if the user submits the dialog without making
|
|
605
|
+
a selection.
|
|
606
|
+
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
607
|
+
as toaster text if the dialog is repeated.
|
|
608
|
+
:param kwargs: Any additional keyword arguments to pass to the field definition.
|
|
609
|
+
:return: The float that the user input into the dialog.
|
|
610
|
+
"""
|
|
611
|
+
if default_value is None:
|
|
612
|
+
default_value = max(0., min_value)
|
|
613
|
+
field = VeloxDoubleFieldDefinition("Input", field_name, field_name, default_value=default_value,
|
|
614
|
+
min_value=min_value, max_value=max_value, editable=editable, **kwargs)
|
|
615
|
+
return self.input_dialog(title, msg, field,
|
|
616
|
+
require_input=require_input, repeat_message=repeat_message)
|
|
617
|
+
|
|
741
618
|
def table_dialog(self,
|
|
742
619
|
title: str,
|
|
743
620
|
msg: str,
|
|
744
|
-
fields:
|
|
745
|
-
values:
|
|
621
|
+
fields: list[AbstractVeloxFieldDefinition],
|
|
622
|
+
values: list[FieldMap],
|
|
746
623
|
*,
|
|
747
624
|
data_type: DataTypeIdentifier = "Default",
|
|
748
625
|
display_name: str | None = None,
|
|
749
626
|
plural_display_name: str | None = None,
|
|
750
627
|
group_by: FieldIdentifier | None = None,
|
|
751
|
-
image_data:
|
|
628
|
+
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
752
629
|
"""
|
|
753
630
|
Create a table dialog where the user may input data into the fields of the table. Requires that the caller
|
|
754
631
|
provide the definitions of every field in the table.
|
|
@@ -757,8 +634,7 @@ class CallbackUtil:
|
|
|
757
634
|
:param msg: The message to display at the top of the form. This can be formatted using HTML elements.
|
|
758
635
|
:param fields: The definitions of the fields to display as table columns. Fields will be displayed in the order
|
|
759
636
|
they are provided in this list.
|
|
760
|
-
:param values: The values to set for each row of the table.
|
|
761
|
-
number of rows to create in the table, with each row using the default values of the field definitions.
|
|
637
|
+
:param values: The values to set for each row of the table.
|
|
762
638
|
:param group_by: If provided, the created table dialog will be grouped by the field with this name by default.
|
|
763
639
|
The user may remove this grouping if they want to.
|
|
764
640
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
@@ -771,18 +647,9 @@ class CallbackUtil:
|
|
|
771
647
|
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
772
648
|
value from the user for that field for each row.
|
|
773
649
|
"""
|
|
774
|
-
# FR-47690: Accept an integer as the values parameter to create a table with that many rows.
|
|
775
|
-
if isinstance(values, int):
|
|
776
|
-
values: list[dict[str, Any]] = [{} for _ in range(values)]
|
|
777
650
|
if not values:
|
|
778
651
|
raise SapioException("No values provided.")
|
|
779
652
|
|
|
780
|
-
# FR-47690: Set default values for fields that aren't present.
|
|
781
|
-
for row in values:
|
|
782
|
-
for field in fields:
|
|
783
|
-
if field.data_field_name not in row:
|
|
784
|
-
row[field.data_field_name] = field.default_value
|
|
785
|
-
|
|
786
653
|
# Convert the group_by parameter to a field name.
|
|
787
654
|
if group_by is not None:
|
|
788
655
|
group_by: str = AliasUtil.to_data_field_name(group_by)
|
|
@@ -793,24 +660,23 @@ class CallbackUtil:
|
|
|
793
660
|
temp_dt.record_image_assignable = bool(image_data)
|
|
794
661
|
|
|
795
662
|
# Send the request to the user.
|
|
796
|
-
request = TableEntryDialogRequest(title, msg, temp_dt,
|
|
663
|
+
request = TableEntryDialogRequest(title, msg, temp_dt, values,
|
|
797
664
|
record_image_data_list=image_data, group_by_field=group_by,
|
|
798
665
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
799
|
-
response: list[FieldMap] = self.
|
|
666
|
+
response: list[FieldMap] = self.__handle_dialog_request(request, self.callback.show_table_entry_dialog)
|
|
800
667
|
return response
|
|
801
668
|
|
|
802
669
|
def record_table_dialog(self,
|
|
803
670
|
title: str,
|
|
804
671
|
msg: str,
|
|
805
|
-
fields:
|
|
806
|
-
records:
|
|
672
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
673
|
+
records: list[SapioRecord],
|
|
807
674
|
editable=None,
|
|
808
675
|
*,
|
|
809
676
|
default_modifier: FieldModifier | None = None,
|
|
810
677
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
811
678
|
group_by: FieldIdentifier | None = None,
|
|
812
|
-
image_data:
|
|
813
|
-
index_field: str | None = None) -> list[FieldMap]:
|
|
679
|
+
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
814
680
|
"""
|
|
815
681
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
816
682
|
a given list of records of a singular type.
|
|
@@ -840,12 +706,6 @@ class CallbackUtil:
|
|
|
840
706
|
The user may remove this grouping if they want to.
|
|
841
707
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
842
708
|
the image data list corresponds to the element at the same index in the records list.
|
|
843
|
-
:param index_field: If provided, the returned field maps will contain a field with this name that is equal to
|
|
844
|
-
the record ID of the record at the same index in the records list. This can be used to map the results
|
|
845
|
-
back to the original records. This is used instead of using a RecordId field, as the RecordId field has
|
|
846
|
-
special behavior in the system that can cause issues if the given records are uncommitted record models
|
|
847
|
-
with negative record IDs, meaning we don't want to have a RecordId field in the field maps provided to the
|
|
848
|
-
system.
|
|
849
709
|
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
850
710
|
value from the user for that field for each row.
|
|
851
711
|
"""
|
|
@@ -857,10 +717,7 @@ class CallbackUtil:
|
|
|
857
717
|
if not records:
|
|
858
718
|
raise SapioException("No records provided.")
|
|
859
719
|
data_type: str = AliasUtil.to_singular_data_type_name(records)
|
|
860
|
-
|
|
861
|
-
field_map_list: list[FieldMap] = self.__get_indexed_field_maps(records, index_field)
|
|
862
|
-
else:
|
|
863
|
-
field_map_list: list[FieldMap] = AliasUtil.to_field_map_list(records)
|
|
720
|
+
field_map_list: list[FieldMap] = AliasUtil.to_field_map_list(records)
|
|
864
721
|
|
|
865
722
|
# Convert the group_by parameter to a field name.
|
|
866
723
|
if group_by is not None:
|
|
@@ -886,20 +743,20 @@ class CallbackUtil:
|
|
|
886
743
|
request = TableEntryDialogRequest(title, msg, temp_dt, field_map_list,
|
|
887
744
|
record_image_data_list=image_data, group_by_field=group_by,
|
|
888
745
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
889
|
-
response: list[FieldMap] = self.
|
|
746
|
+
response: list[FieldMap] = self.__handle_dialog_request(request, self.callback.show_table_entry_dialog)
|
|
890
747
|
return response
|
|
891
748
|
|
|
892
749
|
# FR-47314: Create record form and table dialogs for updating or creating records.
|
|
893
750
|
def set_record_table_dialog(self,
|
|
894
751
|
title: str,
|
|
895
752
|
msg: str,
|
|
896
|
-
fields:
|
|
897
|
-
records:
|
|
753
|
+
fields: list[FieldValue] | DataTypeLayoutIdentifier,
|
|
754
|
+
records: list[SapioRecord],
|
|
898
755
|
*,
|
|
899
756
|
default_modifier: FieldModifier | None = None,
|
|
900
757
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
901
758
|
group_by: FieldIdentifier | None = None,
|
|
902
|
-
image_data:
|
|
759
|
+
image_data: list[bytes] | None = None):
|
|
903
760
|
"""
|
|
904
761
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
905
762
|
a given list of records of a singular type. After the user submits this dialog, the values that the user
|
|
@@ -930,35 +787,27 @@ class CallbackUtil:
|
|
|
930
787
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
931
788
|
the image data list corresponds to the element at the same index in the records list.
|
|
932
789
|
"""
|
|
933
|
-
# Index the records with a field name that is the current time in milliseconds. This is done to avoid
|
|
934
|
-
# collisions with any existing field names.
|
|
935
|
-
index_field: str = f"_{TimeUtil.now_in_millis()}"
|
|
936
790
|
results: list[FieldMap] = self.record_table_dialog(title, msg, fields, records,
|
|
937
791
|
default_modifier=default_modifier,
|
|
938
792
|
field_modifiers=field_modifiers,
|
|
939
|
-
group_by=group_by, image_data=image_data
|
|
940
|
-
index_field=index_field)
|
|
793
|
+
group_by=group_by, image_data=image_data)
|
|
941
794
|
records_by_id: dict[int, SapioRecord] = self.rec_handler.map_by_id(records)
|
|
942
795
|
for result in results:
|
|
943
|
-
|
|
944
|
-
records_by_id[index].set_field_values(result)
|
|
796
|
+
records_by_id[result["RecordId"]].set_field_values(result)
|
|
945
797
|
|
|
946
|
-
# FR-47690: Updated with blank result handling behavior.
|
|
947
798
|
def create_record_table_dialog(self,
|
|
948
799
|
title: str,
|
|
949
800
|
msg: str,
|
|
950
|
-
fields:
|
|
801
|
+
fields: list[FieldValue] | DataTypeLayoutIdentifier,
|
|
951
802
|
wrapper_type: type[WrappedType] | str,
|
|
952
803
|
count: int | tuple[int, int],
|
|
953
804
|
*,
|
|
954
805
|
default_modifier: FieldModifier | None = None,
|
|
955
806
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
956
807
|
group_by: FieldIdentifier | None = None,
|
|
957
|
-
image_data:
|
|
958
|
-
require_input =
|
|
959
|
-
|
|
960
|
-
repeat_message: str | None = "Please provide a value to continue.",
|
|
961
|
-
cancel_message: str | None = "No value was provided. Cancelling dialog.") \
|
|
808
|
+
image_data: list[bytes] | None = None,
|
|
809
|
+
require_input: bool = False,
|
|
810
|
+
repeat_message: str | None = "Please provide a value to continue.") \
|
|
962
811
|
-> list[WrappedType] | list[PyRecordModel]:
|
|
963
812
|
"""
|
|
964
813
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
@@ -992,18 +841,13 @@ class CallbackUtil:
|
|
|
992
841
|
The user may remove this grouping if they want to.
|
|
993
842
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
994
843
|
the image data list corresponds to the element at the same index in the records list.
|
|
995
|
-
:param require_input:
|
|
996
|
-
|
|
997
|
-
:param
|
|
998
|
-
|
|
999
|
-
:param repeat_message: If blank_result_handling is REPEAT and a repeat_message is provided, then that message
|
|
1000
|
-
appears as toaster text when the user provides a blank result.
|
|
1001
|
-
:param cancel_message: If blank_result_handling is CANCEL and a cancel_message is provided, then that message
|
|
1002
|
-
appears as toaster text when the user provides a blank result.
|
|
844
|
+
:param require_input: If true and the user is prompted to input the number of records to create, the request
|
|
845
|
+
will be re-sent if the user submits the dialog without making a selection.
|
|
846
|
+
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
847
|
+
as toaster text if the record count dialog is repeated.
|
|
1003
848
|
:return: A list of the newly created records.
|
|
1004
849
|
"""
|
|
1005
|
-
count: int = self.__prompt_for_count(count, wrapper_type, require_input,
|
|
1006
|
-
cancel_message)
|
|
850
|
+
count: int = self.__prompt_for_count(count, wrapper_type, require_input, repeat_message)
|
|
1007
851
|
if count <= 0:
|
|
1008
852
|
return []
|
|
1009
853
|
records: list[WrappedType] | list[PyRecordModel] = self.rec_handler.add_models(wrapper_type, count)
|
|
@@ -1016,15 +860,14 @@ class CallbackUtil:
|
|
|
1016
860
|
def record_adaptive_dialog(self,
|
|
1017
861
|
title: str,
|
|
1018
862
|
msg: str,
|
|
1019
|
-
fields:
|
|
1020
|
-
records:
|
|
863
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
864
|
+
records: list[SapioRecord],
|
|
1021
865
|
*,
|
|
1022
866
|
default_modifier: FieldModifier | None = None,
|
|
1023
867
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
1024
868
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
1025
869
|
group_by: FieldIdentifier | None = None,
|
|
1026
|
-
image_data:
|
|
1027
|
-
index_field: str | None = None) -> list[FieldMap]:
|
|
870
|
+
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
1028
871
|
"""
|
|
1029
872
|
Create a dialog where the user may input data into the specified fields. The dialog is constructed from
|
|
1030
873
|
a given list of records of a singular type.
|
|
@@ -1060,12 +903,6 @@ class CallbackUtil:
|
|
|
1060
903
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
1061
904
|
the image data list corresponds to the element at the same index in the records list. Only used if the
|
|
1062
905
|
adaptive dialog becomes a table.
|
|
1063
|
-
:param index_field: If provided, the returned field maps will contain a field with this name that is equal to
|
|
1064
|
-
the record ID of the record at the same index in the records list. This can be used to map the results
|
|
1065
|
-
back to the original records. This is used instead of using a RecordId field, as the RecordId field has
|
|
1066
|
-
special behavior in the system that can cause issues if the given records are uncommitted record models
|
|
1067
|
-
with negative record IDs, meaning we don't want to have a RecordId field in the field maps provided to the
|
|
1068
|
-
system. Only used if the adaptive dialog becomes a table.
|
|
1069
906
|
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
1070
907
|
value from the user for that field for each row. Even if a form was displayed, the field values will still
|
|
1071
908
|
be returned in a list.
|
|
@@ -1074,23 +911,23 @@ class CallbackUtil:
|
|
|
1074
911
|
if not count:
|
|
1075
912
|
raise SapioException("No records provided.")
|
|
1076
913
|
if count == 1:
|
|
1077
|
-
return [self.record_form_dialog(title, msg, fields,
|
|
914
|
+
return [self.record_form_dialog(title, msg, fields, records[0], column_positions,
|
|
1078
915
|
default_modifier=default_modifier, field_modifiers=field_modifiers)]
|
|
1079
916
|
return self.record_table_dialog(title, msg, fields, records,
|
|
1080
917
|
default_modifier=default_modifier, field_modifiers=field_modifiers,
|
|
1081
|
-
group_by=group_by, image_data=image_data
|
|
918
|
+
group_by=group_by, image_data=image_data)
|
|
1082
919
|
|
|
1083
920
|
def set_record_adaptive_dialog(self,
|
|
1084
921
|
title: str,
|
|
1085
922
|
msg: str,
|
|
1086
|
-
fields:
|
|
1087
|
-
records:
|
|
923
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
924
|
+
records: list[SapioRecord],
|
|
1088
925
|
*,
|
|
1089
926
|
default_modifier: FieldModifier | None = None,
|
|
1090
927
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
1091
928
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
1092
929
|
group_by: FieldIdentifier | None = None,
|
|
1093
|
-
image_data:
|
|
930
|
+
image_data: list[bytes] | None = None) -> None:
|
|
1094
931
|
"""
|
|
1095
932
|
Create a dialog where the user may input data into the fields of the dialog. The dialog is constructed from
|
|
1096
933
|
a given list of records of a singular type. After the user submits this dialog, the values that the user
|
|
@@ -1132,18 +969,17 @@ class CallbackUtil:
|
|
|
1132
969
|
if not count:
|
|
1133
970
|
raise SapioException("No records provided.")
|
|
1134
971
|
if count == 1:
|
|
1135
|
-
self.set_record_form_dialog(title, msg, fields,
|
|
972
|
+
self.set_record_form_dialog(title, msg, fields, records[0], column_positions,
|
|
1136
973
|
default_modifier=default_modifier, field_modifiers=field_modifiers)
|
|
1137
974
|
else:
|
|
1138
975
|
self.set_record_table_dialog(title, msg, fields, records,
|
|
1139
976
|
default_modifier=default_modifier, field_modifiers=field_modifiers,
|
|
1140
977
|
group_by=group_by, image_data=image_data)
|
|
1141
978
|
|
|
1142
|
-
# FR-47690: Updated with blank result handling behavior.
|
|
1143
979
|
def create_record_adaptive_dialog(self,
|
|
1144
980
|
title: str,
|
|
1145
981
|
msg: str,
|
|
1146
|
-
fields:
|
|
982
|
+
fields: list[FieldValue] | DataTypeLayoutIdentifier,
|
|
1147
983
|
wrapper_type: type[WrappedType] | str,
|
|
1148
984
|
count: int | tuple[int, int],
|
|
1149
985
|
*,
|
|
@@ -1151,11 +987,9 @@ class CallbackUtil:
|
|
|
1151
987
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
1152
988
|
column_positions: dict[str, tuple[int, int]] | None = None,
|
|
1153
989
|
group_by: FieldIdentifier | None = None,
|
|
1154
|
-
image_data:
|
|
1155
|
-
require_input =
|
|
1156
|
-
|
|
1157
|
-
repeat_message: str | None = "Please provide a value to continue.",
|
|
1158
|
-
cancel_message: str | None = "No value was provided. Cancelling dialog.") \
|
|
990
|
+
image_data: list[bytes] | None = None,
|
|
991
|
+
require_input: bool = False,
|
|
992
|
+
repeat_message: str | None = "Please provide a value to continue.") \
|
|
1159
993
|
-> list[WrappedType]:
|
|
1160
994
|
"""
|
|
1161
995
|
Create a dialog where the user may input data into the specified fields. The dialog is constructed from
|
|
@@ -1196,19 +1030,14 @@ class CallbackUtil:
|
|
|
1196
1030
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
1197
1031
|
the image data list corresponds to the element at the same index in the records list. Only used if the
|
|
1198
1032
|
adaptive dialog becomes a table.
|
|
1199
|
-
:param require_input:
|
|
1200
|
-
|
|
1201
|
-
:param
|
|
1202
|
-
|
|
1203
|
-
:param repeat_message: If blank_result_handling is REPEAT and a repeat_message is provided, then that message
|
|
1204
|
-
appears as toaster text when the user provides a blank result.
|
|
1205
|
-
:param cancel_message: If blank_result_handling is CANCEL and a cancel_message is provided, then that message
|
|
1206
|
-
appears as toaster text when the user provides a blank result.
|
|
1033
|
+
:param require_input: If true and the user is prompted to input the number of records to create, the request
|
|
1034
|
+
will be re-sent if the user submits the dialog without making a selection.
|
|
1035
|
+
:param repeat_message: If require_input is true and a repeat_message is provided, then that message appears
|
|
1036
|
+
as toaster text if the record count dialog is repeated.
|
|
1207
1037
|
:return: A list of the newly created records. Even if a form was displayed, the created record will still be
|
|
1208
1038
|
returned in a list.
|
|
1209
1039
|
"""
|
|
1210
|
-
count: int = self.__prompt_for_count(count, wrapper_type, require_input,
|
|
1211
|
-
cancel_message)
|
|
1040
|
+
count: int = self.__prompt_for_count(count, wrapper_type, require_input, repeat_message)
|
|
1212
1041
|
if count <= 0:
|
|
1213
1042
|
return []
|
|
1214
1043
|
if count == 1:
|
|
@@ -1218,20 +1047,17 @@ class CallbackUtil:
|
|
|
1218
1047
|
default_modifier=default_modifier, field_modifiers=field_modifiers,
|
|
1219
1048
|
group_by=group_by, image_data=image_data)
|
|
1220
1049
|
|
|
1221
|
-
# FR-47690: Add group_by and image_data parameters.
|
|
1222
1050
|
def multi_type_table_dialog(self,
|
|
1223
1051
|
title: str,
|
|
1224
1052
|
msg: str,
|
|
1225
|
-
fields:
|
|
1226
|
-
row_contents:
|
|
1053
|
+
fields: list[tuple[DataTypeIdentifier, FieldIdentifier] | AbstractVeloxFieldDefinition],
|
|
1054
|
+
row_contents: list[list[SapioRecord | FieldMap]],
|
|
1227
1055
|
*,
|
|
1228
1056
|
default_modifier: FieldModifier | None = None,
|
|
1229
1057
|
field_modifiers: dict[FieldIdentifier, FieldModifier] | None = None,
|
|
1230
1058
|
data_type: DataTypeIdentifier = "Default",
|
|
1231
1059
|
display_name: str | None = None,
|
|
1232
|
-
plural_display_name: str | None = None
|
|
1233
|
-
group_by: FieldIdentifier | None = None,
|
|
1234
|
-
image_data: list[bytes] | None = None) -> list[FieldMap]:
|
|
1060
|
+
plural_display_name: str | None = None) -> list[FieldMap]:
|
|
1235
1061
|
"""
|
|
1236
1062
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
1237
1063
|
a given list of records of multiple data types or field maps. Provided field names must match with field names
|
|
@@ -1285,10 +1111,6 @@ class CallbackUtil:
|
|
|
1285
1111
|
name.
|
|
1286
1112
|
:param plural_display_name: The plural display name for the temporary data type. If not provided, defaults to
|
|
1287
1113
|
the display name + "s".
|
|
1288
|
-
:param group_by: If provided, the created table dialog will be grouped by the field with this name by default.
|
|
1289
|
-
The user may remove this grouping if they want to.
|
|
1290
|
-
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
1291
|
-
the image data list corresponds to the element at the same index in the values list.
|
|
1292
1114
|
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
1293
1115
|
value from the user for that field for each row.
|
|
1294
1116
|
"""
|
|
@@ -1353,49 +1175,31 @@ class CallbackUtil:
|
|
|
1353
1175
|
field_names.add(name)
|
|
1354
1176
|
|
|
1355
1177
|
# Get the values for each row.
|
|
1356
|
-
# FR-47690: Updated this for loop to better match the Java implementation.
|
|
1357
1178
|
values: list[dict[str, FieldValue]] = []
|
|
1358
1179
|
for row in row_contents:
|
|
1359
1180
|
# The final values for this row:
|
|
1360
1181
|
row_values: dict[str, FieldValue] = {}
|
|
1361
1182
|
|
|
1362
|
-
# Map the records for this row by their data type. If a field map is provided,
|
|
1363
|
-
|
|
1364
|
-
row_records: dict[str, SapioRecord] = {}
|
|
1365
|
-
temp_values: FieldMap = {}
|
|
1183
|
+
# Map the records for this row by their data type. If a field map is provided, its data type is Default.
|
|
1184
|
+
row_records: dict[str, SapioRecord | FieldMap] = {}
|
|
1366
1185
|
for rec in row:
|
|
1367
1186
|
# Toss out null elements.
|
|
1368
1187
|
if rec is None:
|
|
1369
1188
|
continue
|
|
1370
1189
|
# Map records to their data type name. Map field maps to Default.
|
|
1371
|
-
dt: str = "Default" if isinstance(rec, dict) else AliasUtil.
|
|
1372
|
-
if
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
if dt in row_records:
|
|
1377
|
-
raise SapioException(f"The data type \"{dt}\" appears more than once in the given row contents.")
|
|
1378
|
-
row_records[dt] = rec
|
|
1190
|
+
dt: str = "Default" if isinstance(rec, dict) else AliasUtil.to_data_type_names(rec)
|
|
1191
|
+
# Warn if the same data type name appears more than once.
|
|
1192
|
+
if dt in row_records:
|
|
1193
|
+
raise SapioException(f"The data type \"{dt}\" appears more than once in the given row contents.")
|
|
1194
|
+
row_records[dt] = rec
|
|
1379
1195
|
|
|
1380
1196
|
# Get the field values from the above records.
|
|
1381
1197
|
for field in final_fields:
|
|
1382
|
-
value: Any | None = None
|
|
1383
|
-
|
|
1384
1198
|
# Find the object that corresponds to this field given its data type name.
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
if
|
|
1388
|
-
|
|
1389
|
-
# FR-47690: If a value is not provided, then use the default value of the field definition.
|
|
1390
|
-
if fd in temp_values:
|
|
1391
|
-
value = temp_values.get(fd)
|
|
1392
|
-
else:
|
|
1393
|
-
value = field.default_value
|
|
1394
|
-
elif dt in row_records:
|
|
1395
|
-
record: SapioRecord = row_records[dt]
|
|
1396
|
-
# If the record is not null, get the value from the record.
|
|
1397
|
-
if record is not None:
|
|
1398
|
-
value = record.get_field_value(fd)
|
|
1199
|
+
record: SapioRecord | FieldMap | None = row_records.get(field.data_type_name)
|
|
1200
|
+
# This could be either a record, a field map, or null. Convert any records to field maps.
|
|
1201
|
+
if not isinstance(record, dict) and record is not None:
|
|
1202
|
+
record: FieldMap | None = AliasUtil.to_field_map(record)
|
|
1399
1203
|
|
|
1400
1204
|
# Find out if this field had its data type prepended to it. If this is the case, then we need to find
|
|
1401
1205
|
# the true data field name before retrieving the value from the field map.
|
|
@@ -1404,22 +1208,16 @@ class CallbackUtil:
|
|
|
1404
1208
|
name = name.split(".")[1]
|
|
1405
1209
|
|
|
1406
1210
|
# Set the value for this particular field.
|
|
1407
|
-
row_values[
|
|
1211
|
+
row_values[field.data_field_name] = record.get(name) if record else None
|
|
1408
1212
|
values.append(row_values)
|
|
1409
1213
|
|
|
1410
1214
|
# Build a temporary data type for the request.
|
|
1411
1215
|
temp_dt = self.__temp_dt_from_field_defs(data_type, display_name, plural_display_name, final_fields, None)
|
|
1412
|
-
temp_dt.record_image_assignable = bool(image_data)
|
|
1413
|
-
|
|
1414
|
-
# Convert the group_by parameter to a field name.
|
|
1415
|
-
if group_by is not None:
|
|
1416
|
-
group_by: str = AliasUtil.to_data_field_name(group_by)
|
|
1417
1216
|
|
|
1418
1217
|
# Send the request to the user.
|
|
1419
1218
|
request = TableEntryDialogRequest(title, msg, temp_dt, values,
|
|
1420
|
-
record_image_data_list=image_data, group_by_field=group_by,
|
|
1421
1219
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
1422
|
-
response: list[FieldMap] = self.
|
|
1220
|
+
response: list[FieldMap] = self.__handle_dialog_request(request, self.callback.show_table_entry_dialog)
|
|
1423
1221
|
return response
|
|
1424
1222
|
|
|
1425
1223
|
def record_view_dialog(self,
|
|
@@ -1428,7 +1226,7 @@ class CallbackUtil:
|
|
|
1428
1226
|
layout: DataTypeLayoutIdentifier = None,
|
|
1429
1227
|
minimized: bool = False,
|
|
1430
1228
|
access_level: FormAccessLevel | None = None,
|
|
1431
|
-
plugin_path_list:
|
|
1229
|
+
plugin_path_list: list[str] | None = None) -> None:
|
|
1432
1230
|
"""
|
|
1433
1231
|
Create an IDV dialog for the given record. This IDV may use an existing layout already defined in the system,
|
|
1434
1232
|
and can be created to allow the user to edit the field in the IDV, or to be read-only for the user to review.
|
|
@@ -1456,30 +1254,26 @@ class CallbackUtil:
|
|
|
1456
1254
|
# Send the request to the user.
|
|
1457
1255
|
request = DataRecordDialogRequest(title, record, layout, minimized, access_level, plugin_path_list,
|
|
1458
1256
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
1459
|
-
response: bool = self.
|
|
1257
|
+
response: bool = self.__handle_dialog_request(request, self.callback.data_record_form_view_dialog)
|
|
1460
1258
|
# The __handle_dialog_request function only throws a cancelled exception if the response is None, but in
|
|
1461
1259
|
# this case we also want to throw if the response is False.
|
|
1462
1260
|
if not response:
|
|
1463
1261
|
raise SapioUserCancelledException()
|
|
1464
1262
|
|
|
1465
1263
|
# CR-47326: Allow the selection dialog functions to preselect rows/records in the table.
|
|
1466
|
-
# FR-47690: Added shortcut_single_option parameter. Updated with blank result handling behavior.
|
|
1467
1264
|
def selection_dialog(self,
|
|
1468
1265
|
msg: str,
|
|
1469
|
-
fields:
|
|
1470
|
-
values:
|
|
1266
|
+
fields: list[AbstractVeloxFieldDefinition],
|
|
1267
|
+
values: list[FieldMap],
|
|
1471
1268
|
multi_select: bool = True,
|
|
1472
|
-
preselected_rows:
|
|
1269
|
+
preselected_rows: list[FieldMap | RecordIdentifier] | None = None,
|
|
1473
1270
|
*,
|
|
1474
1271
|
data_type: DataTypeIdentifier = "Default",
|
|
1475
1272
|
display_name: str | None = None,
|
|
1476
1273
|
plural_display_name: str | None = None,
|
|
1477
|
-
image_data:
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
blank_result_handling: BlankResultHandling = BlankResultHandling.DEFAULT,
|
|
1481
|
-
repeat_message: str | None = "Please provide a selection to continue.",
|
|
1482
|
-
cancel_message: str | None = "No selection was made. Cancelling dialog.") -> list[FieldMap]:
|
|
1274
|
+
image_data: list[bytes] | None = None,
|
|
1275
|
+
require_selection: bool = False,
|
|
1276
|
+
repeat_message: str | None = "Please provide a selection to continue.") -> list[FieldMap]:
|
|
1483
1277
|
"""
|
|
1484
1278
|
Create a selection dialog for a list of field maps for the user to choose from. Requires that the caller
|
|
1485
1279
|
provide the definitions of every field in the table.
|
|
@@ -1502,23 +1296,14 @@ class CallbackUtil:
|
|
|
1502
1296
|
the display name + "s".
|
|
1503
1297
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
1504
1298
|
the image data list corresponds to the element at the same index in the values list.
|
|
1505
|
-
:param
|
|
1506
|
-
|
|
1507
|
-
:param
|
|
1508
|
-
|
|
1509
|
-
:param blank_result_handling: Determine how to handle the result of a callback when the user provides a blank
|
|
1510
|
-
result.
|
|
1511
|
-
:param repeat_message: If blank_result_handling is REPEAT and a repeat_message is provided, then that message
|
|
1512
|
-
appears as toaster text when the user provides a blank result.
|
|
1513
|
-
:param cancel_message: If blank_result_handling is CANCEL and a cancel_message is provided, then that message
|
|
1514
|
-
appears as toaster text when the user provides a blank result.
|
|
1299
|
+
:param require_selection: If true, the request will be re-sent if the user submits the dialog without making
|
|
1300
|
+
a selection.
|
|
1301
|
+
:param repeat_message: If require_selection is true and a repeat_message is provided, then that message appears
|
|
1302
|
+
as toaster text if the dialog is repeated.
|
|
1515
1303
|
:return: A list of field maps corresponding to the chosen input field maps.
|
|
1516
1304
|
"""
|
|
1517
1305
|
if not values:
|
|
1518
1306
|
raise SapioException("No values provided.")
|
|
1519
|
-
values = list(values)
|
|
1520
|
-
if len(values) == 1 and shortcut_single_option:
|
|
1521
|
-
return [values[0]]
|
|
1522
1307
|
|
|
1523
1308
|
if preselected_rows:
|
|
1524
1309
|
# Confirm that the provided field maps are validly configured to allow the use of preselected rows.
|
|
@@ -1544,7 +1329,6 @@ class CallbackUtil:
|
|
|
1544
1329
|
|
|
1545
1330
|
# Add a RecordId definition to the fields if one is not already present. This is necessary for the
|
|
1546
1331
|
# pre-selected records parameter to function.
|
|
1547
|
-
fields = list(fields)
|
|
1548
1332
|
if "RecordId" not in [x.data_field_name for x in fields]:
|
|
1549
1333
|
builder = FieldBuilder(data_type)
|
|
1550
1334
|
fields.append(builder.long_field("RecordId", abstract_info=AnyFieldInfo(visible=False)))
|
|
@@ -1554,31 +1338,27 @@ class CallbackUtil:
|
|
|
1554
1338
|
temp_dt.record_image_assignable = bool(image_data)
|
|
1555
1339
|
|
|
1556
1340
|
# Send the request to the user.
|
|
1557
|
-
request = TempTableSelectionRequest(temp_dt, msg,
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
# FR-47690: Added shortcut_single_option parameter. Updated with blank result handling behavior.
|
|
1341
|
+
request = TempTableSelectionRequest(temp_dt, msg, values, image_data, preselected_rows, multi_select)
|
|
1342
|
+
# If require_selection is true, repeat the request if the user didn't make a selection.
|
|
1343
|
+
while True:
|
|
1344
|
+
response: list[FieldMap] = self.__handle_dialog_request(request,
|
|
1345
|
+
self.callback.show_temp_table_selection_dialog)
|
|
1346
|
+
if not require_selection or response:
|
|
1347
|
+
break
|
|
1348
|
+
if repeat_message:
|
|
1349
|
+
self.toaster_popup(repeat_message, popup_type=PopupType.Warning)
|
|
1350
|
+
return response
|
|
1351
|
+
|
|
1569
1352
|
def record_selection_dialog(self,
|
|
1570
1353
|
msg: str,
|
|
1571
|
-
fields:
|
|
1572
|
-
records:
|
|
1354
|
+
fields: list[FieldIdentifier | FieldFilterCriteria] | DataTypeLayoutIdentifier,
|
|
1355
|
+
records: list[SapioRecord],
|
|
1573
1356
|
multi_select: bool = True,
|
|
1574
|
-
preselected_records:
|
|
1357
|
+
preselected_records: list[RecordIdentifier] | None = None,
|
|
1575
1358
|
*,
|
|
1576
|
-
image_data:
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
blank_result_handling: BlankResultHandling = BlankResultHandling.DEFAULT,
|
|
1580
|
-
repeat_message: str | None = "Please provide a selection to continue.",
|
|
1581
|
-
cancel_message: str | None = "No selection was made. Cancelling dialog.") \
|
|
1359
|
+
image_data: list[bytes] | None = None,
|
|
1360
|
+
require_selection: bool = False,
|
|
1361
|
+
repeat_message: str | None = "Please provide a selection to continue.") \
|
|
1582
1362
|
-> list[SapioRecord]:
|
|
1583
1363
|
"""
|
|
1584
1364
|
Create a record selection dialog for a list of records for the user to choose from. Provided field names must
|
|
@@ -1600,24 +1380,15 @@ class CallbackUtil:
|
|
|
1600
1380
|
record IDs are provided, the dialog will automatically allow multi-selection of records.
|
|
1601
1381
|
:param image_data: The bytes to the images that should be displayed in the rows of the table. Each element in
|
|
1602
1382
|
the image data list corresponds to the element at the same index in the records list.
|
|
1603
|
-
:param
|
|
1604
|
-
|
|
1605
|
-
:param
|
|
1606
|
-
|
|
1607
|
-
:param blank_result_handling: Determine how to handle the result of a callback when the user provides a blank
|
|
1608
|
-
result.
|
|
1609
|
-
:param repeat_message: If blank_result_handling is REPEAT and a repeat_message is provided, then that message
|
|
1610
|
-
appears as toaster text when the user provides a blank result.
|
|
1611
|
-
:param cancel_message: If blank_result_handling is CANCEL and a cancel_message is provided, then that message
|
|
1612
|
-
appears as toaster text when the user provides a blank result.
|
|
1383
|
+
:param require_selection: If true, the request will be re-sent if the user submits the dialog without making
|
|
1384
|
+
a selection.
|
|
1385
|
+
:param repeat_message: If require_selection is true and a repeat_message is provided, then that message appears
|
|
1386
|
+
as toaster text if the dialog is repeated.
|
|
1613
1387
|
:return: A list of the selected records.
|
|
1614
1388
|
"""
|
|
1615
1389
|
# Get the data type name and field values from the provided records.
|
|
1616
1390
|
if not records:
|
|
1617
1391
|
raise SapioException("No records provided.")
|
|
1618
|
-
records = list(records)
|
|
1619
|
-
if len(records) == 1 and shortcut_single_option:
|
|
1620
|
-
return [records[0]]
|
|
1621
1392
|
data_type: str = AliasUtil.to_singular_data_type_name(records)
|
|
1622
1393
|
field_map_list: list[FieldMap] = AliasUtil.to_field_map_list(records, include_record_id=True)
|
|
1623
1394
|
|
|
@@ -1644,17 +1415,14 @@ class CallbackUtil:
|
|
|
1644
1415
|
|
|
1645
1416
|
# Send the request to the user.
|
|
1646
1417
|
request = TempTableSelectionRequest(temp_dt, msg, field_map_list, image_data, preselected_records, multi_select)
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
self.callback.show_temp_table_selection_dialog,
|
|
1656
|
-
not_blank_func, blank_result_handling,
|
|
1657
|
-
repeat_message, cancel_message)
|
|
1418
|
+
# If require_selection is true, repeat the request if the user didn't make a selection.
|
|
1419
|
+
while True:
|
|
1420
|
+
response: list[FieldMap] = self.__handle_dialog_request(request,
|
|
1421
|
+
self.callback.show_temp_table_selection_dialog)
|
|
1422
|
+
if not require_selection or response:
|
|
1423
|
+
break
|
|
1424
|
+
if repeat_message:
|
|
1425
|
+
self.toaster_popup(repeat_message, popup_type=PopupType.Warning)
|
|
1658
1426
|
|
|
1659
1427
|
# Map the field maps in the response back to the record they come from, returning the chosen record instead of
|
|
1660
1428
|
# the chosen field map.
|
|
@@ -1665,25 +1433,22 @@ class CallbackUtil:
|
|
|
1665
1433
|
return ret_list
|
|
1666
1434
|
|
|
1667
1435
|
# CR-47377: Add allow_creation and default_creation_number to cover new parameters of this request type from 24.12.
|
|
1668
|
-
# FR-47690: Updated with blank result handling behavior.
|
|
1669
1436
|
def input_selection_dialog(self,
|
|
1670
1437
|
wrapper_type: type[WrappedType] | str,
|
|
1671
1438
|
msg: str,
|
|
1672
1439
|
multi_select: bool = True,
|
|
1673
1440
|
only_key_fields: bool = False,
|
|
1674
|
-
search_types:
|
|
1441
|
+
search_types: list[SearchType] | None = None,
|
|
1675
1442
|
scan_criteria: ScanToSelectCriteria | None = None,
|
|
1676
1443
|
custom_search: CustomReport | CustomReportCriteria | str | None = None,
|
|
1677
|
-
preselected_records:
|
|
1678
|
-
record_blacklist:
|
|
1679
|
-
record_whitelist:
|
|
1444
|
+
preselected_records: list[RecordIdentifier] | None = None,
|
|
1445
|
+
record_blacklist: list[RecordIdentifier] | None = None,
|
|
1446
|
+
record_whitelist: list[RecordIdentifier] | None = None,
|
|
1680
1447
|
allow_creation: bool = False,
|
|
1681
1448
|
default_creation_number: int = 1,
|
|
1682
1449
|
*,
|
|
1683
|
-
require_selection =
|
|
1684
|
-
|
|
1685
|
-
repeat_message: str | None = "Please provide a selection to continue.",
|
|
1686
|
-
cancel_message: str | None = "No selection was made. Cancelling dialog.") \
|
|
1450
|
+
require_selection: bool = False,
|
|
1451
|
+
repeat_message: str | None = "Please provide a selection to continue.") \
|
|
1687
1452
|
-> list[WrappedType] | list[PyRecordModel]:
|
|
1688
1453
|
"""
|
|
1689
1454
|
Display a table of records that exist in the system matching the given data type and filter criteria and have
|
|
@@ -1725,14 +1490,10 @@ class CallbackUtil:
|
|
|
1725
1490
|
than 1, then multi-selection must be true. The data type definition of the records being created must have
|
|
1726
1491
|
"Prompt for Number to Add" set to true in order to allow the user to select how many records to create, as
|
|
1727
1492
|
otherwise user will only ever be able to create one record at a time.
|
|
1728
|
-
:param require_selection:
|
|
1729
|
-
|
|
1730
|
-
:param
|
|
1731
|
-
|
|
1732
|
-
:param repeat_message: If blank_result_handling is REPEAT and a repeat_message is provided, then that message
|
|
1733
|
-
appears as toaster text when the user provides a blank result.
|
|
1734
|
-
:param cancel_message: If blank_result_handling is CANCEL and a cancel_message is provided, then that message
|
|
1735
|
-
appears as toaster text when the user provides a blank result.
|
|
1493
|
+
:param require_selection: If true, the request will be re-sent if the user submits the dialog without making
|
|
1494
|
+
a selection.
|
|
1495
|
+
:param repeat_message: If require_selection is true and a repeat_message is provided, then that message appears
|
|
1496
|
+
as toaster text if the dialog is repeated.
|
|
1736
1497
|
:return: A list of the records selected by the user in the dialog, wrapped as record models using the provided
|
|
1737
1498
|
wrapper.
|
|
1738
1499
|
"""
|
|
@@ -1758,25 +1519,23 @@ class CallbackUtil:
|
|
|
1758
1519
|
request = InputSelectionRequest(data_type, msg, search_types, only_key_fields, record_blacklist,
|
|
1759
1520
|
record_whitelist, preselected_records, custom_search, scan_criteria,
|
|
1760
1521
|
multi_select, allow_creation, default_creation_number)
|
|
1522
|
+
# If require_selection is true, repeat the request if the user didn't make a selection.
|
|
1523
|
+
while True:
|
|
1524
|
+
response: list[DataRecord] = self.__handle_dialog_request(request,
|
|
1525
|
+
self.callback.show_input_selection_dialog)
|
|
1526
|
+
if not require_selection or response:
|
|
1527
|
+
break
|
|
1528
|
+
if repeat_message:
|
|
1529
|
+
self.toaster_popup(repeat_message, popup_type=PopupType.Warning)
|
|
1530
|
+
return self.rec_handler.wrap_models(response, wrapper_type)
|
|
1761
1531
|
|
|
1762
|
-
# Reverse compatibility: If require_selection is true and blank_result_handling is not set, then
|
|
1763
|
-
# set blank_result_handling to REPEAT.
|
|
1764
|
-
if require_selection is True and blank_result_handling == BlankResultHandling.DEFAULT:
|
|
1765
|
-
blank_result_handling = BlankResultHandling.REPEAT
|
|
1766
|
-
def not_blank_func(r: list[DataRecord]) -> bool:
|
|
1767
|
-
return bool(r)
|
|
1768
|
-
return self.__send_dialog_blank_results(request, self.callback.show_input_selection_dialog, not_blank_func,
|
|
1769
|
-
blank_result_handling, repeat_message, cancel_message)
|
|
1770
|
-
|
|
1771
|
-
# FR-47690: Deprecated the require_authentication parameter.
|
|
1772
|
-
# noinspection PyUnusedLocal
|
|
1773
1532
|
def esign_dialog(self,
|
|
1774
1533
|
title: str,
|
|
1775
1534
|
msg: str,
|
|
1776
1535
|
show_comment: bool = True,
|
|
1777
|
-
additional_fields:
|
|
1536
|
+
additional_fields: list[AbstractVeloxFieldDefinition] | None = None,
|
|
1778
1537
|
*,
|
|
1779
|
-
require_authentication =
|
|
1538
|
+
require_authentication: bool = False) -> ESigningResponsePojo:
|
|
1780
1539
|
"""
|
|
1781
1540
|
Create an e-sign dialog for the user to interact with.
|
|
1782
1541
|
|
|
@@ -1786,7 +1545,8 @@ class CallbackUtil:
|
|
|
1786
1545
|
user is required to provide an action.
|
|
1787
1546
|
:param additional_fields: Field definitions for additional fields to display in the dialog, for if there is
|
|
1788
1547
|
other information you wish to gather from the user alongside the e-sign.
|
|
1789
|
-
:param require_authentication:
|
|
1548
|
+
:param require_authentication: If true, the request will be re-sent if the user submits the dialog with invalid
|
|
1549
|
+
credentials.
|
|
1790
1550
|
:return: An e-sign response object containing information about the e-sign attempt.
|
|
1791
1551
|
"""
|
|
1792
1552
|
# Construct a temporary data type if any additional fields are provided.
|
|
@@ -1800,9 +1560,10 @@ class CallbackUtil:
|
|
|
1800
1560
|
# Send the request to the user.
|
|
1801
1561
|
request = ESigningRequestPojo(title, msg, show_comment, temp_dt,
|
|
1802
1562
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
1563
|
+
# If require_authentication is true, repeat the request if the user didn't provide valid credentials.
|
|
1803
1564
|
while True:
|
|
1804
|
-
response: ESigningResponsePojo = self.
|
|
1805
|
-
if response.authenticated:
|
|
1565
|
+
response: ESigningResponsePojo = self.__handle_dialog_request(request, self.callback.show_esign_dialog)
|
|
1566
|
+
if not require_authentication or response.authenticated:
|
|
1806
1567
|
break
|
|
1807
1568
|
# This matches the OOB behavior.
|
|
1808
1569
|
self.toaster_popup("Incorrect username/password", popup_type=PopupType.Error)
|
|
@@ -1811,7 +1572,7 @@ class CallbackUtil:
|
|
|
1811
1572
|
popup_type=PopupType.Error)
|
|
1812
1573
|
return response
|
|
1813
1574
|
|
|
1814
|
-
def request_file(self, title: str, exts:
|
|
1575
|
+
def request_file(self, title: str, exts: list[str] | None = None,
|
|
1815
1576
|
show_image_editor: bool = False, show_camera_button: bool = False) -> tuple[str, bytes]:
|
|
1816
1577
|
"""
|
|
1817
1578
|
Request a single file from the user.
|
|
@@ -1838,13 +1599,13 @@ class CallbackUtil:
|
|
|
1838
1599
|
|
|
1839
1600
|
# Send the request to the user.
|
|
1840
1601
|
request = FilePromptRequest(title, show_image_editor, ",".join(exts), show_camera_button)
|
|
1841
|
-
file_path: str = self.
|
|
1602
|
+
file_path: str = self.__handle_dialog_request(request, self.callback.show_file_dialog, data_sink=do_consume)
|
|
1842
1603
|
|
|
1843
1604
|
# Verify that each of the file given matches the expected extension(s).
|
|
1844
1605
|
self.__verify_file(file_path, sink.data, exts)
|
|
1845
1606
|
return file_path, sink.data
|
|
1846
1607
|
|
|
1847
|
-
def request_files(self, title: str, exts:
|
|
1608
|
+
def request_files(self, title: str, exts: list[str] | None = None,
|
|
1848
1609
|
show_image_editor: bool = False, show_camera_button: bool = False) -> dict[str, bytes]:
|
|
1849
1610
|
"""
|
|
1850
1611
|
Request multiple files from the user.
|
|
@@ -1863,7 +1624,7 @@ class CallbackUtil:
|
|
|
1863
1624
|
|
|
1864
1625
|
# Send the request to the user.
|
|
1865
1626
|
request = MultiFilePromptRequest(title, show_image_editor, ",".join(exts), show_camera_button)
|
|
1866
|
-
file_paths: list[str] = self.
|
|
1627
|
+
file_paths: list[str] = self.__handle_dialog_request(request, self.callback.show_multi_file_dialog)
|
|
1867
1628
|
|
|
1868
1629
|
# Verify that each of the files given match the expected extension(s).
|
|
1869
1630
|
ret_dict: dict[str, bytes] = {}
|
|
@@ -1876,7 +1637,7 @@ class CallbackUtil:
|
|
|
1876
1637
|
return ret_dict
|
|
1877
1638
|
|
|
1878
1639
|
@staticmethod
|
|
1879
|
-
def __verify_file(file_path: str, file_bytes: bytes, allowed_extensions:
|
|
1640
|
+
def __verify_file(file_path: str, file_bytes: bytes, allowed_extensions: list[str]) -> None:
|
|
1880
1641
|
"""
|
|
1881
1642
|
Verify that the provided file was read (i.e. the file path and file bytes aren't None or empty) and that it
|
|
1882
1643
|
has the correct file extension. Raises a user error exception if something about the file is incorrect.
|
|
@@ -1887,11 +1648,10 @@ class CallbackUtil:
|
|
|
1887
1648
|
"""
|
|
1888
1649
|
if file_path is None or len(file_path) == 0 or file_bytes is None or len(file_bytes) == 0:
|
|
1889
1650
|
raise SapioUserErrorException("Empty file provided or file unable to be read.")
|
|
1890
|
-
if allowed_extensions:
|
|
1651
|
+
if len(allowed_extensions) != 0:
|
|
1891
1652
|
matches: bool = False
|
|
1892
1653
|
for ext in allowed_extensions:
|
|
1893
|
-
|
|
1894
|
-
if file_path.casefold().endswith("." + ext.lstrip(".").casefold()):
|
|
1654
|
+
if file_path.endswith("." + ext.lstrip(".")):
|
|
1895
1655
|
matches = True
|
|
1896
1656
|
break
|
|
1897
1657
|
if matches is False:
|
|
@@ -1905,8 +1665,8 @@ class CallbackUtil:
|
|
|
1905
1665
|
:param file_name: The name of the file.
|
|
1906
1666
|
:param file_data: The data of the file, provided as either a string or as a bytes array.
|
|
1907
1667
|
"""
|
|
1908
|
-
|
|
1909
|
-
|
|
1668
|
+
data = io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data)
|
|
1669
|
+
self.callback.send_file(file_name, False, data)
|
|
1910
1670
|
|
|
1911
1671
|
def write_zip_file(self, zip_name: str, files: dict[str, str | bytes]) -> None:
|
|
1912
1672
|
"""
|
|
@@ -1915,38 +1675,16 @@ class CallbackUtil:
|
|
|
1915
1675
|
:param zip_name: The name of the zip file.
|
|
1916
1676
|
:param files: A dictionary of the files to add to the zip file.
|
|
1917
1677
|
"""
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
@staticmethod
|
|
1921
|
-
def __get_indexed_field_maps(records: Iterable[SapioRecord], index_field: str) -> list[FieldMap]:
|
|
1922
|
-
"""
|
|
1923
|
-
For dialogs that accept multiple records, we may want to be able to match the returned results back to the
|
|
1924
|
-
records that they're for. In this case, we need to add an index to each record so that we can match them back
|
|
1925
|
-
to the original records. We can't use the RecordId field, as new record models have negative record IDs that
|
|
1926
|
-
cause the callback dialogs to bug out if the RecordId field is present and negative.
|
|
1927
|
-
|
|
1928
|
-
:param records: The records to return indexed field maps of.
|
|
1929
|
-
:param index_field: The name of the field to use as the index. Make sure that this field doesn't exist on the
|
|
1930
|
-
records, as then it will overwrite the existing value.
|
|
1931
|
-
:return: A list of field maps for the records, with an index field added to each. The value of the index on
|
|
1932
|
-
each field map is the record's record ID (even if it's a record model with a negative ID).
|
|
1933
|
-
"""
|
|
1934
|
-
ret_val: list[FieldMap] = []
|
|
1935
|
-
for record in records:
|
|
1936
|
-
field_map: FieldMap = AliasUtil.to_field_map(record)
|
|
1937
|
-
field_map[index_field] = AliasUtil.to_record_id(record)
|
|
1938
|
-
ret_val.append(field_map)
|
|
1939
|
-
return ret_val
|
|
1678
|
+
data = io.BytesIO(FileUtil.zip_files(files))
|
|
1679
|
+
self.callback.send_file(zip_name, False, data)
|
|
1940
1680
|
|
|
1941
1681
|
@staticmethod
|
|
1942
1682
|
def __temp_dt_from_field_defs(data_type: DataTypeIdentifier, display_name: str | None,
|
|
1943
|
-
plural_display_name: str | None, fields:
|
|
1683
|
+
plural_display_name: str | None, fields: list[AbstractVeloxFieldDefinition],
|
|
1944
1684
|
column_positions: dict[str, tuple[int, int]] | None) -> TemporaryDataType:
|
|
1945
1685
|
"""
|
|
1946
1686
|
Construct a Temporary Data Type definition from a provided list of field definitions for use in a callback.
|
|
1947
1687
|
"""
|
|
1948
|
-
if not fields:
|
|
1949
|
-
raise SapioException("No fields provided to create a temporary data type.")
|
|
1950
1688
|
# Get the data type name as a string from the parameters, and set the display name and plural display name if
|
|
1951
1689
|
# they haven't been set.
|
|
1952
1690
|
data_type: str = AliasUtil.to_data_type_name(data_type)
|
|
@@ -1976,7 +1714,7 @@ class CallbackUtil:
|
|
|
1976
1714
|
builder.add_field(field_def, column, span)
|
|
1977
1715
|
return builder.get_temporary_data_type()
|
|
1978
1716
|
|
|
1979
|
-
def __temp_dt_from_field_names(self, data_type: str, fields:
|
|
1717
|
+
def __temp_dt_from_field_names(self, data_type: str, fields: list[FieldIdentifier | FieldFilterCriteria],
|
|
1980
1718
|
column_positions: dict[str, tuple[int, int]] | None,
|
|
1981
1719
|
default_modifier: FieldModifier, field_modifiers: dict[str, FieldModifier]) \
|
|
1982
1720
|
-> TemporaryDataType:
|
|
@@ -1993,7 +1731,6 @@ class CallbackUtil:
|
|
|
1993
1731
|
|
|
1994
1732
|
# Determine if any FieldFilterCriteria were provided. If so, remove them from the fields list so that it
|
|
1995
1733
|
# contains only field identifiers.
|
|
1996
|
-
fields = list(fields)
|
|
1997
1734
|
filter_criteria: list[FieldFilterCriteria] = [x for x in fields if isinstance(x, FieldFilterCriteria)]
|
|
1998
1735
|
for criteria in filter_criteria:
|
|
1999
1736
|
fields.remove(criteria)
|
|
@@ -2066,10 +1803,8 @@ class CallbackUtil:
|
|
|
2066
1803
|
temp_dt.set_field_definition(modifier.modify_field(field_def))
|
|
2067
1804
|
return temp_dt
|
|
2068
1805
|
|
|
2069
|
-
# FR-47690: Updated with blank result handling behavior.
|
|
2070
1806
|
def __prompt_for_count(self, count: tuple[int, int] | int, wrapper_type: type[WrappedType] | str,
|
|
2071
|
-
require_input: bool,
|
|
2072
|
-
cancel_message: str) -> int:
|
|
1807
|
+
require_input: bool, repeat_message: str) -> int:
|
|
2073
1808
|
"""
|
|
2074
1809
|
Given a count value, if it is a tuple representing an allowable range of values for a number of records to
|
|
2075
1810
|
create, prompt the user to input the exact count to use. If the count is already a single integer, simply
|
|
@@ -2082,14 +1817,8 @@ class CallbackUtil:
|
|
|
2082
1817
|
plural: str = self.dt_cache.get_plural_display_name(AliasUtil.to_data_type_name(wrapper_type))
|
|
2083
1818
|
min_val, max_val = count
|
|
2084
1819
|
msg: str = f"How many {plural} should be created? ({min_val} to {max_val})"
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
default_value=min_val)
|
|
2088
|
-
count: int = self.input_dialog(f"Create {plural}", msg, count_field,
|
|
2089
|
-
require_input=require_input, blank_result_handling=blank_result_handling,
|
|
2090
|
-
repeat_message=repeat_message, cancel_message=cancel_message)
|
|
2091
|
-
if count is None:
|
|
2092
|
-
count = 0
|
|
1820
|
+
count: int = self.integer_input_dialog(f"Create {plural}", msg, "Count", min_val, min_val, max_val,
|
|
1821
|
+
require_input=require_input, repeat_message=repeat_message)
|
|
2093
1822
|
return count
|
|
2094
1823
|
|
|
2095
1824
|
def __to_layout(self, data_type: str, layout: DataTypeLayoutIdentifier) -> DataTypeLayout | None:
|
|
@@ -2154,18 +1883,19 @@ class CallbackUtil:
|
|
|
2154
1883
|
raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
|
|
2155
1884
|
return field_def
|
|
2156
1885
|
|
|
2157
|
-
def
|
|
1886
|
+
def __handle_dialog_request(self, request: Any, func: Callable, **kwargs) -> Any:
|
|
2158
1887
|
"""
|
|
2159
1888
|
Send a client callback request to the user that creates a dialog.
|
|
2160
1889
|
|
|
2161
1890
|
This function handles updating the user object's request timeout to match the request timeout of this
|
|
2162
1891
|
CallbackUtil for the duration of the dialog.
|
|
2163
1892
|
If the dialog times out then a SapioDialogTimeoutException is thrown.
|
|
1893
|
+
If the user cancels the dialog then a SapioUserCancelledException is thrown.
|
|
2164
1894
|
|
|
2165
1895
|
:param request: The client callback request to send to the user.
|
|
2166
1896
|
:param func: The ClientCallback function to call with the given request as input.
|
|
2167
1897
|
:param kwargs: Additional keywords for the provided function call.
|
|
2168
|
-
:return: The response from the client callback.
|
|
1898
|
+
:return: The response from the client callback, if one was received.
|
|
2169
1899
|
"""
|
|
2170
1900
|
try:
|
|
2171
1901
|
self.user.timeout_seconds = self.timeout_seconds
|
|
@@ -2174,73 +1904,10 @@ class CallbackUtil:
|
|
|
2174
1904
|
raise SapioDialogTimeoutException()
|
|
2175
1905
|
finally:
|
|
2176
1906
|
self.user.timeout_seconds = self._original_timeout
|
|
2177
|
-
return response
|
|
2178
|
-
|
|
2179
|
-
def __send_dialog(self, request: Any, func: Callable, **kwargs) -> Any:
|
|
2180
|
-
"""
|
|
2181
|
-
Send a client callback request to the user that creates a dialog.
|
|
2182
|
-
|
|
2183
|
-
This function handles updating the user object's request timeout to match the request timeout of this
|
|
2184
|
-
CallbackUtil for the duration of the dialog.
|
|
2185
|
-
If the dialog times out then a SapioDialogTimeoutException is thrown.
|
|
2186
|
-
If the user cancels the dialog then a SapioUserCancelledException is thrown.
|
|
2187
|
-
|
|
2188
|
-
:param request: The client callback request to send to the user.
|
|
2189
|
-
:param func: The ClientCallback function to call with the given request as input.
|
|
2190
|
-
:param kwargs: Additional keywords for the provided function call.
|
|
2191
|
-
:return: The response from the client callback, if one was received.
|
|
2192
|
-
"""
|
|
2193
|
-
response: Any | None = self.__handle_timeout(func, request, **kwargs)
|
|
2194
1907
|
if response is None:
|
|
2195
1908
|
raise SapioUserCancelledException()
|
|
2196
1909
|
return response
|
|
2197
1910
|
|
|
2198
|
-
def __send_dialog_blank_results(self, request: Any, func: Callable, not_blank_func: Callable,
|
|
2199
|
-
handling: BlankResultHandling,
|
|
2200
|
-
repeat_message: str | None, cancel_message: str | None, **kwargs):
|
|
2201
|
-
"""
|
|
2202
|
-
Send a client callback request to the user that creates a dialog.
|
|
2203
|
-
|
|
2204
|
-
This function handles updating the user object's request timeout to match the request timeout of this
|
|
2205
|
-
CallbackUtil for the duration of the dialog.
|
|
2206
|
-
If the dialog times out then a SapioDialogTimeoutException is thrown.
|
|
2207
|
-
If the user cancels the dialog then a SapioUserCancelledException is thrown.
|
|
2208
|
-
If the user provides a blank result, then the handling is used to determine what to do with that result.
|
|
2209
|
-
|
|
2210
|
-
:param request: The client callback request to send to the user.
|
|
2211
|
-
:param func: The ClientCallback function to call with the given request as input.
|
|
2212
|
-
:param not_blank_func: The function to determine whether the provided result is blank or not.
|
|
2213
|
-
:param handling: The handling to use for blank results.
|
|
2214
|
-
:param repeat_message: If handling is REPEAT and a repeat_message is provided, then that message appears as
|
|
2215
|
-
toaster text when the user provides a blank result.
|
|
2216
|
-
:param cancel_message: If handling is CANCEL and a cancel_message is provided, then that message appears as
|
|
2217
|
-
toaster text when the user provides a blank result.
|
|
2218
|
-
:param kwargs: Additional keywords for the provided function call.
|
|
2219
|
-
:return: The response from the client callback, if one was received.
|
|
2220
|
-
"""
|
|
2221
|
-
if handling == BlankResultHandling.DEFAULT or handling is None:
|
|
2222
|
-
handling = self._default_blank_result_handling
|
|
2223
|
-
while True:
|
|
2224
|
-
response: Any | None = self.__handle_timeout(func, request, **kwargs)
|
|
2225
|
-
if response is None:
|
|
2226
|
-
raise SapioUserCancelledException()
|
|
2227
|
-
if not_blank_func(response):
|
|
2228
|
-
return response
|
|
2229
|
-
match handling:
|
|
2230
|
-
case BlankResultHandling.CANCEL:
|
|
2231
|
-
# If the user provided no selection, throw an exception.
|
|
2232
|
-
if cancel_message:
|
|
2233
|
-
self.toaster_popup(cancel_message, popup_type=PopupType.Warning)
|
|
2234
|
-
raise SapioUserCancelledException()
|
|
2235
|
-
case BlankResultHandling.REPEAT:
|
|
2236
|
-
# If the user provided no selection, repeat the dialog.
|
|
2237
|
-
# If a repeatMessage is provided, display it as a toaster popup.
|
|
2238
|
-
if repeat_message:
|
|
2239
|
-
self.toaster_popup(repeat_message, popup_type=PopupType.Warning)
|
|
2240
|
-
case BlankResultHandling.RETURN:
|
|
2241
|
-
# If the user provided no selection, return the blank result.
|
|
2242
|
-
return response
|
|
2243
|
-
|
|
2244
1911
|
|
|
2245
1912
|
class FieldModifier:
|
|
2246
1913
|
"""
|
|
@@ -2316,15 +1983,15 @@ class FieldFilterCriteria:
|
|
|
2316
1983
|
key_field: bool | None
|
|
2317
1984
|
identifier: bool | None
|
|
2318
1985
|
system_field: bool | None
|
|
2319
|
-
field_types:
|
|
2320
|
-
not_field_types:
|
|
1986
|
+
field_types: list[FieldType] | None
|
|
1987
|
+
not_field_types: list[FieldType] | None
|
|
2321
1988
|
matches_tag: str | None
|
|
2322
1989
|
contains_tag: str | None
|
|
2323
1990
|
regex_tag: str | re.Pattern[str] | None
|
|
2324
1991
|
|
|
2325
1992
|
def __init__(self, *, required: bool | None = None, editable: bool | None = None, key_field: bool | None = None,
|
|
2326
1993
|
identifier: bool | None = None, system_field: bool | None = None,
|
|
2327
|
-
field_types:
|
|
1994
|
+
field_types: list[FieldType] | None = None, not_field_types: list[FieldType] | None = None,
|
|
2328
1995
|
matches_tag: str | None = None, contains_tag: str | None = None,
|
|
2329
1996
|
regex_tag: str | re.Pattern[str] | None = None):
|
|
2330
1997
|
"""
|