Skip to content

Commit 0b2b0af

Browse files
committed
0.1.31: Added change password api and PasswordInput widget type. Fixed auto identifiction fk_name for inline models.
1 parent a6b291e commit 0b2b0af

21 files changed

Lines changed: 425 additions & 45 deletions

File tree

docs/index.html

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ <h4 class="title">FastAdmin</h4>
181181
<li class="nav-item">
182182
<a class="nav-link" href="#changelog">Changelog</a>
183183
<ul class="nav flex-column">
184+
<li class="nav-item">
185+
<a class="nav-link" href="#v0_1_31">v0.1.31</a>
186+
</li>
184187
<li class="nav-item">
185188
<a class="nav-link" href="#v0_1_30">v0.1.30</a>
186189
</li>
@@ -230,7 +233,7 @@ <h1>Documentation</h1>
230233
<div class="row">
231234
<div class="col-sm-6 col-lg-4">
232235
<ul class="list-unstyled">
233-
<li><strong>Version:</strong> 0.1.30</li>
236+
<li><strong>Version:</strong> 0.1.31</li>
234237
<li>
235238
<strong>Author:</strong>
236239
<a href="mailto:vsdudakov@gmail.com" target="_blank"
@@ -1225,6 +1228,15 @@ <h3>ModelAdmin methods</h3>
12251228
:params password: a password.
12261229
:return: An user id or None.
12271230
"""
1231+
...
1232+
1233+
1234+
async def change_password(self, id: UUID | int, password: str) -> None:
1235+
"""This method is used to change user password.
1236+
1237+
:params id: An user id.
1238+
:params password: A new password.
1239+
"""
12281240
...
12291241
</code>
12301242
</pre>
@@ -1378,6 +1390,18 @@ <h2>Changelog</h2>
13781390
</p>
13791391
<hr class="small-divider" />
13801392
<!-- <p class="alert alert-info mb-5"> For Future Updates Follow Us <a target="_blank" href="http://themeforest.net/user/harnishdesign?ref=HarnishDesign">@themeforest</a> / <a target="_blank" href="http://facebook.com/harnishdesign">@facebook</a> / <a target="_blank" href="http://twitter.com/harnishdesign">@twitter</a> / <a target="_blank" href="https://dribbble.com/harnishdesign">@Dribbble</a></p> -->
1393+
<h3 id="v0_1_31">
1394+
Version 0.1.31
1395+
<small class="text-muted">(27 March, 2024)</small>
1396+
</h3>
1397+
<ul class="changelog">
1398+
<li>
1399+
Added change password api and PasswordInput widget type.
1400+
</li>
1401+
<li>Fixed auto identifiction fk_name for inline models.</li>
1402+
</ul>
1403+
<hr class="small-divider" />
1404+
13811405
<h3 id="v0_1_30">
13821406
Version 0.1.30
13831407
<small class="text-muted">(26 March, 2024)</small>

examples/djangoorm/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ def authenticate(self, username, password):
8484
return None
8585
return obj.id
8686

87+
def change_password(self, user_id, password):
88+
user = self.model_cls.objects.filter(id=user_id).first()
89+
if not user:
90+
return
91+
# direct saving password is only for tests - use hash
92+
user.password = password
93+
user.save()
94+
8795

8896
class DjangoEventInlineModelAdmin(DjangoInlineModelAdmin):
8997
model = Event

examples/ponyorm/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ def authenticate(self, username, password):
115115
return None
116116
return obj.id
117117

118+
@db_session
119+
def change_password(self, user_id, password):
120+
obj = next((f for f in self.model_cls.select(id=user_id)), None)
121+
if not obj:
122+
return None
123+
# direct saving password is only for tests - use hash
124+
obj.password = password
125+
commit()
126+
118127

119128
class PonyORMEventInlineModelAdmin(PonyORMInlineModelAdmin):
120129
model = Event

examples/sqlalchemy/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,21 @@ class SqlAlchemyUserModelAdmin(SqlAlchemyModelAdmin):
127127
async def authenticate(self, username, password):
128128
sessionmaker = self.get_sessionmaker()
129129
async with sessionmaker() as session:
130-
query = select(User).filter_by(username=username, password=password, is_superuser=True)
130+
query = select(self.model_cls).filter_by(username=username, password=password, is_superuser=True)
131131
result = await session.scalars(query)
132132
obj = result.first()
133133
if not obj:
134134
return None
135135
return obj.id
136136

137+
async def change_password(self, user_id, password):
138+
sessionmaker = self.get_sessionmaker()
139+
async with sessionmaker() as session:
140+
# direct saving password is only for tests - use hash
141+
query = update(self.model_cls).where(User.id.in_([user_id])).values(password=password)
142+
await session.execute(query)
143+
await session.commit()
144+
137145

138146
class SqlAlchemyEventInlineModelAdmin(SqlAlchemyInlineModelAdmin):
139147
model_name_prefix = "sqlalchemy"

examples/tortoiseorm/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ async def authenticate(self, username, password):
8787
return None
8888
return obj.id
8989

90+
async def change_password(self, user_id, password):
91+
user = await self.model_cls.filter(id=user_id).first()
92+
if not user:
93+
return
94+
# direct saving password is only for tests - use hash
95+
user.password = password
96+
await user.save()
97+
9098

9199
class TortoiseORMEventInlineModelAdmin(TortoiseInlineModelAdmin):
92100
model = Event

fastadmin/api/frameworks/django/app/api.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,30 @@ async def add(request: HttpRequest, model: str) -> JsonResponse:
210210
return JsonResponse({"detail": e.detail}, status=e.status_code)
211211

212212

213+
@csrf_exempt
214+
async def change_password(request: HttpRequest, id: UUID | int) -> JsonResponse:
215+
"""This method is used to change a password.
216+
217+
:params id: an id of object.
218+
:params payload: a payload object.
219+
:return: An object.
220+
"""
221+
if request.method != "PATCH":
222+
return JsonResponse({"error": "Method not allowed"}, status=405)
223+
if not is_valid_id(id):
224+
return JsonResponse({"error": "Invalid id. It must be a UUID or an integer."}, status=422)
225+
try:
226+
await api_service.change_password(
227+
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
228+
id,
229+
json.loads(request.body),
230+
)
231+
return JsonResponse(id, safe=False)
232+
233+
except AdminApiException as e:
234+
return JsonResponse({"detail": e.detail}, status=e.status_code)
235+
236+
213237
@csrf_exempt
214238
async def change(request: HttpRequest, model: str, id: UUID | int) -> JsonResponse:
215239
"""This method is used to change an object.

