fastadmin 0.2.21__tar.gz → 0.3.0__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 (48) hide show
  1. {fastadmin-0.2.21 → fastadmin-0.3.0}/PKG-INFO +145 -59
  2. {fastadmin-0.2.21 → fastadmin-0.3.0}/README.md +136 -51
  3. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/exceptions.py +2 -2
  4. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/django/app/api.py +27 -19
  5. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/fastapi/api.py +19 -8
  6. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/flask/api.py +28 -20
  7. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/helpers.py +45 -8
  8. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/schemas.py +1 -1
  9. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/service.py +34 -15
  10. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/base.py +35 -12
  11. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/decorators.py +7 -4
  12. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/orms/django.py +8 -8
  13. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/orms/ponyorm.py +15 -7
  14. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/orms/sqlalchemy.py +28 -15
  15. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/orms/tortoise.py +2 -2
  16. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/schemas.py +1 -1
  17. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/index.min.js +1 -1
  18. {fastadmin-0.2.21 → fastadmin-0.3.0}/pyproject.toml +7 -6
  19. {fastadmin-0.2.21 → fastadmin-0.3.0}/LICENSE +0 -0
  20. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/__init__.py +0 -0
  21. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/__init__.py +0 -0
  22. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/__init__.py +0 -0
  23. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/django/__init__.py +0 -0
  24. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/django/app/__init__.py +0 -0
  25. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/django/app/urls.py +0 -0
  26. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/django/app/views.py +0 -0
  27. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/fastapi/__init__.py +0 -0
  28. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/fastapi/app.py +0 -0
  29. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/fastapi/views.py +0 -0
  30. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/flask/__init__.py +0 -0
  31. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/flask/app.py +0 -0
  32. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/api/frameworks/flask/views.py +0 -0
  33. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/__init__.py +0 -0
  34. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/helpers.py +0 -0
  35. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/models/orms/__init__.py +0 -0
  36. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/settings.py +0 -0
  37. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/assets/worker-6Z7niv9l.js +0 -0
  38. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/assets/worker-C151k0-L.js +0 -0
  39. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/assets/worker-D1fCgYB7.js +0 -0
  40. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/assets/worker-D3TWcJOI.js +0 -0
  41. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/assets/worker-DRNcaZ-V.js +0 -0
  42. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/assets/worker-MF2p-l5_.js +0 -0
  43. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/images/favicon.png +0 -0
  44. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/images/header-logo.svg +0 -0
  45. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/images/sign-in-logo.svg +0 -0
  46. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/index.html +0 -0
  47. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/static/index.min.css +0 -0
  48. {fastadmin-0.2.21 → fastadmin-0.3.0}/fastadmin/templates/index.html +0 -0
@@ -1,12 +1,13 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: fastadmin
3
- Version: 0.2.21
3
+ Version: 0.3.0
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
+ License-File: LICENSE
6
7
  Keywords: fastapi,admin
7
8
  Author: Seva D
8
9
  Author-email: vsdudakov@gmail.com
9
- Requires-Python: >=3.10,<4.0
10
+ Requires-Python: >=3.12,<4.0
10
11
  Classifier: Environment :: Console
11
12
  Classifier: Framework :: Django
12
13
  Classifier: Framework :: FastAPI
@@ -14,10 +15,9 @@ Classifier: Framework :: Flask
14
15
  Classifier: License :: OSI Approved :: MIT License
15
16
  Classifier: Operating System :: OS Independent
16
17
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
21
  Classifier: Topic :: Software Development :: Documentation
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Topic :: Software Development :: Widget Sets
@@ -27,32 +27,32 @@ Provides-Extra: flask
27
27
  Provides-Extra: pony
28
28
  Provides-Extra: sqlalchemy
29
29
  Provides-Extra: tortoise-orm
30
- Requires-Dist: asgiref (>=3.7.0,<4.0.0)
31
- Requires-Dist: django (==5.*) ; extra == "django"
30
+ Requires-Dist: asgiref (>=3.11.1,<4.0.0)
31
+ Requires-Dist: django (==6.*) ; extra == "django"
32
32
  Requires-Dist: fastapi (==0.*) ; extra == "fastapi"
33
33
  Requires-Dist: flask (==3.*) ; extra == "flask"
34
+ Requires-Dist: greenlet (>=3.0,<4.0) ; extra == "sqlalchemy"
34
35
  Requires-Dist: pony (==0.*) ; extra == "pony"
35
36
  Requires-Dist: pyjwt (>=2.6.0,<3.0.0)
