sqladmin 0.22.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 +6 -8
- sqladmin/_validators.py +14 -8
- sqladmin/ajax.py +2 -2
- sqladmin/application.py +35 -20
- sqladmin/fields.py +29 -20
- sqladmin/filters.py +74 -45
- sqladmin/formatters.py +1 -1
- sqladmin/forms.py +140 -51
- sqladmin/helpers.py +12 -5
- sqladmin/models.py +71 -29
- sqladmin/pretty_export.py +75 -0
- sqladmin/templates/sqladmin/details.html +7 -7
- sqladmin/templates/sqladmin/list.html +13 -2
- sqladmin/templating.py +1 -1
- sqladmin/widgets.py +20 -14
- {sqladmin-0.22.0.dist-info → sqladmin-0.23.0.dist-info}/METADATA +15 -15
- {sqladmin-0.22.0.dist-info → sqladmin-0.23.0.dist-info}/RECORD +21 -21
- {sqladmin-0.22.0.dist-info → sqladmin-0.23.0.dist-info}/WHEEL +1 -1
- sqladmin-0.22.0.dist-info/licenses/LICENSE.md +0 -27
sqladmin/filters.py
CHANGED
|
@@ -20,20 +20,20 @@ from sqladmin._types import MODEL_ATTR
|
|
|
20
20
|
try:
|
|
21
21
|
import uuid
|
|
22
22
|
|
|
23
|
-
from sqlalchemy import Uuid
|
|
23
|
+
from sqlalchemy import Uuid # type: ignore[attr-defined]
|
|
24
24
|
|
|
25
25
|
HAS_UUID_SUPPORT = True
|
|
26
26
|
except ImportError:
|
|
27
27
|
# Fallback for SQLAlchemy < 2.0
|
|
28
28
|
HAS_UUID_SUPPORT = False
|
|
29
|
-
Uuid = None
|
|
29
|
+
Uuid = None # type: ignore[misc, assignment]
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def get_parameter_name(column: MODEL_ATTR) -> str:
|
|
33
33
|
if isinstance(column, str):
|
|
34
34
|
return column
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
|
|
36
|
+
return column.key
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def prettify_attribute_name(name: str) -> str:
|
|
@@ -76,7 +76,10 @@ class BooleanFilter:
|
|
|
76
76
|
self.parameter_name = parameter_name or get_parameter_name(column)
|
|
77
77
|
|
|
78
78
|
async def lookups(
|
|
79
|
-
self,
|
|
79
|
+
self,
|
|
80
|
+
request: Request,
|
|
81
|
+
model: Any,
|
|
82
|
+
run_query: Callable[[Select], Any],
|
|
80
83
|
) -> List[Tuple[str, str]]:
|
|
81
84
|
return [
|
|
82
85
|
("all", "All"),
|
|
@@ -88,10 +91,11 @@ class BooleanFilter:
|
|
|
88
91
|
column_obj = get_column_obj(self.column, model)
|
|
89
92
|
if value == "true":
|
|
90
93
|
return query.filter(column_obj.is_(True))
|
|
91
|
-
|
|
94
|
+
|
|
95
|
+
if value == "false":
|
|
92
96
|
return query.filter(column_obj.is_(False))
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
|
|
98
|
+
return query
|
|
95
99
|
|
|
96
100
|
|
|
97
101
|
class AllUniqueStringValuesFilter:
|
|
@@ -108,7 +112,10 @@ class AllUniqueStringValuesFilter:
|
|
|
108
112
|
self.parameter_name = parameter_name or get_parameter_name(column)
|
|
109
113
|
|
|
110
114
|
async def lookups(
|
|
111
|
-
self,
|
|
115
|
+
self,
|
|
116
|
+
request: Request,
|
|
117
|
+
model: Any,
|
|
118
|
+
run_query: Callable[[Select], Any],
|
|
112
119
|
) -> List[Tuple[str, str]]:
|
|
113
120
|
column_obj = get_column_obj(self.column, model)
|
|
114
121
|
|
|
@@ -141,7 +148,10 @@ class StaticValuesFilter:
|
|
|
141
148
|
self.values = values
|
|
142
149
|
|
|
143
150
|
async def lookups(
|
|
144
|
-
self,
|
|
151
|
+
self,
|
|
152
|
+
request: Request,
|
|
153
|
+
model: Any,
|
|
154
|
+
run_query: Callable[[Select], Any],
|
|
145
155
|
) -> List[Tuple[str, str]]:
|
|
146
156
|
return [("", "All")] + self.values
|
|
147
157
|
|
|
@@ -170,13 +180,18 @@ class ForeignKeyFilter:
|
|
|
170
180
|
self.parameter_name = parameter_name or get_parameter_name(foreign_key)
|
|
171
181
|
|
|
172
182
|
async def lookups(
|
|
173
|
-
self,
|
|
183
|
+
self,
|
|
184
|
+
request: Request,
|
|
185
|
+
model: Any,
|
|
186
|
+
run_query: Callable[[Select], Any],
|
|
174
187
|
) -> List[Tuple[str, str]]:
|
|
175
188
|
foreign_key_obj = get_column_obj(self.foreign_key, model)
|
|
176
189
|
if self.foreign_model is None and isinstance(self.foreign_display_field, str):
|
|
177
190
|
raise ValueError("foreign_model is required for string foreign key filters")
|
|
178
191
|
if self.foreign_model is None:
|
|
179
|
-
|
|
192
|
+
if isinstance(self.foreign_display_field, str):
|
|
193
|
+
raise ValueError("foreign_model should not be string")
|
|
194
|
+
|
|
180
195
|
foreign_display_field_obj = self.foreign_display_field
|
|
181
196
|
else:
|
|
182
197
|
foreign_display_field_obj = get_column_obj(
|
|
@@ -227,22 +242,24 @@ class OperationColumnFilter:
|
|
|
227
242
|
("starts_with", "Starts with"),
|
|
228
243
|
("ends_with", "Ends with"),
|
|
229
244
|
]
|
|
230
|
-
|
|
245
|
+
|
|
246
|
+
if self._is_numeric_type(column_obj):
|
|
231
247
|
return [
|
|
232
248
|
("equals", "Equals"),
|
|
233
249
|
("greater_than", "Greater than"),
|
|
234
250
|
("less_than", "Less than"),
|
|
235
251
|
]
|
|
236
|
-
|
|
252
|
+
|
|
253
|
+
if self._is_uuid_type(column_obj):
|
|
237
254
|
return [
|
|
238
255
|
("equals", "Equals"),
|
|
239
256
|
("contains", "Contains"),
|
|
240
257
|
("starts_with", "Starts with"),
|
|
241
258
|
]
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
259
|
+
|
|
260
|
+
return [
|
|
261
|
+
("equals", "Equals"),
|
|
262
|
+
]
|
|
246
263
|
|
|
247
264
|
def get_operation_options_for_model(self, model: Any) -> List[Tuple[str, str]]:
|
|
248
265
|
"""Return operation options based on column type for given model"""
|
|
@@ -269,38 +286,45 @@ class OperationColumnFilter:
|
|
|
269
286
|
|
|
270
287
|
column_type = column_obj.type
|
|
271
288
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return int(value)
|
|
289
|
+
converters = [
|
|
290
|
+
((String, Text, _Binary), str),
|
|
291
|
+
((Integer, BigInteger, SmallInteger), int),
|
|
292
|
+
((Numeric, Float), float),
|
|
293
|
+
]
|
|
278
294
|
|
|
279
|
-
|
|
280
|
-
|
|
295
|
+
try:
|
|
296
|
+
for types, converter in converters:
|
|
297
|
+
if isinstance(column_type, types):
|
|
298
|
+
return converter(value)
|
|
281
299
|
|
|
282
|
-
# UUID support for SQLAlchemy 2.0+
|
|
283
300
|
if HAS_UUID_SUPPORT and isinstance(column_type, Uuid):
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
301
|
+
return (
|
|
302
|
+
str(value.strip())
|
|
303
|
+
if operation in ("contains", "starts_with")
|
|
304
|
+
else uuid.UUID(value.strip())
|
|
305
|
+
)
|
|
289
306
|
|
|
290
307
|
except (ValueError, TypeError):
|
|
291
308
|
return None
|
|
292
309
|
|
|
293
|
-
return
|
|
310
|
+
return value
|
|
294
311
|
|
|
295
312
|
async def lookups(
|
|
296
|
-
self,
|
|
313
|
+
self,
|
|
314
|
+
request: Request,
|
|
315
|
+
model: Any,
|
|
316
|
+
run_query: Callable[[Select], Any],
|
|
297
317
|
) -> List[Tuple[str, str]]:
|
|
298
318
|
# This method is not used for has_operator=True filters
|
|
299
319
|
# The UI uses get_operation_options_for_model instead
|
|
300
320
|
return []
|
|
301
321
|
|
|
302
322
|
async def get_filtered_query(
|
|
303
|
-
self,
|
|
323
|
+
self,
|
|
324
|
+
query: Select,
|
|
325
|
+
operation: str,
|
|
326
|
+
value: Any,
|
|
327
|
+
model: Any,
|
|
304
328
|
) -> Select:
|
|
305
329
|
"""Handle filtering with separate operation and value parameters"""
|
|
306
330
|
if not value or value == "" or not operation:
|
|
@@ -308,7 +332,9 @@ class OperationColumnFilter:
|
|
|
308
332
|
|
|
309
333
|
column_obj = get_column_obj(self.column, model)
|
|
310
334
|
converted_value = self._convert_value_for_column(
|
|
311
|
-
str(value).strip(),
|
|
335
|
+
str(value).strip(),
|
|
336
|
+
column_obj,
|
|
337
|
+
operation,
|
|
312
338
|
)
|
|
313
339
|
|
|
314
340
|
if converted_value is None:
|
|
@@ -319,22 +345,25 @@ class OperationColumnFilter:
|
|
|
319
345
|
# For UUID, cast to text for LIKE operations
|
|
320
346
|
search_value = f"%{str(value).strip()}%"
|
|
321
347
|
return query.filter(column_obj.cast(String).ilike(search_value))
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
348
|
+
|
|
349
|
+
return query.filter(column_obj.ilike(f"%{str(value).strip()}%"))
|
|
350
|
+
|
|
351
|
+
if operation == "equals":
|
|
325
352
|
return query.filter(column_obj == converted_value)
|
|
326
|
-
|
|
353
|
+
|
|
354
|
+
if operation == "starts_with":
|
|
327
355
|
if self._is_uuid_type(column_obj):
|
|
328
356
|
# For UUID, cast to text for LIKE operations
|
|
329
357
|
search_value = f"{str(value).strip()}%"
|
|
330
358
|
return query.filter(column_obj.cast(String).ilike(search_value))
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
|
|
360
|
+
return query.filter(column_obj.startswith(str(value).strip()))
|
|
361
|
+
|
|
362
|
+
if operation == "ends_with":
|
|
334
363
|
return query.filter(column_obj.endswith(str(value).strip()))
|
|
335
|
-
|
|
364
|
+
if operation == "greater_than":
|
|
336
365
|
return query.filter(column_obj > converted_value)
|
|
337
|
-
|
|
366
|
+
if operation == "less_than":
|
|
338
367
|
return query.filter(column_obj < converted_value)
|
|
339
368
|
|
|
340
369
|
return query
|
sqladmin/formatters.py
CHANGED
|
@@ -11,7 +11,7 @@ def empty_formatter(value: Any) -> str:
|
|
|
11
11
|
def bool_formatter(value: bool) -> Markup:
|
|
12
12
|
"""Return check icon if value is `True` or X otherwise."""
|
|
13
13
|
icon_class = "fa-check text-success" if value else "fa-times text-danger"
|
|
14
|
-
return Markup(
|
|
14
|
+
return Markup("<i class='fa {}'></i>").format(icon_class)
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
BASE_FORMATTERS = {
|
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
|
|