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.
Files changed (49) hide show
  1. {fastadmin-0.4.8 → fastadmin-0.4.9}/PKG-INFO +1 -1
  2. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/api.py +2 -0
  3. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/api.py +3 -0
  4. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/api.py +2 -0
  5. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/service.py +8 -0
  6. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/base.py +42 -2
  7. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/ponyorm.py +1 -1
  8. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/index.min.js +2 -2
  9. {fastadmin-0.4.8 → fastadmin-0.4.9}/pyproject.toml +4 -1
  10. {fastadmin-0.4.8 → fastadmin-0.4.9}/LICENSE +0 -0
  11. {fastadmin-0.4.8 → fastadmin-0.4.9}/README.md +0 -0
  12. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/__init__.py +0 -0
  13. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/__init__.py +0 -0
  14. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/exceptions.py +0 -0
  15. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/__init__.py +0 -0
  16. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/__init__.py +0 -0
  17. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/__init__.py +0 -0
  18. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/urls.py +0 -0
  19. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/django/app/views.py +0 -0
  20. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/__init__.py +0 -0
  21. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/app.py +0 -0
  22. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/fastapi/views.py +0 -0
  23. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/__init__.py +0 -0
  24. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/app.py +0 -0
  25. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/frameworks/flask/views.py +0 -0
  26. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/helpers.py +0 -0
  27. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/api/schemas.py +0 -0
  28. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/__init__.py +0 -0
  29. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/decorators.py +0 -0
  30. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/helpers.py +0 -0
  31. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/__init__.py +0 -0
  32. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/django.py +0 -0
  33. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/sqlalchemy.py +0 -0
  34. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/orms/tortoise.py +0 -0
  35. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/models/schemas.py +0 -0
  36. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/settings.py +0 -0
  37. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-6Z7niv9l.js +0 -0
  38. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-C151k0-L.js +0 -0
  39. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-C9KPOOKD.js +0 -0
  40. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-D1fCgYB7.js +0 -0
  41. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-D3TWcJOI.js +0 -0
  42. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-DRNcaZ-V.js +0 -0
  43. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/assets/worker-MF2p-l5_.js +0 -0
  44. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/images/favicon.png +0 -0
  45. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/images/header-logo.svg +0 -0
  46. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/images/sign-in-logo.svg +0 -0
  47. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/index.html +0 -0
  48. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/static/index.min.css +0 -0
  49. {fastadmin-0.4.8 → fastadmin-0.4.9}/fastadmin/templates/index.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastadmin
3
- Version: 0.4.8
3
+ Version: 0.4.9
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
@@ -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