sqladmin 0.21.0__py3-none-any.whl → 0.23.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqladmin/__init__.py +0 -2
- sqladmin/_menu.py +1 -1
- sqladmin/_queries.py +22 -14
- sqladmin/_types.py +36 -6
- sqladmin/_validators.py +14 -8
- sqladmin/ajax.py +2 -2
- sqladmin/application.py +35 -20
- sqladmin/fields.py +29 -20
- sqladmin/filters.py +206 -11
- sqladmin/formatters.py +1 -1
- sqladmin/forms.py +140 -51
- sqladmin/helpers.py +20 -7
- sqladmin/models.py +95 -32
- sqladmin/pretty_export.py +75 -0
- sqladmin/templates/sqladmin/details.html +7 -7
- sqladmin/templates/sqladmin/list.html +62 -13
- sqladmin/templating.py +1 -1
- sqladmin/widgets.py +20 -14
- {sqladmin-0.21.0.dist-info → sqladmin-0.23.0.dist-info}/METADATA +17 -16
- {sqladmin-0.21.0.dist-info → sqladmin-0.23.0.dist-info}/RECORD +21 -21
- {sqladmin-0.21.0.dist-info → sqladmin-0.23.0.dist-info}/WHEEL +1 -1
- sqladmin-0.21.0.dist-info/licenses/LICENSE.md +0 -27
sqladmin/forms.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
# mypy: disable-error-code="return-value"
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
The converters are from Flask-Admin project.
|
|
3
5
|
"""
|
|
6
|
+
|
|
4
7
|
from __future__ import annotations
|
|
5
8
|
|
|
6
9
|
import enum
|
|
@@ -24,7 +27,6 @@ from sqlalchemy.orm import (
|
|
|
24
27
|
sessionmaker,
|
|
25
28
|
)
|
|
26
29
|
from sqlalchemy.sql.elements import Label
|
|
27
|
-
from sqlalchemy.sql.schema import Column
|
|
28
30
|
from wtforms import (
|
|
29
31
|
BooleanField,
|
|
30
32
|
DecimalField,
|
|
@@ -75,11 +77,9 @@ else:
|
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
class Validator(Protocol):
|
|
78
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
79
|
-
... # pragma: no cover
|
|
80
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # pragma: no cover
|
|
80
81
|
|
|
81
|
-
def __call__(self, form: Form, field: Field) -> None:
|
|
82
|
-
... # pragma: no cover
|
|
82
|
+
def __call__(self, form: Form, field: Field) -> None: ... # pragma: no cover
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
class ConverterCallable(Protocol):
|
|
@@ -88,8 +88,7 @@ class ConverterCallable(Protocol):
|
|
|
88
88
|
model: type,
|
|
89
89
|
prop: MODEL_PROPERTY,
|
|
90
90
|
kwargs: dict[str, Any],
|
|
91
|
-
) -> UnboundField:
|
|
92
|
-
... # pragma: no cover
|
|
91
|
+
) -> UnboundField: ... # pragma: no cover
|
|
93
92
|
|
|
94
93
|
|
|
95
94
|
T_CC = TypeVar("T_CC", bound=ConverterCallable)
|
|
@@ -163,9 +162,14 @@ class ModelConverterBase:
|
|
|
163
162
|
return kwargs
|
|
164
163
|
|
|
165
164
|
def _prepare_column(
|
|
166
|
-
self,
|
|
165
|
+
self,
|
|
166
|
+
prop: ColumnProperty,
|
|
167
|
+
form_include_pk: bool,
|
|
168
|
+
kwargs: dict,
|
|
167
169
|
) -> Union[dict, None]:
|
|
168
|
-
|
|
170
|
+
if len(prop.columns) != 1:
|
|
171
|
+
raise NotImplementedError("Multiple-column properties are not supported")
|
|
172
|
+
|
|
169
173
|
column = prop.columns[0]
|
|
170
174
|
|
|
171
175
|
if (column.primary_key or column.foreign_keys) and not form_include_pk:
|
|
@@ -210,7 +214,7 @@ class ModelConverterBase:
|
|
|
210
214
|
loader: QueryAjaxModelLoader | None = None,
|
|
211
215
|
) -> dict:
|
|
212
216
|
nullable = True
|
|
213
|
-
for pair in prop.local_remote_pairs:
|
|
217
|
+
for pair in prop.local_remote_pairs or []:
|
|
214
218
|
if not pair[0].nullable:
|
|
215
219
|
nullable = False
|
|
216
220
|
|
|
@@ -290,9 +294,9 @@ class ModelConverterBase:
|
|
|
290
294
|
form_include_pk: bool,
|
|
291
295
|
label: str | None = None,
|
|
292
296
|
override: type[Field] | None = None,
|
|
293
|
-
form_ajax_refs: dict[str, QueryAjaxModelLoader] =
|
|
297
|
+
form_ajax_refs: dict[str, QueryAjaxModelLoader] | None = None,
|
|
294
298
|
) -> UnboundField:
|
|
295
|
-
loader = form_ajax_refs.get(prop.key)
|
|
299
|
+
loader = (form_ajax_refs or {}).get(prop.key)
|
|
296
300
|
kwargs = await self._prepare_kwargs(
|
|
297
301
|
prop=prop,
|
|
298
302
|
session_maker=session_maker,
|
|
@@ -307,7 +311,9 @@ class ModelConverterBase:
|
|
|
307
311
|
return None
|
|
308
312
|
|
|
309
313
|
if override is not None:
|
|
310
|
-
|
|
314
|
+
if not issubclass(override, Field):
|
|
315
|
+
raise TypeError("Expected Field, got %s" % type(override))
|
|
316
|
+
|
|
311
317
|
return override(**kwargs)
|
|
312
318
|
|
|
313
319
|
multiple = (
|
|
@@ -317,10 +323,8 @@ class ModelConverterBase:
|
|
|
317
323
|
)
|
|
318
324
|
|
|
319
325
|
if loader:
|
|
320
|
-
if multiple
|
|
321
|
-
|
|
322
|
-
else:
|
|
323
|
-
return AjaxSelectField(loader, **kwargs)
|
|
326
|
+
field = AjaxSelectMultipleField if multiple else AjaxSelectField
|
|
327
|
+
return field(loader, **kwargs)
|
|
324
328
|
|
|
325
329
|
converter = self.get_converter(prop=prop)
|
|
326
330
|
return converter(model=model, prop=prop, kwargs=kwargs)
|
|
@@ -333,14 +337,17 @@ class ModelConverter(ModelConverterBase):
|
|
|
333
337
|
@staticmethod
|
|
334
338
|
def _string_common(prop: ColumnProperty) -> list[Validator]:
|
|
335
339
|
li = []
|
|
336
|
-
column
|
|
337
|
-
if isinstance(column.type.length, int) and column.type.length:
|
|
338
|
-
li.append(validators.Length(max=column.type.length))
|
|
340
|
+
column = prop.columns[0]
|
|
341
|
+
if isinstance(column.type.length, int) and column.type.length: # type: ignore[attr-defined]
|
|
342
|
+
li.append(validators.Length(max=column.type.length)) # type: ignore[attr-defined]
|
|
339
343
|
return li
|
|
340
344
|
|
|
341
345
|
@converts("String", "CHAR") # includes Unicode
|
|
342
346
|
def conv_string(
|
|
343
|
-
self,
|
|
347
|
+
self,
|
|
348
|
+
model: type,
|
|
349
|
+
prop: ColumnProperty,
|
|
350
|
+
kwargs: dict[str, Any],
|
|
344
351
|
) -> UnboundField:
|
|
345
352
|
extra_validators = self._string_common(prop)
|
|
346
353
|
kwargs.setdefault("validators", [])
|
|
@@ -349,7 +356,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
349
356
|
|
|
350
357
|
@converts("Text", "LargeBinary", "Binary") # includes UnicodeText
|
|
351
358
|
def conv_text(
|
|
352
|
-
self,
|
|
359
|
+
self,
|
|
360
|
+
model: type,
|
|
361
|
+
prop: ColumnProperty,
|
|
362
|
+
kwargs: dict[str, Any],
|
|
353
363
|
) -> UnboundField:
|
|
354
364
|
kwargs.setdefault("validators", [])
|
|
355
365
|
extra_validators = self._string_common(prop)
|
|
@@ -358,7 +368,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
358
368
|
|
|
359
369
|
@converts("Boolean", "dialects.mssql.base.BIT")
|
|
360
370
|
def conv_boolean(
|
|
361
|
-
self,
|
|
371
|
+
self,
|
|
372
|
+
model: type,
|
|
373
|
+
prop: ColumnProperty,
|
|
374
|
+
kwargs: dict[str, Any],
|
|
362
375
|
) -> UnboundField:
|
|
363
376
|
if not prop.columns[0].nullable:
|
|
364
377
|
kwargs.setdefault("render_kw", {})
|
|
@@ -372,27 +385,42 @@ class ModelConverter(ModelConverterBase):
|
|
|
372
385
|
|
|
373
386
|
@converts("Date")
|
|
374
387
|
def conv_date(
|
|
375
|
-
self,
|
|
388
|
+
self,
|
|
389
|
+
model: type,
|
|
390
|
+
prop: ColumnProperty,
|
|
391
|
+
kwargs: dict[str, Any],
|
|
376
392
|
) -> UnboundField:
|
|
377
393
|
return DateField(**kwargs)
|
|
378
394
|
|
|
379
395
|
@converts("Time")
|
|
380
396
|
def conv_time(
|
|
381
|
-
self,
|
|
397
|
+
self,
|
|
398
|
+
model: type,
|
|
399
|
+
prop: ColumnProperty,
|
|
400
|
+
kwargs: dict[str, Any],
|
|
382
401
|
) -> UnboundField:
|
|
383
402
|
return TimeField(**kwargs)
|
|
384
403
|
|
|
385
404
|
@converts("DateTime")
|
|
386
405
|
def conv_datetime(
|
|
387
|
-
self,
|
|
406
|
+
self,
|
|
407
|
+
model: type,
|
|
408
|
+
prop: ColumnProperty,
|
|
409
|
+
kwargs: dict[str, Any],
|
|
388
410
|
) -> UnboundField:
|
|
389
411
|
return DateTimeField(**kwargs)
|
|
390
412
|
|
|
391
413
|
@converts("Enum")
|
|
392
414
|
def conv_enum(
|
|
393
|
-
self,
|
|
415
|
+
self,
|
|
416
|
+
model: type,
|
|
417
|
+
prop: ColumnProperty,
|
|
418
|
+
kwargs: dict[str, Any],
|
|
394
419
|
) -> UnboundField:
|
|
395
|
-
available_choices = [
|
|
420
|
+
available_choices = [
|
|
421
|
+
(e, e)
|
|
422
|
+
for e in prop.columns[0].type.enums # type: ignore[attr-defined]
|
|
423
|
+
]
|
|
396
424
|
accepted_values = [choice[0] for choice in available_choices]
|
|
397
425
|
|
|
398
426
|
if prop.columns[0].nullable:
|
|
@@ -410,13 +438,19 @@ class ModelConverter(ModelConverterBase):
|
|
|
410
438
|
|
|
411
439
|
@converts("Integer") # includes BigInteger and SmallInteger
|
|
412
440
|
def conv_integer(
|
|
413
|
-
self,
|
|
441
|
+
self,
|
|
442
|
+
model: type,
|
|
443
|
+
prop: ColumnProperty,
|
|
444
|
+
kwargs: dict[str, Any],
|
|
414
445
|
) -> UnboundField:
|
|
415
446
|
return IntegerField(**kwargs)
|
|
416
447
|
|
|
417
448
|
@converts("Numeric") # includes DECIMAL, Float/FLOAT, REAL, and DOUBLE
|
|
418
449
|
def conv_decimal(
|
|
419
|
-
self,
|
|
450
|
+
self,
|
|
451
|
+
model: type,
|
|
452
|
+
prop: ColumnProperty,
|
|
453
|
+
kwargs: dict[str, Any],
|
|
420
454
|
) -> UnboundField:
|
|
421
455
|
# override default decimal places limit, use database defaults instead
|
|
422
456
|
kwargs.setdefault("places", None)
|
|
@@ -424,13 +458,19 @@ class ModelConverter(ModelConverterBase):
|
|
|
424
458
|
|
|
425
459
|
@converts("JSON", "JSONB")
|
|
426
460
|
def conv_json(
|
|
427
|
-
self,
|
|
461
|
+
self,
|
|
462
|
+
model: type,
|
|
463
|
+
prop: ColumnProperty,
|
|
464
|
+
kwargs: dict[str, Any],
|
|
428
465
|
) -> UnboundField:
|
|
429
466
|
return JSONField(**kwargs)
|
|
430
467
|
|
|
431
468
|
@converts("Interval")
|
|
432
469
|
def conv_interval(
|
|
433
|
-
self,
|
|
470
|
+
self,
|
|
471
|
+
model: type,
|
|
472
|
+
prop: ColumnProperty,
|
|
473
|
+
kwargs: dict[str, Any],
|
|
434
474
|
) -> UnboundField:
|
|
435
475
|
kwargs["render_kw"]["placeholder"] = "Like: 1 day 1:25:33.652"
|
|
436
476
|
return IntervalField(**kwargs)
|
|
@@ -441,7 +481,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
441
481
|
"sqlalchemy_utils.types.ip_address.IPAddressType",
|
|
442
482
|
)
|
|
443
483
|
def conv_ip_address(
|
|
444
|
-
self,
|
|
484
|
+
self,
|
|
485
|
+
model: type,
|
|
486
|
+
prop: ColumnProperty,
|
|
487
|
+
kwargs: dict[str, Any],
|
|
445
488
|
) -> UnboundField:
|
|
446
489
|
kwargs.setdefault("validators", [])
|
|
447
490
|
kwargs["validators"].append(validators.IPAddress(ipv4=True, ipv6=True))
|
|
@@ -452,7 +495,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
452
495
|
"sqlalchemy.dialects.postgresql.types.MACADDR",
|
|
453
496
|
)
|
|
454
497
|
def conv_mac_address(
|
|
455
|
-
self,
|
|
498
|
+
self,
|
|
499
|
+
model: type,
|
|
500
|
+
prop: ColumnProperty,
|
|
501
|
+
kwargs: dict[str, Any],
|
|
456
502
|
) -> UnboundField:
|
|
457
503
|
kwargs.setdefault("validators", [])
|
|
458
504
|
kwargs["validators"].append(validators.MacAddress())
|
|
@@ -465,23 +511,33 @@ class ModelConverter(ModelConverterBase):
|
|
|
465
511
|
"sqlalchemy_utils.types.uuid.UUIDType",
|
|
466
512
|
)
|
|
467
513
|
def conv_uuid(
|
|
468
|
-
self,
|
|
514
|
+
self,
|
|
515
|
+
model: type,
|
|
516
|
+
prop: ColumnProperty,
|
|
517
|
+
kwargs: dict[str, Any],
|
|
469
518
|
) -> UnboundField:
|
|
470
519
|
kwargs.setdefault("validators", [])
|
|
471
520
|
kwargs["validators"].append(validators.UUID())
|
|
472
521
|
return StringField(**kwargs)
|
|
473
522
|
|
|
474
523
|
@converts(
|
|
475
|
-
"sqlalchemy.dialects.postgresql.base.ARRAY",
|
|
524
|
+
"sqlalchemy.dialects.postgresql.base.ARRAY",
|
|
525
|
+
"sqlalchemy.sql.sqltypes.ARRAY",
|
|
476
526
|
)
|
|
477
|
-
def
|
|
478
|
-
self,
|
|
527
|
+
def conv_array(
|
|
528
|
+
self,
|
|
529
|
+
model: type,
|
|
530
|
+
prop: ColumnProperty,
|
|
531
|
+
kwargs: dict[str, Any],
|
|
479
532
|
) -> UnboundField:
|
|
480
533
|
return Select2TagsField(**kwargs)
|
|
481
534
|
|
|
482
535
|
@converts("sqlalchemy_utils.types.email.EmailType")
|
|
483
536
|
def conv_email(
|
|
484
|
-
self,
|
|
537
|
+
self,
|
|
538
|
+
model: type,
|
|
539
|
+
prop: ColumnProperty,
|
|
540
|
+
kwargs: dict[str, Any],
|
|
485
541
|
) -> UnboundField:
|
|
486
542
|
kwargs.setdefault("validators", [])
|
|
487
543
|
kwargs["validators"].append(validators.Email())
|
|
@@ -489,7 +545,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
489
545
|
|
|
490
546
|
@converts("sqlalchemy_utils.types.url.URLType")
|
|
491
547
|
def conv_url(
|
|
492
|
-
self,
|
|
548
|
+
self,
|
|
549
|
+
model: type,
|
|
550
|
+
prop: ColumnProperty,
|
|
551
|
+
kwargs: dict[str, Any],
|
|
493
552
|
) -> UnboundField:
|
|
494
553
|
kwargs.setdefault("validators", [])
|
|
495
554
|
kwargs["validators"].append(validators.URL())
|
|
@@ -497,7 +556,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
497
556
|
|
|
498
557
|
@converts("sqlalchemy_utils.types.currency.CurrencyType")
|
|
499
558
|
def conv_currency(
|
|
500
|
-
self,
|
|
559
|
+
self,
|
|
560
|
+
model: type,
|
|
561
|
+
prop: ColumnProperty,
|
|
562
|
+
kwargs: dict[str, Any],
|
|
501
563
|
) -> UnboundField:
|
|
502
564
|
kwargs.setdefault("validators", [])
|
|
503
565
|
kwargs["validators"].append(CurrencyValidator())
|
|
@@ -505,17 +567,23 @@ class ModelConverter(ModelConverterBase):
|
|
|
505
567
|
|
|
506
568
|
@converts("sqlalchemy_utils.types.timezone.TimezoneType")
|
|
507
569
|
def conv_timezone(
|
|
508
|
-
self,
|
|
570
|
+
self,
|
|
571
|
+
model: type,
|
|
572
|
+
prop: ColumnProperty,
|
|
573
|
+
kwargs: dict[str, Any],
|
|
509
574
|
) -> UnboundField:
|
|
510
575
|
kwargs.setdefault("validators", [])
|
|
511
576
|
kwargs["validators"].append(
|
|
512
|
-
TimezoneValidator(coerce_function=prop.columns[0].type._coerce)
|
|
577
|
+
TimezoneValidator(coerce_function=prop.columns[0].type._coerce) # type: ignore[attr-defined]
|
|
513
578
|
)
|
|
514
579
|
return StringField(**kwargs)
|
|
515
580
|
|
|
516
581
|
@converts("sqlalchemy_utils.types.phone_number.PhoneNumberType")
|
|
517
582
|
def conv_phone_number(
|
|
518
|
-
self,
|
|
583
|
+
self,
|
|
584
|
+
model: type,
|
|
585
|
+
prop: ColumnProperty,
|
|
586
|
+
kwargs: dict[str, Any],
|
|
519
587
|
) -> UnboundField:
|
|
520
588
|
kwargs.setdefault("validators", [])
|
|
521
589
|
kwargs["validators"].append(PhoneNumberValidator())
|
|
@@ -523,7 +591,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
523
591
|
|
|
524
592
|
@converts("sqlalchemy_utils.types.color.ColorType")
|
|
525
593
|
def conv_color(
|
|
526
|
-
self,
|
|
594
|
+
self,
|
|
595
|
+
model: type,
|
|
596
|
+
prop: ColumnProperty,
|
|
597
|
+
kwargs: dict[str, Any],
|
|
527
598
|
) -> UnboundField:
|
|
528
599
|
kwargs.setdefault("validators", [])
|
|
529
600
|
kwargs["validators"].append(ColorValidator())
|
|
@@ -532,7 +603,10 @@ class ModelConverter(ModelConverterBase):
|
|
|
532
603
|
@converts("sqlalchemy_utils.types.choice.ChoiceType")
|
|
533
604
|
@no_type_check
|
|
534
605
|
def convert_choice_type(
|
|
535
|
-
self,
|
|
606
|
+
self,
|
|
607
|
+
model: type,
|
|
608
|
+
prop: ColumnProperty,
|
|
609
|
+
kwargs: dict[str, Any],
|
|
536
610
|
) -> UnboundField:
|
|
537
611
|
available_choices = []
|
|
538
612
|
column = prop.columns[0]
|
|
@@ -561,32 +635,47 @@ class ModelConverter(ModelConverterBase):
|
|
|
561
635
|
|
|
562
636
|
@converts("fastapi_storages.integrations.sqlalchemy.FileType")
|
|
563
637
|
def conv_file(
|
|
564
|
-
self,
|
|
638
|
+
self,
|
|
639
|
+
model: type,
|
|
640
|
+
prop: ColumnProperty,
|
|
641
|
+
kwargs: dict[str, Any],
|
|
565
642
|
) -> UnboundField:
|
|
566
643
|
return FileField(**kwargs)
|
|
567
644
|
|
|
568
645
|
@converts("fastapi_storages.integrations.sqlalchemy.ImageType")
|
|
569
646
|
def conv_image(
|
|
570
|
-
self,
|
|
647
|
+
self,
|
|
648
|
+
model: type,
|
|
649
|
+
prop: ColumnProperty,
|
|
650
|
+
kwargs: dict[str, Any],
|
|
571
651
|
) -> UnboundField:
|
|
572
652
|
return FileField(**kwargs)
|
|
573
653
|
|
|
574
654
|
@converts("ONETOONE")
|
|
575
655
|
def conv_one_to_one(
|
|
576
|
-
self,
|
|
656
|
+
self,
|
|
657
|
+
model: type,
|
|
658
|
+
prop: RelationshipProperty,
|
|
659
|
+
kwargs: dict[str, Any],
|
|
577
660
|
) -> UnboundField:
|
|
578
661
|
kwargs["allow_blank"] = True
|
|
579
662
|
return QuerySelectField(**kwargs)
|
|
580
663
|
|
|
581
664
|
@converts("MANYTOONE")
|
|
582
665
|
def conv_many_to_one(
|
|
583
|
-
self,
|
|
666
|
+
self,
|
|
667
|
+
model: type,
|
|
668
|
+
prop: RelationshipProperty,
|
|
669
|
+
kwargs: dict[str, Any],
|
|
584
670
|
) -> UnboundField:
|
|
585
671
|
return QuerySelectField(**kwargs)
|
|
586
672
|
|
|
587
673
|
@converts("MANYTOMANY", "ONETOMANY")
|
|
588
674
|
def conv_many_to_many(
|
|
589
|
-
self,
|
|
675
|
+
self,
|
|
676
|
+
model: type,
|
|
677
|
+
prop: RelationshipProperty,
|
|
678
|
+
kwargs: dict[str, Any],
|
|
590
679
|
) -> UnboundField:
|
|
591
680
|
return QuerySelectMultipleField(**kwargs)
|
|
592
681
|
|
sqladmin/helpers.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import re
|
|
7
7
|
import unicodedata
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
|
-
from datetime import timedelta
|
|
9
|
+
from datetime import date, datetime, time, timedelta
|
|
10
10
|
from typing import (
|
|
11
11
|
Any,
|
|
12
12
|
AsyncGenerator,
|
|
@@ -123,7 +123,11 @@ def secure_filename(filename: str) -> str:
|
|
|
123
123
|
if (
|
|
124
124
|
os.name == "nt"
|
|
125
125
|
and filename
|
|
126
|
-
and filename.split(
|
|
126
|
+
and filename.split(
|
|
127
|
+
".",
|
|
128
|
+
maxsplit=1,
|
|
129
|
+
)[0].upper()
|
|
130
|
+
in _windows_device_files
|
|
127
131
|
):
|
|
128
132
|
filename = f"_{filename}" # pragma: no cover
|
|
129
133
|
|
|
@@ -226,13 +230,21 @@ def object_identifier_values(id_string: str, model: Any) -> tuple:
|
|
|
226
230
|
pks = get_primary_keys(model)
|
|
227
231
|
for pk, part in zip(pks, _object_identifier_parts(id_string, model)):
|
|
228
232
|
type_ = get_column_python_type(pk)
|
|
229
|
-
value
|
|
233
|
+
value: Any
|
|
234
|
+
if issubclass(type_, (date, datetime, time)):
|
|
235
|
+
value = type_.fromisoformat(part)
|
|
236
|
+
elif issubclass(type_, bool):
|
|
237
|
+
value = False if part == "False" else type_(part)
|
|
238
|
+
else:
|
|
239
|
+
value = type_(part) # type: ignore [call-arg]
|
|
230
240
|
values.append(value)
|
|
231
241
|
return tuple(values)
|
|
232
242
|
|
|
233
243
|
|
|
234
244
|
def get_direction(prop: MODEL_PROPERTY) -> str:
|
|
235
|
-
|
|
245
|
+
if not isinstance(prop, RelationshipProperty):
|
|
246
|
+
raise TypeError("Expected RelationshipProperty, got %s" % type(prop))
|
|
247
|
+
|
|
236
248
|
name = prop.direction.name
|
|
237
249
|
if name == "ONETOMANY" and not prop.uselist:
|
|
238
250
|
return "ONETOONE"
|
|
@@ -279,10 +291,11 @@ def parse_interval(value: str) -> timedelta | None:
|
|
|
279
291
|
def is_falsy_value(value: Any) -> bool:
|
|
280
292
|
if value is None:
|
|
281
293
|
return True
|
|
282
|
-
|
|
294
|
+
|
|
295
|
+
if not value and isinstance(value, str):
|
|
283
296
|
return True
|
|
284
|
-
|
|
285
|
-
|
|
297
|
+
|
|
298
|
+
return False
|
|
286
299
|
|
|
287
300
|
|
|
288
301
|
def choice_type_coerce_factory(type_: Any) -> Callable[[Any], Any]:
|