Skip to content

Commit 6ea0878

Browse files
committed
Fixes for upload
1 parent 4deceee commit 6ea0878

29 files changed

Lines changed: 341 additions & 98 deletions

File tree

README.md

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -434,20 +434,22 @@ Register ORM models
434434

435435

436436
```python
437+
import typing as tp
437438
from uuid import UUID
438439

439440
import bcrypt
440441
from tortoise import fields
441442
from tortoise.models import Model
442443

443-
from fastadmin import TortoiseModelAdmin, register
444+
from fastadmin import TortoiseModelAdmin, WidgetType, register
444445

445446

446447
class User(Model):
447448
username = fields.CharField(max_length=255, unique=True)
448449
hash_password = fields.CharField(max_length=255)
449450
is_superuser = fields.BooleanField(default=False)
450451
is_active = fields.BooleanField(default=False)
452+
avatar_url = fields.TextField(null=True)
451453

452454
def __str__(self):
453455
return self.username
@@ -460,15 +462,39 @@ class UserAdmin(TortoiseModelAdmin):
460462
list_display_links = ("id", "username")
461463
list_filter = ("id", "username", "is_superuser", "is_active")
462464
search_fields = ("username",)
463-
464-
async def authenticate(self, username: str, password: str) -> UUID | int | None:
465-
user = await User.filter(username=username, is_superuser=True).first()
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()
466480
if not user:
467481
return None
468482
if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
469483
return None
470484
return user.id
471485

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+
472498
```
473499

474500

@@ -542,8 +568,11 @@ class UserAdmin(DjangoModelAdmin):
542568

543569

544570
```python
571+
import typing as tp
572+
import uuid
573+
545574
import bcrypt
546-
from sqlalchemy import Boolean, Integer, String, select
575+
from sqlalchemy import Boolean, Integer, String, Text, select, update
547576
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
548577
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
549578

@@ -568,6 +597,7 @@ class User(Base):
568597
hash_password: Mapped[str] = mapped_column(String(length=255), nullable=False)
569598
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
570599
is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
600+
avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True)
571601

572602
def __str__(self):
573603
return self.username
@@ -581,17 +611,33 @@ class UserAdmin(SqlAlchemyModelAdmin):
581611
list_filter = ("id", "username", "is_superuser", "is_active")
582612
search_fields = ("username",)
583613

584-
async def authenticate(self, username, password):
614+
async def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
585615
sessionmaker = self.get_sessionmaker()
586616
async with sessionmaker() as session:
587-
query = select(User).filter_by(username=username, password=password, is_superuser=True)
617+
query = select(self.model_cls).filter_by(username=username, password=password, is_superuser=True)
588618
result = await session.scalars(query)
589-
user = result.first()
590-
if not user:
619+
obj = result.first()
620+
if not obj:
591621
return None
592-
if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
622+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
593623
return None
594-
return user.id
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()
595641

596642
```
597643

@@ -614,8 +660,11 @@ class UserAdmin(SqlAlchemyModelAdmin):
614660

615661

616662
```python
663+
import typing as tp
664+
import uuid
665+
617666
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
619668

620669
from fastadmin import PonyORMModelAdmin, register
621670

@@ -630,6 +679,7 @@ class User(db.Entity): # type: ignore [name-defined]
630679
hash_password = Required(str)
631680
is_superuser = Required(bool, default=False)
632681
is_active = Required(bool, default=False)
682+
avatar_url = Optional(LongStr, nullable=True)
633683

634684
def __str__(self):
635685
return self.username
@@ -644,13 +694,31 @@ class UserAdmin(PonyORMModelAdmin):
644694
search_fields = ("username",)
645695

646696
@db_session
647-
def authenticate(self, username, password):
648-
user = next((f for f in self.model_cls.select(username=username, password=password, is_superuser=True)), None)
649-
if not user:
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:
650700
return None
651-
if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
701+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
652702
return None
653-
return user.id
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()
654722

655723
```
656724

docs/build.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ def read_cls_docstring(cls):
4545

