fastadmin 0.2.20__tar.gz → 0.2.22__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.2.20 → fastadmin-0.2.22}/PKG-INFO +86 -18
- {fastadmin-0.2.20 → fastadmin-0.2.22}/README.md +85 -17
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/base.py +6 -3
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/index.min.js +52 -52
- {fastadmin-0.2.20 → fastadmin-0.2.22}/pyproject.toml +1 -1
- {fastadmin-0.2.20 → fastadmin-0.2.22}/LICENSE +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/exceptions.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/django/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/django/app/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/django/app/api.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/django/app/urls.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/django/app/views.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/fastapi/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/fastapi/api.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/fastapi/app.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/fastapi/views.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/flask/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/flask/api.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/flask/app.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/frameworks/flask/views.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/helpers.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/schemas.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/api/service.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/decorators.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/helpers.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/orms/__init__.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/orms/django.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/orms/ponyorm.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/orms/sqlalchemy.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/orms/tortoise.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/models/schemas.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/settings.py +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/assets/worker-6Z7niv9l.js +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/assets/worker-C151k0-L.js +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/assets/worker-D1fCgYB7.js +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/assets/worker-D3TWcJOI.js +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/assets/worker-DRNcaZ-V.js +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/assets/worker-MF2p-l5_.js +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/images/favicon.png +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/images/header-logo.svg +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/images/sign-in-logo.svg +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/index.html +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/static/index.min.css +0 -0
- {fastadmin-0.2.20 → fastadmin-0.2.22}/fastadmin/templates/index.html +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: fastadmin
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.22
|
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
|
Keywords: fastapi,admin
|
@@ -475,13 +475,14 @@ Register ORM models
|
|
475
475
|
|
476
476
|
|
477
477
|
```python
|
478
|
+
import typing as tp
|
478
479
|
from uuid import UUID
|
479
480
|
|
480
481
|
import bcrypt
|
481
482
|
from tortoise import fields
|
482
483
|
from tortoise.models import Model
|
483
484
|
|
484
|
-
from fastadmin import TortoiseModelAdmin, register
|
485
|
+
from fastadmin import TortoiseModelAdmin, WidgetType, register
|
485
486
|
|
486
487
|
|
487
488
|
class User(Model):
|
@@ -489,6 +490,7 @@ class User(Model):
|
|
489
490
|
hash_password = fields.CharField(max_length=255)
|
490
491
|
is_superuser = fields.BooleanField(default=False)
|
491
492
|
is_active = fields.BooleanField(default=False)
|
493
|
+
avatar_url = fields.TextField(null=True)
|
492
494
|
|
493
495
|
def __str__(self):
|
494
496
|
return self.username
|
@@ -501,15 +503,39 @@ class UserAdmin(TortoiseModelAdmin):
|
|
501
503
|
list_display_links = ("id", "username")
|
502
504
|
list_filter = ("id", "username", "is_superuser", "is_active")
|
503
505
|
search_fields = ("username",)
|
504
|
-
|
505
|
-
|
506
|
-
|
506
|
+
formfield_overrides = { # noqa: RUF012
|
507
|
+
"username": (WidgetType.SlugInput, {"required": True}),
|
508
|
+
"password": (WidgetType.PasswordInput, {"passwordModalForm": True}),
|
509
|
+
"avatar_url": (
|
510
|
+
WidgetType.Upload,
|
511
|
+
{
|
512
|
+
"required": False,
|
513
|
+
# Disable crop image for upload field
|
514
|
+
# "disableCropImage": True,
|
515
|
+
},
|
516
|
+
),
|
517
|
+
}
|
518
|
+
|
519
|
+
async def authenticate(self, username: str, password: str) -> int | None:
|
520
|
+
user = await self.model_cls.filter(phone=username, is_superuser=True).first()
|
507
521
|
if not user:
|
508
522
|
return None
|
509
523
|
if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
|
510
524
|
return None
|
511
525
|
return user.id
|
512
526
|
|
527
|
+
async def change_password(self, id: UUID | int, password: str) -> None:
|
528
|
+
user = await self.model_cls.filter(id=id).first()
|
529
|
+
if not user:
|
530
|
+
return
|
531
|
+
user.hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
532
|
+
await user.save(update_fields=("hash_password",))
|
533
|
+
|
534
|
+
async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
|
535
|
+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
|
536
|
+
setattr(obj, field, base64)
|
537
|
+
await obj.save(update_fields=(field,))
|
538
|
+
|
513
539
|
```
|
514
540
|
|
515
541
|
|
@@ -583,8 +609,11 @@ class UserAdmin(DjangoModelAdmin):
|
|
583
609
|
|
584
610
|
|
585
611
|
```python
|
612
|
+
import typing as tp
|
613
|
+
import uuid
|
614
|
+
|
586
615
|
import bcrypt
|
587
|
-
from sqlalchemy import Boolean, Integer, String, select
|
616
|
+
from sqlalchemy import Boolean, Integer, String, Text, select, update
|
588
617
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
589
618
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
590
619
|
|
@@ -609,6 +638,7 @@ class User(Base):
|
|
609
638
|
hash_password: Mapped[str] = mapped_column(String(length=255), nullable=False)
|
610
639
|
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
611
640
|
is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
641
|
+
avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True)
|
612
642
|
|
613
643
|
def __str__(self):
|
614
644
|
return self.username
|
@@ -622,17 +652,33 @@ class UserAdmin(SqlAlchemyModelAdmin):
|
|
622
652
|
list_filter = ("id", "username", "is_superuser", "is_active")
|
623
653
|
search_fields = ("username",)
|
624
654
|
|
625
|
-
async def authenticate(self, username, password):
|
655
|
+
async def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
|
626
656
|
sessionmaker = self.get_sessionmaker()
|
627
657
|
async with sessionmaker() as session:
|
628
|
-
query = select(
|
658
|
+
query = select(self.model_cls).filter_by(username=username, password=password, is_superuser=True)
|
629
659
|
result = await session.scalars(query)
|
630
|
-
|
631
|
-
if not
|
660
|
+
obj = result.first()
|
661
|
+
if not obj:
|
632
662
|
return None
|
633
|
-
if not bcrypt.checkpw(password.encode(),
|
663
|
+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
|
634
664
|
return None
|
635
|
-
return
|
665
|
+
return obj.id
|
666
|
+
|
667
|
+
async def change_password(self, id: uuid.UUID | int, password: str) -> None:
|
668
|
+
sessionmaker = self.get_sessionmaker()
|
669
|
+
async with sessionmaker() as session:
|
670
|
+
hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
671
|
+
query = update(self.model_cls).where(User.id.in_([id])).values(hash_password=hash_password)
|
672
|
+
await session.execute(query)
|
673
|
+
await session.commit()
|
674
|
+
|
675
|
+
async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
|
676
|
+
sessionmaker = self.get_sessionmaker()
|
677
|
+
async with sessionmaker() as session:
|
678
|
+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
|
679
|
+
query = update(self.model_cls).where(User.id.in_([obj.id])).values(**{field: base64})
|
680
|
+
await session.execute(query)
|
681
|
+
await session.commit()
|
636
682
|
|
637
683
|
```
|
638
684
|
|
@@ -655,8 +701,11 @@ class UserAdmin(SqlAlchemyModelAdmin):
|
|
655
701
|
|
656
702
|
|
657
703
|
```python
|
704
|
+
import typing as tp
|
705
|
+
import uuid
|
706
|
+
|
658
707
|
import bcrypt
|
659
|
-
from pony.orm import Database, PrimaryKey, Required, db_session
|
708
|
+
from pony.orm import Database, LongStr, Optional, PrimaryKey, Required, commit, db_session
|
660
709
|
|
661
710
|
from fastadmin import PonyORMModelAdmin, register
|
662
711
|
|
@@ -671,6 +720,7 @@ class User(db.Entity): # type: ignore [name-defined]
|
|
671
720
|
hash_password = Required(str)
|
672
721
|
is_superuser = Required(bool, default=False)
|
673
722
|
is_active = Required(bool, default=False)
|
723
|
+
avatar_url = Optional(LongStr, nullable=True)
|
674
724
|
|
675
725
|
def __str__(self):
|
676
726
|
return self.username
|
@@ -685,13 +735,31 @@ class UserAdmin(PonyORMModelAdmin):
|
|
685
735
|
search_fields = ("username",)
|
686
736
|
|
687
737
|
@db_session
|
688
|
-
def authenticate(self, username, password):
|
689
|
-
|
690
|
-
if not
|
738
|
+
def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
|
739
|
+
obj = next((f for f in User.select(username=username, password=password, is_superuser=True)), None) # fmt: skip
|
740
|
+
if not obj:
|
691
741
|
return None
|
692
|
-
if not bcrypt.checkpw(password.encode(),
|
742
|
+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
|
693
743
|
return None
|
694
|
-
return
|
744
|
+
return obj.id
|
745
|
+
|
746
|
+
@db_session
|
747
|
+
def change_password(self, id: uuid.UUID | int, password: str) -> None:
|
748
|
+
obj = next((f for f in self.model_cls.select(id=id)), None)
|
749
|
+
if not obj:
|
750
|
+
return
|
751
|
+
hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
752
|
+
obj.hash_password = hash_password
|
753
|
+
commit()
|
754
|
+
|
755
|
+
@db_session
|
756
|
+
def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
|
757
|
+
obj = next((f for f in self.model_cls.select(id=obj.id)), None)
|
758
|
+
if not obj:
|
759
|
+
return
|
760
|
+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
|
761
|
+
setattr(obj, field, base64)
|
762
|
+
commit()
|
695
763
|
|
696
764
|
```
|
697
765
|
|
@@ -434,13 +434,14 @@ Register ORM models
|
|
434
434
|
|
435
435
|
|
436
436
|
```python
|
437
|
+
import typing as tp
|
437
438
|
from uuid import UUID
|
438
439
|
|
439
440
|
import bcrypt
|
440
441
|
from tortoise import fields
|
441
442
|
from tortoise.models import Model
|
442
443
|
|
443
|
-
from fastadmin import TortoiseModelAdmin, register
|
444
|
+
from fastadmin import TortoiseModelAdmin, WidgetType, register
|
444
445
|
|
445
446
|
|
446
447
|
class User(Model):
|
@@ -448,6 +449,7 @@ class User(Model):
|
|
448
449
|
hash_password = fields.CharField(max_length=255)
|
449
450
|
is_superuser = fields.BooleanField(default=False)
|
450
451
|
is_active = fields.BooleanField(default=False)
|
452
|
+
avatar_url = fields.TextField(null=True)
|
451
453
|
|
452
454
|
def __str__(self):
|
453
455
|
return self.username
|
@@ -460,15 +462,39 @@ class UserAdmin(TortoiseModelAdmin):
|
|
460
462
|
list_display_links = ("id", "username")
|
461
463
|
list_filter = ("id", "username", "is_superuser", "is_active")
|
462
464
|
search_fields = ("username",)
|
463
|
-
|
464
|
-
|
465
|
-
|
465
|
+
formfield_overrides = { # noqa: RUF012
|
466
|
+
"username": (WidgetType.SlugInput, {"required": True}),
|
467
|
+
"password": (WidgetType.PasswordInput, {"passwordModalForm": True}),
|
468
|
+
"avatar_url": (
|
469
|
+
WidgetType.Upload,
|
470
|
+
{
|
471
|
+
"required": False,
|
472
|
+
# Disable crop image for upload field
|
473
|
+
# "disableCropImage": True,
|
474
|
+
},
|
475
|
+
),
|
476
|
+
}
|
477
|
+
|
478
|
+
async def authenticate(self, username: str, password: str) -> int | None:
|
479
|
+
user = await self.model_cls.filter(phone=username, is_superuser=True).first()
|
466
480
|
if not user:
|
467
481
|
return None
|
468
482
|
if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
|
469
483
|
return None
|
470
484
|
return user.id
|
471
485
|
|
486
|
+
async def change_password(self, id: UUID | int, password: str) -> None:
|
487
|
+
user = await self.model_cls.filter(id=id).first()
|
488
|
+
if not user:
|
489
|
+
return
|
490
|
+
user.hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
491
|
+
await user.save(update_fields=("hash_password",))
|
492
|
+
|
493
|
+
async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
|
494
|
+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
|
495
|
+
setattr(obj, field, base64)
|
496
|
+
await obj.save(update_fields=(field,))
|
497
|
+
|
472
498
|
```
|
473
499
|
|
474
500
|
|
@@ -542,8 +568,11 @@ class UserAdmin(DjangoModelAdmin):
|
|
542
568
|
|
543
569
|
|
544
570
|
```python
|
571
|
+
import typing as tp
|
572
|
+
import uuid
|
573
|
+
|
545
574
|
import bcrypt
|
546
|
-
from sqlalchemy import Boolean, Integer, String, select
|
575
|
+
from sqlalchemy import Boolean, Integer, String, Text, select, update
|
547
576
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
548
577
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
549
578
|
|
@@ -568,6 +597,7 @@ class User(Base):
|
|
568
597
|
hash_password: Mapped[str] = mapped_column(String(length=255), nullable=False)
|
569
598
|
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
570
599
|
is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
600
|
+
avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True)
|
571
601
|
|
572
602
|
def __str__(self):
|
573
603
|
return self.username
|
@@ -581,17 +611,33 @@ class UserAdmin(SqlAlchemyModelAdmin):
|
|
581
611
|
list_filter = ("id", "username", "is_superuser", "is_active")
|
582
612
|
search_fields = ("username",)
|
583
613
|
|
584
|
-
async def authenticate(self, username, password):
|
614
|
+
async def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
|
585
615
|
sessionmaker = self.get_sessionmaker()
|
586
616
|
async with sessionmaker() as session:
|
587
|
-
query = select(
|
617
|
+
query = select(self.model_cls).filter_by(username=username, password=password, is_superuser=True)
|
588
618
|
result = await session.scalars(query)
|
589
|
-
|
590
|
-
if not
|
619
|
+
obj = result.first()
|
620
|
+
if not obj:
|
591
621
|
return None
|
592
|
-
if not bcrypt.checkpw(password.encode(),
|
622
|
+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
|
593
623
|
return None
|
594
|
-
return
|
624
|
+
return obj.id
|
625
|
+
|
626
|
+
async def change_password(self, id: uuid.UUID | int, password: str) -> None:
|
627
|
+
sessionmaker = self.get_sessionmaker()
|
628
|
+
async with sessionmaker() as session:
|
629
|
+
hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
630
|
+
query = update(self.model_cls).where(User.id.in_([id])).values(hash_password=hash_password)
|
631
|
+
await session.execute(query)
|
632
|
+
await session.commit()
|
633
|
+
|
634
|
+
async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
|
635
|
+
sessionmaker = self.get_sessionmaker()
|
636
|
+
async with sessionmaker() as session:
|
637
|
+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
|
638
|
+
query = update(self.model_cls).where(User.id.in_([obj.id])).values(**{field: base64})
|
639
|
+
await session.execute(query)
|
640
|
+
await session.commit()
|
595
641
|
|
596
642
|
```
|
597
643
|
|
@@ -614,8 +660,11 @@ class UserAdmin(SqlAlchemyModelAdmin):
|
|
614
660
|
|
615
661
|
|
616
662
|
```python
|
663
|
+
import typing as tp
|
664
|
+
import uuid
|
665
|
+
|
617
666
|
import bcrypt
|
618
|
-
from pony.orm import Database, PrimaryKey, Required, db_session
|
667
|
+
from pony.orm import Database, LongStr, Optional, PrimaryKey, Required, commit, db_session
|
619
668
|
|
620
669
|
from fastadmin import PonyORMModelAdmin, register
|
621
670
|
|
@@ -630,6 +679,7 @@ class User(db.Entity): # type: ignore [name-defined]
|
|
630
679
|
hash_password = Required(str)
|
631
680
|
is_superuser = Required(bool, default=False)
|
632
681
|
is_active = Required(bool, default=False)
|
682
|
+
avatar_url = Optional(LongStr, nullable=True)
|
633
683
|
|
634
684
|
def __str__(self):
|
635
685
|
return self.username
|
@@ -644,13 +694,31 @@ class UserAdmin(PonyORMModelAdmin):
|
|
644
694
|
search_fields = ("username",)
|
645
695
|
|
646
696
|
@db_session
|
647
|
-
def authenticate(self, username, password):
|
648
|
-
|
649
|
-
if not
|
697
|
+
def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
|
698
|
+
obj = next((f for f in User.select(username=username, password=password, is_superuser=True)), None) # fmt: skip
|
699
|
+
if not obj:
|
650
700
|
return None
|
651
|
-
if not bcrypt.checkpw(password.encode(),
|
701
|
+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
|
652
702
|
return None
|
653
|
-
return
|
703
|
+
return obj.id
|
704
|
+
|
705
|
+
@db_session
|
706
|
+
def change_password(self, id: uuid.UUID | int, password: str) -> None:
|
707
|
+
obj = next((f for f in self.model_cls.select(id=id)), None)
|
708
|
+
if not obj:
|
709
|
+
return
|
710
|
+
hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
711
|
+
obj.hash_password = hash_password
|
712
|
+
commit()
|
713
|
+
|
714
|
+
@db_session
|
715
|
+
def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
|
716
|
+
obj = next((f for f in self.model_cls.select(id=obj.id)), None)
|
717
|
+
if not obj:
|
718
|
+
return
|
719
|
+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
|
720
|
+
setattr(obj, field, base64)
|
721
|
+
commit()
|
654
722
|
|
655
723
|
```
|
656
724
|
|
@@ -9,7 +9,6 @@ from uuid import UUID
|
|
9
9
|
|
10
10
|
from asgiref.sync import sync_to_async
|
11
11
|
|
12
|
-
from fastadmin.api.helpers import is_valid_base64
|
13
12
|
from fastadmin.api.schemas import ExportFormat
|
14
13
|
from fastadmin.models.schemas import DashboardWidgetType, ModelFieldWidgetSchema, WidgetType
|
15
14
|
|
@@ -456,8 +455,12 @@ class BaseModelAdmin:
|
|
456
455
|
return None
|
457
456
|
|
458
457
|
for upload_field in upload_fields:
|
459
|
-
if upload_field.name in payload
|
460
|
-
|
458
|
+
if upload_field.name in payload:
|
459
|
+
if inspect.iscoroutinefunction(self.orm_save_upload_field):
|
460
|
+
orm_save_upload_field_fn = self.orm_save_upload_field
|
461
|
+
else:
|
462
|
+
orm_save_upload_field_fn = sync_to_async(self.orm_save_upload_field) # type: ignore [arg-type]
|
463
|
+
await orm_save_upload_field_fn(obj, upload_field.column_name, payload[upload_field.name])
|
461
464
|
|
462
465
|
for m2m_field in m2m_fields:
|
463
466
|
if m2m_field.name in payload:
|