@@ -434,20 +434,22 @@ Register ORM models
434434
435435
436436``` python
437+ import typing as tp
437438from uuid import UUID
438439
439440import bcrypt
440441from tortoise import fields
441442from tortoise.models import Model
442443
443- from fastadmin import TortoiseModelAdmin, register
444+ from fastadmin import TortoiseModelAdmin, WidgetType, register
444445
445446
446447class 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+
545574import bcrypt
546- from sqlalchemy import Boolean, Integer, String, select
575+ from sqlalchemy import Boolean, Integer, String, Text, select, update
547576from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
548577from 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+
617666import 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
620669from 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
0 commit comments