fastadmin 0.4.8__tar.gz → 0.4.9__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.4.8 → fastadmin-0.4.9}/PKG-INFO +1 -1
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/api.py +2 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/api.py +3 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/api.py +2 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/service.py +8 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/base.py +42 -2
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/ponyorm.py +1 -1
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/index.min.js +2 -2
- {fastadmin-0.4.8 → fastadmin-0.4.9}/pyproject.toml +4 -1
- {fastadmin-0.4.8 → fastadmin-0.4.9}/LICENSE +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/README.md +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/exceptions.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/urls.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/views.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/app.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/views.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/app.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/views.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/helpers.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/schemas.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/decorators.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/helpers.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/__init__.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/django.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/sqlalchemy.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/tortoise.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/schemas.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/settings.py +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-6Z7niv9l.js +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-C151k0-L.js +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-C9KPOOKD.js +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-D1fCgYB7.js +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-D3TWcJOI.js +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-DRNcaZ-V.js +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-MF2p-l5_.js +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/images/favicon.png +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/images/header-logo.svg +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/images/sign-in-logo.svg +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/index.html +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/index.min.css +0 -0
- {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/templates/index.html +0 -0
|
@@ -301,12 +301,14 @@ async def upload_file(
|
|
|
301
301
|
return JsonResponse({"error": "File not found"}, status=400)
|
|
302
302
|
file_name = file.name
|
|
303
303
|
file_content = file.read()
|
|
304
|
+
obj_id = request.GET.get("id") or None
|
|
304
305
|
file_url = await api_service.upload_file(
|
|
305
306
|
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
306
307
|
model,
|
|
307
308
|
field_name,
|
|
308
309
|
file_name,
|
|
309
310
|
file_content,
|
|
311
|
+
id=obj_id,
|
|
310
312
|
request=request,
|
|
311
313
|
)
|
|
312
314
|
return JsonResponse(file_url, safe=False)
|
|
@@ -244,6 +244,7 @@ async def upload_file(
|
|
|
244
244
|
model: str,
|
|
245
245
|
field_name: str,
|
|
246
246
|
file: UploadFile,
|
|
247
|
+
id: str | None = None,
|
|
247
248
|
) -> str:
|
|
248
249
|
"""This method is used to upload files.
|
|
249
250
|
|
|
@@ -251,6 +252,7 @@ async def upload_file(
|
|
|
251
252
|
:params model: a name of model.
|
|
252
253
|
:params field_name: a name of field.
|
|
253
254
|
:params file: a file object.
|
|
255
|
+
:params id: an optional id of an existing object (change page). None on the add page.
|
|
254
256
|
:return: A file url.
|
|
255
257
|
"""
|
|
256
258
|
try:
|
|
@@ -264,6 +266,7 @@ async def upload_file(
|
|
|
264
266
|
field_name,
|
|
265
267
|
file_name,
|
|
266
268
|
file_content,
|
|
269
|
+
id=id,
|
|
267
270
|
request=request,
|
|
268
271
|
)
|
|
269
272
|
except AdminApiException as e:
|
|
@@ -267,12 +267,14 @@ async def upload_file(
|
|
|
267
267
|
http_exception.code = 422
|
|
268
268
|
raise http_exception from None
|
|
269
269
|
file_content = file.read()
|
|
270
|
+
obj_id = request.args.get("id") or None
|
|
270
271
|
return await api_service.upload_file(
|
|
271
272
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
272
273
|
model,
|
|
273
274
|
field_name,
|
|
274
275
|
file_name,
|
|
275
276
|
file_content,
|
|
277
|
+
id=obj_id,
|
|
276
278
|
request=request,
|
|
277
279
|
)
|
|
278
280
|
except AdminApiException as e:
|
|
@@ -329,6 +329,7 @@ class ApiService:
|
|
|
329
329
|
field_name: str,
|
|
330
330
|
file_name: str,
|
|
331
331
|
file_content: bytes,
|
|
332
|
+
id: UUID | int | str | None = None,
|
|
332
333
|
request: Any | None = None,
|
|
333
334
|
) -> str:
|
|
334
335
|
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
@@ -339,6 +340,10 @@ class ApiService:
|
|
|
339
340
|
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
340
341
|
|
|
341
342
|
try:
|
|
343
|
+
obj = await admin_model.orm_get_obj(id) if id is not None else None
|
|
344
|
+
if id is not None and obj is None:
|
|
345
|
+
raise AdminApiException(404, detail=f"{model} with id {id} not found.")
|
|
346
|
+
|
|
342
347
|
if inspect.iscoroutinefunction(admin_model.upload_file):
|
|
343
348
|
upload_file_fn = admin_model.upload_file
|
|
344
349
|
else:
|
|
@@ -348,7 +353,10 @@ class ApiService:
|
|
|
348
353
|
field_name,
|
|
349
354
|
file_name,
|
|
350
355
|
file_content,
|
|
356
|
+
obj=obj,
|
|
351
357
|
)
|
|
358
|
+
except AdminApiException:
|
|
359
|
+
raise
|
|
352
360
|
except Exception as e:
|
|
353
361
|
raise AdminApiException(500, detail=f"Error uploading file for {model}: {e}") from e
|
|
354
362
|
|
|
@@ -14,6 +14,8 @@ from asgiref.sync import sync_to_async
|
|
|
14
14
|
from fastadmin.api.schemas import ExportFormat
|
|
15
15
|
from fastadmin.models.schemas import ModelFieldWidgetSchema, WidgetType
|
|
16
16
|
|
|
17
|
+
Model = Any
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
class BaseModelAdmin:
|
|
19
21
|
"""Base class for model admin"""
|
|
@@ -373,7 +375,7 @@ class BaseModelAdmin:
|
|
|
373
375
|
return sort_by
|
|
374
376
|
|
|
375
377
|
async def serialize_obj_attributes(
|
|
376
|
-
self, obj: Any, attributes_to_serizalize: list[ModelFieldWidgetSchema]
|
|
378
|
+
self, obj: Any, attributes_to_serizalize: list[ModelFieldWidgetSchema], list_view: bool = False
|
|
377
379
|
) -> dict[str, Any]:
|
|
378
380
|
"""Serialize orm model obj attribute to dict.
|
|
379
381
|
|
|
@@ -388,7 +390,10 @@ class BaseModelAdmin:
|
|
|
388
390
|
# Avoid scientific notation for Decimal values in API responses,
|
|
389
391
|
# e.g. 3.75E+3 -> "3750"
|
|
390
392
|
value = format(value, "f")
|
|
393
|
+
|
|
391
394
|
serialized_dict[field.name] = value
|
|
395
|
+
if not list_view and field.form_widget_type in (WidgetType.UploadFile, WidgetType.UploadImage) and value:
|
|
396
|
+
serialized_dict[f"{field.name}__url"] = await self.get_file_url(field.name, value, obj)
|
|
392
397
|
if inspect.iscoroutinefunction(obj.__str__):
|
|
393
398
|
str_fn = obj.__str__
|
|
394
399
|
else:
|
|
@@ -435,7 +440,7 @@ class BaseModelAdmin:
|
|
|
435
440
|
else:
|
|
436
441
|
attributes_to_serizalize.append(field)
|
|
437
442
|
|
|
438
|
-
obj_dict.update(await self.serialize_obj_attributes(obj, attributes_to_serizalize))
|
|
443
|
+
obj_dict.update(await self.serialize_obj_attributes(obj, attributes_to_serizalize, list_view=list_view))
|
|
439
444
|
|
|
440
445
|
for field_name in fields_for_serialize:
|
|
441
446
|
display_field_function = getattr(self, field_name, None)
|
|
@@ -464,6 +469,7 @@ class BaseModelAdmin:
|
|
|
464
469
|
return datetime.datetime.fromisoformat(value).date()
|
|
465
470
|
case WidgetType.DateTimePicker:
|
|
466
471
|
return datetime.datetime.fromisoformat(value)
|
|
472
|
+
|
|
467
473
|
case _:
|
|
468
474
|
return value
|
|
469
475
|
|
|
@@ -541,17 +547,51 @@ class BaseModelAdmin:
|
|
|
541
547
|
"""
|
|
542
548
|
await self.orm_delete_obj(id)
|
|
543
549
|
|
|
550
|
+
async def get_file_url(self, field_name: str, value: str, obj: Any | None = None) -> str:
|
|
551
|
+
"""Return the display URL for an uploaded file field.
|
|
552
|
+
|
|
553
|
+
Called during serialization for every ``UploadFile`` and ``UploadImage``
|
|
554
|
+
field that has a non-empty stored value. The result is emitted alongside
|
|
555
|
+
the stored value as ``{field_name}__url`` and forwarded to the upload
|
|
556
|
+
widget as ``valueRepr`` so the clickable link can differ from the raw
|
|
557
|
+
stored key (e.g. an S3 presigned URL instead of an ``s3://`` URI).
|
|
558
|
+
|
|
559
|
+
The stored ``value`` is never modified — it is still what the form
|
|
560
|
+
submits on save — so there is no risk of data corruption.
|
|
561
|
+
|
|
562
|
+
Override to return a dynamic URL. Example using aiobotocore:
|
|
563
|
+
|
|
564
|
+
.. code-block:: python
|
|
565
|
+
|
|
566
|
+
async def get_file_url(self, field_name: str, value: str, obj=None) -> str:
|
|
567
|
+
bucket, key = value.replace("s3://", "").split("/", 1)
|
|
568
|
+
async with aiobotocore.session.get_session().create_client("s3") as s3:
|
|
569
|
+
return await s3.generate_presigned_url(
|
|
570
|
+
"get_object",
|
|
571
|
+
Params={"Bucket": bucket, "Key": key},
|
|
572
|
+
ExpiresIn=3600,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
:params field_name: name of the upload field being serialized.
|
|
576
|
+
:params value: raw stored value (e.g. ``"s3://bucket/key"`` or ``"/media/file.pdf"``).
|
|
577
|
+
:params obj: the orm/db model instance being serialized, or None.
|
|
578
|
+
:return: URL string to expose to the frontend for display/download.
|
|
579
|
+
"""
|
|
580
|
+
return value
|
|
581
|
+
|
|
544
582
|
async def upload_file(
|
|
545
583
|
self,
|
|
546
584
|
field_name: str,
|
|
547
585
|
file_name: str,
|
|
548
586
|
file_content: bytes,
|
|
587
|
+
obj: Any | None = None,
|
|
549
588
|
) -> str:
|
|
550
589
|
"""This method is used to upload files.
|
|
551
590
|
|
|
552
591
|
:params field_name: a name of field.
|
|
553
592
|
:params file_name: a name of file.
|
|
554
593
|
:params file_content: a content of file.
|
|
594
|
+
:params obj: an orm/db model object. None on the add (create) page; the existing model instance on the change (edit) page.
|
|
555
595
|
:return: A file url.
|
|
556
596
|
"""
|
|
557
597
|
raise NotImplementedError
|
|
@@ -443,7 +443,7 @@ class PonyORMMixin:
|
|
|
443
443
|
@sync_to_async
|
|
444
444
|
@db_session
|
|
445
445
|
def serialize_obj_attributes(
|
|
446
|
-
self, obj: Any, attributes_to_serizalize: list[ModelFieldWidgetSchema]
|
|
446
|
+
self, obj: Any, attributes_to_serizalize: list[ModelFieldWidgetSchema], list_view: bool = False
|
|
447
447
|
) -> dict[str, Any]:
|
|
448
448
|
"""Serialize orm model obj attribute to dict.
|
|
449
449
|
|