36
37
  Requires-Dist: sqlalchemy[asyncio] (==2.*) ; extra == "sqlalchemy"
37
- Requires-Dist: tortoise-orm (==0.*) ; extra == "tortoise-orm"
38
+ Requires-Dist: tortoise-orm (==1.*) ; extra == "tortoise-orm"
38
39
  Project-URL: Homepage, https://github.com/vsdudakov/fastadmin
39
40
  Project-URL: Repository, https://github.com/vsdudakov/fastadmin
40
41
  Description-Content-Type: text/markdown
41
42
 
42
- ## Admin Dashboard App for FastAPI/Flask/Django
43
+ # Admin Dashboard for FastAPI / Flask / Django
43
44
 
44
45
  [![codecov](https://codecov.io/gh/vsdudakov/fastadmin/branch/main/graph/badge.svg?token=RNGX5HOW3T)](https://codecov.io/gh/vsdudakov/fastadmin)
45
46
  [![License](https://img.shields.io/github/license/vsdudakov/fastadmin)](https://github.com/vsdudakov/fastadmin/blob/master/LICENSE)
46
47
  [![PyPi](https://badgen.net/pypi/v/fastadmin)](https://pypi.org/project/fastadmin/)
47
- [![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-3100/)
48
- [![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3110/)
49
48
  [![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/)
49
+ [![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/release/python-3130/)
50
50
 
51
51
  ## Screenshots
52
52
 
53
- ![SignIn View](https://raw.githubusercontent.com/vsdudakov/fastadmin/main/docs/assets/images/signin.png)
54
- ![List View](https://raw.githubusercontent.com/vsdudakov/fastadmin/main/docs/assets/images/list.png)
55
- ![Change View](https://raw.githubusercontent.com/vsdudakov/fastadmin/main/docs/assets/images/change.png)
53
+ ![Sign-in view](https://raw.githubusercontent.com/vsdudakov/fastadmin/main/docs/assets/images/signin.png)
54
+ ![List view](https://raw.githubusercontent.com/vsdudakov/fastadmin/main/docs/assets/images/list.png)
55
+ ![Change view](https://raw.githubusercontent.com/vsdudakov/fastadmin/main/docs/assets/images/change.png)
56
56
 
57
57
  <p align="center">
58
58
  <a href="https://twitter.com/intent/tweet?text=Admin%20Dashboard%20For%20FastAPI&url=https://github.com/vsdudakov/fastadmin&hashtags=FastAPI,AdminDashboard">
@@ -65,7 +65,7 @@ Description-Content-Type: text/markdown
65
65
  ## Introduction
66
66
 
67
67
 
68
- <a href='https://github.com/vsdudakov/fastadmin' target='_blank'>FastAdmin</a> is an easy-to-use Admin Dashboard App for FastAPI/Django/Flask inspired by Django Admin.
68
+ <a href='https://github.com/vsdudakov/fastadmin' target='_blank'>FastAdmin</a> is an easy-to-use admin dashboard for FastAPI, Django, and Flask, inspired by Django Admin.
69
69
 
70
70
 
71
71
 
@@ -81,7 +81,7 @@ Description-Content-Type: text/markdown
81
81
 
82
82
 
83
83
 
84
- FastAdmin was built with relations in mind and admiration for the excellent and popular Django Admin. It's engraved in its design that you may configure your admin dashboard for FastAPI/Django/Flask easiest way.
84
+ FastAdmin is built with relationships in mind and admiration for Django Admin. Its design focuses on making it as easy as possible to configure your admin dashboard for FastAPI, Django, or Flask.
85
85
 
86
86
 
87
87
 
@@ -97,7 +97,7 @@ FastAdmin was built with relations in mind and admiration for the excellent and
97
97
 
98
98
 
99
99
 
100
- FastAdmin is designed to be minimalistic, functional and yet familiar.
100
+ FastAdmin aims to be minimal, functional, and familiar.
101
101
 
102
102
 
103
103
 
@@ -123,7 +123,7 @@ FastAdmin is designed to be minimalistic, functional and yet familiar.
123
123
 
124
124
 
125
125
 
126
- If you have any questions that are beyond the scope of the documentation, Please feel free to email <a href='mailto:vsdudakov@gmail.com' target='_blank'>us</a>.
126
+ If you have questions beyond this documentation, feel free to <a href='mailto:vsdudakov@gmail.com' target='_blank'>email us</a>.
127
127
 
128
128
 
129
129
 
@@ -141,7 +141,7 @@ If you have any questions that are beyond the scope of the documentation, Please
141
141
 
142
142
 
143
143
 
144
- Follow the steps below to setup FastAdmin:
144
+ Follow the steps below to set up FastAdmin:
145
145
 
146
146
 
147
147
 
@@ -155,7 +155,7 @@ Follow the steps below to setup FastAdmin:
155
155
 
156
156
 
157
157
 
158
- Install the package using pip:
158
+ Install the package with pip:
159
159
 
160
160
 
161
161
 
@@ -175,7 +175,7 @@ Install the package using pip:
175
175
 
176
176
 
177
177
 
178
- Note: For zsh and macos use: <code>pip install fastadmin[fastapi,django]</code>
178
+ On zsh and macOS, use quotes: <code>pip install 'fastadmin[fastapi,django]'</code>
179
179
 
180
180
 
181
181
 
@@ -197,13 +197,13 @@ Note: For zsh and macos use: <code>pip install fastadmin[fastapi,django]</code>
197
197
 
198
198
  ```bash
199
199
 
200
- pip install fastadmin[fastapi,django] # for fastapi with django orm
201
- pip install fastadmin[fastapi,tortoise-orm] # for fastapi with tortoise orm
202
- pip install fastadmin[fastapi,pony] # for fastapi with pony orm
203
- pip install fastadmin[fastapi,sqlalchemy] # for fastapi with sqlalchemy orm
204
- pip install fastadmin[django] # for django with django orm
205
- pip install fastadmin[django,pony] # for django with pony orm
206
- pip install fastadmin[flask,sqlalchemy] # for flask with sqlalchemy
200
+ pip install fastadmin[fastapi,django] # FastAPI with Django ORM
201
+ pip install fastadmin[fastapi,tortoise-orm] # FastAPI with Tortoise ORM
202
+ pip install fastadmin[fastapi,pony] # FastAPI with Pony ORM
203
+ pip install fastadmin[fastapi,sqlalchemy] # FastAPI with SQLAlchemy (includes greenlet)
204
+ pip install fastadmin[django] # Django with Django ORM
205
+ pip install fastadmin[django,pony] # Django with Pony ORM
206
+ pip install fastadmin[flask,sqlalchemy] # Flask with SQLAlchemy (includes greenlet)
207
207
 
208
208
  ```
209
209
 
@@ -213,7 +213,7 @@ pip install fastadmin[flask,sqlalchemy] # for flask with sqlalchemy
213
213
 
214
214
 
215
215
 
216
- Install the package using poetry:
216
+ Or install with Poetry:
217
217
 
218
218
 
219
219
 
@@ -239,13 +239,13 @@ Install the package using poetry:
239
239
 
240
240
  ```bash
241
241
 
242
- poetry add 'fastadmin[fastapi,django]' # for fastapi with django orm
243
- poetry add 'fastadmin[fastapi,tortoise-orm]' # for fastapi with tortoise orm
244
- poetry add 'fastadmin[fastapi,pony]' # for fastapi with pony orm
245
- poetry add 'fastadmin[fastapi,sqlalchemy]' # for fastapi with sqlalchemy orm
246
- poetry add 'fastadmin[django]' # for django with django orm
247
- poetry add 'fastadmin[django,pony]' # for django with pony orm
248
- poetry add 'fastadmin[flask,sqlalchemy]' # for flask with sqlalchemy
242
+ poetry add 'fastadmin[fastapi,django]'
243
+ poetry add 'fastadmin[fastapi,tortoise-orm]'
244
+ poetry add 'fastadmin[fastapi,pony]'
245
+ poetry add 'fastadmin[fastapi,sqlalchemy]'
246
+ poetry add 'fastadmin[django]'
247
+ poetry add 'fastadmin[django,pony]'
248
+ poetry add 'fastadmin[flask,sqlalchemy]'
249
249
 
250
250
  ```
251
251
 
@@ -255,11 +255,11 @@ poetry add 'fastadmin[flask,sqlalchemy]' # for flask with sqlalchemy
255
255
 
256
256
 
257
257
 
258
- Configure required settings using virtual environment variables:
259
258
 
260
259
 
261
260
 
262
261
 
262
+ When using SQLAlchemy, the <code>greenlet</code> package is required (included in the <code>fastadmin[sqlalchemy]</code> extra).
263
263
 
264
264
 
265
265
 
@@ -271,11 +271,27 @@ Configure required settings using virtual environment variables:
271
271
 
272
272
 
273
273
 
274
+ Configure the required settings with environment variables:
274
275
 
275
276
 
276
277
 
277
278
 
278
- Note: You can add these variables to .env and use python-dotenv to load them. See all settings <a href='https://vsdudakov.github.io/fastadmin#settings'>here</a>
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+ You can add these variables to a <code>.env</code> file and load them with python-dotenv. See <a href='https://vsdudakov.github.io/fastadmin#settings'>all settings</a> in the full documentation.
279
295
 
280
296
 
281
297
 
@@ -314,7 +330,7 @@ export ADMIN_SECRET_KEY=secret_key
314
330
 
315
331
 
316
332
 
317
- Setup FastAdmin for a framework
333
+ Set up FastAdmin for your framework
318
334
 
319
335
 
320
336
 
@@ -475,13 +491,14 @@ Register ORM models
475
491
 
476
492
 
477
493
  ```python
494
+ import typing as tp
478
495
  from uuid import UUID
479
496
 
480
497
  import bcrypt
481
498
  from tortoise import fields
482
499
  from tortoise.models import Model
483
500
 
484
- from fastadmin import TortoiseModelAdmin, register
501
+ from fastadmin import TortoiseModelAdmin, WidgetType, register
485
502
 
486
503
 
487
504
  class User(Model):
@@ -489,6 +506,7 @@ class User(Model):
489
506
  hash_password = fields.CharField(max_length=255)
490
507
  is_superuser = fields.BooleanField(default=False)
491
508
  is_active = fields.BooleanField(default=False)
509
+ avatar_url = fields.TextField(null=True)
492
510
 
493
511
  def __str__(self):
494
512
  return self.username
@@ -501,15 +519,39 @@ class UserAdmin(TortoiseModelAdmin):
501
519
  list_display_links = ("id", "username")
502
520
  list_filter = ("id", "username", "is_superuser", "is_active")
503
521
  search_fields = ("username",)
504
-
505
- async def authenticate(self, username: str, password: str) -> UUID | int | None:
506
- user = await User.filter(username=username, is_superuser=True).first()
522
+ formfield_overrides = { # noqa: RUF012
523
+ "username": (WidgetType.SlugInput, {"required": True}),
524
+ "password": (WidgetType.PasswordInput, {"passwordModalForm": True}),
525
+ "avatar_url": (
526
+ WidgetType.Upload,
527
+ {
528
+ "required": False,
529
+ # Disable crop image for upload field
530
+ # "disableCropImage": True,
531
+ },
532
+ ),
533
+ }
534
+
535
+ async def authenticate(self, username: str, password: str) -> int | None:
536
+ user = await self.model_cls.filter(username=username, is_superuser=True).first()
507
537
  if not user:
508
538
  return None
509
539
  if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
510
540
  return None
511
541
  return user.id
512
542
 
543
+ async def change_password(self, id: UUID | int | str, password: str) -> None:
544
+ user = await self.model_cls.filter(id=id).first()
545
+ if not user:
546
+ return
547
+ user.hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
548
+ await user.save(update_fields=("hash_password",))
549
+
550
+ async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
551
+ # convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
552
+ setattr(obj, field, base64)
553
+ await obj.save(update_fields=(field,))
554
+
513
555
  ```
514
556
 
515
557
 
@@ -569,7 +611,7 @@ class UserAdmin(DjangoModelAdmin):
569
611
 
570
612
 
571
613
 
572
- ### SQL Alchemy
614
+ ### SQLAlchemy
573
615
 
574
616
 
575
617
 
@@ -583,8 +625,11 @@ class UserAdmin(DjangoModelAdmin):
583
625
 
584
626
 
585
627
  ```python
628
+ import typing as tp
629
+ import uuid
630
+
586
631
  import bcrypt
587
- from sqlalchemy import Boolean, Integer, String, select
632
+ from sqlalchemy import Boolean, Integer, String, Text, select, update
588
633
  from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
589
634
  from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
590
635
 
@@ -609,6 +654,7 @@ class User(Base):
609
654
  hash_password: Mapped[str] = mapped_column(String(length=255), nullable=False)
610
655
  is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
611
656
  is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
657
+ avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True)
612
658
 
613
659
  def __str__(self):
614
660
  return self.username
@@ -622,17 +668,33 @@ class UserAdmin(SqlAlchemyModelAdmin):
622
668
  list_filter = ("id", "username", "is_superuser", "is_active")
623
669
  search_fields = ("username",)
624
670
 
625
- async def authenticate(self, username, password):
671
+ async def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
626
672
  sessionmaker = self.get_sessionmaker()
627
673
  async with sessionmaker() as session:
628
- query = select(User).filter_by(username=username, password=password, is_superuser=True)
674
+ query = select(self.model_cls).filter_by(username=username, password=password, is_superuser=True)
629
675
  result = await session.scalars(query)
630
- user = result.first()
631
- if not user:
676
+ obj = result.first()
677
+ if not obj:
632
678
  return None
633
- if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
679
+ if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
634
680
  return None
635
- return user.id
681
+ return obj.id
682
+
683
+ async def change_password(self, id: uuid.UUID | int | str, password: str) -> None:
684
+ sessionmaker = self.get_sessionmaker()
685
+ async with sessionmaker() as session:
686
+ hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
687
+ query = update(self.model_cls).where(User.id.in_([id])).values(hash_password=hash_password)
688
+ await session.execute(query)
689
+ await session.commit()
690
+
691
+ async def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
692
+ sessionmaker = self.get_sessionmaker()
693
+ async with sessionmaker() as session:
694
+ # convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
695
+ query = update(self.model_cls).where(User.id.in_([obj.id])).values(**{field: base64})
696
+ await session.execute(query)
697
+ await session.commit()
636
698
 
637
699
  ```
638
700
 
@@ -655,8 +717,11 @@ class UserAdmin(SqlAlchemyModelAdmin):
655
717
 
656
718
 
657
719
  ```python
720
+ import typing as tp
721
+ import uuid
722
+
658
723
  import bcrypt
659
- from pony.orm import Database, PrimaryKey, Required, db_session
724
+ from pony.orm import Database, LongStr, Optional, PrimaryKey, Required, commit, db_session
660
725
 
661
726
  from fastadmin import PonyORMModelAdmin, register
662
727
 
@@ -671,6 +736,7 @@ class User(db.Entity): # type: ignore [name-defined]
671
736
  hash_password = Required(str)
672
737
  is_superuser = Required(bool, default=False)
673
738
  is_active = Required(bool, default=False)
739
+ avatar_url = Optional(LongStr, nullable=True)
674
740
 
675
741
  def __str__(self):
676
742
  return self.username
@@ -685,13 +751,31 @@ class UserAdmin(PonyORMModelAdmin):
685
751
  search_fields = ("username",)
686
752
 
687
753
  @db_session
688
- def authenticate(self, username, password):
689
- user = next((f for f in self.model_cls.select(username=username, password=password, is_superuser=True)), None)
690
- if not user:
754
+ def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
755
+ obj = next((f for f in User.select(username=username, password=password, is_superuser=True)), None) # fmt: skip
756
+ if not obj:
691
757
  return None
692
- if not bcrypt.checkpw(password.encode(), user.hash_password.encode()):
758
+ if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
693
759
  return None
694
- return user.id
760
+ return obj.id
761
+
762
+ @db_session
763
+ def change_password(self, id: uuid.UUID | int, password: str) -> None:
764
+ obj = next((f for f in self.model_cls.select(id=id)), None)
765
+ if not obj:
766
+ return
767
+ hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
768
+ obj.hash_password = hash_password
769
+ commit()
770
+
771
+ @db_session
772
+ def orm_save_upload_field(self, obj: tp.Any, field: str, base64: str) -> None:
773
+ obj = next((f for f in self.model_cls.select(id=obj.id)), None)
774
+ if not obj:
775
+ return
776
+ # convert base64 to bytes, upload to s3/filestorage, get url and save or save base64 as is to db (don't recomment it)
777
+ setattr(obj, field, base64)
778
+ commit()
695
779
 
696
780
  ```
697
781
 
@@ -717,7 +801,9 @@ class UserAdmin(PonyORMModelAdmin):
717
801
 
718
802
 
719
803
  ## Documentation
720
- See full documentation [here](https://vsdudakov.github.io/fastadmin).
804
+
805
+ Full documentation is available at [vsdudakov.github.io/fastadmin](https://vsdudakov.github.io/fastadmin).
721
806
 
722
807
  ## License
723
- This project is licensed under the MIT License - see the [LICENSE](https://github.com/vsdudakov/fastadmin/blob/main/LICENSE) file for details.
808
+
809
+ This project is licensed under the MIT License — see the [LICENSE](https://github.com/vsdudakov/fastadmin/blob/main/LICENSE) file for details.