fastadmin/api/frameworks/django/app/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from fastadmin.settings import ROOT_DIR
88

9-
from .api import action, add, change, configuration, delete, export, get, list, me, sign_in, sign_out
9+
from .api import action, add, change, change_password, configuration, delete, export, get, list, me, sign_in, sign_out
1010
from .views import index
1111

1212

@@ -20,6 +20,7 @@ def get_admin_urls():
2020
path("api/list/<str:model>", list),
2121
path("api/retrieve/<str:model>/<str:id>", get),
2222
path("api/add/<str:model>", add),
23+
path("api/change-password/<str:id>", change_password),
2324
path("api/change/<str:model>/<str:id>", change),
2425
path("api/export/<str:model>", export),
2526
path("api/delete/<str:model>/<str:id>", delete),

fastadmin/api/frameworks/fastapi/api.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ async def sign_in(
2525
) -> None:
2626
"""This method is used to sign in.
2727
28+
:params request: a request object.
2829
:params response: a response object.
2930
:params payload: a payload object.
3031
:return: None.
@@ -49,6 +50,7 @@ async def sign_out(
4950
) -> None:
5051
"""This method is used to sign out.
5152
53+
:params request: a request object.
5254
:params response: a response object.
5355
:return: None.
5456
"""
@@ -165,6 +167,25 @@ async def add(
165167
raise HTTPException(e.status_code, detail=e.detail)
166168

167169

170+
@router.patch("/change-password/{id}")
171+
async def change_password(
172+
request: Request,
173+
id: UUID | int,
174+
payload: dict,
175+
) -> UUID | int:
176+
"""This method is used to change password.
177+
178+
:params id: an id of object.
179+
:params payload: a payload object.
180+
:return: An object.
181+
"""
182+
try:
183+
await api_service.change_password(request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None), id, payload)
184+
return id
185+
except AdminApiException as e:
186+
raise HTTPException(e.status_code, detail=e.detail)
187+
188+
168189
@router.patch("/change/{model}/{id}")
169190
async def change(
170191
request: Request,

fastadmin/api/frameworks/flask/api.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,32 @@ async def add(model: str) -> dict:
163163
raise http_exception
164164

165165

166+
@api_router.route("/change-password/<string:id>", methods=["PATCH"])
167+
async def change_password(id: UUID | int) -> UUID | int:
168+
"""This method is used to change password.
169+
170+
:params id: an id of object.
171+
:params payload: a payload object.
172+
:return: An object.
173+
"""
174+
if not is_valid_id(id):
175+
http_exception = HTTPException("Invalid id. It must be a UUID or an integer.")
176+
http_exception.code = 422
177+
raise http_exception
178+
try:
179+
payload: dict = request.json
180+
await api_service.change_password(
181+
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
182+
id,
183+
payload,
184+
)
185+
return id
186+
except AdminApiException as e:
187+
http_exception = HTTPException(e.detail)
188+
http_exception.code = e.status_code
189+
raise http_exception
190+
191+
166192
@api_router.route("/change/<string:model>/<string:id>", methods=["PATCH"])
167193
async def change(model: str, id: UUID | int) -> dict:
168194
"""This method is used to change an object.

fastadmin/api/schemas.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from enum import Enum
22
from uuid import UUID
33

4-
from pydantic import BaseModel
4+
from pydantic import BaseModel, validator
5+
6+
from fastadmin.api.exceptions import AdminApiException
57

68

79
class ExportFormat(str, Enum):
@@ -28,6 +30,19 @@ class SignInInputSchema(BaseModel):
2830
password: str
2931

3032

33+
class ChangePasswordInputSchema(BaseModel):
34+
"""Change password input schema"""
35+
36+
password: str
37+
confirm_password: str
38+
39+
@validator("confirm_password")
40+
def passwords_match(cls, v, values, **kwargs):
41+
if "password" in values and v != values["password"]:
42+
raise AdminApiException(422, detail="Passwords do not match")
43+
return v
44+
45+
3146
class ExportInputSchema(BaseModel):
3247
"""Export input schema"""
3348

0 commit comments

Comments
 (0)