fastadmin 0.3.1__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.
Files changed (50) hide show
  1. {fastadmin-0.3.1 → fastadmin-0.3.3}/PKG-INFO +1 -1
  2. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/views.py +8 -1
  3. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/views.py +7 -1
  4. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/views.py +7 -1
  5. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/helpers.py +0 -15
  6. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/base.py +35 -3
  7. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/helpers.py +1 -1
  8. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/orms/sqlalchemy.py +9 -0
  9. fastadmin-0.3.3/fastadmin/static/assets/worker-C9KPOOKD.js +9 -0
  10. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/index.min.css +1 -7
  11. fastadmin-0.3.3/fastadmin/static/index.min.js +680 -0
  12. {fastadmin-0.3.1 → fastadmin-0.3.3}/pyproject.toml +1 -1
  13. fastadmin-0.3.1/fastadmin/static/index.min.js +0 -901
  14. {fastadmin-0.3.1 → fastadmin-0.3.3}/LICENSE +0 -0
  15. {fastadmin-0.3.1 → fastadmin-0.3.3}/README.md +0 -0
  16. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/__init__.py +0 -0
  17. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/__init__.py +0 -0
  18. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/exceptions.py +0 -0
  19. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/__init__.py +0 -0
  20. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/__init__.py +0 -0
  21. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/__init__.py +0 -0
  22. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/api.py +0 -0
  23. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/django/app/urls.py +0 -0
  24. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/__init__.py +0 -0
  25. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/api.py +0 -0
  26. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/fastapi/app.py +0 -0
  27. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/__init__.py +0 -0
  28. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/api.py +0 -0
  29. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/frameworks/flask/app.py +0 -0
  30. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/schemas.py +0 -0
  31. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/api/service.py +0 -0
  32. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/__init__.py +0 -0
  33. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/decorators.py +0 -0
  34. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/orms/__init__.py +0 -0
  35. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/orms/django.py +0 -0
  36. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/orms/ponyorm.py +0 -0
  37. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/orms/tortoise.py +0 -0
  38. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/models/schemas.py +0 -0
  39. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/settings.py +0 -0
  40. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/assets/worker-6Z7niv9l.js +0 -0
  41. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/assets/worker-C151k0-L.js +0 -0
  42. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/assets/worker-D1fCgYB7.js +0 -0
  43. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/assets/worker-D3TWcJOI.js +0 -0
  44. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/assets/worker-DRNcaZ-V.js +0 -0
  45. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/assets/worker-MF2p-l5_.js +0 -0
  46. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/images/favicon.png +0 -0
  47. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/images/header-logo.svg +0 -0
  48. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/images/sign-in-logo.svg +0 -0
  49. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/static/index.html +0 -0
  50. {fastadmin-0.3.1 → fastadmin-0.3.3}/fastadmin/templates/index.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastadmin
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: FastAdmin is an easy-to-use Admin Dashboard App for FastAPI/Flask/Django inspired by Django Admin.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -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": settings.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": settings.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": settings.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
- return datetime.datetime.fromisoformat(value).time()
408
- case WidgetType.DatePicker | WidgetType.DateTimePicker:
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.serialize_obj(obj)
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