fastadmin 0.3.2__tar.gz → 0.3.3__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.
- {fastadmin-0.3.2 → fastadmin-0.3.3}/PKG-INFO +1 -1
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/views.py +8 -1
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/views.py +7 -1
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/views.py +7 -1
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/helpers.py +0 -15
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/base.py +35 -3
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/helpers.py +1 -1
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/orms/sqlalchemy.py +9 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/index.min.js +70 -70
- {fastadmin-0.3.2 → fastadmin-0.3.3}/pyproject.toml +1 -1
- {fastadmin-0.3.2 → fastadmin-0.3.3}/LICENSE +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/README.md +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/exceptions.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/api.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/urls.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/api.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/app.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/api.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/app.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/schemas.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/api/service.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/decorators.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/orms/__init__.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/orms/django.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/orms/ponyorm.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/orms/tortoise.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/models/schemas.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/settings.py +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/assets/worker-6Z7niv9l.js +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/assets/worker-C151k0-L.js +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/assets/worker-C9KPOOKD.js +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/assets/worker-D1fCgYB7.js +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/assets/worker-D3TWcJOI.js +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/assets/worker-DRNcaZ-V.js +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/assets/worker-MF2p-l5_.js +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/images/favicon.png +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/images/header-logo.svg +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/images/sign-in-logo.svg +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/index.html +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/static/index.min.css +0 -0
- {fastadmin-0.3.2 → fastadmin-0.3.3}/fastadmin/templates/index.html +0 -0
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
1
3
|
from django.http import HttpResponse
|
|
2
4
|
|
|
3
5
|
from fastadmin.api.helpers import get_template
|
|
4
6
|
from fastadmin.settings import ROOT_DIR, settings
|
|
5
7
|
|
|
6
8
|
|
|
9
|
+
def _get_admin_prefix() -> str:
|
|
10
|
+
"""Return admin URL prefix from env at request time so it respects os.environ set after import."""
|
|
11
|
+
return os.getenv("ADMIN_PREFIX", settings.ADMIN_PREFIX)
|
|
12
|
+
|
|
13
|
+
|
|
7
14
|
def index(request):
|
|
8
15
|
"""This method is used to render index page.
|
|
9
16
|
|
|
@@ -13,7 +20,7 @@ def index(request):
|
|
|
13
20
|
template = get_template(
|
|
14
21
|
ROOT_DIR / "templates" / "index.html",
|
|
15
22
|
{
|
|
16
|
-
"ADMIN_PREFIX":
|
|
23
|
+
"ADMIN_PREFIX": _get_admin_prefix(),
|
|
17
24
|
},
|
|
18
25
|
)
|
|
19
26
|
return HttpResponse(template)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
|
|
3
4
|
from fastapi import APIRouter
|
|
4
5
|
from fastapi.responses import HTMLResponse
|
|
@@ -10,6 +11,11 @@ logger = logging.getLogger(__name__)
|
|
|
10
11
|
router = APIRouter()
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
def _get_admin_prefix() -> str:
|
|
15
|
+
"""Return admin URL prefix from env at request time so it respects os.environ set after import."""
|
|
16
|
+
return os.getenv("ADMIN_PREFIX", settings.ADMIN_PREFIX)
|
|
17
|
+
|
|
18
|
+
|
|
13
19
|
@router.get("/", response_class=HTMLResponse)
|
|
14
20
|
def index():
|
|
15
21
|
"""This method is used to render index page.
|
|
@@ -20,6 +26,6 @@ def index():
|
|
|
20
26
|
return get_template(
|
|
21
27
|
ROOT_DIR / "templates" / "index.html",
|
|
22
28
|
{
|
|
23
|
-
"ADMIN_PREFIX":
|
|
29
|
+
"ADMIN_PREFIX": _get_admin_prefix(),
|
|
24
30
|
},
|
|
25
31
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
|
|
3
4
|
from flask import Blueprint
|
|
4
5
|
|
|
@@ -12,6 +13,11 @@ views_router = Blueprint(
|
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
def _get_admin_prefix() -> str:
|
|
17
|
+
"""Return admin URL prefix from env at request time so it respects os.environ set after import."""
|
|
18
|
+
return os.getenv("ADMIN_PREFIX", settings.ADMIN_PREFIX)
|
|
19
|
+
|
|
20
|
+
|
|
15
21
|
@views_router.route("/")
|
|
16
22
|
def index():
|
|
17
23
|
"""This method is used to render index page.
|
|
@@ -21,6 +27,6 @@ def index():
|
|
|
21
27
|
return get_template(
|
|
22
28
|
ROOT_DIR / "templates" / "index.html",
|
|
23
29
|
{
|
|
24
|
-
"ADMIN_PREFIX":
|
|
30
|
+
"ADMIN_PREFIX": _get_admin_prefix(),
|
|
25
31
|
},
|
|
26
32
|
)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import binascii
|
|
3
1
|
from pathlib import Path
|
|
4
2
|
from uuid import UUID
|
|
5
3
|
|
|
@@ -105,19 +103,6 @@ def is_valid_id(id: UUID | int | str) -> bool:
|
|
|
105
103
|
return False
|
|
106
104
|
|
|
107
105
|
|
|
108
|
-
def is_valid_base64(value: str) -> bool:
|
|
109
|
-
"""Check if a string is a valid base64.
|
|
110
|
-
|
|
111
|
-
:param value: A string to test.
|
|
112
|
-
:return: True if s is a valid base64, False otherwise.
|
|
113
|
-
"""
|
|
114
|
-
try:
|
|
115
|
-
base64.decodebytes(value.encode("ascii"))
|
|
116
|
-
return True
|
|
117
|
-
except binascii.Error:
|
|
118
|
-
return False
|
|
119
|
-
|
|
120
|
-
|
|
121
106
|
def get_template(template: Path, context: dict) -> str:
|
|
122
107
|
with template.open("r") as file:
|
|
123
108
|
content = file.read()
|
|
@@ -247,6 +247,16 @@ class BaseModelAdmin:
|
|
|
247
247
|
"""
|
|
248
248
|
raise NotImplementedError
|
|
249
249
|
|
|
250
|
+
async def orm_serialize_obj_by_id(self, id: UUID | int | str) -> dict | None:
|
|
251
|
+
"""Serialize object by id (e.g. in a single session to avoid DetachedInstanceError).
|
|
252
|
+
|
|
253
|
+
Override in ORM layer to run get+serialize inside one session when needed.
|
|
254
|
+
"""
|
|
255
|
+
obj = await self.orm_get_obj(id)
|
|
256
|
+
if obj is None:
|
|
257
|
+
return None
|
|
258
|
+
return await self.serialize_obj(obj)
|
|
259
|
+
|
|
250
260
|
async def orm_save_obj(self, id: UUID | Any | None, payload: dict) -> Any:
|
|
251
261
|
"""This method is used to save orm/db model object.
|
|
252
262
|
|
|
@@ -361,6 +371,23 @@ class BaseModelAdmin:
|
|
|
361
371
|
serialized_dict["__str__"] = await str_fn()
|
|
362
372
|
return serialized_dict
|
|
363
373
|
|
|
374
|
+
async def _serialize_obj_after_save(self, obj: Any) -> dict:
|
|
375
|
+
"""Serialize object after save; re-fetch if detached (e.g. SQLAlchemy after commit)."""
|
|
376
|
+
try:
|
|
377
|
+
return await self.serialize_obj(obj)
|
|
378
|
+
except Exception as exc:
|
|
379
|
+
# SQLAlchemy DetachedInstanceError when session is closed after commit
|
|
380
|
+
if exc.__class__.__name__ != "DetachedInstanceError":
|
|
381
|
+
raise
|
|
382
|
+
pk_name = self.get_model_pk_name(self.model_cls)
|
|
383
|
+
pk = getattr(obj, pk_name, None)
|
|
384
|
+
if pk is None:
|
|
385
|
+
raise
|
|
386
|
+
result = await self.orm_serialize_obj_by_id(pk)
|
|
387
|
+
if result is None:
|
|
388
|
+
raise
|
|
389
|
+
return result
|
|
390
|
+
|
|
364
391
|
async def serialize_obj(self, obj: Any, list_view: bool = False) -> dict:
|
|
365
392
|
"""Serialize orm model obj to dict.
|
|
366
393
|
|
|
@@ -404,8 +431,13 @@ class BaseModelAdmin:
|
|
|
404
431
|
return value
|
|
405
432
|
match field.form_widget_type:
|
|
406
433
|
case WidgetType.TimePicker:
|
|
407
|
-
|
|
408
|
-
|
|
434
|
+
try:
|
|
435
|
+
return datetime.datetime.fromisoformat(value).time()
|
|
436
|
+
except ValueError:
|
|
437
|
+
return datetime.time.fromisoformat(value)
|
|
438
|
+
case WidgetType.DatePicker:
|
|
439
|
+
return datetime.datetime.fromisoformat(value).date()
|
|
440
|
+
case WidgetType.DateTimePicker:
|
|
409
441
|
return datetime.datetime.fromisoformat(value)
|
|
410
442
|
case _:
|
|
411
443
|
return value
|
|
@@ -483,7 +515,7 @@ class BaseModelAdmin:
|
|
|
483
515
|
if m2m_field.name in payload:
|
|
484
516
|
await self.orm_save_m2m_ids(obj, m2m_field.column_name, payload[m2m_field.name])
|
|
485
517
|
|
|
486
|
-
return await self.
|
|
518
|
+
return await self._serialize_obj_after_save(obj)
|
|
487
519
|
|
|
488
520
|
async def delete_model(self, id: UUID | int | str) -> None:
|
|
489
521
|
"""This method is used to delete orm/db model object.
|
|
@@ -198,7 +198,7 @@ async def generate_models_schema(
|
|
|
198
198
|
empty_value_display=admin_model_obj.empty_value_display,
|
|
199
199
|
filter_widget_type=None,
|
|
200
200
|
filter_widget_props=None,
|
|
201
|
-
width=None,
|
|
201
|
+
width=admin_model_obj.list_display_widths.get(field_name, None),
|
|
202
202
|
),
|
|
203
203
|
add_configuration=None,
|
|
204
204
|
change_configuration=None,
|
|
@@ -329,6 +329,15 @@ class SqlAlchemyMixin:
|
|
|
329
329
|
async with sessionmaker() as session:
|
|
330
330
|
return await session.get(self.model_cls, id)
|
|
331
331
|
|
|
332
|
+
async def orm_serialize_obj_by_id(self, id: UUID | int | str) -> dict | None:
|
|
333
|
+
"""Serialize object by id inside a single session to avoid DetachedInstanceError."""
|
|
334
|
+
sessionmaker = self.get_sessionmaker()
|
|
335
|
+
async with sessionmaker() as session:
|
|
336
|
+
obj = await session.get(self.model_cls, id)
|
|
337
|
+
if obj is None:
|
|
338
|
+
return None
|
|
339
|
+
return await self.serialize_obj(obj)
|
|
340
|
+
|
|
332
341
|
def _get_foreign_key_fields(self) -> list[str]:
|
|
333
342
|
"""Returns a list of foreign key fields for the model.
|
|
334
343
|
|