Skip to content

Commit f2eb2b9

Browse files
committed
feat: Enhance permission handling by flushing direct permissions and extracting perm_data for User model
1 parent f1403b5 commit f2eb2b9

3 files changed

Lines changed: 79 additions & 60 deletions

File tree

fastapi_admin_kit/admin/builtin_models.py

Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,33 @@
77

88

99
async def flush_pending_perm_ops(request):
10-
"""No-op — direct permissions are now saved immediately."""
11-
pass
10+
"""Flush pending direct-permission writes for the user on the request."""
11+
from sqlalchemy import delete
12+
13+
from fastapi_admin_kit.auth.models import UserPermission
14+
from fastapi_admin_kit.db import get_db_session
15+
16+
perm_ids = getattr(request.state, "_admin_perm_perm_ids", None)
17+
if not perm_ids or not isinstance(perm_ids, list):
18+
return
19+
20+
# Get the user object from request state
21+
user_obj = getattr(request.state, "_admin_perm_user_obj", None)
22+
if user_obj is None:
23+
return
24+
25+
request.state._admin_perm_perm_ids = None
26+
session = get_db_session(request)
27+
if session is None:
28+
return
29+
30+
# Delete existing direct permissions
31+
await session.execute(delete(UserPermission).where(UserPermission.user_id == user_obj.id))
32+
33+
# Insert new permissions
34+
for perm_id in perm_ids:
35+
up = UserPermission(user_id=user_obj.id, permission_id=perm_id)
36+
session.add(up)
1237

1338

1439
def _get_table_names() -> list[str]:
@@ -89,50 +114,16 @@ def after_create(self, obj, request=None):
89114
return
90115
perm_data = getattr(request.state, "_admin_perm_data", None)
91116
if perm_data:
92-
self._save_direct_permissions_after_commit(obj, perm_data, request)
117+
request.state._admin_perm_perm_ids = perm_data
118+
request.state._admin_perm_user_obj = obj
93119

94120
def after_update(self, obj, request=None):
95121
if request is None:
96122
return
97123
perm_data = getattr(request.state, "_admin_perm_data", None)
98124
if perm_data:
99-
self._save_direct_permissions_after_commit(obj, perm_data, request)
100-
101-
def _save_direct_permissions_after_commit(self, obj, perm_data, request):
102-
"""Save direct user permissions after the user object is committed.
103-
104-
perm_data: list of permission IDs, e.g. [1, 3, 5]
105-
"""
106-
import asyncio
107-
108-
from sqlalchemy import delete
109-
110-
from fastapi_admin_kit.auth.models import UserPermission
111-
112-
perm_ids = perm_data if isinstance(perm_data, list) else []
113-
114-
async def _do_save():
115-
from fastapi_admin_kit.db import get_db_session
116-
117-
session = get_db_session(request)
118-
# Delete existing direct permissions for this user
119-
await session.execute(delete(UserPermission).where(UserPermission.user_id == obj.id))
120-
121-
# Insert new direct permissions
122-
for perm_id in perm_ids:
123-
up = UserPermission(user_id=obj.id, permission_id=perm_id)
124-
session.add(up)
125-
126-
await session.commit()
127-
128-
try:
129-
loop = asyncio.get_event_loop()
130-
if loop.is_running():
131-
asyncio.ensure_future(_do_save())
132-
else:
133-
loop.run_until_complete(_do_save())
134-
except RuntimeError:
135-
asyncio.run(_do_save())
125+
request.state._admin_perm_perm_ids = perm_data
126+
request.state._admin_perm_user_obj = obj
136127

137128
async def get_form_context(self, context, obj=None, request=None):
138129
"""Load direct permissions for the user being edited."""
@@ -168,26 +159,11 @@ async def get_form_context(self, context, obj=None, request=None):
168159
return context
169160

170161
def process_form_data(self, data, request=None):
171-
"""Extract perm_data from form data and store on request for after_create/after_update."""
172-
import json
173-
174-
perm_data_raw = data.pop("perm_data", None)
175-
if perm_data_raw is None and request is not None:
176-
# Fallback: try to get from request state if already parsed
177-
perm_data_raw = getattr(request.state, "_admin_perm_data_raw", None)
178-
179-
if perm_data_raw is not None:
180-
try:
181-
if isinstance(perm_data_raw, str):
182-
perm_data = json.loads(perm_data_raw)
183-
else:
184-
perm_data = perm_data_raw
185-
except (json.JSONDecodeError, TypeError):
186-
perm_data = {}
187-
188-
if request is not None and perm_data:
189-
request.state._admin_perm_data = perm_data
162+
"""Extract perm_data from request state and store for after_create/after_update.
190163
164+
perm_data should already be extracted by the view and stored on
165+
request.state._admin_perm_data before this is called.
166+
"""
191167
return data
192168

193169

fastapi_admin_kit/auth/csrf.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,17 @@ async def forbidden_handler(request: Request, exc: HTTPException) -> Response:
276276
if is_html:
277277
templates = request.app.state.admin_jinja_env
278278
detail = exc.detail or "You do not have permission to access this resource."
279+
context = {"admin_path": admin_path, "detail": detail}
280+
try:
281+
from fastapi_admin_kit.views.sidebar import inject_sidebar_context
282+
283+
await inject_sidebar_context(request, context)
284+
except Exception:
285+
pass
279286
return templates.TemplateResponse(
280287
request,
281288
"pages/403.html",
282-
{"admin_path": admin_path, "detail": detail},
289+
context,
283290
status_code=403,
284291
)
285292
from starlette.responses import JSONResponse

fastapi_admin_kit/views/class_views.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,24 @@ async def html_response(self, request: Request) -> Response:
421421

422422
# POST
423423
parsed, errors = await self.form_parser.parse(request)
424+
425+
# Extract perm_data for User model direct permissions
426+
if self.registered.table_name == "admin_users":
427+
import json
428+
429+
form = await request.form()
430+
perm_data_raw = form.get("perm_data")
431+
if perm_data_raw:
432+
try:
433+
perm_data = (
434+
json.loads(perm_data_raw)
435+
if isinstance(perm_data_raw, str)
436+
else perm_data_raw
437+
)
438+
request.state._admin_perm_data = perm_data
439+
except (json.JSONDecodeError, TypeError):
440+
pass
441+
424442
if errors:
425443
session = get_db_session(request)
426444
await session.rollback()
@@ -694,6 +712,24 @@ async def html_response(self, request: Request, id: Any = None) -> Response:
694712

695713
# POST
696714
parsed, errors = await self.form_parser.parse(request, obj=obj)
715+
716+
# Extract perm_data for User model direct permissions
717+
if self.registered.table_name == "admin_users":
718+
import json
719+
720+
form = await request.form()
721+
perm_data_raw = form.get("perm_data")
722+
if perm_data_raw:
723+
try:
724+
perm_data = (
725+
json.loads(perm_data_raw)
726+
if isinstance(perm_data_raw, str)
727+
else perm_data_raw
728+
)
729+
request.state._admin_perm_data = perm_data
730+
except (json.JSONDecodeError, TypeError):
731+
pass
732+
697733
if errors:
698734
session = get_db_session(request)
699735
await session.rollback()

0 commit comments

Comments
 (0)