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/__init__.py
CHANGED
sqladmin/_menu.py
CHANGED
sqladmin/_queries.py
CHANGED
|
@@ -40,11 +40,13 @@ class Query:
|
|
|
40
40
|
conditions = []
|
|
41
41
|
for value in values:
|
|
42
42
|
conditions.append(
|
|
43
|
-
and_(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
and_( # type: ignore[type-var]
|
|
44
|
+
*(
|
|
45
|
+
pk == value
|
|
46
|
+
for pk, value in zip(
|
|
47
|
+
target_pks,
|
|
48
|
+
object_identifier_values(value, target),
|
|
49
|
+
)
|
|
48
50
|
)
|
|
49
51
|
)
|
|
50
52
|
)
|
|
@@ -65,10 +67,10 @@ class Query:
|
|
|
65
67
|
# ``relation.local_remote_pairs`` is ordered by the foreign keys
|
|
66
68
|
# but the values are ordered by the primary keys. This dict
|
|
67
69
|
# ensures we write the correct value to the fk fields
|
|
68
|
-
pk_value =
|
|
70
|
+
pk_value = dict(zip(pks, values))
|
|
69
71
|
|
|
70
|
-
for fk, pk in relation.local_remote_pairs:
|
|
71
|
-
setattr(obj, fk.name, pk_value[pk])
|
|
72
|
+
for fk, pk in relation.local_remote_pairs or []:
|
|
73
|
+
setattr(obj, fk.name, pk_value[pk]) # type: ignore[index]
|
|
72
74
|
|
|
73
75
|
return obj
|
|
74
76
|
|
|
@@ -217,18 +219,24 @@ class Query:
|
|
|
217
219
|
|
|
218
220
|
async def delete(self, obj: Any, request: Request) -> None:
|
|
219
221
|
if self.model_view.is_async:
|
|
220
|
-
|
|
222
|
+
coro = self._delete_async(obj, request)
|
|
221
223
|
else:
|
|
222
|
-
|
|
224
|
+
coro = anyio.to_thread.run_sync(self._delete_sync, obj, request)
|
|
225
|
+
|
|
226
|
+
return await coro
|
|
223
227
|
|
|
224
228
|
async def insert(self, data: dict, request: Request) -> Any:
|
|
225
229
|
if self.model_view.is_async:
|
|
226
|
-
|
|
230
|
+
coro = self._insert_async(data, request)
|
|
227
231
|
else:
|
|
228
|
-
|
|
232
|
+
coro = anyio.to_thread.run_sync(self._insert_sync, data, request)
|
|
233
|
+
|
|
234
|
+
return await coro
|
|
229
235
|
|
|
230
236
|
async def update(self, pk: Any, data: dict, request: Request) -> Any:
|
|
231
237
|
if self.model_view.is_async:
|
|
232
|
-
|
|
238
|
+
coro = self._update_async(pk, data, request)
|
|
233
239
|
else:
|
|
234
|
-
|
|
240
|
+
coro = anyio.to_thread.run_sync(self._update_sync, pk, data, request)
|
|
241
|
+
|
|
242
|
+
return await coro
|
sqladmin/_types.py
CHANGED
|
@@ -28,11 +28,11 @@ class SimpleColumnFilter(Protocol):
|
|
|
28
28
|
|
|
29
29
|
async def lookups(
|
|
30
30
|
self, request: Request, model: Any, run_query: Callable[[Select], Any]
|
|
31
|
-
) -> List[Tuple[str, str]]:
|
|
32
|
-
... # pragma: no cover
|
|
31
|
+
) -> List[Tuple[str, str]]: ... # pragma: no cover
|
|
33
32
|
|
|
34
|
-
async def get_filtered_query(
|
|
35
|
-
|
|
33
|
+
async def get_filtered_query(
|
|
34
|
+
self, query: Select, value: Any, model: Any
|
|
35
|
+
) -> Select: ... # pragma: no cover
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@runtime_checkable
|
|
@@ -45,13 +45,11 @@ class OperationColumnFilter(Protocol):
|
|
|
45
45
|
|
|
46
46
|
async def lookups(
|
|
47
47
|
self, request: Request, model: Any, run_query: Callable[[Select], Any]
|
|
48
|
-
) -> List[Tuple[str, str]]:
|
|
49
|
-
... # pragma: no cover
|
|
48
|
+
) -> List[Tuple[str, str]]: ... # pragma: no cover
|
|
50
49
|
|
|
51
50
|
async def get_filtered_query(
|
|
52
51
|
self, query: Select, operation: str, value: Any, model: Any
|
|
53
|
-
) -> Select:
|
|
54
|
-
... # pragma: no cover
|
|
52
|
+
) -> Select: ... # pragma: no cover
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
ColumnFilter = Union[SimpleColumnFilter, OperationColumnFilter]
|
sqladmin/_validators.py
CHANGED
|
@@ -11,8 +11,10 @@ class CurrencyValidator:
|
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
Currency(field.data)
|
|
14
|
-
except (TypeError, ValueError):
|
|
15
|
-
raise ValidationError(
|
|
14
|
+
except (TypeError, ValueError) as exc:
|
|
15
|
+
raise ValidationError(
|
|
16
|
+
"Not a valid ISO currency code (e.g. USD, EUR, CNY)."
|
|
17
|
+
) from exc
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class PhoneNumberValidator:
|
|
@@ -23,8 +25,8 @@ class PhoneNumberValidator:
|
|
|
23
25
|
|
|
24
26
|
try:
|
|
25
27
|
PhoneNumber(field.data)
|
|
26
|
-
except PhoneNumberParseException:
|
|
27
|
-
raise ValidationError("Not a valid phone number.")
|
|
28
|
+
except PhoneNumberParseException as exc:
|
|
29
|
+
raise ValidationError("Not a valid phone number.") from exc
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class ColorValidator:
|
|
@@ -35,8 +37,10 @@ class ColorValidator:
|
|
|
35
37
|
|
|
36
38
|
try:
|
|
37
39
|
Color(field.data)
|
|
38
|
-
except ValueError:
|
|
39
|
-
raise ValidationError(
|
|
40
|
+
except ValueError as exc:
|
|
41
|
+
raise ValidationError(
|
|
42
|
+
'Not a valid color (e.g. "red", "#f00", "#ff0000").'
|
|
43
|
+
) from exc
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
class TimezoneValidator:
|
|
@@ -48,5 +52,7 @@ class TimezoneValidator:
|
|
|
48
52
|
def __call__(self, form: Form, field: Field) -> None:
|
|
49
53
|
try:
|
|
50
54
|
self.coerce_function(str(field.data))
|
|
51
|
-
except Exception:
|
|
52
|
-
raise ValidationError(
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
raise ValidationError(
|
|
57
|
+
"Not a valid timezone (e.g. 'Asia/Singapore')."
|
|
58
|
+
) from exc
|
sqladmin/ajax.py
CHANGED
|
@@ -93,8 +93,8 @@ def create_ajax_loader(
|
|
|
93
93
|
|
|
94
94
|
try:
|
|
95
95
|
attr = mapper.relationships[name]
|
|
96
|
-
except KeyError:
|
|
97
|
-
raise ValueError(f"{model_admin.model}.{name} is not a relation.")
|
|
96
|
+
except KeyError as exc:
|
|
97
|
+
raise ValueError(f"{model_admin.model}.{name} is not a relation.") from exc
|
|
98
98
|
|
|
99
99
|
remote_model = attr.mapper.class_
|
|
100
100
|
return QueryAjaxModelLoader(name, remote_model, model_admin, **options)
|
sqladmin/application.py
CHANGED
|
@@ -42,7 +42,7 @@ from sqladmin.models import BaseView, ModelView
|
|
|
42
42
|
from sqladmin.templating import Jinja2Templates
|
|
43
43
|
|
|
44
44
|
if TYPE_CHECKING:
|
|
45
|
-
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
45
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker # type: ignore[attr-defined]
|
|
46
46
|
|
|
47
47
|
__all__ = [
|
|
48
48
|
"Admin",
|
|
@@ -83,10 +83,13 @@ class BaseAdmin:
|
|
|
83
83
|
|
|
84
84
|
if session_maker:
|
|
85
85
|
self.session_maker = session_maker
|
|
86
|
-
elif isinstance(engine, Engine):
|
|
86
|
+
elif isinstance(self.engine, Engine):
|
|
87
87
|
self.session_maker = sessionmaker(bind=self.engine, class_=Session)
|
|
88
88
|
else:
|
|
89
|
-
self.session_maker = sessionmaker(
|
|
89
|
+
self.session_maker = sessionmaker(
|
|
90
|
+
bind=self.engine, # type: ignore[arg-type]
|
|
91
|
+
class_=AsyncSession,
|
|
92
|
+
)
|
|
90
93
|
|
|
91
94
|
self.session_maker.configure(autoflush=False, autocommit=False)
|
|
92
95
|
self.is_async = is_async_session_maker(self.session_maker)
|
|
@@ -113,7 +116,7 @@ class BaseAdmin:
|
|
|
113
116
|
templates.env.globals["min"] = min
|
|
114
117
|
templates.env.globals["zip"] = zip
|
|
115
118
|
templates.env.globals["admin"] = self
|
|
116
|
-
templates.env.globals["is_list"] = lambda x: isinstance(x, list)
|
|
119
|
+
templates.env.globals["is_list"] = lambda x: isinstance(x, (list, set))
|
|
117
120
|
templates.env.globals["get_object_identifier"] = get_object_identifier
|
|
118
121
|
|
|
119
122
|
return templates
|
|
@@ -181,14 +184,14 @@ class BaseAdmin:
|
|
|
181
184
|
func, "_label"
|
|
182
185
|
)
|
|
183
186
|
if getattr(func, "_add_in_detail"):
|
|
184
|
-
view_instance._custom_actions_in_detail[
|
|
185
|
-
getattr(func, "
|
|
186
|
-
|
|
187
|
+
view_instance._custom_actions_in_detail[getattr(func, "_slug")] = (
|
|
188
|
+
getattr(func, "_label")
|
|
189
|
+
)
|
|
187
190
|
|
|
188
191
|
if getattr(func, "_confirmation_message"):
|
|
189
|
-
view_instance._custom_actions_confirmation[
|
|
190
|
-
getattr(func, "
|
|
191
|
-
|
|
192
|
+
view_instance._custom_actions_confirmation[getattr(func, "_slug")] = (
|
|
193
|
+
getattr(func, "_confirmation_message")
|
|
194
|
+
)
|
|
192
195
|
|
|
193
196
|
def _handle_expose_decorated_func(
|
|
194
197
|
self,
|
|
@@ -345,7 +348,7 @@ class Admin(BaseAdminView):
|
|
|
345
348
|
```
|
|
346
349
|
"""
|
|
347
350
|
|
|
348
|
-
def __init__(
|
|
351
|
+
def __init__( # type: ignore[no-any-unimported]
|
|
349
352
|
self,
|
|
350
353
|
app: Starlette,
|
|
351
354
|
engine: ENGINE_TYPE | None = None,
|
|
@@ -373,7 +376,7 @@ class Admin(BaseAdminView):
|
|
|
373
376
|
super().__init__(
|
|
374
377
|
app=app,
|
|
375
378
|
engine=engine,
|
|
376
|
-
session_maker=session_maker,
|
|
379
|
+
session_maker=session_maker, # type: ignore[arg-type]
|
|
377
380
|
base_url=base_url,
|
|
378
381
|
title=title,
|
|
379
382
|
logo_url=logo_url,
|
|
@@ -388,7 +391,9 @@ class Admin(BaseAdminView):
|
|
|
388
391
|
async def http_exception(
|
|
389
392
|
request: Request, exc: Exception
|
|
390
393
|
) -> Response | Awaitable[Response]:
|
|
391
|
-
|
|
394
|
+
if not isinstance(exc, HTTPException):
|
|
395
|
+
raise TypeError("Expected HTTPException, got %s" % type(exc))
|
|
396
|
+
|
|
392
397
|
context = {
|
|
393
398
|
"status_code": exc.status_code,
|
|
394
399
|
"message": exc.detail,
|
|
@@ -630,7 +635,11 @@ class Admin(BaseAdminView):
|
|
|
630
635
|
return await model_view.export_data(rows, export_type=export_type)
|
|
631
636
|
|
|
632
637
|
async def login(self, request: Request) -> Response:
|
|
633
|
-
|
|
638
|
+
if self.authentication_backend is None:
|
|
639
|
+
raise HTTPException(
|
|
640
|
+
status_code=503,
|
|
641
|
+
detail="Authentication backend not configured.",
|
|
642
|
+
)
|
|
634
643
|
|
|
635
644
|
context = {}
|
|
636
645
|
if request.method == "GET":
|
|
@@ -646,7 +655,11 @@ class Admin(BaseAdminView):
|
|
|
646
655
|
return RedirectResponse(request.url_for("admin:index"), status_code=302)
|
|
647
656
|
|
|
648
657
|
async def logout(self, request: Request) -> Response:
|
|
649
|
-
|
|
658
|
+
if self.authentication_backend is None:
|
|
659
|
+
raise HTTPException(
|
|
660
|
+
status_code=503,
|
|
661
|
+
detail="Authentication backend not configured.",
|
|
662
|
+
)
|
|
650
663
|
|
|
651
664
|
response = await self.authentication_backend.logout(request)
|
|
652
665
|
|
|
@@ -669,8 +682,8 @@ class Admin(BaseAdminView):
|
|
|
669
682
|
|
|
670
683
|
try:
|
|
671
684
|
loader: QueryAjaxModelLoader = model_view._form_ajax_refs[name]
|
|
672
|
-
except KeyError:
|
|
673
|
-
raise HTTPException(status_code=400)
|
|
685
|
+
except KeyError as exc:
|
|
686
|
+
raise HTTPException(status_code=400) from exc
|
|
674
687
|
|
|
675
688
|
data = [loader.format(m) for m in await loader.get_list(term)]
|
|
676
689
|
return JSONResponse({"results": data})
|
|
@@ -688,10 +701,12 @@ class Admin(BaseAdminView):
|
|
|
688
701
|
|
|
689
702
|
if form.get("save") == "Save":
|
|
690
703
|
return request.url_for("admin:list", identity=identity)
|
|
691
|
-
|
|
704
|
+
|
|
705
|
+
if form.get("save") == "Save and continue editing" or (
|
|
692
706
|
form.get("save") == "Save as new" and model_view.save_as_continue
|
|
693
707
|
):
|
|
694
708
|
return request.url_for("admin:edit", identity=identity, pk=identifier)
|
|
709
|
+
|
|
695
710
|
return request.url_for("admin:create", identity=identity)
|
|
696
711
|
|
|
697
712
|
async def _handle_form_data(self, request: Request, obj: Any = None) -> FormData:
|
|
@@ -743,7 +758,7 @@ class Admin(BaseAdminView):
|
|
|
743
758
|
def expose(
|
|
744
759
|
path: str,
|
|
745
760
|
*,
|
|
746
|
-
methods: list[str] =
|
|
761
|
+
methods: list[str] | None = None,
|
|
747
762
|
identity: str | None = None,
|
|
748
763
|
include_in_schema: bool = True,
|
|
749
764
|
) -> Callable[..., Any]:
|
|
@@ -753,7 +768,7 @@ def expose(
|
|
|
753
768
|
def wrap(func):
|
|
754
769
|
func._exposed = True
|
|
755
770
|
func._path = path
|
|
756
|
-
func._methods = methods
|
|
771
|
+
func._methods = methods or ["GET"]
|
|
757
772
|
func._identity = identity or func.__name__
|
|
758
773
|
func._include_in_schema = include_in_schema
|
|
759
774
|
return login_required(func)
|
sqladmin/fields.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
# mypy: disable-error-code="override"
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import json
|
|
4
6
|
import operator
|
|
7
|
+
from enum import Enum
|
|
5
8
|
from typing import Any, Callable, Generator
|
|
6
9
|
|
|
7
10
|
from wtforms import Form, ValidationError, fields, widgets
|
|
@@ -29,7 +32,7 @@ class DateField(fields.DateField):
|
|
|
29
32
|
Add custom DatePickerWidget for data-format and data-date-format fields
|
|
30
33
|
"""
|
|
31
34
|
|
|
32
|
-
widget = sqladmin_widgets.DatePickerWidget()
|
|
35
|
+
widget = sqladmin_widgets.DatePickerWidget() # type: ignore[assignment]
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class DateTimeField(fields.DateTimeField):
|
|
@@ -37,7 +40,7 @@ class DateTimeField(fields.DateTimeField):
|
|
|
37
40
|
Allows modifying the datetime format of a DateTimeField using form_args.
|
|
38
41
|
"""
|
|
39
42
|
|
|
40
|
-
widget = sqladmin_widgets.DateTimePickerWidget()
|
|
43
|
+
widget = sqladmin_widgets.DateTimePickerWidget() # type: ignore[assignment]
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
class IntervalField(fields.StringField):
|
|
@@ -53,7 +56,7 @@ class IntervalField(fields.StringField):
|
|
|
53
56
|
if not interval:
|
|
54
57
|
raise ValueError("Invalide timedelta format.")
|
|
55
58
|
|
|
56
|
-
self.data = interval
|
|
59
|
+
self.data = interval # type: ignore[assignment]
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
class SelectField(fields.SelectField):
|
|
@@ -80,13 +83,15 @@ class SelectField(fields.SelectField):
|
|
|
80
83
|
for choice in choices:
|
|
81
84
|
if isinstance(choice, tuple):
|
|
82
85
|
yield (choice[0], choice[1], self.coerce(choice[0]) == self.data, {})
|
|
83
|
-
|
|
86
|
+
elif isinstance(choice, Enum):
|
|
84
87
|
yield (
|
|
85
88
|
choice.value,
|
|
86
89
|
choice.name,
|
|
87
90
|
self.coerce(choice.value) == self.data,
|
|
88
91
|
{},
|
|
89
92
|
)
|
|
93
|
+
else:
|
|
94
|
+
yield (str(choice), str(choice), self.coerce(choice) == self.data, {})
|
|
90
95
|
|
|
91
96
|
def process_formdata(self, valuelist: list[str]) -> None:
|
|
92
97
|
if valuelist:
|
|
@@ -95,8 +100,10 @@ class SelectField(fields.SelectField):
|
|
|
95
100
|
else:
|
|
96
101
|
try:
|
|
97
102
|
self.data = self.coerce(valuelist[0])
|
|
98
|
-
except ValueError:
|
|
99
|
-
raise ValueError(
|
|
103
|
+
except ValueError as exc:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
self.gettext("Invalid Choice: could not coerce")
|
|
106
|
+
) from exc
|
|
100
107
|
|
|
101
108
|
def pre_validate(self, form: Form) -> None:
|
|
102
109
|
if self.allow_blank and self.data is None:
|
|
@@ -109,10 +116,11 @@ class JSONField(fields.TextAreaField):
|
|
|
109
116
|
def _value(self) -> str:
|
|
110
117
|
if self.raw_data:
|
|
111
118
|
return self.raw_data[0]
|
|
112
|
-
|
|
119
|
+
|
|
120
|
+
if self.data:
|
|
113
121
|
return str(json.dumps(self.data, ensure_ascii=False))
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
|
|
123
|
+
return "{}"
|
|
116
124
|
|
|
117
125
|
def process_formdata(self, valuelist: list[str]) -> None:
|
|
118
126
|
if valuelist:
|
|
@@ -125,8 +133,8 @@ class JSONField(fields.TextAreaField):
|
|
|
125
133
|
|
|
126
134
|
try:
|
|
127
135
|
self.data = json.loads(valuelist[0])
|
|
128
|
-
except ValueError:
|
|
129
|
-
raise ValueError(self.gettext("Invalid JSON"))
|
|
136
|
+
except ValueError as exc:
|
|
137
|
+
raise ValueError(self.gettext("Invalid JSON")) from exc
|
|
130
138
|
|
|
131
139
|
|
|
132
140
|
class QuerySelectField(fields.SelectFieldBase):
|
|
@@ -168,7 +176,7 @@ class QuerySelectField(fields.SelectFieldBase):
|
|
|
168
176
|
return self._data
|
|
169
177
|
|
|
170
178
|
@data.setter
|
|
171
|
-
def data(self, data: tuple) -> None:
|
|
179
|
+
def data(self, data: tuple | None) -> None:
|
|
172
180
|
self._data = data
|
|
173
181
|
self._formdata = None
|
|
174
182
|
|
|
@@ -251,7 +259,8 @@ class QuerySelectMultipleField(QuerySelectField):
|
|
|
251
259
|
for pk, _ in self._select_data:
|
|
252
260
|
if not formdata:
|
|
253
261
|
break
|
|
254
|
-
|
|
262
|
+
|
|
263
|
+
if pk in formdata:
|
|
255
264
|
formdata.remove(pk)
|
|
256
265
|
data.append(pk)
|
|
257
266
|
if formdata:
|
|
@@ -260,7 +269,7 @@ class QuerySelectMultipleField(QuerySelectField):
|
|
|
260
269
|
return self._data
|
|
261
270
|
|
|
262
271
|
@data.setter
|
|
263
|
-
def data(self, data: tuple) -> None:
|
|
272
|
+
def data(self, data: tuple | None) -> None:
|
|
264
273
|
self._data = data
|
|
265
274
|
self._formdata = None
|
|
266
275
|
|
|
@@ -280,7 +289,8 @@ class QuerySelectMultipleField(QuerySelectField):
|
|
|
280
289
|
def pre_validate(self, form: Form) -> None:
|
|
281
290
|
if self._invalid_formdata:
|
|
282
291
|
raise ValidationError(self.gettext("Not a valid choice"))
|
|
283
|
-
|
|
292
|
+
|
|
293
|
+
if self.data:
|
|
284
294
|
pk_list = [x[0] for x in self._select_data]
|
|
285
295
|
for v in self.data:
|
|
286
296
|
if v not in pk_list: # pragma: no cover
|
|
@@ -330,7 +340,7 @@ class AjaxSelectField(fields.SelectFieldBase):
|
|
|
330
340
|
|
|
331
341
|
|
|
332
342
|
class AjaxSelectMultipleField(fields.SelectFieldBase):
|
|
333
|
-
widget = sqladmin_widgets.AjaxSelect2Widget(multiple=True)
|
|
343
|
+
widget = sqladmin_widgets.AjaxSelect2Widget(multiple=True) # type: ignore[assignment]
|
|
334
344
|
separator = ","
|
|
335
345
|
|
|
336
346
|
def __init__(
|
|
@@ -371,10 +381,9 @@ class AjaxSelectMultipleField(fields.SelectFieldBase):
|
|
|
371
381
|
|
|
372
382
|
|
|
373
383
|
class Select2TagsField(fields.SelectField):
|
|
374
|
-
widget = sqladmin_widgets.Select2TagsWidget()
|
|
384
|
+
widget = sqladmin_widgets.Select2TagsWidget() # type: ignore[assignment]
|
|
375
385
|
|
|
376
|
-
def pre_validate(self, form: Form) -> None:
|
|
377
|
-
...
|
|
386
|
+
def pre_validate(self, form: Form) -> None: ...
|
|
378
387
|
|
|
379
388
|
def process_formdata(self, valuelist: list) -> None:
|
|
380
389
|
self.data = valuelist
|
|
@@ -388,4 +397,4 @@ class FileField(fields.FileField):
|
|
|
388
397
|
File field which is clearable.
|
|
389
398
|
"""
|
|
390
399
|
|
|
391
|
-
widget = sqladmin_widgets.FileInputWidget()
|
|
400
|
+
widget = sqladmin_widgets.FileInputWidget() # type: ignore[assignment]
|