4646
def get_versions():
4747
return [
48+
{
49+
"version": "0.2.22",
50+
"changes": [
51+
"Fix upload base64 widget. Add new props disableCropImage. Fixed examples.",
52+
],
53+
},
4854
{
4955
"version": "0.2.21",
5056
"changes": [

docs/code/models/tortoise.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import typing as tp
12
from uuid import UUID
23

34
import bcrypt
@@ -13,6 +14,8 @@ class ModelUser(Model):
1314
is_superuser = fields.BooleanField(default=False)
1415
is_active = fields.BooleanField(default=False)
1516

17+
avatar_url = fields.TextField(null=True)
18+
1619
def __str__(self):
1720
return self.username
1821

@@ -37,6 +40,14 @@ class UserAdmin(TortoiseModelAdmin):
3740
formfield_overrides = { # noqa: RUF012
3841
"username": (WidgetType.SlugInput, {"required": True}),
3942
"password": (WidgetType.PasswordInput, {"passwordModalForm": True}),
43+
"avatar_url": (
44+
WidgetType.Upload,
45+
{
46+
"required": False,
47+
# Disable crop image for upload field
48+
# "disableCropImage": True,
49+
},
50+
),
4051
}
4152
actions = (
4253
*TortoiseModelAdmin.actions,
@@ -66,3 +77,8 @@ async def activate(self, ids: list[int]) -> None:
6677
@action(description="Deactivate")
6778
async def deactivate(self, ids: list[int]) -> None:
6879
await self.model_cls.filter(id__in=ids).update(is_active=False)
80+
81+
async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
82+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
83+
setattr(obj, field, base64)
84+
await obj.save(update_fields=(field,))

docs/code/quick_tutorial/ponyorm.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import typing as tp
2+
import uuid
3+
14
import bcrypt
2-
from pony.orm import Database, PrimaryKey, Required, db_session
5+
from pony.orm import Database, LongStr, Optional, PrimaryKey, Required, commit, db_session
36

47
from fastadmin import PonyORMModelAdmin, register
58

@@ -14,6 +17,7 @@ class User(db.Entity): # type: ignore [name-defined]
1417
hash_password = Required(str)
1518
is_superuser = Required(bool, default=False)
1619
is_active = Required(bool, default=False)
20+
avatar_url = Optional(LongStr, nullable=True)
1721

1822
def __str__(self):
1923
return self.username
@@ -28,10 +32,28 @@ class UserAdmin(PonyORMModelAdmin):
2832
search_fields = ("username",)
2933

3034
@db_session
31-
def authenticate(self, username, password):
32-
user = next((f for f in self.model_cls.select(username=username, password=password, is_superuser=True)), None)
33-
if not user:
35+
def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
36+
obj = next((f for f in User.select(username=username, password=password, is_superuser=True)), None) # fmt: skip
37+
if not obj:
3438
return None
35-
if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
39+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
3640
return None
37-
return user.id
41+
return obj.id
42+
43+
@db_session
44+
def change_password(self, id: uuid.UUID | int, password: str) -> None:
45+
obj = next((f for f in self.model_cls.select(id=id)), None)
46+
if not obj:
47+
return
48+
hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
49+
obj.hash_password = hash_password
50+
commit()
51+
52+
@db_session
53+
def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
54+
obj = next((f for f in self.model_cls.select(id=obj.id)), None)
55+
if not obj:
56+
return
57+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
58+
setattr(obj, field, base64)
59+
commit()

docs/code/quick_tutorial/sqlalchemy.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import typing as tp
2+
import uuid
3+
14
import bcrypt
2-
from sqlalchemy import Boolean, Integer, String, select
5+
from sqlalchemy import Boolean, Integer, String, Text, select, update
36
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
47
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
58

@@ -24,6 +27,7 @@ class User(Base):
2427
hash_password: Mapped[str] = mapped_column(String(length=255), nullable=False)
2528
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
2629
is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
30+
avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True)
2731

2832
def __str__(self):
2933
return self.username
@@ -37,14 +41,30 @@ class UserAdmin(SqlAlchemyModelAdmin):
3741
list_filter = ("id", "username", "is_superuser", "is_active")
3842
search_fields = ("username",)
3943

40-
async def authenticate(self, username, password):
44+
async def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
4145
sessionmaker = self.get_sessionmaker()
4246
async with sessionmaker() as session:
43-
query = select(User).filter_by(username=username, password=password, is_superuser=True)
47+
query = select(self.model_cls).filter_by(username=username, password=password, is_superuser=True)
4448
result = await session.scalars(query)
45-
user = result.first()
46-
if not user:
49+
obj = result.first()
50+
if not obj:
4751
return None
48-
if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
52+
if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
4953
return None
50-
return user.id
54+
return obj.id
55+
56+
async def change_password(self, id: uuid.UUID | int, password: str) -> None:
57+
sessionmaker = self.get_sessionmaker()
58+
async with sessionmaker() as session:
59+
hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
60+
query = update(self.model_cls).where(User.id.in_([id])).values(hash_password=hash_password)
61+
await session.execute(query)
62+
await session.commit()
63+
64+
async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
65+
sessionmaker = self.get_sessionmaker()
66+
async with sessionmaker() as session:
67+
# convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
68+
query = update(self.model_cls).where(User.id.in_([obj.id])).values(**{field: base64})
69+
await session.execute(query)
70+
await session.commit()

0 commit comments

Comments
 (0)