sqladmin 0.17.0__py3-none-any.whl → 0.19.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 +1 -1
- sqladmin/_menu.py +11 -9
- sqladmin/_queries.py +8 -6
- sqladmin/ajax.py +12 -5
- sqladmin/application.py +39 -35
- sqladmin/authentication.py +4 -2
- sqladmin/fields.py +35 -33
- sqladmin/forms.py +66 -61
- sqladmin/helpers.py +8 -10
- sqladmin/models.py +60 -2
- sqladmin/pagination.py +10 -3
- sqladmin/statics/css/tabler-icons.min.css +4 -0
- sqladmin/statics/css/tabler-icons.min.css.map +1 -0
- sqladmin/statics/webfonts/tabler-icons.woff2 +0 -0
- sqladmin/templates/sqladmin/_macros.html +33 -0
- sqladmin/templates/sqladmin/base.html +4 -0
- sqladmin/templates/sqladmin/create.html +2 -18
- sqladmin/templates/sqladmin/edit.html +8 -24
- sqladmin/templates/sqladmin/list.html +1 -1
- sqladmin/templating.py +9 -7
- sqladmin/widgets.py +4 -1
- {sqladmin-0.17.0.dist-info → sqladmin-0.19.0.dist-info}/METADATA +1 -1
- {sqladmin-0.17.0.dist-info → sqladmin-0.19.0.dist-info}/RECORD +25 -22
- {sqladmin-0.17.0.dist-info → sqladmin-0.19.0.dist-info}/WHEEL +1 -1
- {sqladmin-0.17.0.dist-info → sqladmin-0.19.0.dist-info}/licenses/LICENSE.md +0 -0
sqladmin/__init__.py
CHANGED
sqladmin/_menu.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
from starlette.datastructures import URL
|
|
4
6
|
from starlette.requests import Request
|
|
@@ -8,11 +10,11 @@ if TYPE_CHECKING:
|
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class ItemMenu:
|
|
11
|
-
def __init__(self, name: str, icon:
|
|
13
|
+
def __init__(self, name: str, icon: str | None = None) -> None:
|
|
12
14
|
self.name = name
|
|
13
15
|
self.icon = icon
|
|
14
|
-
self.parent:
|
|
15
|
-
self.children:
|
|
16
|
+
self.parent: "ItemMenu" | None = None
|
|
17
|
+
self.children: list["ItemMenu"] = []
|
|
16
18
|
|
|
17
19
|
def add_child(self, item: "ItemMenu") -> None:
|
|
18
20
|
item.parent = self
|
|
@@ -27,7 +29,7 @@ class ItemMenu:
|
|
|
27
29
|
def is_active(self, request: Request) -> bool:
|
|
28
30
|
return False
|
|
29
31
|
|
|
30
|
-
def url(self, request: Request) ->
|
|
32
|
+
def url(self, request: Request) -> str | URL:
|
|
31
33
|
return "#"
|
|
32
34
|
|
|
33
35
|
@property
|
|
@@ -53,9 +55,9 @@ class CategoryMenu(ItemMenu):
|
|
|
53
55
|
class ViewMenu(ItemMenu):
|
|
54
56
|
def __init__(
|
|
55
57
|
self,
|
|
56
|
-
view:
|
|
58
|
+
view: "BaseView" | "ModelView",
|
|
57
59
|
name: str,
|
|
58
|
-
icon:
|
|
60
|
+
icon: str | None = None,
|
|
59
61
|
) -> None:
|
|
60
62
|
super().__init__(name=name, icon=icon)
|
|
61
63
|
self.view = view
|
|
@@ -69,7 +71,7 @@ class ViewMenu(ItemMenu):
|
|
|
69
71
|
def is_active(self, request: Request) -> bool:
|
|
70
72
|
return self.view.identity == request.path_params.get("identity")
|
|
71
73
|
|
|
72
|
-
def url(self, request: Request) ->
|
|
74
|
+
def url(self, request: Request) -> str | URL:
|
|
73
75
|
if self.view.is_model:
|
|
74
76
|
return request.url_for("admin:list", identity=self.view.identity)
|
|
75
77
|
return request.url_for(f"admin:{self.view.identity}")
|
|
@@ -85,7 +87,7 @@ class ViewMenu(ItemMenu):
|
|
|
85
87
|
|
|
86
88
|
class Menu:
|
|
87
89
|
def __init__(self) -> None:
|
|
88
|
-
self.items:
|
|
90
|
+
self.items: list[ItemMenu] = []
|
|
89
91
|
|
|
90
92
|
def add(self, item: ItemMenu) -> None:
|
|
91
93
|
# Only works for one-level menu
|
sqladmin/_queries.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
2
4
|
|
|
3
5
|
import anyio
|
|
4
6
|
from sqlalchemy import select
|
|
@@ -24,7 +26,7 @@ class Query:
|
|
|
24
26
|
def __init__(self, model_view: "ModelView") -> None:
|
|
25
27
|
self.model_view = model_view
|
|
26
28
|
|
|
27
|
-
def _get_to_many_stmt(self, relation: MODEL_PROPERTY, values:
|
|
29
|
+
def _get_to_many_stmt(self, relation: MODEL_PROPERTY, values: list[Any]) -> Select:
|
|
28
30
|
target = relation.mapper.class_
|
|
29
31
|
|
|
30
32
|
target_pks = get_primary_keys(target)
|
|
@@ -131,7 +133,7 @@ class Query:
|
|
|
131
133
|
setattr(obj, key, value)
|
|
132
134
|
return obj
|
|
133
135
|
|
|
134
|
-
def _update_sync(self, pk: Any, data:
|
|
136
|
+
def _update_sync(self, pk: Any, data: dict[str, Any], request: Request) -> Any:
|
|
135
137
|
stmt = self.model_view._stmt_by_identifier(pk)
|
|
136
138
|
|
|
137
139
|
with self.model_view.session_maker(expire_on_commit=False) as session:
|
|
@@ -147,7 +149,7 @@ class Query:
|
|
|
147
149
|
return obj
|
|
148
150
|
|
|
149
151
|
async def _update_async(
|
|
150
|
-
self, pk: Any, data:
|
|
152
|
+
self, pk: Any, data: dict[str, Any], request: Request
|
|
151
153
|
) -> Any:
|
|
152
154
|
stmt = self.model_view._stmt_by_identifier(pk)
|
|
153
155
|
|
|
@@ -187,7 +189,7 @@ class Query:
|
|
|
187
189
|
await session.commit()
|
|
188
190
|
await self.model_view.after_model_delete(obj, request)
|
|
189
191
|
|
|
190
|
-
def _insert_sync(self, data:
|
|
192
|
+
def _insert_sync(self, data: dict[str, Any], request: Request) -> Any:
|
|
191
193
|
obj = self.model_view.model()
|
|
192
194
|
|
|
193
195
|
with self.model_view.session_maker(expire_on_commit=False) as session:
|
|
@@ -202,7 +204,7 @@ class Query:
|
|
|
202
204
|
)
|
|
203
205
|
return obj
|
|
204
206
|
|
|
205
|
-
async def _insert_async(self, data:
|
|
207
|
+
async def _insert_async(self, data: dict[str, Any], request: Request) -> Any:
|
|
206
208
|
obj = self.model_view.model()
|
|
207
209
|
|
|
208
210
|
async with self.model_view.session_maker(expire_on_commit=False) as session:
|
sqladmin/ajax.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
2
4
|
|
|
3
5
|
from sqlalchemy import String, cast, inspect, or_, select
|
|
4
6
|
|
|
@@ -24,6 +26,7 @@ class QueryAjaxModelLoader:
|
|
|
24
26
|
self.model_admin = model_admin
|
|
25
27
|
self.fields = options.get("fields", {})
|
|
26
28
|
self.order_by = options.get("order_by")
|
|
29
|
+
self.limit = options.get("limit", DEFAULT_PAGE_SIZE)
|
|
27
30
|
|
|
28
31
|
pks = get_primary_keys(self.model)
|
|
29
32
|
self.pk = pks[0] if len(pks) == 1 else None
|
|
@@ -52,13 +55,13 @@ class QueryAjaxModelLoader:
|
|
|
52
55
|
|
|
53
56
|
return remote_fields
|
|
54
57
|
|
|
55
|
-
def format(self, model: type) ->
|
|
58
|
+
def format(self, model: type) -> dict[str, Any]:
|
|
56
59
|
if not model:
|
|
57
60
|
return {}
|
|
58
61
|
|
|
59
62
|
return {"id": str(get_object_identifier(model)), "text": str(model)}
|
|
60
63
|
|
|
61
|
-
async def get_list(self, term: str
|
|
64
|
+
async def get_list(self, term: str) -> list[Any]:
|
|
62
65
|
stmt = select(self.model)
|
|
63
66
|
|
|
64
67
|
# no type casting to string if a ColumnAssociationProxyInstance is given
|
|
@@ -69,9 +72,13 @@ class QueryAjaxModelLoader:
|
|
|
69
72
|
stmt = stmt.filter(or_(*filters))
|
|
70
73
|
|
|
71
74
|
if self.order_by:
|
|
72
|
-
|
|
75
|
+
if isinstance(self.order_by, list):
|
|
76
|
+
for o in self.order_by:
|
|
77
|
+
stmt = stmt.order_by(o)
|
|
78
|
+
else:
|
|
79
|
+
stmt = stmt.order_by(self.order_by)
|
|
73
80
|
|
|
74
|
-
stmt = stmt.limit(limit)
|
|
81
|
+
stmt = stmt.limit(self.limit)
|
|
75
82
|
result = await self.model_admin._run_query(stmt)
|
|
76
83
|
return result
|
|
77
84
|
|
sqladmin/application.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import inspect
|
|
2
4
|
import io
|
|
3
5
|
import logging
|
|
@@ -7,12 +9,7 @@ from typing import (
|
|
|
7
9
|
Any,
|
|
8
10
|
Awaitable,
|
|
9
11
|
Callable,
|
|
10
|
-
List,
|
|
11
|
-
Optional,
|
|
12
12
|
Sequence,
|
|
13
|
-
Tuple,
|
|
14
|
-
Type,
|
|
15
|
-
Union,
|
|
16
13
|
cast,
|
|
17
14
|
no_type_check,
|
|
18
15
|
)
|
|
@@ -66,14 +63,15 @@ class BaseAdmin:
|
|
|
66
63
|
def __init__(
|
|
67
64
|
self,
|
|
68
65
|
app: Starlette,
|
|
69
|
-
engine:
|
|
70
|
-
session_maker:
|
|
66
|
+
engine: ENGINE_TYPE | None = None,
|
|
67
|
+
session_maker: sessionmaker | None = None,
|
|
71
68
|
base_url: str = "/admin",
|
|
72
69
|
title: str = "Admin",
|
|
73
|
-
logo_url:
|
|
70
|
+
logo_url: str | None = None,
|
|
71
|
+
favicon_url: str | None = None,
|
|
74
72
|
templates_dir: str = "templates",
|
|
75
|
-
middlewares:
|
|
76
|
-
authentication_backend:
|
|
73
|
+
middlewares: Sequence[Middleware] | None = None,
|
|
74
|
+
authentication_backend: AuthenticationBackend | None = None,
|
|
77
75
|
) -> None:
|
|
78
76
|
self.app = app
|
|
79
77
|
self.engine = engine
|
|
@@ -81,6 +79,7 @@ class BaseAdmin:
|
|
|
81
79
|
self.templates_dir = templates_dir
|
|
82
80
|
self.title = title
|
|
83
81
|
self.logo_url = logo_url
|
|
82
|
+
self.favicon_url = favicon_url
|
|
84
83
|
|
|
85
84
|
if session_maker:
|
|
86
85
|
self.session_maker = session_maker
|
|
@@ -100,7 +99,7 @@ class BaseAdmin:
|
|
|
100
99
|
|
|
101
100
|
self.admin = Starlette(middleware=middlewares)
|
|
102
101
|
self.templates = self.init_templating_engine()
|
|
103
|
-
self._views:
|
|
102
|
+
self._views: list[BaseView | ModelView] = []
|
|
104
103
|
self._menu = Menu()
|
|
105
104
|
|
|
106
105
|
def init_templating_engine(self) -> Jinja2Templates:
|
|
@@ -120,7 +119,7 @@ class BaseAdmin:
|
|
|
120
119
|
return templates
|
|
121
120
|
|
|
122
121
|
@property
|
|
123
|
-
def views(self) ->
|
|
122
|
+
def views(self) -> list[BaseView | ModelView]:
|
|
124
123
|
"""Get list of ModelView and BaseView instances lazily.
|
|
125
124
|
|
|
126
125
|
Returns:
|
|
@@ -136,7 +135,7 @@ class BaseAdmin:
|
|
|
136
135
|
|
|
137
136
|
raise HTTPException(status_code=404)
|
|
138
137
|
|
|
139
|
-
def add_view(self, view:
|
|
138
|
+
def add_view(self, view: type[ModelView] | type[BaseView]) -> None:
|
|
140
139
|
"""Add ModelView or BaseView classes to Admin.
|
|
141
140
|
This is a shortcut that will handle both `add_model_view` and `add_base_view`.
|
|
142
141
|
"""
|
|
@@ -149,10 +148,10 @@ class BaseAdmin:
|
|
|
149
148
|
|
|
150
149
|
def _find_decorated_funcs(
|
|
151
150
|
self,
|
|
152
|
-
view:
|
|
153
|
-
view_instance:
|
|
151
|
+
view: type[BaseView | ModelView],
|
|
152
|
+
view_instance: BaseView | ModelView,
|
|
154
153
|
handle_fn: Callable[
|
|
155
|
-
[MethodType,
|
|
154
|
+
[MethodType, type[BaseView | ModelView], BaseView | ModelView],
|
|
156
155
|
None,
|
|
157
156
|
],
|
|
158
157
|
) -> None:
|
|
@@ -164,8 +163,8 @@ class BaseAdmin:
|
|
|
164
163
|
def _handle_action_decorated_func(
|
|
165
164
|
self,
|
|
166
165
|
func: MethodType,
|
|
167
|
-
view:
|
|
168
|
-
view_instance:
|
|
166
|
+
view: type[BaseView | ModelView],
|
|
167
|
+
view_instance: BaseView | ModelView,
|
|
169
168
|
) -> None:
|
|
170
169
|
if hasattr(func, "_action"):
|
|
171
170
|
view_instance = cast(ModelView, view_instance)
|
|
@@ -194,8 +193,8 @@ class BaseAdmin:
|
|
|
194
193
|
def _handle_expose_decorated_func(
|
|
195
194
|
self,
|
|
196
195
|
func: MethodType,
|
|
197
|
-
view:
|
|
198
|
-
view_instance:
|
|
196
|
+
view: type[BaseView | ModelView],
|
|
197
|
+
view_instance: BaseView | ModelView,
|
|
199
198
|
) -> None:
|
|
200
199
|
if hasattr(func, "_exposed"):
|
|
201
200
|
self.admin.add_route(
|
|
@@ -208,7 +207,7 @@ class BaseAdmin:
|
|
|
208
207
|
|
|
209
208
|
view.identity = getattr(func, "_identity")
|
|
210
209
|
|
|
211
|
-
def add_model_view(self, view:
|
|
210
|
+
def add_model_view(self, view: type[ModelView]) -> None:
|
|
212
211
|
"""Add ModelView to the Admin.
|
|
213
212
|
|
|
214
213
|
???+ usage
|
|
@@ -237,7 +236,7 @@ class BaseAdmin:
|
|
|
237
236
|
self._views.append(view_instance)
|
|
238
237
|
self._build_menu(view_instance)
|
|
239
238
|
|
|
240
|
-
def add_base_view(self, view:
|
|
239
|
+
def add_base_view(self, view: type[BaseView]) -> None:
|
|
241
240
|
"""Add BaseView to the Admin.
|
|
242
241
|
|
|
243
242
|
???+ usage
|
|
@@ -265,7 +264,7 @@ class BaseAdmin:
|
|
|
265
264
|
self._views.append(view_instance)
|
|
266
265
|
self._build_menu(view_instance)
|
|
267
266
|
|
|
268
|
-
def _build_menu(self, view:
|
|
267
|
+
def _build_menu(self, view: ModelView | BaseView) -> None:
|
|
269
268
|
if view.category:
|
|
270
269
|
menu = CategoryMenu(name=view.category)
|
|
271
270
|
menu.add_child(ViewMenu(view=view, name=view.name, icon=view.icon))
|
|
@@ -338,15 +337,16 @@ class Admin(BaseAdminView):
|
|
|
338
337
|
def __init__(
|
|
339
338
|
self,
|
|
340
339
|
app: Starlette,
|
|
341
|
-
engine:
|
|
342
|
-
session_maker:
|
|
340
|
+
engine: ENGINE_TYPE | None = None,
|
|
341
|
+
session_maker: sessionmaker | "async_sessionmaker" | None = None,
|
|
343
342
|
base_url: str = "/admin",
|
|
344
343
|
title: str = "Admin",
|
|
345
|
-
logo_url:
|
|
346
|
-
|
|
344
|
+
logo_url: str | None = None,
|
|
345
|
+
favicon_url: str | None = None,
|
|
346
|
+
middlewares: Sequence[Middleware] | None = None,
|
|
347
347
|
debug: bool = False,
|
|
348
348
|
templates_dir: str = "templates",
|
|
349
|
-
authentication_backend:
|
|
349
|
+
authentication_backend: AuthenticationBackend | None = None,
|
|
350
350
|
) -> None:
|
|
351
351
|
"""
|
|
352
352
|
Args:
|
|
@@ -356,6 +356,7 @@ class Admin(BaseAdminView):
|
|
|
356
356
|
base_url: Base URL for Admin interface.
|
|
357
357
|
title: Admin title.
|
|
358
358
|
logo_url: URL of logo to be displayed instead of title.
|
|
359
|
+
favicon_url: URL of favicon to be displayed.
|
|
359
360
|
"""
|
|
360
361
|
|
|
361
362
|
super().__init__(
|
|
@@ -365,6 +366,7 @@ class Admin(BaseAdminView):
|
|
|
365
366
|
base_url=base_url,
|
|
366
367
|
title=title,
|
|
367
368
|
logo_url=logo_url,
|
|
369
|
+
favicon_url=favicon_url,
|
|
368
370
|
templates_dir=templates_dir,
|
|
369
371
|
middlewares=middlewares,
|
|
370
372
|
authentication_backend=authentication_backend,
|
|
@@ -374,7 +376,7 @@ class Admin(BaseAdminView):
|
|
|
374
376
|
|
|
375
377
|
async def http_exception(
|
|
376
378
|
request: Request, exc: Exception
|
|
377
|
-
) ->
|
|
379
|
+
) -> Response | Awaitable[Response]:
|
|
378
380
|
assert isinstance(exc, HTTPException)
|
|
379
381
|
context = {
|
|
380
382
|
"status_code": exc.status_code,
|
|
@@ -509,6 +511,7 @@ class Admin(BaseAdminView):
|
|
|
509
511
|
model_view = self._find_model_view(identity)
|
|
510
512
|
|
|
511
513
|
Form = await model_view.scaffold_form()
|
|
514
|
+
model_view._validate_form_class(model_view._form_create_rules, Form)
|
|
512
515
|
form_data = await self._handle_form_data(request)
|
|
513
516
|
form = Form(form_data)
|
|
514
517
|
|
|
@@ -559,6 +562,7 @@ class Admin(BaseAdminView):
|
|
|
559
562
|
raise HTTPException(status_code=404)
|
|
560
563
|
|
|
561
564
|
Form = await model_view.scaffold_form()
|
|
565
|
+
model_view._validate_form_class(model_view._form_edit_rules, Form)
|
|
562
566
|
context = {
|
|
563
567
|
"obj": model,
|
|
564
568
|
"model_view": model_view,
|
|
@@ -660,7 +664,7 @@ class Admin(BaseAdminView):
|
|
|
660
664
|
|
|
661
665
|
def get_save_redirect_url(
|
|
662
666
|
self, request: Request, form: FormData, model_view: ModelView, obj: Any
|
|
663
|
-
) ->
|
|
667
|
+
) -> str | URL:
|
|
664
668
|
"""
|
|
665
669
|
Get the redirect URL after a save action
|
|
666
670
|
which is triggered from create/edit page.
|
|
@@ -685,7 +689,7 @@ class Admin(BaseAdminView):
|
|
|
685
689
|
"""
|
|
686
690
|
|
|
687
691
|
form = await request.form()
|
|
688
|
-
form_data:
|
|
692
|
+
form_data: list[tuple[str, str | UploadFile]] = []
|
|
689
693
|
for key, value in form.multi_items():
|
|
690
694
|
if not isinstance(value, UploadFile):
|
|
691
695
|
form_data.append((key, value))
|
|
@@ -726,8 +730,8 @@ class Admin(BaseAdminView):
|
|
|
726
730
|
def expose(
|
|
727
731
|
path: str,
|
|
728
732
|
*,
|
|
729
|
-
methods:
|
|
730
|
-
identity:
|
|
733
|
+
methods: list[str] = ["GET"],
|
|
734
|
+
identity: str | None = None,
|
|
731
735
|
include_in_schema: bool = True,
|
|
732
736
|
) -> Callable[..., Any]:
|
|
733
737
|
"""Expose View with information."""
|
|
@@ -746,8 +750,8 @@ def expose(
|
|
|
746
750
|
|
|
747
751
|
def action(
|
|
748
752
|
name: str,
|
|
749
|
-
label:
|
|
750
|
-
confirmation_message:
|
|
753
|
+
label: str | None = None,
|
|
754
|
+
confirmation_message: str | None = None,
|
|
751
755
|
*,
|
|
752
756
|
include_in_schema: bool = True,
|
|
753
757
|
add_in_detail: bool = True,
|
sqladmin/authentication.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import functools
|
|
2
4
|
import inspect
|
|
3
|
-
from typing import Any, Callable
|
|
5
|
+
from typing import Any, Callable
|
|
4
6
|
|
|
5
7
|
from starlette.middleware import Middleware
|
|
6
8
|
from starlette.requests import Request
|
|
@@ -33,7 +35,7 @@ class AuthenticationBackend:
|
|
|
33
35
|
"""
|
|
34
36
|
raise NotImplementedError()
|
|
35
37
|
|
|
36
|
-
async def authenticate(self, request: Request) ->
|
|
38
|
+
async def authenticate(self, request: Request) -> Response | bool:
|
|
37
39
|
"""Implement authenticate logic here.
|
|
38
40
|
This method will be called for each incoming request
|
|
39
41
|
to validate the authentication.
|
sqladmin/fields.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
import operator
|
|
3
|
-
from typing import Any, Callable,
|
|
5
|
+
from typing import Any, Callable, Generator
|
|
4
6
|
|
|
5
7
|
from wtforms import Form, ValidationError, fields, widgets
|
|
6
8
|
|
|
@@ -43,7 +45,7 @@ class IntervalField(fields.StringField):
|
|
|
43
45
|
A text field which stores a `datetime.timedelta` object.
|
|
44
46
|
"""
|
|
45
47
|
|
|
46
|
-
def process_formdata(self, valuelist:
|
|
48
|
+
def process_formdata(self, valuelist: list[str]) -> None:
|
|
47
49
|
if not valuelist:
|
|
48
50
|
return
|
|
49
51
|
|
|
@@ -57,19 +59,19 @@ class IntervalField(fields.StringField):
|
|
|
57
59
|
class SelectField(fields.SelectField):
|
|
58
60
|
def __init__(
|
|
59
61
|
self,
|
|
60
|
-
label:
|
|
61
|
-
validators:
|
|
62
|
+
label: str | None = None,
|
|
63
|
+
validators: list | None = None,
|
|
62
64
|
coerce: type = str,
|
|
63
|
-
choices:
|
|
65
|
+
choices: list | Callable | None = None,
|
|
64
66
|
allow_blank: bool = False,
|
|
65
|
-
blank_text:
|
|
67
|
+
blank_text: str | None = None,
|
|
66
68
|
**kwargs: Any,
|
|
67
69
|
) -> None:
|
|
68
70
|
super().__init__(label, validators, coerce, choices, **kwargs)
|
|
69
71
|
self.allow_blank = allow_blank
|
|
70
72
|
self.blank_text = blank_text or " "
|
|
71
73
|
|
|
72
|
-
def iter_choices(self) -> Generator[
|
|
74
|
+
def iter_choices(self) -> Generator[tuple[str, str, bool, dict], None, None]:
|
|
73
75
|
choices = self.choices or []
|
|
74
76
|
|
|
75
77
|
if self.allow_blank:
|
|
@@ -86,7 +88,7 @@ class SelectField(fields.SelectField):
|
|
|
86
88
|
{},
|
|
87
89
|
)
|
|
88
90
|
|
|
89
|
-
def process_formdata(self, valuelist:
|
|
91
|
+
def process_formdata(self, valuelist: list[str]) -> None:
|
|
90
92
|
if valuelist:
|
|
91
93
|
if valuelist[0] == "__None":
|
|
92
94
|
self.data = None
|
|
@@ -112,7 +114,7 @@ class JSONField(fields.TextAreaField):
|
|
|
112
114
|
else:
|
|
113
115
|
return "{}"
|
|
114
116
|
|
|
115
|
-
def process_formdata(self, valuelist:
|
|
117
|
+
def process_formdata(self, valuelist: list[str]) -> None:
|
|
116
118
|
if valuelist:
|
|
117
119
|
value = valuelist[0]
|
|
118
120
|
|
|
@@ -132,10 +134,10 @@ class QuerySelectField(fields.SelectFieldBase):
|
|
|
132
134
|
|
|
133
135
|
def __init__(
|
|
134
136
|
self,
|
|
135
|
-
data:
|
|
136
|
-
label:
|
|
137
|
-
validators:
|
|
138
|
-
get_label:
|
|
137
|
+
data: list | None = None,
|
|
138
|
+
label: str | None = None,
|
|
139
|
+
validators: list | None = None,
|
|
140
|
+
get_label: Callable | str | None = None,
|
|
139
141
|
allow_blank: bool = False,
|
|
140
142
|
blank_text: str = "",
|
|
141
143
|
**kwargs: Any,
|
|
@@ -153,11 +155,11 @@ class QuerySelectField(fields.SelectFieldBase):
|
|
|
153
155
|
|
|
154
156
|
self.allow_blank = allow_blank
|
|
155
157
|
self.blank_text = blank_text
|
|
156
|
-
self._data:
|
|
157
|
-
self._formdata:
|
|
158
|
+
self._data: tuple | None
|
|
159
|
+
self._formdata: str | list[str] | None
|
|
158
160
|
|
|
159
161
|
@property
|
|
160
|
-
def data(self) ->
|
|
162
|
+
def data(self) -> tuple | None:
|
|
161
163
|
if self._formdata is not None:
|
|
162
164
|
for pk, _ in self._select_data:
|
|
163
165
|
if pk == self._formdata:
|
|
@@ -170,7 +172,7 @@ class QuerySelectField(fields.SelectFieldBase):
|
|
|
170
172
|
self._data = data
|
|
171
173
|
self._formdata = None
|
|
172
174
|
|
|
173
|
-
def iter_choices(self) -> Generator[
|
|
175
|
+
def iter_choices(self) -> Generator[tuple[str, str, bool, dict], None, None]:
|
|
174
176
|
if self.allow_blank:
|
|
175
177
|
yield ("__None", self.blank_text, self.data is None, {})
|
|
176
178
|
|
|
@@ -186,7 +188,7 @@ class QuerySelectField(fields.SelectFieldBase):
|
|
|
186
188
|
for pk, label in self._select_data:
|
|
187
189
|
yield (pk, self.get_label(label), str(pk) == primary_key, {})
|
|
188
190
|
|
|
189
|
-
def process_formdata(self, valuelist:
|
|
191
|
+
def process_formdata(self, valuelist: list[str]) -> None:
|
|
190
192
|
if valuelist:
|
|
191
193
|
if self.allow_blank and valuelist[0] == "__None":
|
|
192
194
|
self.data = None
|
|
@@ -220,9 +222,9 @@ class QuerySelectMultipleField(QuerySelectField):
|
|
|
220
222
|
|
|
221
223
|
def __init__(
|
|
222
224
|
self,
|
|
223
|
-
data:
|
|
224
|
-
label:
|
|
225
|
-
validators:
|
|
225
|
+
data: list | None = None,
|
|
226
|
+
label: str | None = None,
|
|
227
|
+
validators: list | None = None,
|
|
226
228
|
default: Any = None,
|
|
227
229
|
**kwargs: Any,
|
|
228
230
|
) -> None:
|
|
@@ -238,11 +240,11 @@ class QuerySelectMultipleField(QuerySelectField):
|
|
|
238
240
|
"allow_blank=True does not do anything for QuerySelectMultipleField."
|
|
239
241
|
)
|
|
240
242
|
self._invalid_formdata = False
|
|
241
|
-
self._formdata:
|
|
242
|
-
self._data:
|
|
243
|
+
self._formdata: list[str] | None = None
|
|
244
|
+
self._data: tuple | None = None
|
|
243
245
|
|
|
244
246
|
@property
|
|
245
|
-
def data(self) ->
|
|
247
|
+
def data(self) -> tuple | None:
|
|
246
248
|
formdata = self._formdata
|
|
247
249
|
if formdata is not None:
|
|
248
250
|
data = []
|
|
@@ -262,7 +264,7 @@ class QuerySelectMultipleField(QuerySelectField):
|
|
|
262
264
|
self._data = data
|
|
263
265
|
self._formdata = None
|
|
264
266
|
|
|
265
|
-
def iter_choices(self) -> Generator[
|
|
267
|
+
def iter_choices(self) -> Generator[tuple[str, Any, bool, dict], None, None]:
|
|
266
268
|
if self.data is not None:
|
|
267
269
|
primary_keys = (
|
|
268
270
|
self.data
|
|
@@ -272,7 +274,7 @@ class QuerySelectMultipleField(QuerySelectField):
|
|
|
272
274
|
for pk, label in self._select_data:
|
|
273
275
|
yield (pk, self.get_label(label), pk in primary_keys, {})
|
|
274
276
|
|
|
275
|
-
def process_formdata(self, valuelist:
|
|
277
|
+
def process_formdata(self, valuelist: list[str]) -> None:
|
|
276
278
|
self._formdata = list(set(valuelist))
|
|
277
279
|
|
|
278
280
|
def pre_validate(self, form: Form) -> None:
|
|
@@ -292,8 +294,8 @@ class AjaxSelectField(fields.SelectFieldBase):
|
|
|
292
294
|
def __init__(
|
|
293
295
|
self,
|
|
294
296
|
loader: QueryAjaxModelLoader,
|
|
295
|
-
label:
|
|
296
|
-
validators:
|
|
297
|
+
label: str | None = None,
|
|
298
|
+
validators: list | None = None,
|
|
297
299
|
allow_blank: bool = False,
|
|
298
300
|
**kwargs: Any,
|
|
299
301
|
) -> None:
|
|
@@ -334,9 +336,9 @@ class AjaxSelectMultipleField(fields.SelectFieldBase):
|
|
|
334
336
|
def __init__(
|
|
335
337
|
self,
|
|
336
338
|
loader: QueryAjaxModelLoader,
|
|
337
|
-
label:
|
|
338
|
-
validators:
|
|
339
|
-
default:
|
|
339
|
+
label: str | None = None,
|
|
340
|
+
validators: list | None = None,
|
|
341
|
+
default: list | None = None,
|
|
340
342
|
allow_blank: bool = False,
|
|
341
343
|
**kwargs: Any,
|
|
342
344
|
) -> None:
|
|
@@ -344,7 +346,7 @@ class AjaxSelectMultipleField(fields.SelectFieldBase):
|
|
|
344
346
|
self.loader = loader
|
|
345
347
|
self.allow_blank = allow_blank
|
|
346
348
|
default = default or []
|
|
347
|
-
self._formdata:
|
|
349
|
+
self._formdata: set[Any] = set()
|
|
348
350
|
|
|
349
351
|
super().__init__(label, validators, default=default, **kwargs)
|
|
350
352
|
|
|
@@ -377,7 +379,7 @@ class Select2TagsField(fields.SelectField):
|
|
|
377
379
|
def process_formdata(self, valuelist: list) -> None:
|
|
378
380
|
self.data = valuelist
|
|
379
381
|
|
|
380
|
-
def process_data(self, value:
|
|
382
|
+
def process_data(self, value: list | None) -> None:
|
|
381
383
|
self.data = value or []
|
|
382
384
|
|
|
383
385
|
|