sqladmin 0.16.0__tar.gz → 0.17.0__tar.gz
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-0.16.0 → sqladmin-0.17.0}/PKG-INFO +10 -3
- {sqladmin-0.16.0 → sqladmin-0.17.0}/README.md +8 -1
- {sqladmin-0.16.0 → sqladmin-0.17.0}/pyproject.toml +1 -1
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/__init__.py +1 -1
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/_menu.py +1 -1
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/_queries.py +2 -2
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/application.py +32 -17
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/forms.py +1 -1
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/helpers.py +6 -3
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/models.py +50 -32
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/css/main.css +1 -1
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/_macros.html +14 -10
- sqladmin-0.17.0/sqladmin/templates/sqladmin/base.html +35 -0
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/create.html +10 -5
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/details.html +20 -14
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/edit.html +17 -9
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/error.html +2 -2
- sqladmin-0.17.0/sqladmin/templates/sqladmin/index.html +3 -0
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/layout.html +4 -4
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/list.html +62 -38
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/login.html +2 -2
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/modals/delete.html +1 -1
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/modals/details_action_confirmation.html +3 -2
- {sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/modals/list_action_confirmation.html +3 -2
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/widgets.py +20 -12
- sqladmin-0.16.0/sqladmin/templates/base.html +0 -32
- sqladmin-0.16.0/sqladmin/templates/index.html +0 -3
- {sqladmin-0.16.0 → sqladmin-0.17.0}/.gitignore +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/LICENSE.md +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/_types.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/_validators.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/ajax.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/authentication.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/exceptions.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/fields.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/formatters.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/pagination.py +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/py.typed +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/css/flatpickr.min.css +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/css/fontawesome.min.css +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/css/select2.min.css +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/css/tabler.min.css +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/js/bootstrap.min.js +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/js/flatpickr.min.js +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/js/jquery.min.js +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/js/main.js +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/js/popper.min.js +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/js/select2.full.min.js +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/js/tabler.min.js +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/webfonts/fa-brands-400.woff2 +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/webfonts/fa-regular-400.woff2 +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/statics/webfonts/fa-solid-900.woff2 +0 -0
- {sqladmin-0.16.0 → sqladmin-0.17.0}/sqladmin/templating.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: sqladmin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: SQLAlchemy admin for FastAPI and Starlette
|
|
5
5
|
Project-URL: Documentation, https://aminalaee.dev/sqladmin
|
|
6
6
|
Project-URL: Issues, https://github.com/aminalaee/sqladmin/issues
|
|
@@ -82,9 +82,16 @@ Main features include:
|
|
|
82
82
|
|
|
83
83
|
## Installation
|
|
84
84
|
|
|
85
|
+
Install using `pip`:
|
|
86
|
+
|
|
85
87
|
```shell
|
|
86
88
|
$ pip install sqladmin
|
|
87
|
-
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This will install the full version of sqladmin with optional dependencies:
|
|
92
|
+
|
|
93
|
+
```shell
|
|
94
|
+
$ pip install "sqladmin[full]"
|
|
88
95
|
```
|
|
89
96
|
|
|
90
97
|
---
|
|
@@ -49,9 +49,16 @@ Main features include:
|
|
|
49
49
|
|
|
50
50
|
## Installation
|
|
51
51
|
|
|
52
|
+
Install using `pip`:
|
|
53
|
+
|
|
52
54
|
```shell
|
|
53
55
|
$ pip install sqladmin
|
|
54
|
-
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This will install the full version of sqladmin with optional dependencies:
|
|
59
|
+
|
|
60
|
+
```shell
|
|
61
|
+
$ pip install "sqladmin[full]"
|
|
55
62
|
```
|
|
56
63
|
|
|
57
64
|
---
|
|
@@ -42,7 +42,7 @@ class ItemMenu:
|
|
|
42
42
|
class CategoryMenu(ItemMenu):
|
|
43
43
|
def is_active(self, request: Request) -> bool:
|
|
44
44
|
return any(
|
|
45
|
-
c.
|
|
45
|
+
c.is_visible(request) and c.is_accessible(request) for c in self.children
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
@property
|
|
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, List
|
|
|
3
3
|
import anyio
|
|
4
4
|
from sqlalchemy import select
|
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
|
-
from sqlalchemy.orm import Session,
|
|
6
|
+
from sqlalchemy.orm import Session, selectinload
|
|
7
7
|
from sqlalchemy.sql.expression import Select, and_, or_
|
|
8
8
|
from starlette.requests import Request
|
|
9
9
|
|
|
@@ -152,7 +152,7 @@ class Query:
|
|
|
152
152
|
stmt = self.model_view._stmt_by_identifier(pk)
|
|
153
153
|
|
|
154
154
|
for relation in self.model_view._form_relations:
|
|
155
|
-
stmt = stmt.options(
|
|
155
|
+
stmt = stmt.options(selectinload(relation))
|
|
156
156
|
|
|
157
157
|
async with self.model_view.session_maker(expire_on_commit=False) as session:
|
|
158
158
|
result = await session.execute(stmt)
|
|
@@ -5,6 +5,7 @@ from types import MethodType
|
|
|
5
5
|
from typing import (
|
|
6
6
|
TYPE_CHECKING,
|
|
7
7
|
Any,
|
|
8
|
+
Awaitable,
|
|
8
9
|
Callable,
|
|
9
10
|
List,
|
|
10
11
|
Optional,
|
|
@@ -15,14 +16,14 @@ from typing import (
|
|
|
15
16
|
cast,
|
|
16
17
|
no_type_check,
|
|
17
18
|
)
|
|
18
|
-
from urllib.parse import urljoin
|
|
19
|
+
from urllib.parse import parse_qsl, urljoin
|
|
19
20
|
|
|
20
21
|
from jinja2 import ChoiceLoader, FileSystemLoader, PackageLoader
|
|
21
22
|
from sqlalchemy.engine import Engine
|
|
22
23
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
23
24
|
from sqlalchemy.orm import Session, sessionmaker
|
|
24
25
|
from starlette.applications import Starlette
|
|
25
|
-
from starlette.datastructures import URL, FormData, UploadFile
|
|
26
|
+
from starlette.datastructures import URL, FormData, MultiDict, UploadFile
|
|
26
27
|
from starlette.exceptions import HTTPException
|
|
27
28
|
from starlette.middleware import Middleware
|
|
28
29
|
from starlette.requests import Request
|
|
@@ -371,15 +372,17 @@ class Admin(BaseAdminView):
|
|
|
371
372
|
|
|
372
373
|
statics = StaticFiles(packages=["sqladmin"])
|
|
373
374
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
375
|
+
async def http_exception(
|
|
376
|
+
request: Request, exc: Exception
|
|
377
|
+
) -> Union[Response, Awaitable[Response]]:
|
|
378
|
+
assert isinstance(exc, HTTPException)
|
|
379
|
+
context = {
|
|
380
|
+
"status_code": exc.status_code,
|
|
381
|
+
"message": exc.detail,
|
|
382
|
+
}
|
|
383
|
+
return await self.templates.TemplateResponse(
|
|
384
|
+
request, "sqladmin/error.html", context, status_code=exc.status_code
|
|
385
|
+
)
|
|
383
386
|
|
|
384
387
|
routes = [
|
|
385
388
|
Mount("/statics", app=statics, name="statics"),
|
|
@@ -417,7 +420,7 @@ class Admin(BaseAdminView):
|
|
|
417
420
|
]
|
|
418
421
|
|
|
419
422
|
self.admin.router.routes = routes
|
|
420
|
-
|
|
423
|
+
self.admin.exception_handlers = {HTTPException: http_exception}
|
|
421
424
|
self.admin.debug = debug
|
|
422
425
|
self.app.mount(base_url, app=self.admin, name="admin")
|
|
423
426
|
|
|
@@ -425,7 +428,7 @@ class Admin(BaseAdminView):
|
|
|
425
428
|
async def index(self, request: Request) -> Response:
|
|
426
429
|
"""Index route which can be overridden to create dashboards."""
|
|
427
430
|
|
|
428
|
-
return await self.templates.TemplateResponse(request, "index.html")
|
|
431
|
+
return await self.templates.TemplateResponse(request, "sqladmin/index.html")
|
|
429
432
|
|
|
430
433
|
@login_required
|
|
431
434
|
async def list(self, request: Request) -> Response:
|
|
@@ -437,6 +440,14 @@ class Admin(BaseAdminView):
|
|
|
437
440
|
pagination = await model_view.list(request)
|
|
438
441
|
pagination.add_pagination_urls(request.url)
|
|
439
442
|
|
|
443
|
+
if (
|
|
444
|
+
pagination.page * pagination.page_size
|
|
445
|
+
> pagination.count + pagination.page_size
|
|
446
|
+
):
|
|
447
|
+
raise HTTPException(
|
|
448
|
+
status_code=400, detail="Invalid page or pageSize parameter"
|
|
449
|
+
)
|
|
450
|
+
|
|
440
451
|
context = {"model_view": model_view, "pagination": pagination}
|
|
441
452
|
return await self.templates.TemplateResponse(
|
|
442
453
|
request, model_view.list_template, context
|
|
@@ -482,7 +493,11 @@ class Admin(BaseAdminView):
|
|
|
482
493
|
|
|
483
494
|
await model_view.delete_model(request, pk)
|
|
484
495
|
|
|
485
|
-
|
|
496
|
+
referer_url = URL(request.headers.get("referer", ""))
|
|
497
|
+
referer_params = MultiDict(parse_qsl(referer_url.query))
|
|
498
|
+
url = URL(str(request.url_for("admin:list", identity=identity)))
|
|
499
|
+
url = url.include_query_params(**referer_params)
|
|
500
|
+
return Response(content=str(url))
|
|
486
501
|
|
|
487
502
|
@login_required
|
|
488
503
|
async def create(self, request: Request) -> Response:
|
|
@@ -539,7 +554,7 @@ class Admin(BaseAdminView):
|
|
|
539
554
|
identity = request.path_params["identity"]
|
|
540
555
|
model_view = self._find_model_view(identity)
|
|
541
556
|
|
|
542
|
-
model = await model_view.get_object_for_edit(request
|
|
557
|
+
model = await model_view.get_object_for_edit(request)
|
|
543
558
|
if not model:
|
|
544
559
|
raise HTTPException(status_code=404)
|
|
545
560
|
|
|
@@ -606,13 +621,13 @@ class Admin(BaseAdminView):
|
|
|
606
621
|
|
|
607
622
|
context = {}
|
|
608
623
|
if request.method == "GET":
|
|
609
|
-
return await self.templates.TemplateResponse(request, "login.html")
|
|
624
|
+
return await self.templates.TemplateResponse(request, "sqladmin/login.html")
|
|
610
625
|
|
|
611
626
|
ok = await self.authentication_backend.login(request)
|
|
612
627
|
if not ok:
|
|
613
628
|
context["error"] = "Invalid credentials."
|
|
614
629
|
return await self.templates.TemplateResponse(
|
|
615
|
-
request, "login.html", context, status_code=400
|
|
630
|
+
request, "sqladmin/login.html", context, status_code=400
|
|
616
631
|
)
|
|
617
632
|
|
|
618
633
|
return RedirectResponse(request.url_for("admin:index"), status_code=302)
|
|
@@ -169,7 +169,7 @@ class ModelConverterBase:
|
|
|
169
169
|
if (column.primary_key or column.foreign_keys) and not form_include_pk:
|
|
170
170
|
return None
|
|
171
171
|
|
|
172
|
-
default = getattr(column, "default", None)
|
|
172
|
+
default = getattr(column, "default", None) or kwargs.get("default")
|
|
173
173
|
|
|
174
174
|
if default is not None:
|
|
175
175
|
# Only actually change default if it has an attribute named
|
|
@@ -159,7 +159,7 @@ class _PseudoBuffer:
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
def stream_to_csv(
|
|
162
|
-
callback: Callable[[Writer], AsyncGenerator[T, None]]
|
|
162
|
+
callback: Callable[[Writer], AsyncGenerator[T, None]],
|
|
163
163
|
) -> Generator[T, None, None]:
|
|
164
164
|
"""Function that takes a callable (that yields from a CSV Writer), and
|
|
165
165
|
provides it a writer that streams the output directly instead of
|
|
@@ -241,10 +241,13 @@ def get_direction(prop: MODEL_PROPERTY) -> str:
|
|
|
241
241
|
|
|
242
242
|
def get_column_python_type(column: Column) -> type:
|
|
243
243
|
try:
|
|
244
|
-
if hasattr(column.type, "impl"):
|
|
245
|
-
return column.type.impl.python_type
|
|
246
244
|
return column.type.python_type
|
|
247
245
|
except NotImplementedError:
|
|
246
|
+
if hasattr(column.type, "impl"):
|
|
247
|
+
try:
|
|
248
|
+
return column.type.impl.python_type
|
|
249
|
+
except NotImplementedError:
|
|
250
|
+
...
|
|
248
251
|
return str
|
|
249
252
|
|
|
250
253
|
|
|
@@ -20,11 +20,12 @@ from urllib.parse import urlencode
|
|
|
20
20
|
import anyio
|
|
21
21
|
from sqlalchemy import Column, String, asc, cast, desc, func, inspect, or_
|
|
22
22
|
from sqlalchemy.exc import NoInspectionAvailable
|
|
23
|
-
from sqlalchemy.orm import
|
|
23
|
+
from sqlalchemy.orm import selectinload, sessionmaker
|
|
24
24
|
from sqlalchemy.orm.exc import DetachedInstanceError
|
|
25
25
|
from sqlalchemy.sql.elements import ClauseElement
|
|
26
26
|
from sqlalchemy.sql.expression import Select, select
|
|
27
27
|
from starlette.datastructures import URL
|
|
28
|
+
from starlette.exceptions import HTTPException
|
|
28
29
|
from starlette.requests import Request
|
|
29
30
|
from starlette.responses import StreamingResponse
|
|
30
31
|
from wtforms import Field, Form
|
|
@@ -316,7 +317,7 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
316
317
|
```
|
|
317
318
|
"""
|
|
318
319
|
|
|
319
|
-
column_default_sort: ClassVar[Union[
|
|
320
|
+
column_default_sort: ClassVar[Union[MODEL_ATTR, Tuple[MODEL_ATTR, bool], list]] = []
|
|
320
321
|
"""Default sort column if no sorting is applied.
|
|
321
322
|
|
|
322
323
|
???+ example
|
|
@@ -414,17 +415,17 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
414
415
|
"""
|
|
415
416
|
|
|
416
417
|
# Templates
|
|
417
|
-
list_template: ClassVar[str] = "list.html"
|
|
418
|
-
"""List view template. Default is `list.html`."""
|
|
418
|
+
list_template: ClassVar[str] = "sqladmin/list.html"
|
|
419
|
+
"""List view template. Default is `sqladmin/list.html`."""
|
|
419
420
|
|
|
420
|
-
create_template: ClassVar[str] = "create.html"
|
|
421
|
-
"""Create view template. Default is `create.html`."""
|
|
421
|
+
create_template: ClassVar[str] = "sqladmin/create.html"
|
|
422
|
+
"""Create view template. Default is `sqladmin/create.html`."""
|
|
422
423
|
|
|
423
|
-
details_template: ClassVar[str] = "details.html"
|
|
424
|
-
"""Details view template. Default is `details.html`."""
|
|
424
|
+
details_template: ClassVar[str] = "sqladmin/details.html"
|
|
425
|
+
"""Details view template. Default is `sqladmin/details.html`."""
|
|
425
426
|
|
|
426
|
-
edit_template: ClassVar[str] = "edit.html"
|
|
427
|
-
"""Edit view template. Default is `edit.html`."""
|
|
427
|
+
edit_template: ClassVar[str] = "sqladmin/edit.html"
|
|
428
|
+
"""Edit view template. Default is `sqladmin/edit.html`."""
|
|
428
429
|
|
|
429
430
|
# Export
|
|
430
431
|
column_export_list: ClassVar[List[MODEL_ATTR]] = []
|
|
@@ -672,12 +673,10 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
672
673
|
self._export_prop_names = self.get_export_columns()
|
|
673
674
|
|
|
674
675
|
self._search_fields = [
|
|
675
|
-
attr
|
|
676
|
-
for attr in self.column_searchable_list
|
|
676
|
+
self._get_prop_name(attr) for attr in self.column_searchable_list
|
|
677
677
|
]
|
|
678
678
|
self._sort_fields = [
|
|
679
|
-
attr
|
|
680
|
-
for attr in self.column_sortable_list
|
|
679
|
+
self._get_prop_name(attr) for attr in self.column_sortable_list
|
|
681
680
|
]
|
|
682
681
|
|
|
683
682
|
self._form_ajax_refs = {}
|
|
@@ -727,6 +726,9 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
727
726
|
pk=get_object_identifier(obj),
|
|
728
727
|
)
|
|
729
728
|
|
|
729
|
+
def _get_prop_name(self, prop: MODEL_ATTR) -> str:
|
|
730
|
+
return prop if isinstance(prop, str) else prop.key
|
|
731
|
+
|
|
730
732
|
def _get_default_sort(self) -> List[Tuple[str, bool]]:
|
|
731
733
|
if self.column_default_sort:
|
|
732
734
|
if isinstance(self.column_default_sort, list):
|
|
@@ -745,6 +747,17 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
745
747
|
|
|
746
748
|
return value
|
|
747
749
|
|
|
750
|
+
def validate_page_number(self, number: Union[str, None], default: int) -> int:
|
|
751
|
+
if not number:
|
|
752
|
+
return default
|
|
753
|
+
|
|
754
|
+
try:
|
|
755
|
+
return int(number)
|
|
756
|
+
except ValueError:
|
|
757
|
+
raise HTTPException(
|
|
758
|
+
status_code=400, detail="Invalid page or pageSize parameter"
|
|
759
|
+
)
|
|
760
|
+
|
|
748
761
|
async def count(self, request: Request, stmt: Optional[Select] = None) -> int:
|
|
749
762
|
if stmt is None:
|
|
750
763
|
stmt = self.count_query(request)
|
|
@@ -752,14 +765,14 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
752
765
|
return rows[0]
|
|
753
766
|
|
|
754
767
|
async def list(self, request: Request) -> Pagination:
|
|
755
|
-
page =
|
|
756
|
-
page_size =
|
|
768
|
+
page = self.validate_page_number(request.query_params.get("page"), 1)
|
|
769
|
+
page_size = self.validate_page_number(request.query_params.get("pageSize"), 0)
|
|
757
770
|
page_size = min(page_size or self.page_size, max(self.page_size_options))
|
|
758
771
|
search = request.query_params.get("search", None)
|
|
759
772
|
|
|
760
773
|
stmt = self.list_query(request)
|
|
761
774
|
for relation in self._list_relations:
|
|
762
|
-
stmt = stmt.options(
|
|
775
|
+
stmt = stmt.options(selectinload(relation))
|
|
763
776
|
|
|
764
777
|
stmt = self.sort_query(stmt, request)
|
|
765
778
|
|
|
@@ -789,7 +802,7 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
789
802
|
stmt = self.list_query(request).limit(limit)
|
|
790
803
|
|
|
791
804
|
for relation in self._list_relations:
|
|
792
|
-
stmt = stmt.options(
|
|
805
|
+
stmt = stmt.options(selectinload(relation))
|
|
793
806
|
|
|
794
807
|
rows = await self._run_query(stmt)
|
|
795
808
|
return rows
|
|
@@ -802,16 +815,12 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
802
815
|
stmt = self._stmt_by_identifier(value)
|
|
803
816
|
|
|
804
817
|
for relation in self._details_relations:
|
|
805
|
-
stmt = stmt.options(
|
|
818
|
+
stmt = stmt.options(selectinload(relation))
|
|
806
819
|
|
|
807
820
|
return await self._get_object_by_pk(stmt)
|
|
808
821
|
|
|
809
|
-
async def get_object_for_edit(self,
|
|
810
|
-
stmt = self.
|
|
811
|
-
|
|
812
|
-
for relation in self._form_relations:
|
|
813
|
-
stmt = stmt.options(joinedload(relation))
|
|
814
|
-
|
|
822
|
+
async def get_object_for_edit(self, request: Request) -> Any:
|
|
823
|
+
stmt = self.edit_form_query(request)
|
|
815
824
|
return await self._get_object_by_pk(stmt)
|
|
816
825
|
|
|
817
826
|
async def get_object_for_delete(self, value: Any) -> Any:
|
|
@@ -881,9 +890,9 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
881
890
|
if include == "__all__":
|
|
882
891
|
return self._prop_names
|
|
883
892
|
elif include:
|
|
884
|
-
return [
|
|
893
|
+
return [self._get_prop_name(item) for item in include]
|
|
885
894
|
elif exclude:
|
|
886
|
-
exclude = [
|
|
895
|
+
exclude = [self._get_prop_name(item) for item in exclude]
|
|
887
896
|
return [prop for prop in self._prop_names if prop not in exclude]
|
|
888
897
|
return defaults
|
|
889
898
|
|
|
@@ -956,10 +965,7 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
956
965
|
) -> Dict[str, Any]:
|
|
957
966
|
pairs = {}
|
|
958
967
|
for label, value in pair.items():
|
|
959
|
-
|
|
960
|
-
pairs[label] = value
|
|
961
|
-
else:
|
|
962
|
-
pairs[label.key] = value
|
|
968
|
+
pairs[self._get_prop_name(label)] = value
|
|
963
969
|
return pairs
|
|
964
970
|
|
|
965
971
|
async def delete_model(self, request: Request, pk: Any) -> None:
|
|
@@ -1047,6 +1053,18 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
1047
1053
|
|
|
1048
1054
|
return select(self.model)
|
|
1049
1055
|
|
|
1056
|
+
def edit_form_query(self, request: Request) -> Select:
|
|
1057
|
+
"""
|
|
1058
|
+
The SQLAlchemy select expression used for the edit form page which can be
|
|
1059
|
+
customized. By default it will select the object by primary key(s) without any
|
|
1060
|
+
additional filters.
|
|
1061
|
+
"""
|
|
1062
|
+
|
|
1063
|
+
stmt = self._stmt_by_identifier(request.path_params["pk"])
|
|
1064
|
+
for relation in self._form_relations:
|
|
1065
|
+
stmt = stmt.options(selectinload(relation))
|
|
1066
|
+
return stmt
|
|
1067
|
+
|
|
1050
1068
|
def count_query(self, request: Request) -> Select:
|
|
1051
1069
|
"""
|
|
1052
1070
|
The SQLAlchemy select expression used for the count query
|
|
@@ -1075,7 +1093,7 @@ class ModelView(BaseView, metaclass=ModelViewMeta):
|
|
|
1075
1093
|
for sort_field, is_desc in sort_fields:
|
|
1076
1094
|
model = self.model
|
|
1077
1095
|
|
|
1078
|
-
parts = sort_field.split(".")
|
|
1096
|
+
parts = self._get_prop_name(sort_field).split(".")
|
|
1079
1097
|
for part in parts[:-1]:
|
|
1080
1098
|
model = getattr(model, part).mapper.class_
|
|
1081
1099
|
stmt = stmt.join(model)
|
{sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/_macros.html
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{% macro menu_category(menu, request) %}
|
|
2
|
-
{% if menu.
|
|
2
|
+
{% if menu.is_active(request) %}
|
|
3
3
|
<li class="nav-item dropdown">
|
|
4
|
-
<a class="nav-link dropdown-toggle {% if menu.is_active(request) %}active{% endif %}" data-bs-toggle="dropdown"
|
|
4
|
+
<a class="nav-link dropdown-toggle {% if menu.is_active(request) %}active{% endif %}" data-bs-toggle="dropdown"
|
|
5
|
+
href="#">
|
|
5
6
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
|
6
7
|
{% if menu.icon %}<i class="{{ menu.icon }}"></i>{% endif %}
|
|
7
8
|
</span>
|
|
@@ -11,12 +12,15 @@
|
|
|
11
12
|
<div class="dropdown-menu-columns">
|
|
12
13
|
<div class="dropdown-menu-column">
|
|
13
14
|
{% for sub_menu in menu.children %}
|
|
14
|
-
|
|
15
|
+
{% if sub_menu.is_visible(request) and sub_menu.is_accessible(request) %}
|
|
16
|
+
<a class="nav-link ps-lg-3 {% if sub_menu.is_active(request) %}active{% endif %}"
|
|
17
|
+
href="{{ sub_menu.url(request) }}">
|
|
15
18
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
|
16
19
|
{% if sub_menu.icon %}<i class="{{ sub_menu.icon }}"></i>{% endif %}
|
|
17
20
|
</span>
|
|
18
21
|
<span class="nav-link-title">{{ sub_menu.display_name }}</span>
|
|
19
22
|
</a>
|
|
23
|
+
{% endif %}
|
|
20
24
|
{% endfor %}
|
|
21
25
|
</div>
|
|
22
26
|
</div>
|
|
@@ -40,12 +44,12 @@
|
|
|
40
44
|
|
|
41
45
|
{% macro display_menu(menu, request) %}
|
|
42
46
|
<div class="navbar-nav">
|
|
43
|
-
{% for item in menu.items %}
|
|
44
|
-
{% if item.type_ == "View" %}
|
|
45
|
-
{{ menu_item(item, request) }}
|
|
46
|
-
{% elif item.type_ == "Category" %}
|
|
47
|
-
{{ menu_category(item, request) }}
|
|
48
|
-
{% endif %}
|
|
49
|
-
{% endfor %}
|
|
47
|
+
{% for item in menu.items %}
|
|
48
|
+
{% if item.type_ == "View" %}
|
|
49
|
+
{{ menu_item(item, request) }}
|
|
50
|
+
{% elif item.type_ == "Category" %}
|
|
51
|
+
{{ menu_category(item, request) }}
|
|
52
|
+
{% endif %}
|
|
53
|
+
{% endfor %}
|
|
50
54
|
</div>
|
|
51
55
|
{% endmacro %}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
7
|
+
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/tabler.min.css') }}">
|
|
8
|
+
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/fontawesome.min.css') }}">
|
|
9
|
+
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/select2.min.css') }}">
|
|
10
|
+
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/flatpickr.min.css') }}">
|
|
11
|
+
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/main.css') }}">
|
|
12
|
+
{% block head %}
|
|
13
|
+
{% endblock %}
|
|
14
|
+
<title>{{ admin.title }}</title>
|
|
15
|
+
</head>
|
|
16
|
+
|
|
17
|
+
<body>
|
|
18
|
+
{% block body %}
|
|
19
|
+
<main>
|
|
20
|
+
{% block main %}
|
|
21
|
+
{% endblock %}
|
|
22
|
+
</main>
|
|
23
|
+
{% endblock %}
|
|
24
|
+
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/jquery.min.js') }}"></script>
|
|
25
|
+
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/tabler.min.js') }}"></script>
|
|
26
|
+
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/popper.min.js') }}"></script>
|
|
27
|
+
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/bootstrap.min.js') }}"></script>
|
|
28
|
+
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/select2.full.min.js') }}"></script>
|
|
29
|
+
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/flatpickr.min.js') }}"></script>
|
|
30
|
+
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/main.js') }}"></script>
|
|
31
|
+
{% block tail %}
|
|
32
|
+
{% endblock %}
|
|
33
|
+
</body>
|
|
34
|
+
|
|
35
|
+
</html>
|
{sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/create.html
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% extends "layout.html" %}
|
|
1
|
+
{% extends "sqladmin/layout.html" %}
|
|
2
2
|
{% block content %}
|
|
3
3
|
<div class="col-12">
|
|
4
4
|
<div class="card">
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
<div class="card-body border-bottom py-3">
|
|
9
9
|
<form action="{{ url_for('admin:create', identity=model_view.identity) }}" method="POST"
|
|
10
10
|
enctype="multipart/form-data">
|
|
11
|
+
<div class="row">
|
|
12
|
+
{% if error %}
|
|
13
|
+
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
|
14
|
+
{% endif %}
|
|
15
|
+
</div>
|
|
11
16
|
<fieldset class="form-fieldset">
|
|
12
17
|
{% for field in form %}
|
|
13
18
|
<div class="mb-3 form-group row">
|
|
@@ -21,14 +26,14 @@
|
|
|
21
26
|
{% for error in field.errors %}
|
|
22
27
|
<div class="invalid-feedback">{{ error }}</div>
|
|
23
28
|
{% endfor %}
|
|
29
|
+
{% if field.description %}
|
|
30
|
+
<small class="text-muted">{{ field.description }}</small>
|
|
31
|
+
{% endif %}
|
|
24
32
|
</div>
|
|
25
33
|
</div>
|
|
26
34
|
{% endfor %}
|
|
27
35
|
</fieldset>
|
|
28
36
|
<div class="row">
|
|
29
|
-
{% if error %}
|
|
30
|
-
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
|
31
|
-
{% endif %}
|
|
32
37
|
<div class="col-md-2">
|
|
33
38
|
<a href="{{ url_for('admin:list', identity=model_view.identity) }}" class="btn">
|
|
34
39
|
Cancel
|
|
@@ -46,4 +51,4 @@
|
|
|
46
51
|
</div>
|
|
47
52
|
</div>
|
|
48
53
|
</div>
|
|
49
|
-
{% endblock %}
|
|
54
|
+
{% endblock %}
|
{sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/details.html
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
{% extends "layout.html" %}
|
|
1
|
+
{% extends "sqladmin/layout.html" %}
|
|
2
2
|
{% block content %}
|
|
3
3
|
<div class="col-12">
|
|
4
4
|
<div class="card">
|
|
5
5
|
<div class="card-header">
|
|
6
6
|
<h3 class="card-title">
|
|
7
7
|
{% for pk in model_view.pk_columns -%}
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
{{ pk.name }}
|
|
9
|
+
{%- if not loop.last %};{% endif -%}
|
|
10
10
|
{% endfor %}: {{ get_object_identifier(model) }}</h3>
|
|
11
11
|
</div>
|
|
12
12
|
<div class="card-body border-bottom py-3">
|
|
@@ -27,12 +27,13 @@
|
|
|
27
27
|
{% if name in model_view._relation_names %}
|
|
28
28
|
{% if is_list( value ) %}
|
|
29
29
|
<td>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
{% for elem, formatted_elem in zip(value, formatted_value) %}
|
|
31
|
+
<a href="{{ model_view._build_url_for('admin:details', request, elem) }}">({{ formatted_elem }})</a>
|
|
32
|
+
{% endfor %}
|
|
33
33
|
</td>
|
|
34
34
|
{% else %}
|
|
35
|
-
<td><a href="{{ model_view._url_for_details_with_prop(request, model, name) }}">{{ formatted_value }}</a
|
|
35
|
+
<td><a href="{{ model_view._url_for_details_with_prop(request, model, name) }}">{{ formatted_value }}</a>
|
|
36
|
+
</td>
|
|
36
37
|
{% endif %}
|
|
37
38
|
{% else %}
|
|
38
39
|
<td>{{ formatted_value }}</td>
|
|
@@ -51,7 +52,9 @@
|
|
|
51
52
|
</div>
|
|
52
53
|
{% if model_view.can_delete %}
|
|
53
54
|
<div class="col-md-1">
|
|
54
|
-
<a href="#" data-name="{{ model_view.name }}" data-pk="{{ get_object_identifier(model) }}"
|
|
55
|
+
<a href="#" data-name="{{ model_view.name }}" data-pk="{{ get_object_identifier(model) }}"
|
|
56
|
+
data-url="{{ model_view._url_for_delete(request, model) }}" data-bs-toggle="modal"
|
|
57
|
+
data-bs-target="#modal-delete" class="btn btn-danger">
|
|
55
58
|
Delete
|
|
56
59
|
</a>
|
|
57
60
|
</div>
|
|
@@ -66,11 +69,13 @@
|
|
|
66
69
|
{% for custom_action,label in model_view._custom_actions_in_detail.items() %}
|
|
67
70
|
<div class="col-md-1">
|
|
68
71
|
{% if custom_action in model_view._custom_actions_confirmation %}
|
|
69
|
-
<a href="#" class="btn btn-secondary" data-bs-toggle="modal"
|
|
72
|
+
<a href="#" class="btn btn-secondary" data-bs-toggle="modal"
|
|
73
|
+
data-bs-target="#modal-confirmation-{{ custom_action }}">
|
|
70
74
|
{{ label }}
|
|
71
75
|
</a>
|
|
72
76
|
{% else %}
|
|
73
|
-
<a href="{{ model_view._url_for_action(request, custom_action) }}?pks={{ get_object_identifier(model) }}"
|
|
77
|
+
<a href="{{ model_view._url_for_action(request, custom_action) }}?pks={{ get_object_identifier(model) }}"
|
|
78
|
+
class="btn btn-secondary">
|
|
74
79
|
{{ label }}
|
|
75
80
|
</a>
|
|
76
81
|
{% endif %}
|
|
@@ -82,15 +87,16 @@
|
|
|
82
87
|
</div>
|
|
83
88
|
</div>
|
|
84
89
|
{% if model_view.can_delete %}
|
|
85
|
-
{% include 'modals/delete.html' %}
|
|
90
|
+
{% include 'sqladmin/modals/delete.html' %}
|
|
86
91
|
{% endif %}
|
|
87
92
|
|
|
88
93
|
{% for custom_action in model_view._custom_actions_in_detail %}
|
|
89
94
|
{% if custom_action in model_view._custom_actions_confirmation %}
|
|
90
|
-
{% with confirmation_message = model_view._custom_actions_confirmation[custom_action], custom_action=custom_action,
|
|
91
|
-
|
|
95
|
+
{% with confirmation_message = model_view._custom_actions_confirmation[custom_action], custom_action=custom_action,
|
|
96
|
+
url=model_view._url_for_action(request, custom_action) + '?pks=' + (get_object_identifier(model) | string) %}
|
|
97
|
+
{% include 'sqladmin/modals/details_action_confirmation.html' %}
|
|
92
98
|
{% endwith %}
|
|
93
99
|
{% endif %}
|
|
94
100
|
{% endfor %}
|
|
95
101
|
|
|
96
|
-
{% endblock %}
|
|
102
|
+
{% endblock %}
|
{sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/edit.html
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% extends "layout.html" %}
|
|
1
|
+
{% extends "sqladmin/layout.html" %}
|
|
2
2
|
{% block content %}
|
|
3
3
|
<div class="col-12">
|
|
4
4
|
<div class="card">
|
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
<h3 class="card-title">Edit {{ model_view.name }}</h3>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="card-body border-bottom py-3">
|
|
9
|
-
<form action="{{ model_view._build_url_for('admin:edit', request, obj) }}" method="POST"
|
|
9
|
+
<form action="{{ model_view._build_url_for('admin:edit', request, obj) }}" method="POST"
|
|
10
|
+
enctype="multipart/form-data">
|
|
11
|
+
<div class="row">
|
|
12
|
+
{% if error %}
|
|
13
|
+
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
|
14
|
+
{% endif %}
|
|
15
|
+
</div>
|
|
10
16
|
<fieldset class="form-fieldset">
|
|
11
17
|
{% for field in form %}
|
|
12
18
|
<div class="mb-3 form-group row">
|
|
@@ -20,14 +26,14 @@
|
|
|
20
26
|
{% for error in field.errors %}
|
|
21
27
|
<div class="invalid-feedback">{{ error }}</div>
|
|
22
28
|
{% endfor %}
|
|
29
|
+
{% if field.description %}
|
|
30
|
+
<small class="text-muted">{{ field.description }}</small>
|
|
31
|
+
{% endif %}
|
|
23
32
|
</div>
|
|
24
33
|
</div>
|
|
25
34
|
{% endfor %}
|
|
26
35
|
</fieldset>
|
|
27
36
|
<div class="row">
|
|
28
|
-
{% if error %}
|
|
29
|
-
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
|
30
|
-
{% endif %}
|
|
31
37
|
<div class="col-md-2">
|
|
32
38
|
<a href="{{ url_for('admin:list', identity=model_view.identity) }}" class="btn">
|
|
33
39
|
Cancel
|
|
@@ -37,10 +43,12 @@
|
|
|
37
43
|
<div class="btn-group flex-wrap" data-toggle="buttons">
|
|
38
44
|
<input type="submit" name="save" value="Save" class="btn">
|
|
39
45
|
<input type="submit" name="save" value="Save and continue editing" class="btn">
|
|
40
|
-
{% if model_view.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
{% if model_view.can_create %}
|
|
47
|
+
{% if model_view.save_as %}
|
|
48
|
+
<input type="submit" name="save" value="Save as new" class="btn">
|
|
49
|
+
{% else %}
|
|
50
|
+
<input type="submit" name="save" value="Save and add another" class="btn">
|
|
51
|
+
{% endif %}
|
|
44
52
|
{% endif %}
|
|
45
53
|
</div>
|
|
46
54
|
</div>
|
{sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/layout.html
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
{% extends "base.html" %}
|
|
2
|
-
{% from '_macros.html' import display_menu %}
|
|
1
|
+
{% extends "sqladmin/base.html" %}
|
|
2
|
+
{% from 'sqladmin/_macros.html' import display_menu %}
|
|
3
3
|
{% block body %}
|
|
4
4
|
<div class="wrapper">
|
|
5
5
|
<aside class="navbar navbar-expand-lg navbar-vertical navbar-expand-md navbar-dark">
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<span class="navbar-toggler-icon"></span>
|
|
20
20
|
</button>
|
|
21
21
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
22
|
-
|
|
22
|
+
{{ display_menu(admin._menu, request) }}
|
|
23
23
|
</div>
|
|
24
24
|
</nav>
|
|
25
25
|
{% if admin.authentication_backend %}
|
|
@@ -52,4 +52,4 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
</div>
|
|
55
|
-
{% endblock %}
|
|
55
|
+
{% endblock %}
|
{sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/list.html
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% extends "layout.html" %}
|
|
1
|
+
{% extends "sqladmin/layout.html" %}
|
|
2
2
|
{% block content %}
|
|
3
3
|
<div class="col-12">
|
|
4
4
|
<div class="card">
|
|
@@ -8,18 +8,22 @@
|
|
|
8
8
|
{% if model_view.can_export %}
|
|
9
9
|
{% if model_view.export_types | length > 1 %}
|
|
10
10
|
<div class="ms-3 d-inline-block dropdown">
|
|
11
|
-
<a href="#" class="btn btn-secondary dropdown-toggle" id="dropdownMenuButton1" data-bs-toggle="dropdown"
|
|
11
|
+
<a href="#" class="btn btn-secondary dropdown-toggle" id="dropdownMenuButton1" data-bs-toggle="dropdown"
|
|
12
|
+
aria-expanded="false">
|
|
12
13
|
Export
|
|
13
14
|
</a>
|
|
14
15
|
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
|
|
15
16
|
{% for export_type in model_view.export_types %}
|
|
16
|
-
<li><a class="dropdown-item"
|
|
17
|
+
<li><a class="dropdown-item"
|
|
18
|
+
href="{{ url_for('admin:export', identity=model_view.identity, export_type=export_type) }}">{{
|
|
19
|
+
export_type | upper }}</a></li>
|
|
17
20
|
{% endfor %}
|
|
18
21
|
</ul>
|
|
19
22
|
</div>
|
|
20
23
|
{% elif model_view.export_types | length == 1 %}
|
|
21
24
|
<div class="ms-3 d-inline-block">
|
|
22
|
-
<a href="{{ url_for('admin:export', identity=model_view.identity, export_type=model_view.export_types[0]) }}"
|
|
25
|
+
<a href="{{ url_for('admin:export', identity=model_view.identity, export_type=model_view.export_types[0]) }}"
|
|
26
|
+
class="btn btn-secondary">
|
|
23
27
|
Export
|
|
24
28
|
</a>
|
|
25
29
|
</div>
|
|
@@ -37,23 +41,27 @@
|
|
|
37
41
|
<div class="card-body border-bottom py-3">
|
|
38
42
|
<div class="d-flex justify-content-between">
|
|
39
43
|
<div class="dropdown col-4">
|
|
40
|
-
<button {% if not model_view.can_delete and not model_view._custom_actions_in_list %} disabled {% endif %}
|
|
44
|
+
<button {% if not model_view.can_delete and not model_view._custom_actions_in_list %} disabled {% endif %}
|
|
45
|
+
class="btn btn-light dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown"
|
|
46
|
+
aria-haspopup="true" aria-expanded="false">
|
|
41
47
|
Actions
|
|
42
48
|
</button>
|
|
43
49
|
{% if model_view.can_delete or model_view._custom_actions_in_list %}
|
|
44
50
|
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
|
45
51
|
{% if model_view.can_delete %}
|
|
46
|
-
<a class="dropdown-item" id="action-delete" href="#" data-name="{{ model_view.name }}"
|
|
52
|
+
<a class="dropdown-item" id="action-delete" href="#" data-name="{{ model_view.name }}"
|
|
53
|
+
data-url="{{ url_for('admin:delete', identity=model_view.identity) }}" data-bs-toggle="modal"
|
|
54
|
+
data-bs-target="#modal-delete">Delete selected items</a>
|
|
47
55
|
{% endif %}
|
|
48
56
|
{% for custom_action, label in model_view._custom_actions_in_list.items() %}
|
|
49
57
|
{% if custom_action in model_view._custom_actions_confirmation %}
|
|
50
|
-
<a class="dropdown-item" id="action-customconfirm-{{ custom_action }}" href="#"
|
|
51
|
-
|
|
58
|
+
<a class="dropdown-item" id="action-customconfirm-{{ custom_action }}" href="#" data-bs-toggle="modal"
|
|
59
|
+
data-bs-target="#modal-confirmation-{{ custom_action }}">
|
|
52
60
|
{{ label }}
|
|
53
61
|
</a>
|
|
54
62
|
{% else %}
|
|
55
63
|
<a class="dropdown-item" id="action-custom-{{ custom_action }}" href="#"
|
|
56
|
-
|
|
64
|
+
data-url="{{ model_view._url_for_action(request, custom_action) }}">
|
|
57
65
|
{{ label }}
|
|
58
66
|
</a>
|
|
59
67
|
{% endif %}
|
|
@@ -64,9 +72,12 @@
|
|
|
64
72
|
{% if model_view.column_searchable_list %}
|
|
65
73
|
<div class="col-md-4 text-muted">
|
|
66
74
|
<div class="input-group">
|
|
67
|
-
<input id="search-input" type="text" class="form-control"
|
|
75
|
+
<input id="search-input" type="text" class="form-control"
|
|
76
|
+
placeholder="Search: {{ model_view.search_placeholder() }}"
|
|
77
|
+
value="{{ request.query_params.get('search', '') }}">
|
|
68
78
|
<button id="search-button" class="btn" type="button">Search</button>
|
|
69
|
-
<button id="search-reset" class="btn" type="button" {% if not request.query_params.get('search')
|
|
79
|
+
<button id="search-reset" class="btn" type="button" {% if not request.query_params.get('search')
|
|
80
|
+
%}disabled{% endif %}><i class="fa-solid fa-times"></i></button>
|
|
70
81
|
</div>
|
|
71
82
|
</div>
|
|
72
83
|
{% endif %}
|
|
@@ -76,16 +87,19 @@
|
|
|
76
87
|
<table class="table card-table table-vcenter text-nowrap">
|
|
77
88
|
<thead>
|
|
78
89
|
<tr>
|
|
79
|
-
<th class="w-1"><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select all"
|
|
90
|
+
<th class="w-1"><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select all"
|
|
91
|
+
id="select-all"></th>
|
|
80
92
|
<th class="w-1"></th>
|
|
81
93
|
{% for name in model_view._list_prop_names %}
|
|
82
94
|
{% set label = model_view._column_labels.get(name, name) %}
|
|
83
95
|
<th>
|
|
84
96
|
{% if name in model_view._sort_fields %}
|
|
85
97
|
{% if request.query_params.get("sortBy") == name and request.query_params.get("sort") == "asc" %}
|
|
86
|
-
<a href="{{ request.url.include_query_params(sort='desc') }}"><i class="fa-solid fa-arrow-
|
|
98
|
+
<a href="{{ request.url.include_query_params(sort='desc') }}"><i class="fa-solid fa-arrow-up"></i> {{
|
|
99
|
+
label }}</a>
|
|
87
100
|
{% elif request.query_params.get("sortBy") == name and request.query_params.get("sort") == "desc" %}
|
|
88
|
-
<a href="{{ request.url.include_query_params(sort='asc') }}"><i class="fa-solid fa-arrow-
|
|
101
|
+
<a href="{{ request.url.include_query_params(sort='asc') }}"><i class="fa-solid fa-arrow-down"></i> {{ label
|
|
102
|
+
}}</a>
|
|
89
103
|
{% else %}
|
|
90
104
|
<a href="{{ request.url.include_query_params(sortBy=name, sort='asc') }}">{{ label }}</a>
|
|
91
105
|
{% endif %}
|
|
@@ -105,17 +119,21 @@
|
|
|
105
119
|
</td>
|
|
106
120
|
<td class="text-end">
|
|
107
121
|
{% if model_view.can_view_details %}
|
|
108
|
-
<a href="{{ model_view._build_url_for('admin:details', request, row) }}" data-bs-toggle="tooltip"
|
|
122
|
+
<a href="{{ model_view._build_url_for('admin:details', request, row) }}" data-bs-toggle="tooltip"
|
|
123
|
+
data-bs-placement="top" title="View">
|
|
109
124
|
<span class="me-1"><i class="fa-solid fa-eye"></i></span>
|
|
110
125
|
</a>
|
|
111
126
|
{% endif %}
|
|
112
127
|
{% if model_view.can_edit %}
|
|
113
|
-
<a href="{{ model_view._build_url_for('admin:edit', request, row) }}" data-bs-toggle="tooltip"
|
|
128
|
+
<a href="{{ model_view._build_url_for('admin:edit', request, row) }}" data-bs-toggle="tooltip"
|
|
129
|
+
data-bs-placement="top" title="Edit">
|
|
114
130
|
<span class="me-1"><i class="fa-solid fa-pen-to-square"></i></span>
|
|
115
131
|
</a>
|
|
116
132
|
{% endif %}
|
|
117
133
|
{% if model_view.can_delete %}
|
|
118
|
-
<a href="#" data-name="{{ model_view.name }}" data-pk="{{ get_object_identifier(row) }}"
|
|
134
|
+
<a href="#" data-name="{{ model_view.name }}" data-pk="{{ get_object_identifier(row) }}"
|
|
135
|
+
data-url="{{ model_view._url_for_delete(request, row) }}" data-bs-toggle="modal"
|
|
136
|
+
data-bs-target="#modal-delete" title="Delete">
|
|
119
137
|
<span class="me-1"><i class="fa-solid fa-trash"></i></span>
|
|
120
138
|
</a>
|
|
121
139
|
{% endif %}
|
|
@@ -125,9 +143,9 @@
|
|
|
125
143
|
{% if name in model_view._relation_names %}
|
|
126
144
|
{% if is_list( value ) %}
|
|
127
145
|
<td>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
146
|
+
{% for elem, formatted_elem in zip(value, formatted_value) %}
|
|
147
|
+
<a href="{{ model_view._build_url_for('admin:details', request, elem) }}">({{ formatted_elem }})</a>
|
|
148
|
+
{% endfor %}
|
|
131
149
|
</td>
|
|
132
150
|
{% else %}
|
|
133
151
|
<td><a href="{{ model_view._url_for_details_with_prop(request, row, name) }}">{{ formatted_value }}</a></td>
|
|
@@ -142,35 +160,40 @@
|
|
|
142
160
|
</table>
|
|
143
161
|
</div>
|
|
144
162
|
<div class="card-footer d-flex justify-content-between align-items-center gap-2">
|
|
145
|
-
<p class="m-0 text-muted">Showing <span>{{ ((pagination.page - 1) * pagination.page_size) + 1 }}</span> to
|
|
163
|
+
<p class="m-0 text-muted">Showing <span>{{ ((pagination.page - 1) * pagination.page_size) + 1 }}</span> to
|
|
164
|
+
<span>{{ min(pagination.page * pagination.page_size, pagination.count) }}</span> of <span>{{ pagination.count
|
|
165
|
+
}}</span> items
|
|
166
|
+
</p>
|
|
146
167
|
<ul class="pagination m-0 ms-auto">
|
|
147
168
|
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
|
|
148
169
|
{% if pagination.has_previous %}
|
|
149
170
|
<a class="page-link" href="{{ pagination.previous_page.url }}">
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
171
|
+
{% else %}
|
|
172
|
+
<a class="page-link" href="#">
|
|
173
|
+
{% endif %}
|
|
174
|
+
<i class="fa-solid fa-chevron-left"></i>
|
|
175
|
+
prev
|
|
176
|
+
</a>
|
|
156
177
|
</li>
|
|
157
178
|
{% for page_control in pagination.page_controls %}
|
|
158
|
-
<li class="page-item {% if page_control.number == pagination.page %}active{% endif %}"><a class="page-link"
|
|
179
|
+
<li class="page-item {% if page_control.number == pagination.page %}active{% endif %}"><a class="page-link"
|
|
180
|
+
href="{{ page_control.url }}">{{ page_control.number }}</a></li>
|
|
159
181
|
{% endfor %}
|
|
160
182
|
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
|
161
183
|
{% if pagination.has_next %}
|
|
162
184
|
<a class="page-link" href="{{ pagination.next_page.url }}">
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
185
|
+
{% else %}
|
|
186
|
+
<a class="page-link" href="#">
|
|
187
|
+
{% endif %}
|
|
188
|
+
next
|
|
189
|
+
<i class="fa-solid fa-chevron-right"></i>
|
|
190
|
+
</a>
|
|
169
191
|
</li>
|
|
170
192
|
</ul>
|
|
171
193
|
<div class="dropdown text-muted">
|
|
172
194
|
Show
|
|
173
|
-
<a href="#" class="btn btn-sm btn-light dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
|
|
195
|
+
<a href="#" class="btn btn-sm btn-light dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
|
|
196
|
+
aria-expanded="false">
|
|
174
197
|
{{ request.query_params.get("pageSize") or model_view.page_size }} / Page
|
|
175
198
|
</a>
|
|
176
199
|
<div class="dropdown-menu">
|
|
@@ -184,13 +207,14 @@
|
|
|
184
207
|
</div>
|
|
185
208
|
</div>
|
|
186
209
|
{% if model_view.can_delete %}
|
|
187
|
-
{% include 'modals/delete.html' %}
|
|
210
|
+
{% include 'sqladmin/modals/delete.html' %}
|
|
188
211
|
{% endif %}
|
|
189
212
|
|
|
190
213
|
{% for custom_action in model_view._custom_actions_in_list %}
|
|
191
214
|
{% if custom_action in model_view._custom_actions_confirmation %}
|
|
192
|
-
{% with confirmation_message = model_view._custom_actions_confirmation[custom_action], custom_action=custom_action,
|
|
193
|
-
|
|
215
|
+
{% with confirmation_message = model_view._custom_actions_confirmation[custom_action], custom_action=custom_action,
|
|
216
|
+
url=model_view._url_for_action(request, custom_action) %}
|
|
217
|
+
{% include 'sqladmin/modals/list_action_confirmation.html' %}
|
|
194
218
|
{% endwith %}
|
|
195
219
|
{% endif %}
|
|
196
220
|
{% endfor %}
|
{sqladmin-0.16.0/sqladmin/templates → sqladmin-0.17.0/sqladmin/templates/sqladmin}/login.html
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% extends "base.html" %}
|
|
1
|
+
{% extends "sqladmin/base.html" %}
|
|
2
2
|
{% block body %}
|
|
3
3
|
<div class="d-flex align-items-center justify-content-center vh-100">
|
|
4
4
|
<form class="Fcol-lg-6 col-md-6 card card-md" action="{{ url_for('admin:login') }}" method="POST" autocomplete="off">
|
|
@@ -34,4 +34,4 @@
|
|
|
34
34
|
</div>
|
|
35
35
|
</form>
|
|
36
36
|
</div>
|
|
37
|
-
{% endblock %}
|
|
37
|
+
{% endblock %}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<div class="modal modal-blur fade" id="modal-confirmation-{{ custom_action }}" tabindex="-1" role="dialog"
|
|
1
|
+
<div class="modal modal-blur fade" id="modal-confirmation-{{ custom_action }}" tabindex="-1" role="dialog"
|
|
2
|
+
aria-hidden="true">
|
|
2
3
|
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
|
3
4
|
<div class="modal-content">
|
|
4
5
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
@@ -25,4 +26,4 @@
|
|
|
25
26
|
</div>
|
|
26
27
|
</div>
|
|
27
28
|
</div>
|
|
28
|
-
</div>
|
|
29
|
+
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<div class="modal modal-blur fade" id="modal-confirmation-{{ custom_action }}" tabindex="-1" role="dialog"
|
|
1
|
+
<div class="modal modal-blur fade" id="modal-confirmation-{{ custom_action }}" tabindex="-1" role="dialog"
|
|
2
|
+
aria-hidden="true">
|
|
2
3
|
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
|
3
4
|
<div class="modal-content">
|
|
4
5
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
@@ -25,4 +26,4 @@
|
|
|
25
26
|
</div>
|
|
26
27
|
</div>
|
|
27
28
|
</div>
|
|
28
|
-
</div>
|
|
29
|
+
</div>
|
|
@@ -75,15 +75,23 @@ class FileInputWidget(widgets.FileInput):
|
|
|
75
75
|
"""
|
|
76
76
|
|
|
77
77
|
def __call__(self, field: Field, **kwargs: Any) -> str:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
78
|
+
if not field.flags.required:
|
|
79
|
+
checkbox_id = f"{field.id}_checkbox"
|
|
80
|
+
checkbox_label = Markup(
|
|
81
|
+
f'<label class="form-check-label" for="{checkbox_id}">Clear</label>'
|
|
82
|
+
)
|
|
83
|
+
checkbox_input = Markup(
|
|
84
|
+
f'<input class="form-check-input" type="checkbox" id="{checkbox_id}" name="{checkbox_id}">' # noqa: E501
|
|
85
|
+
)
|
|
86
|
+
checkbox = Markup(
|
|
87
|
+
f'<div class="form-check">{checkbox_input}{checkbox_label}</div>'
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
checkbox = Markup()
|
|
91
|
+
|
|
92
|
+
if field.data:
|
|
93
|
+
current_value = Markup(f"<p>Currently: {field.data}</p>")
|
|
94
|
+
field.flags.required = False
|
|
95
|
+
return current_value + checkbox + super().__call__(field, **kwargs)
|
|
96
|
+
else:
|
|
97
|
+
return super().__call__(field, **kwargs)
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
|
6
|
-
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/tabler.min.css') }}">
|
|
7
|
-
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/fontawesome.min.css') }}">
|
|
8
|
-
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/select2.min.css') }}">
|
|
9
|
-
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/flatpickr.min.css') }}">
|
|
10
|
-
<link rel="stylesheet" href="{{ url_for('admin:statics', path='css/main.css') }}">
|
|
11
|
-
{% block head %}
|
|
12
|
-
{% endblock %}
|
|
13
|
-
<title>{{ admin.title }}</title>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
{% block body %}
|
|
17
|
-
<main>
|
|
18
|
-
{% block main %}
|
|
19
|
-
{% endblock %}
|
|
20
|
-
</main>
|
|
21
|
-
{% endblock %}
|
|
22
|
-
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/jquery.min.js') }}"></script>
|
|
23
|
-
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/tabler.min.js') }}"></script>
|
|
24
|
-
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/popper.min.js') }}"></script>
|
|
25
|
-
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/bootstrap.min.js') }}"></script>
|
|
26
|
-
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/select2.full.min.js') }}"></script>
|
|
27
|
-
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/flatpickr.min.js') }}"></script>
|
|
28
|
-
<script type="text/javascript" src="{{ url_for('admin:statics', path='js/main.js') }}"></script>
|
|
29
|
-
{% block tail %}
|
|
30
|
-
{% endblock %}
|
|
31
|
-
</body>
|
|
32
|
-
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|